157 lines
5.3 KiB
JavaScript
157 lines
5.3 KiB
JavaScript
/* global
|
|
$
|
|
CodeMirror
|
|
prefs
|
|
t
|
|
*/
|
|
|
|
'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 = {
|
|
autoCloseBrackets: prefs.get('editor.autoCloseBrackets'),
|
|
mode: 'css',
|
|
lineNumbers: true,
|
|
lineWrapping: prefs.get('editor.lineWrapping'),
|
|
foldGutter: true,
|
|
gutters: [
|
|
'CodeMirror-linenumbers',
|
|
'CodeMirror-foldgutter',
|
|
...(prefs.get('editor.linter') ? ['CodeMirror-lint-markers'] : []),
|
|
],
|
|
matchBrackets: true,
|
|
hintOptions: {},
|
|
lintReportDelay: prefs.get('editor.lintReportDelay'),
|
|
styleActiveLine: true,
|
|
theme: prefs.get('editor.theme'),
|
|
keyMap: prefs.get('editor.keyMap'),
|
|
extraKeys: Object.assign(CodeMirror.defaults.extraKeys || {}, {
|
|
// independent of current keyMap; some are implemented only for the edit page
|
|
'Alt-Enter': 'toggleStyle',
|
|
'Alt-PageDown': 'nextEditor',
|
|
'Alt-PageUp': 'prevEditor',
|
|
'Ctrl-Pause': 'toggleEditorFocus',
|
|
}),
|
|
maxHighlightLength: 100e3,
|
|
};
|
|
|
|
Object.assign(CodeMirror.defaults, defaults, prefs.get('editor.options'));
|
|
|
|
// Adding hotkeys to some keymaps except 'basic' which is primitive by design
|
|
const KM = CodeMirror.keyMap;
|
|
const extras = Object.values(CodeMirror.defaults.extraKeys);
|
|
if (!extras.includes('jumpToLine')) {
|
|
KM.sublime['Ctrl-G'] = 'jumpToLine';
|
|
KM.emacsy['Ctrl-G'] = 'jumpToLine';
|
|
KM.pcDefault['Ctrl-J'] = 'jumpToLine';
|
|
KM.macDefault['Cmd-J'] = 'jumpToLine';
|
|
}
|
|
if (!extras.includes('autocomplete')) {
|
|
// will be used by 'sublime' on PC via fallthrough
|
|
KM.pcDefault['Ctrl-Space'] = 'autocomplete';
|
|
// OSX uses Ctrl-Space and Cmd-Space for something else
|
|
KM.macDefault['Alt-Space'] = 'autocomplete';
|
|
// copied from 'emacs' keymap
|
|
KM.emacsy['Alt-/'] = 'autocomplete';
|
|
// 'vim' and 'emacs' define their own autocomplete hotkeys
|
|
}
|
|
if (!extras.includes('blockComment')) {
|
|
KM.sublime['Shift-Ctrl-/'] = 'commentSelection';
|
|
}
|
|
if (navigator.appVersion.includes('Windows')) {
|
|
// 'pcDefault' keymap on Windows should have F3/Shift-F3/Ctrl-R
|
|
if (!extras.includes('findNext')) KM.pcDefault['F3'] = 'findNext';
|
|
if (!extras.includes('findPrev')) KM.pcDefault['Shift-F3'] = 'findPrev';
|
|
if (!extras.includes('replace')) KM.pcDefault['Ctrl-R'] = 'replace';
|
|
// try to remap non-interceptable (Shift-)Ctrl-N/T/W hotkeys
|
|
// Note: modifier order in CodeMirror is S-C-A
|
|
for (const char of ['N', 'T', 'W']) {
|
|
for (const remap of [
|
|
{from: 'Ctrl-', to: ['Alt-', 'Ctrl-Alt-']},
|
|
{from: 'Shift-Ctrl-', to: ['Ctrl-Alt-', 'Shift-Ctrl-Alt-']},
|
|
]) {
|
|
const oldKey = remap.from + char;
|
|
for (const km of Object.values(KM)) {
|
|
const command = km[oldKey];
|
|
if (!command) continue;
|
|
for (const newMod of remap.to) {
|
|
const newKey = newMod + char;
|
|
if (newKey in km) continue;
|
|
km[newKey] = command;
|
|
delete km[oldKey];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Object.assign(CodeMirror.mimeModes['text/css'].propertyKeywords, {
|
|
'content-visibility': true,
|
|
'overflow-anchor': true,
|
|
'overscroll-behavior': true,
|
|
});
|
|
Object.assign(CodeMirror.mimeModes['text/css'].colorKeywords, {
|
|
'darkgrey': true,
|
|
'darkslategrey': true,
|
|
'dimgrey': true,
|
|
'lightgrey': true,
|
|
'lightslategrey': true,
|
|
'slategrey': true,
|
|
});
|
|
|
|
Object.assign(CodeMirror.prototype, {
|
|
/**
|
|
* @param {'less' | 'stylus' | ?} [pp] - any value besides `less` or `stylus` sets `css` mode
|
|
* @param {boolean} [force]
|
|
*/
|
|
setPreprocessor(pp, force) {
|
|
const name = pp === 'less' ? 'text/x-less' : pp === 'stylus' ? pp : 'css';
|
|
const m = this.doc.mode;
|
|
if (force || (m.helperType ? m.helperType !== pp : m.name !== name)) {
|
|
this.setOption('mode', name);
|
|
}
|
|
},
|
|
/** Superfast GC-friendly check that runs until the first non-space line */
|
|
isBlank() {
|
|
let filled;
|
|
this.eachLine(({text}) => (filled = text && /\S/.test(text)));
|
|
return !filled;
|
|
},
|
|
/**
|
|
* Sets cursor and centers it in view if `pos` was out of view
|
|
* @param {CodeMirror.Pos} pos
|
|
* @param {CodeMirror.Pos} [end] - will set a selection from `pos` to `end`
|
|
*/
|
|
jumpToPos(pos, end = pos) {
|
|
const {curOp} = this;
|
|
if (!curOp) this.startOperation();
|
|
const coords = this.cursorCoords(pos, 'window');
|
|
const b = this.display.wrapper.getBoundingClientRect();
|
|
if (coords.top < Math.max(0, b.top + this.defaultTextHeight() * 2) ||
|
|
coords.bottom > Math.min(window.innerHeight, b.bottom - 100)) {
|
|
this.scrollIntoView(pos, b.height / 2);
|
|
}
|
|
CodeMirror.prototype.setSelection.call(this, pos, end);
|
|
if (!curOp) this.endOperation();
|
|
},
|
|
});
|
|
|
|
Object.assign(CodeMirror.commands, {
|
|
jumpToLine(cm) {
|
|
const cur = cm.getCursor();
|
|
const oldDialog = $('.CodeMirror-dialog', cm.display.wrapper);
|
|
if (oldDialog) cm.focus(); // close the currently opened minidialog
|
|
cm.openDialog(t.template.jumpToLine.cloneNode(true), str => {
|
|
const [line, ch] = str.match(/^\s*(\d+)(?:\s*:\s*(\d+))?\s*$|$/);
|
|
if (line) cm.setCursor(line - 1, ch ? ch - 1 : cur.ch);
|
|
}, {value: cur.line + 1});
|
|
},
|
|
});
|
|
})();
|