codemirror-default.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. /* global $ */// dom.js
  2. /* global CodeMirror */
  3. /* global editor */
  4. /* global prefs */
  5. /* global t */// localization.js
  6. 'use strict';
  7. (() => {
  8. // CodeMirror miserably fails on keyMap='' so let's ensure it's not
  9. if (!prefs.get('editor.keyMap')) {
  10. prefs.reset('editor.keyMap');
  11. }
  12. const defaults = {
  13. autoCloseBrackets: prefs.get('editor.autoCloseBrackets'),
  14. mode: 'css',
  15. lineNumbers: true,
  16. lineWrapping: prefs.get('editor.lineWrapping'),
  17. foldGutter: true,
  18. gutters: [
  19. 'CodeMirror-linenumbers',
  20. 'CodeMirror-foldgutter',
  21. ...(prefs.get('editor.linter') ? ['CodeMirror-lint-markers'] : []),
  22. ],
  23. matchBrackets: true,
  24. hintOptions: {},
  25. lintReportDelay: prefs.get('editor.lintReportDelay'),
  26. styleActiveLine: true,
  27. theme: prefs.get('editor.theme'),
  28. keyMap: prefs.get('editor.keyMap'),
  29. extraKeys: Object.assign(CodeMirror.defaults.extraKeys || {}, {
  30. // independent of current keyMap; some are implemented only for the edit page
  31. 'Alt-Enter': 'toggleStyle',
  32. 'Alt-PageDown': 'nextEditor',
  33. 'Alt-PageUp': 'prevEditor',
  34. 'Ctrl-Pause': 'toggleEditorFocus',
  35. }),
  36. maxHighlightLength: 100e3,
  37. };
  38. Object.assign(CodeMirror.defaults, defaults, prefs.get('editor.options'));
  39. // Adding hotkeys to some keymaps except 'basic' which is primitive by design
  40. require(Object.values(typeof editor === 'object' && editor.lazyKeymaps || {}), () => {
  41. const KM = CodeMirror.keyMap;
  42. const extras = Object.values(CodeMirror.defaults.extraKeys);
  43. if (!extras.includes('jumpToLine')) {
  44. KM.sublime['Ctrl-G'] = 'jumpToLine';
  45. KM.emacsy['Ctrl-G'] = 'jumpToLine';
  46. KM.pcDefault['Ctrl-J'] = 'jumpToLine';
  47. KM.macDefault['Cmd-J'] = 'jumpToLine';
  48. }
  49. if (!extras.includes('autocomplete')) {
  50. // will be used by 'sublime' on PC via fallthrough
  51. KM.pcDefault['Ctrl-Space'] = 'autocomplete';
  52. // OSX uses Ctrl-Space and Cmd-Space for something else
  53. KM.macDefault['Alt-Space'] = 'autocomplete';
  54. // copied from 'emacs' keymap
  55. KM.emacsy['Alt-/'] = 'autocomplete';
  56. // 'vim' and 'emacs' define their own autocomplete hotkeys
  57. }
  58. if (!extras.includes('blockComment')) {
  59. KM.sublime['Shift-Ctrl-/'] = 'commentSelection';
  60. }
  61. if (navigator.appVersion.includes('Windows')) {
  62. // 'pcDefault' keymap on Windows should have F3/Shift-F3/Ctrl-R
  63. if (!extras.includes('findNext')) KM.pcDefault['F3'] = 'findNext';
  64. if (!extras.includes('findPrev')) KM.pcDefault['Shift-F3'] = 'findPrev';
  65. if (!extras.includes('replace')) KM.pcDefault['Ctrl-R'] = 'replace';
  66. // try to remap non-interceptable (Shift-)Ctrl-N/T/W hotkeys
  67. // Note: modifier order in CodeMirror is S-C-A
  68. for (const char of ['N', 'T', 'W']) {
  69. for (const remap of [
  70. {from: 'Ctrl-', to: ['Alt-', 'Ctrl-Alt-']},
  71. {from: 'Shift-Ctrl-', to: ['Ctrl-Alt-', 'Shift-Ctrl-Alt-']},
  72. ]) {
  73. const oldKey = remap.from + char;
  74. for (const km of Object.values(KM)) {
  75. const command = km[oldKey];
  76. if (!command) continue;
  77. for (const newMod of remap.to) {
  78. const newKey = newMod + char;
  79. if (newKey in km) continue;
  80. km[newKey] = command;
  81. delete km[oldKey];
  82. break;
  83. }
  84. }
  85. }
  86. }
  87. }
  88. });
  89. Object.assign(CodeMirror.prototype, {
  90. /**
  91. * @param {'less' | 'stylus' | ?} [pp] - any value besides `less` or `stylus` sets `css` mode
  92. * @param {boolean} [force]
  93. */
  94. setPreprocessor(pp, force) {
  95. const name = pp === 'less' ? 'text/x-less' : pp === 'stylus' ? pp : 'css';
  96. const m = this.doc.mode;
  97. if (force || (m.helperType ? m.helperType !== pp : m.name !== name)) {
  98. this.setOption('mode', name);
  99. }
  100. },
  101. /** Superfast GC-friendly check that runs until the first non-space line */
  102. isBlank() {
  103. let filled;
  104. this.eachLine(({text}) => (filled = text && /\S/.test(text)));
  105. return !filled;
  106. },
  107. /**
  108. * Sets cursor and centers it in view if `pos` was out of view
  109. * @param {CodeMirror.Pos} pos
  110. * @param {CodeMirror.Pos} [end] - will set a selection from `pos` to `end`
  111. */
  112. jumpToPos(pos, end = pos) {
  113. const {curOp} = this;
  114. if (!curOp) this.startOperation();
  115. const y = this.cursorCoords(pos, 'window').top;
  116. const rect = this.display.wrapper.getBoundingClientRect();
  117. // case 1) outside of CM viewport or too close to edge so tell CM to render a new viewport
  118. if (y < rect.top + 50 || y > rect.bottom - 100) {
  119. this.scrollIntoView(pos, rect.height / 2);
  120. // case 2) inside CM viewport but outside of window viewport so just scroll the window
  121. } else if (y < 0 || y > innerHeight) {
  122. editor.scrollToEditor(this);
  123. }
  124. // Using prototype since our bookmark patch sets cm.setSelection to jumpToPos
  125. CodeMirror.prototype.setSelection.call(this, pos, end);
  126. if (!curOp) this.endOperation();
  127. },
  128. });
  129. Object.assign(CodeMirror.commands, {
  130. jumpToLine(cm) {
  131. const cur = cm.getCursor();
  132. const oldDialog = $('.CodeMirror-dialog', cm.display.wrapper);
  133. if (oldDialog) cm.focus(); // close the currently opened minidialog
  134. cm.openDialog(t.template.jumpToLine.cloneNode(true), str => {
  135. const [line, ch] = str.match(/^\s*(\d+)(?:\s*:\s*(\d+))?\s*$|$/);
  136. if (line) cm.setCursor(line - 1, ch ? ch - 1 : cur.ch);
  137. }, {value: cur.line + 1});
  138. },
  139. });
  140. })();