2017-12-20 23:29:24 +00:00
|
|
|
/* global CodeMirror prefs loadScript editor editors */
|
2017-09-24 08:54:21 +00:00
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
// CodeMirror miserably fails on keyMap='' so let's ensure it's not
|
|
|
|
if (!prefs.get('editor.keyMap')) {
|
|
|
|
prefs.reset('editor.keyMap');
|
|
|
|
}
|
|
|
|
|
|
|
|
const defaults = {
|
|
|
|
mode: 'css',
|
|
|
|
lineNumbers: true,
|
2017-12-02 22:52:46 +00:00
|
|
|
lineWrapping: prefs.get('editor.lineWrapping'),
|
2017-09-24 08:54:21 +00:00
|
|
|
foldGutter: true,
|
|
|
|
gutters: [
|
|
|
|
'CodeMirror-linenumbers',
|
|
|
|
'CodeMirror-foldgutter',
|
|
|
|
...(prefs.get('editor.linter') ? ['CodeMirror-lint-markers'] : []),
|
|
|
|
],
|
|
|
|
matchBrackets: true,
|
|
|
|
highlightSelectionMatches: {showToken: /[#.\-\w]/, annotateScrollbar: true},
|
|
|
|
hintOptions: {},
|
|
|
|
lintReportDelay: prefs.get('editor.lintReportDelay'),
|
|
|
|
styleActiveLine: true,
|
|
|
|
theme: 'default',
|
|
|
|
keyMap: prefs.get('editor.keyMap'),
|
2017-12-08 02:45:27 +00:00
|
|
|
extraKeys: Object.assign(CodeMirror.defaults.extraKeys || {}, {
|
2017-09-24 08:54:21 +00:00
|
|
|
// independent of current keyMap
|
|
|
|
'Alt-Enter': 'toggleStyle',
|
|
|
|
'Alt-PageDown': 'nextEditor',
|
2017-12-24 08:20:37 +00:00
|
|
|
'Alt-PageUp': 'prevEditor',
|
|
|
|
'Ctrl-Pause': 'toggleEditorFocus',
|
2017-12-08 02:45:27 +00:00
|
|
|
}),
|
2017-12-02 23:02:22 +00:00
|
|
|
maxHighlightLength: 100e3,
|
2017-12-22 13:23:20 +00:00
|
|
|
configureMouse: (cm, repeat) => repeat === 'double' ? {unit: selectTokenOnDoubleclick} : {},
|
2017-09-24 08:54:21 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Object.assign(CodeMirror.defaults, defaults, prefs.get('editor.options'));
|
|
|
|
|
|
|
|
// 'basic' keymap only has basic keys by design, so we skip it
|
|
|
|
|
|
|
|
const extraKeysCommands = {};
|
|
|
|
Object.keys(CodeMirror.defaults.extraKeys).forEach(key => {
|
|
|
|
extraKeysCommands[CodeMirror.defaults.extraKeys[key]] = true;
|
|
|
|
});
|
|
|
|
if (!extraKeysCommands.jumpToLine) {
|
|
|
|
CodeMirror.keyMap.sublime['Ctrl-G'] = 'jumpToLine';
|
|
|
|
CodeMirror.keyMap.emacsy['Ctrl-G'] = 'jumpToLine';
|
|
|
|
CodeMirror.keyMap.pcDefault['Ctrl-J'] = 'jumpToLine';
|
|
|
|
CodeMirror.keyMap.macDefault['Cmd-J'] = 'jumpToLine';
|
|
|
|
}
|
|
|
|
if (!extraKeysCommands.autocomplete) {
|
|
|
|
// will be used by 'sublime' on PC via fallthrough
|
|
|
|
CodeMirror.keyMap.pcDefault['Ctrl-Space'] = 'autocomplete';
|
|
|
|
// OSX uses Ctrl-Space and Cmd-Space for something else
|
|
|
|
CodeMirror.keyMap.macDefault['Alt-Space'] = 'autocomplete';
|
|
|
|
// copied from 'emacs' keymap
|
|
|
|
CodeMirror.keyMap.emacsy['Alt-/'] = 'autocomplete';
|
|
|
|
// 'vim' and 'emacs' define their own autocomplete hotkeys
|
|
|
|
}
|
|
|
|
if (!extraKeysCommands.blockComment) {
|
2018-03-03 20:31:21 +00:00
|
|
|
CodeMirror.keyMap.sublime['Shift-Ctrl-/'] = 'commentSelection';
|
2017-09-24 08:54:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (navigator.appVersion.includes('Windows')) {
|
2017-12-17 19:00:51 +00:00
|
|
|
// 'pcDefault' keymap on Windows should have F3/Shift-F3/Ctrl-R
|
2017-09-24 08:54:21 +00:00
|
|
|
if (!extraKeysCommands.findNext) {
|
|
|
|
CodeMirror.keyMap.pcDefault['F3'] = 'findNext';
|
|
|
|
}
|
|
|
|
if (!extraKeysCommands.findPrev) {
|
|
|
|
CodeMirror.keyMap.pcDefault['Shift-F3'] = 'findPrev';
|
|
|
|
}
|
2017-12-17 19:00:51 +00:00
|
|
|
if (!extraKeysCommands.replace) {
|
|
|
|
CodeMirror.keyMap.pcDefault['Ctrl-R'] = 'replace';
|
|
|
|
}
|
2017-09-24 08:54:21 +00:00
|
|
|
|
|
|
|
// try to remap non-interceptable Ctrl-(Shift-)N/T/W hotkeys
|
|
|
|
['N', 'T', 'W'].forEach(char => {
|
|
|
|
[
|
|
|
|
{from: 'Ctrl-', to: ['Alt-', 'Ctrl-Alt-']},
|
|
|
|
// Note: modifier order in CodeMirror is S-C-A
|
|
|
|
{from: 'Shift-Ctrl-', to: ['Ctrl-Alt-', 'Shift-Ctrl-Alt-']}
|
|
|
|
].forEach(remap => {
|
|
|
|
const oldKey = remap.from + char;
|
|
|
|
Object.keys(CodeMirror.keyMap).forEach(keyMapName => {
|
|
|
|
const keyMap = CodeMirror.keyMap[keyMapName];
|
|
|
|
const command = keyMap[oldKey];
|
|
|
|
if (!command) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
remap.to.some(newMod => {
|
|
|
|
const newKey = newMod + char;
|
|
|
|
if (!(newKey in keyMap)) {
|
|
|
|
delete keyMap[oldKey];
|
|
|
|
keyMap[newKey] = command;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-11-23 10:01:27 +00:00
|
|
|
Object.assign(CodeMirror.mimeModes['text/css'].propertyKeywords, {
|
|
|
|
'mix-blend-mode': true,
|
|
|
|
'isolation': true,
|
2018-03-08 23:34:29 +00:00
|
|
|
// nonstandard https://compat.spec.whatwg.org/
|
|
|
|
'box-reflect': true,
|
|
|
|
'text-fill-color': true,
|
|
|
|
'text-stroke': true,
|
|
|
|
'text-stroke-color': true,
|
|
|
|
'text-stroke-width': true,
|
|
|
|
// end
|
2017-11-23 10:01:27 +00:00
|
|
|
});
|
|
|
|
Object.assign(CodeMirror.mimeModes['text/css'].valueKeywords, {
|
|
|
|
'isolate': true,
|
|
|
|
});
|
2017-12-19 21:42:03 +00:00
|
|
|
Object.assign(CodeMirror.mimeModes['text/css'].colorKeywords, {
|
|
|
|
'darkgrey': true,
|
|
|
|
'darkslategrey': true,
|
|
|
|
'dimgrey': true,
|
|
|
|
'grey': true,
|
|
|
|
'lightgrey': true,
|
|
|
|
'lightslategrey': true,
|
|
|
|
'slategrey': true,
|
|
|
|
});
|
2017-11-23 09:51:16 +00:00
|
|
|
|
2017-09-24 08:54:21 +00:00
|
|
|
const MODE = {
|
|
|
|
stylus: 'stylus',
|
|
|
|
uso: 'css'
|
|
|
|
};
|
|
|
|
|
2017-11-26 21:45:21 +00:00
|
|
|
CodeMirror.defineExtension('setPreprocessor', function (preprocessor, force = false) {
|
2017-11-22 00:38:29 +00:00
|
|
|
const mode = MODE[preprocessor] || 'css';
|
2017-11-26 21:45:21 +00:00
|
|
|
if ((this.doc.mode || {}).name === mode && !force) {
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
if (mode === 'css') {
|
|
|
|
this.setOption('mode', mode);
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
return loadScript(`/vendor/codemirror/mode/${mode}/${mode}.js`).then(() => {
|
2017-11-22 00:38:29 +00:00
|
|
|
this.setOption('mode', mode);
|
|
|
|
});
|
2017-11-01 03:00:41 +00:00
|
|
|
});
|
2017-11-14 23:50:53 +00:00
|
|
|
|
|
|
|
CodeMirror.defineExtension('isBlank', function () {
|
|
|
|
// superfast checking as it runs only until the first non-blank line
|
|
|
|
let isBlank = true;
|
|
|
|
this.doc.eachLine(line => {
|
|
|
|
if (line.text && line.text.trim()) {
|
|
|
|
isBlank = false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return isBlank;
|
|
|
|
});
|
2017-12-22 13:23:20 +00:00
|
|
|
|
|
|
|
function selectTokenOnDoubleclick(cm, pos) {
|
2017-12-22 13:46:30 +00:00
|
|
|
let {ch} = pos;
|
2017-12-23 01:42:20 +00:00
|
|
|
const {line, sticky} = pos;
|
|
|
|
const {text, styles} = cm.getLineHandle(line);
|
|
|
|
|
|
|
|
const execAt = (rx, i) => (rx.lastIndex = i) && null || rx.exec(text);
|
|
|
|
const at = (rx, i) => (rx.lastIndex = i) && null || rx.test(text);
|
2018-02-28 02:15:28 +00:00
|
|
|
const atWord = ch => at(/\w/y, ch);
|
|
|
|
const atSpace = ch => at(/\s/y, ch);
|
2017-12-23 01:42:20 +00:00
|
|
|
|
|
|
|
const atTokenEnd = styles.indexOf(ch, 1);
|
|
|
|
ch += atTokenEnd < 0 ? 0 : sticky === 'before' && atWord(ch - 1) ? 0 : atSpace(ch + 1) ? 0 : 1;
|
|
|
|
ch = Math.min(text.length, ch);
|
|
|
|
const type = cm.getTokenTypeAt({line, ch: ch + (sticky === 'after' ? 1 : 0)});
|
|
|
|
if (atTokenEnd > 0) ch--;
|
|
|
|
|
2017-12-22 13:23:20 +00:00
|
|
|
const isCss = type && !/^(comment|string)/.test(type);
|
|
|
|
const isNumber = type === 'number';
|
2017-12-23 01:42:20 +00:00
|
|
|
const isSpace = atSpace(ch);
|
|
|
|
let wordChars =
|
2018-02-28 02:15:28 +00:00
|
|
|
isNumber ? /[-+\w.%]/y :
|
|
|
|
isCss ? /[-\w@]/y :
|
|
|
|
isSpace ? /\s/y :
|
|
|
|
atWord(ch) ? /\w/y : /[^\w\s]/y;
|
2017-12-22 13:23:20 +00:00
|
|
|
|
2017-12-23 01:42:20 +00:00
|
|
|
let a = ch;
|
|
|
|
while (a && at(wordChars, a)) a--;
|
2018-02-28 02:15:28 +00:00
|
|
|
a += !a && at(wordChars, a) || isCss && at(/[.!#@]/y, a) ? 0 : at(wordChars, a + 1);
|
2017-12-23 01:42:20 +00:00
|
|
|
|
|
|
|
let b, found;
|
2017-12-22 13:23:20 +00:00
|
|
|
|
|
|
|
if (isNumber) {
|
2018-02-28 02:15:28 +00:00
|
|
|
b = a + execAt(/[+-]?[\d.]+(e\d+)?|$/yi, a)[0].length;
|
2017-12-23 01:42:20 +00:00
|
|
|
found = b >= ch;
|
|
|
|
if (!found) {
|
|
|
|
a = b;
|
|
|
|
ch = a;
|
2017-12-22 13:23:20 +00:00
|
|
|
}
|
|
|
|
}
|
2017-12-23 01:42:20 +00:00
|
|
|
|
|
|
|
if (!found) {
|
2018-02-28 02:15:28 +00:00
|
|
|
wordChars = isCss ? /[-\w]*/y : new RegExp(wordChars.source + '*', 'uy');
|
2017-12-23 01:42:20 +00:00
|
|
|
b = ch + execAt(wordChars, ch)[0].length;
|
|
|
|
}
|
2017-12-22 13:23:20 +00:00
|
|
|
|
|
|
|
return {
|
2017-12-23 01:42:20 +00:00
|
|
|
from: {line, ch: a},
|
|
|
|
to: {line, ch: b},
|
2017-12-22 13:23:20 +00:00
|
|
|
};
|
|
|
|
}
|
2017-09-24 08:54:21 +00:00
|
|
|
})();
|
2017-11-23 13:28:55 +00:00
|
|
|
|
2017-11-25 01:29:56 +00:00
|
|
|
// eslint-disable-next-line no-unused-expressions
|
|
|
|
CodeMirror.hint && (() => {
|
2017-11-23 13:28:55 +00:00
|
|
|
const USO_VAR = 'uso-variable';
|
|
|
|
const USO_VALID_VAR = 'variable-3 ' + USO_VAR;
|
|
|
|
const USO_INVALID_VAR = 'error ' + USO_VAR;
|
2018-02-28 02:15:28 +00:00
|
|
|
const RX_IMPORTANT = /(i(m(p(o(r(t(a(nt?)?)?)?)?)?)?)?)?(?=\b|\W|$)/iy;
|
2017-11-23 13:28:55 +00:00
|
|
|
|
2017-11-25 01:29:56 +00:00
|
|
|
const originalHelper = CodeMirror.hint.css || (() => {});
|
2017-11-23 13:28:55 +00:00
|
|
|
CodeMirror.registerHelper('hint', 'css', function (cm) {
|
2018-01-14 13:16:31 +00:00
|
|
|
const pos = cm.getCursor();
|
|
|
|
const {line, ch} = pos;
|
2017-11-23 13:28:55 +00:00
|
|
|
const {styles, text} = cm.getLineHandle(line);
|
2018-01-14 13:16:31 +00:00
|
|
|
if (!styles) return originalHelper(cm);
|
|
|
|
const {style, index} = cm.getStyleAtPos({styles, pos: ch}) || {};
|
|
|
|
if (style && (style.startsWith('comment') || style.startsWith('string'))) {
|
2017-11-25 01:29:56 +00:00
|
|
|
return originalHelper(cm);
|
2017-11-23 13:28:55 +00:00
|
|
|
}
|
2018-02-28 02:15:28 +00:00
|
|
|
if (text[ch - 1] === '!' && /i|\W|^$/i.test(text[ch] || '')) {
|
2018-01-14 13:16:31 +00:00
|
|
|
RX_IMPORTANT.lastIndex = ch;
|
|
|
|
return {
|
|
|
|
list: ['important'],
|
|
|
|
from: pos,
|
|
|
|
to: {line, ch: ch + RX_IMPORTANT.exec(text)[0].length},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
let prev = index > 2 ? styles[index - 2] : 0;
|
|
|
|
let end = styles[index];
|
|
|
|
if (text[prev] === '#') {
|
|
|
|
return {list: [], from: pos, to: pos};
|
|
|
|
}
|
|
|
|
if (!editor || !style || !style.includes(USO_VAR)) {
|
|
|
|
return originalHelper(cm);
|
2017-11-23 13:28:55 +00:00
|
|
|
}
|
2018-01-14 13:16:31 +00:00
|
|
|
const adjust = text[prev] === '/' ? 4 : 0;
|
|
|
|
prev += adjust;
|
|
|
|
end -= adjust;
|
|
|
|
const leftPart = text.slice(prev, ch);
|
|
|
|
const list = Object.keys(editor.getStyle().usercssData.vars)
|
|
|
|
.filter(name => name.startsWith(leftPart));
|
|
|
|
return {
|
|
|
|
list,
|
|
|
|
from: {line, ch: prev},
|
|
|
|
to: {line, ch: end},
|
|
|
|
};
|
2017-11-23 13:28:55 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
const hooks = CodeMirror.mimeModes['text/css'].tokenHooks;
|
|
|
|
const originalCommentHook = hooks['/'];
|
|
|
|
hooks['/'] = tokenizeUsoVariables;
|
|
|
|
|
|
|
|
function tokenizeUsoVariables(stream) {
|
|
|
|
const token = originalCommentHook.apply(this, arguments);
|
|
|
|
if (token[1] !== 'comment') {
|
|
|
|
return token;
|
|
|
|
}
|
|
|
|
const {string, start, pos} = stream;
|
|
|
|
// /*[[install-key]]*/
|
|
|
|
// 01234 43210
|
|
|
|
if (string[start + 2] === '[' &&
|
|
|
|
string[start + 3] === '[' &&
|
|
|
|
string[pos - 3] === ']' &&
|
|
|
|
string[pos - 4] === ']') {
|
2017-11-29 10:21:31 +00:00
|
|
|
const vars = typeof editor !== 'undefined' && (editor.getStyle().usercssData || {}).vars;
|
2017-12-06 03:29:54 +00:00
|
|
|
const name = vars && string.slice(start + 4, pos - 4);
|
|
|
|
if (vars && Object.hasOwnProperty.call(vars, name.endsWith('-rgb') ? name.slice(0, -4) : name)) {
|
2017-11-23 13:28:55 +00:00
|
|
|
token[0] = USO_VALID_VAR;
|
|
|
|
} else {
|
|
|
|
token[0] = USO_INVALID_VAR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return token;
|
|
|
|
}
|
|
|
|
})();
|