@jupyterlab/codemirror 4.0.0-alpha.10 → 4.0.0-alpha.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/editor.js CHANGED
@@ -3,51 +3,23 @@
3
3
  // Distributed under the terms of the Modified BSD License.
4
4
  // / <reference types="codemirror"/>
5
5
  // / <reference types="codemirror/searchcursor"/>
6
- var __rest = (this && this.__rest) || function (s, e) {
7
- var t = {};
8
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
9
- t[p] = s[p];
10
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
11
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
12
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
13
- t[p[i]] = s[p[i]];
14
- }
15
- return t;
16
- };
17
6
  import { showDialog } from '@jupyterlab/apputils';
18
7
  import { CodeEditor } from '@jupyterlab/codeeditor';
19
8
  import { nullTranslator } from '@jupyterlab/translation';
20
9
  import { ArrayExt } from '@lumino/algorithm';
21
- import { JSONExt, UUID } from '@lumino/coreutils';
10
+ import { UUID } from '@lumino/coreutils';
22
11
  import { DisposableDelegate } from '@lumino/disposable';
23
12
  import { Poll } from '@lumino/polling';
24
13
  import { Signal } from '@lumino/signaling';
25
- import CodeMirror from 'codemirror';
26
- import 'codemirror/addon/comment/comment.js';
27
- import 'codemirror/addon/display/rulers.js';
28
- import 'codemirror/addon/edit/closebrackets.js';
29
- import 'codemirror/addon/edit/matchbrackets.js';
30
- import 'codemirror/addon/fold/brace-fold.js';
31
- import 'codemirror/addon/fold/comment-fold.js';
32
- import 'codemirror/addon/fold/foldcode.js';
33
- import 'codemirror/addon/fold/foldgutter.js';
34
- import 'codemirror/addon/fold/indent-fold.js';
35
- import 'codemirror/addon/fold/markdown-fold.js';
36
- import 'codemirror/addon/fold/xml-fold.js';
37
- import 'codemirror/addon/mode/simple';
38
- import 'codemirror/addon/scroll/scrollpastend.js';
39
- import 'codemirror/addon/search/jump-to-line';
40
- import 'codemirror/addon/search/search';
41
- import 'codemirror/addon/search/searchcursor';
42
- import 'codemirror/addon/selection/active-line';
43
- import 'codemirror/addon/selection/mark-selection';
44
- import 'codemirror/addon/selection/selection-pointer';
45
- import 'codemirror/addon/edit/trailingspace.js';
46
- import 'codemirror/keymap/emacs.js';
47
- import 'codemirror/keymap/sublime.js';
48
- import { CodemirrorBinding } from 'y-codemirror';
14
+ import { indentMore, insertNewlineAndIndent, insertTab } from '@codemirror/commands';
15
+ import { ensureSyntaxTree } from '@codemirror/language';
16
+ import { EditorSelection, EditorState, Prec, StateEffect, StateField } from '@codemirror/state';
17
+ import { Decoration, EditorView, WidgetType } from '@codemirror/view';
18
+ import { yCollab } from 'y-codemirror.next';
49
19
  import { Mode } from './mode';
50
- // import 'codemirror/keymap/vim.js'; lazy loading of vim mode is available in ../codemirror-extension/index.ts
20
+ import { Configuration } from './editorconfiguration';
21
+ import './codemirror-ipython';
22
+ import './codemirror-ipythongfm';
51
23
  /**
52
24
  * The class name added to CodeMirrorWidget instances.
53
25
  */
@@ -76,9 +48,6 @@ const DOWN_ARROW = 40;
76
48
  * The time that a collaborator name hover persists.
77
49
  */
78
50
  const HOVER_TIMEOUT = 1000;
79
- // @todo Remove the duality of having a modeldb and a y-codemirror
80
- // binding as it just introduces a lot of additional complexity without gaining anything.
81
- const USE_YCODEMIRROR_BINDING = true;
82
51
  /**
83
52
  * CodeMirror editor.
84
53
  */
@@ -87,18 +56,16 @@ export class CodeMirrorEditor {
87
56
  * Construct a CodeMirror editor.
88
57
  */
89
58
  constructor(options) {
90
- var _a;
91
59
  /**
92
60
  * A signal emitted when either the top or bottom edge is requested.
93
61
  */
94
62
  this.edgeRequested = new Signal(this);
95
- this.selectionMarkers = {};
63
+ this._selectionMarkers = {};
96
64
  this._keydownHandlers = new Array();
97
- this._changeGuard = false;
98
65
  this._uuid = '';
99
- this._needsRefresh = false;
100
66
  this._isDisposed = false;
101
67
  this._lastChange = null;
68
+ this._editorConfig = new Configuration.EditorConfiguration();
102
69
  const host = (this.host = options.host);
103
70
  this.translator = options.translator || nullTranslator;
104
71
  this._trans = this.translator.load('jupyterlab');
@@ -108,21 +75,110 @@ export class CodeMirrorEditor {
108
75
  host.addEventListener('blur', this, true);
109
76
  host.addEventListener('scroll', this, true);
110
77
  this._uuid = options.uuid || UUID.uuid4();
78
+ // State and effects for handling the selection marks
79
+ this._addMark = StateEffect.define();
80
+ this._removeMark = StateEffect.define();
81
+ this._markField = StateField.define({
82
+ create: () => {
83
+ return Decoration.none;
84
+ },
85
+ update: (marks, transaction) => {
86
+ marks = marks.map(transaction.changes);
87
+ for (let ef of transaction.effects) {
88
+ if (ef.is(this._addMark)) {
89
+ let e = ef;
90
+ const decorations = this._buildMarkDecoration(e.value.uuid, e.value.selections);
91
+ marks = marks.update({ add: decorations });
92
+ this._selectionMarkers[e.value.uuid] = decorations;
93
+ }
94
+ else if (ef.is(this._removeMark)) {
95
+ let e = ef;
96
+ for (let rd of ef.value.decorations) {
97
+ marks = marks.update({
98
+ filter: (from, to, value) => {
99
+ return !(from === rd.from &&
100
+ to === rd.to &&
101
+ value === rd.value);
102
+ }
103
+ });
104
+ }
105
+ delete this._selectionMarkers[e.value.uuid];
106
+ }
107
+ }
108
+ return marks;
109
+ },
110
+ provide: f => EditorView.decorations.from(f)
111
+ });
111
112
  // Handle selection style.
112
113
  const style = options.selectionStyle || {};
113
114
  this._selectionStyle = Object.assign(Object.assign({}, CodeEditor.defaultSelectionStyle), style);
114
115
  const model = (this._model = options.model);
115
116
  const config = options.config || {};
116
117
  const fullConfig = (this._config = Object.assign(Object.assign({}, CodeMirrorEditor.defaultConfig), config));
117
- const editor = (this._editor = Private.createEditor(host, fullConfig));
118
118
  this._initializeEditorBinding();
119
+ // Extension for handling DOM events
120
+ const domEventHandlers = EditorView.domEventHandlers({
121
+ keydown: (event, view) => {
122
+ const index = ArrayExt.findFirstIndex(this._keydownHandlers, handler => {
123
+ if (handler(this, event) === true) {
124
+ event.preventDefault();
125
+ return true;
126
+ }
127
+ return false;
128
+ });
129
+ if (index === -1) {
130
+ return this.onKeydown(event);
131
+ }
132
+ return false;
133
+ }
134
+ });
135
+ const updateListener = EditorView.updateListener.of((update) => {
136
+ this._onDocChanged(update);
137
+ });
138
+ // The list of internal strings is available at https://codemirror.net/examples/translate/
139
+ const translation = EditorState.phrases.of({
140
+ // @codemirror/view
141
+ 'Control character': this._trans.__('Control character'),
142
+ // @codemirror/commands
143
+ 'Selection deleted': this._trans.__('Selection deleted'),
144
+ // @codemirror/language
145
+ 'Folded lines': this._trans.__('Folded lines'),
146
+ 'Unfolded lines': this._trans.__('Unfolded lines'),
147
+ to: this._trans.__('to'),
148
+ 'folded code': this._trans.__('folded code'),
149
+ unfold: this._trans.__('unfold'),
150
+ 'Fold line': this._trans.__('Fold line'),
151
+ 'Unfold line': this._trans.__('Unfold line'),
152
+ // @codemirror/search
153
+ 'Go to line': this._trans.__('Go to line'),
154
+ go: this._trans.__('go'),
155
+ Find: this._trans.__('Find'),
156
+ Replace: this._trans.__('Replace'),
157
+ next: this._trans.__('next'),
158
+ previous: this._trans.__('previous'),
159
+ all: this._trans.__('all'),
160
+ 'match case': this._trans.__('match case'),
161
+ replace: this._trans.__('replace'),
162
+ 'replace all': this._trans.__('replace all'),
163
+ close: this._trans.__('close'),
164
+ 'current match': this._trans.__('current match'),
165
+ 'replaced $ matches': this._trans.__('replaced $ matches'),
166
+ 'replaced match on line $': this._trans.__('replaced match on line $'),
167
+ 'on line': this._trans.__('on line'),
168
+ // @codemirror/autocomplete
169
+ Completions: this._trans.__('Completions'),
170
+ // @codemirror/lint
171
+ Diagnostics: this._trans.__('Diagnostics'),
172
+ 'No diagnostics': this._trans.__('No diagnostics')
173
+ });
174
+ this._editor = Private.createEditor(host, fullConfig, this._yeditorBinding, this._editorConfig, [
175
+ this._markField,
176
+ Prec.high(domEventHandlers),
177
+ updateListener,
178
+ translation
179
+ ]);
119
180
  // every time the model is switched, we need to re-initialize the editor binding
120
181
  this.model.sharedModelSwitched.connect(this._initializeEditorBinding, this);
121
- const doc = editor.getDoc();
122
- // Handle initial values for text, mimetype, and selections.
123
- if (!USE_YCODEMIRROR_BINDING) {
124
- doc.setValue(model.value.text);
125
- }
126
182
  this._onMimeTypeChanged();
127
183
  this._onCursorActivity();
128
184
  this._poll = new Poll({
@@ -135,71 +191,19 @@ export class CodeMirrorEditor {
135
191
  return this._lastChange ? 'when-hidden' : true;
136
192
  }
137
193
  });
138
- // Connect to changes.
139
- if (!USE_YCODEMIRROR_BINDING) {
140
- model.value.changed.connect(this._onValueChanged, this);
141
- }
142
194
  model.mimeTypeChanged.connect(this._onMimeTypeChanged, this);
143
195
  model.selections.changed.connect(this._onSelectionsChanged, this);
144
- CodeMirror.on(editor, 'keydown', (editor, event) => {
145
- const index = ArrayExt.findFirstIndex(this._keydownHandlers, handler => {
146
- if (handler(this, event) === true) {
147
- event.preventDefault();
148
- return true;
149
- }
150
- return false;
151
- });
152
- if (index === -1) {
153
- this.onKeydown(event);
154
- }
155
- });
156
- if (USE_YCODEMIRROR_BINDING) {
157
- (_a = this._yeditorBinding) === null || _a === void 0 ? void 0 : _a.on('cursorActivity', () => this._onCursorActivity());
158
- }
159
- else {
160
- CodeMirror.on(editor, 'cursorActivity', () => this._onCursorActivity());
161
- CodeMirror.on(editor.getDoc(), 'beforeChange', (instance, change) => {
162
- this._beforeDocChanged(instance, change);
163
- });
164
- }
165
- CodeMirror.on(editor.getDoc(), 'change', (instance, change) => {
166
- // Manually refresh after setValue to make sure editor is properly sized.
167
- if (change.origin === 'setValue' && this.hasFocus()) {
168
- this.refresh();
169
- }
170
- this._lastChange = change;
171
- });
172
- // Turn off paste handling in codemirror since sometimes we want to
173
- // replace it with our own.
174
- editor.on('paste', (instance, event) => {
175
- var _a;
176
- const handlePaste = (_a = this._config['handlePaste']) !== null && _a !== void 0 ? _a : true;
177
- if (!handlePaste) {
178
- event.codemirrorIgnore = true;
179
- }
180
- });
181
- // Manually refresh on paste to make sure editor is properly sized.
182
- editor.getWrapperElement().addEventListener('paste', () => {
183
- if (this.hasFocus()) {
184
- this.refresh();
185
- }
186
- });
187
196
  }
188
197
  /**
189
198
  * Initialize the editor binding.
190
199
  */
191
200
  _initializeEditorBinding() {
192
- var _a;
193
- if (!USE_YCODEMIRROR_BINDING) {
194
- return;
195
- }
196
- (_a = this._yeditorBinding) === null || _a === void 0 ? void 0 : _a.destroy();
197
201
  const sharedModel = this.model.sharedModel;
198
- const opts = sharedModel.undoManager
199
- ? { yUndoManager: sharedModel.undoManager }
200
- : {};
201
- const awareness = sharedModel.awareness;
202
- this._yeditorBinding = new CodemirrorBinding(sharedModel.ysource, this.editor, awareness, opts);
202
+ this._yeditorBinding = {
203
+ text: sharedModel.ysource,
204
+ awareness: sharedModel.awareness,
205
+ undoManager: sharedModel.undoManager
206
+ };
203
207
  }
204
208
  /**
205
209
  * The uuid of this editor;
@@ -229,13 +233,13 @@ export class CodeMirrorEditor {
229
233
  * Get the codemirror doc wrapped by the widget.
230
234
  */
231
235
  get doc() {
232
- return this._editor.getDoc();
236
+ return this._editor.state.doc;
233
237
  }
234
238
  /**
235
239
  * Get the number of lines in the editor.
236
240
  */
237
241
  get lineCount() {
238
- return this.doc.lineCount();
242
+ return this.doc.lines;
239
243
  }
240
244
  /**
241
245
  * Returns a model for this editor.
@@ -247,13 +251,13 @@ export class CodeMirrorEditor {
247
251
  * The height of a line in the editor in pixels.
248
252
  */
249
253
  get lineHeight() {
250
- return this._editor.defaultTextHeight();
254
+ return this._editor.defaultLineHeight;
251
255
  }
252
256
  /**
253
257
  * The widget of a character in the editor in pixels.
254
258
  */
255
259
  get charWidth() {
256
- return this._editor.defaultCharWidth();
260
+ return this._editor.defaultCharacterWidth;
257
261
  }
258
262
  /**
259
263
  * Tests whether the editor is disposed.
@@ -272,12 +276,10 @@ export class CodeMirrorEditor {
272
276
  this.host.removeEventListener('focus', this, true);
273
277
  this.host.removeEventListener('blur', this, true);
274
278
  this.host.removeEventListener('scroll', this, true);
275
- if (this._yeditorBinding) {
276
- this._yeditorBinding.destroy();
277
- }
278
279
  this._keydownHandlers.length = 0;
279
280
  this._poll.dispose();
280
281
  Signal.clearData(this);
282
+ this.editor.destroy();
281
283
  }
282
284
  /**
283
285
  * Get a config option for the editor.
@@ -292,7 +294,7 @@ export class CodeMirrorEditor {
292
294
  // Don't bother setting the option if it is already the same.
293
295
  if (this._config[option] !== value) {
294
296
  this._config[option] = value;
295
- Private.setOption(this.editor, option, value, this._config);
297
+ this._editorConfig.reconfigureExtension(this._editor, option, value);
296
298
  }
297
299
  }
298
300
  /**
@@ -304,37 +306,34 @@ export class CodeMirrorEditor {
304
306
  * is set.
305
307
  */
306
308
  setOptions(options) {
307
- const editor = this._editor;
308
- editor.startOperation();
309
- for (const key in options) {
310
- const k = key;
311
- editor.operation(() => {
312
- this.setOption(k, options[k]);
313
- });
314
- }
315
- editor.endOperation();
309
+ this._config = Object.assign(Object.assign({}, this._config), options);
310
+ this._editorConfig.reconfigureExtensions(this._editor, options);
311
+ }
312
+ injectExtension(ext) {
313
+ this._editorConfig.injectExtension(this._editor, ext);
316
314
  }
317
315
  /**
318
316
  * Returns the content for the given line number.
319
317
  */
320
318
  getLine(line) {
321
- return this.doc.getLine(line);
319
+ // TODO: CM6 remove +1 when CM6 first line number has propagated
320
+ line = line + 1;
321
+ return line <= this.doc.lines ? this.doc.line(line).text : undefined;
322
322
  }
323
323
  /**
324
324
  * Find an offset for the given position.
325
325
  */
326
326
  getOffsetAt(position) {
327
- return this.doc.indexFromPos({
328
- ch: position.column,
329
- line: position.line
330
- });
327
+ // TODO: CM6 remove +1 when CM6 first line number has propagated
328
+ return this.doc.line(position.line + 1).from + position.column;
331
329
  }
332
330
  /**
333
331
  * Find a position for the given offset.
334
332
  */
335
333
  getPositionAt(offset) {
336
- const { ch, line } = this.doc.posFromIndex(offset);
337
- return { line, column: ch };
334
+ // TODO: CM6 remove -1 when CM6 first line number has propagated
335
+ const line = this.doc.lineAt(offset);
336
+ return { line: line.number - 1, column: offset - line.from };
338
337
  }
339
338
  /**
340
339
  * Undo one edit (if any undo events are stored).
@@ -353,7 +352,7 @@ export class CodeMirrorEditor {
353
352
  */
354
353
  clearHistory() {
355
354
  var _a, _b;
356
- (_b = (_a = this._yeditorBinding) === null || _a === void 0 ? void 0 : _a.yUndoManager) === null || _b === void 0 ? void 0 : _b.clear();
355
+ (_b = (_a = this._yeditorBinding) === null || _a === void 0 ? void 0 : _a.undoManager) === null || _b === void 0 ? void 0 : _b.clear();
357
356
  }
358
357
  /**
359
358
  * Brings browser focus to this editor text.
@@ -365,78 +364,41 @@ export class CodeMirrorEditor {
365
364
  * Test whether the editor has keyboard focus.
366
365
  */
367
366
  hasFocus() {
368
- return this._editor.getWrapperElement().contains(document.activeElement);
367
+ return this._editor.hasFocus;
369
368
  }
370
369
  /**
371
370
  * Explicitly blur the editor.
372
371
  */
373
372
  blur() {
374
- this._editor.getInputField().blur();
375
- }
376
- /**
377
- * Repaint editor.
378
- */
379
- refresh() {
380
- this._editor.refresh();
381
- this._needsRefresh = false;
373
+ this._editor.contentDOM.blur();
382
374
  }
383
375
  /**
384
376
  * Refresh the editor if it is focused;
385
377
  * otherwise postpone refreshing till focusing.
386
378
  */
387
379
  resizeToFit() {
388
- if (this.hasFocus()) {
389
- this.refresh();
390
- }
391
- else {
392
- this._needsRefresh = true;
393
- }
394
380
  this._clearHover();
395
381
  }
396
- // todo: docs, maybe define overlay options as a type?
397
- addOverlay(mode, options) {
398
- this._editor.addOverlay(mode, options);
399
- }
400
- removeOverlay(mode) {
401
- this._editor.removeOverlay(mode);
402
- }
403
- getSearchCursor(query, start, caseFold) {
404
- return this._editor.getDoc().getSearchCursor(query, start, caseFold);
405
- }
406
- getCursor(start) {
407
- return this._editor.getDoc().getCursor(start);
408
- }
409
382
  get state() {
410
383
  return this._editor.state;
411
384
  }
412
- operation(fn) {
413
- return this._editor.operation(fn);
414
- }
415
385
  firstLine() {
416
- return this._editor.getDoc().firstLine();
386
+ // TODO: return 1 when CM6 first line number has propagated
387
+ return 0;
417
388
  }
418
389
  lastLine() {
419
- return this._editor.getDoc().lastLine();
420
- }
421
- scrollIntoView(pos, margin) {
422
- this._editor.scrollIntoView(pos, margin);
423
- }
424
- scrollIntoViewCentered(pos) {
425
- var _a, _b;
426
- const top = this._editor.charCoords(pos, 'local').top;
427
- const height = this._editor.getWrapperElement().offsetHeight;
428
- (_b = (_a = this.host).scrollIntoView) === null || _b === void 0 ? void 0 : _b.call(_a, {
429
- behavior: 'auto',
430
- block: 'center',
431
- inline: 'center'
432
- });
433
- this._editor.scrollTo(null, top - height / 2);
390
+ return this.doc.lines - 1;
434
391
  }
435
392
  cursorCoords(where, mode) {
436
- return this._editor.cursorCoords(where, mode);
393
+ const selection = this.state.selection.main;
394
+ const pos = where ? selection.from : selection.to;
395
+ const rect = this.editor.coordsAtPos(pos);
396
+ return rect;
437
397
  }
438
398
  getRange(from, to, separator) {
439
- return this._editor.getDoc().getRange(from, to, separator);
399
+ const fromOffset = this.getOffsetAt(this._toPosition(from));
400
+ const toOffset = this.getOffsetAt(this._toPosition(to));
401
+ return this.state.sliceDoc(fromOffset, toOffset);
440
402
  }
441
403
  /**
442
404
  * Add a keydown handler to the editor.
@@ -451,41 +413,31 @@ export class CodeMirrorEditor {
451
413
  ArrayExt.removeAllWhere(this._keydownHandlers, val => val === handler);
452
414
  });
453
415
  }
454
- /**
455
- * Set the size of the editor in pixels.
456
- */
457
- setSize(dimension) {
458
- if (dimension) {
459
- this._editor.setSize(dimension.width, dimension.height);
460
- }
461
- else {
462
- this._editor.setSize(null, null);
463
- }
464
- this._needsRefresh = false;
465
- }
466
416
  /**
467
417
  * Reveal the given position in the editor.
468
418
  */
469
419
  revealPosition(position) {
470
- const cmPosition = this._toCodeMirrorPosition(position);
471
- this._editor.scrollIntoView(cmPosition);
420
+ const offset = this.getOffsetAt(position);
421
+ this._editor.dispatch({
422
+ effects: EditorView.scrollIntoView(offset)
423
+ });
472
424
  }
473
425
  /**
474
426
  * Reveal the given selection in the editor.
475
427
  */
476
428
  revealSelection(selection) {
477
- const range = {
478
- from: this._toCodeMirrorPosition(selection.start),
479
- to: this._toCodeMirrorPosition(selection.end)
480
- };
481
- this._editor.scrollIntoView(range);
429
+ const start = this.getOffsetAt(selection.start);
430
+ const end = this.getOffsetAt(selection.end);
431
+ this._editor.dispatch({
432
+ effects: EditorView.scrollIntoView(EditorSelection.range(start, end))
433
+ });
482
434
  }
483
435
  /**
484
436
  * Get the window coordinates given a cursor position.
485
437
  */
486
438
  getCoordinateForPosition(position) {
487
- const pos = this._toCodeMirrorPosition(position);
488
- const rect = this.editor.charCoords(pos, 'page');
439
+ const offset = this.getOffsetAt(position);
440
+ const rect = this.editor.coordsAtPos(offset);
489
441
  return rect;
490
442
  }
491
443
  /**
@@ -497,14 +449,18 @@ export class CodeMirrorEditor {
497
449
  * contained in the editor.
498
450
  */
499
451
  getPositionForCoordinate(coordinate) {
500
- return this._toPosition(this.editor.coordsChar(coordinate)) || null;
452
+ const offset = this.editor.posAtCoords({
453
+ x: coordinate.left,
454
+ y: coordinate.top
455
+ });
456
+ return this.getPositionAt(offset) || null;
501
457
  }
502
458
  /**
503
459
  * Returns the primary position of the cursor, never `null`.
504
460
  */
505
461
  getCursorPosition() {
506
- const cursor = this.doc.getCursor();
507
- return this._toPosition(cursor);
462
+ const offset = this.state.selection.main.head;
463
+ return this.getPositionAt(offset);
508
464
  }
509
465
  /**
510
466
  * Set the primary position of the cursor.
@@ -513,12 +469,15 @@ export class CodeMirrorEditor {
513
469
  * This will remove any secondary cursors.
514
470
  */
515
471
  setCursorPosition(position, options) {
516
- const cursor = this._toCodeMirrorPosition(position);
517
- this.doc.setCursor(cursor, undefined, options);
472
+ const offset = this.getOffsetAt(position);
473
+ this.editor.dispatch({
474
+ selection: { anchor: offset },
475
+ scrollIntoView: true
476
+ });
518
477
  // If the editor does not have focus, this cursor change
519
478
  // will get screened out in _onCursorsChanged(). Make an
520
479
  // exception for this method.
521
- if (!this.editor.hasFocus()) {
480
+ if (!this.editor.hasFocus) {
522
481
  this.model.selections.set(this.uuid, this.getSelections());
523
482
  }
524
483
  }
@@ -538,11 +497,15 @@ export class CodeMirrorEditor {
538
497
  * Gets the selections for all the cursors, never `null` or empty.
539
498
  */
540
499
  getSelections() {
541
- const selections = this.doc.listSelections();
500
+ const selections = this.state.selection.ranges; //= [{anchor: number, head: number}]
542
501
  if (selections.length > 0) {
543
- return selections.map(selection => this._toSelection(selection));
502
+ const sel = selections.map(r => ({
503
+ anchor: this._toCodeMirrorPosition(this.getPositionAt(r.from)),
504
+ head: this._toCodeMirrorPosition(this.getPositionAt(r.to))
505
+ }));
506
+ return sel.map(selection => this._toSelection(selection));
544
507
  }
545
- const cursor = this.doc.getCursor();
508
+ const cursor = this._toCodeMirrorPosition(this.getPositionAt(this.state.selection.main.head));
546
509
  const selection = this._toSelection({ anchor: cursor, head: cursor });
547
510
  return [selection];
548
511
  }
@@ -552,8 +515,10 @@ export class CodeMirrorEditor {
552
515
  * Passing an empty array resets a cursor position to the start of a document.
553
516
  */
554
517
  setSelections(selections) {
555
- const cmSelections = this._toCodeMirrorSelections(selections);
556
- this.doc.setSelections(cmSelections, 0);
518
+ const sel = selections.length
519
+ ? selections.map(r => EditorSelection.range(this.getOffsetAt(r.start), this.getOffsetAt(r.end)))
520
+ : [EditorSelection.range(0, 0)];
521
+ this.editor.dispatch({ selection: EditorSelection.create(sel) });
557
522
  }
558
523
  /**
559
524
  * Replaces the current selection with the given text.
@@ -561,41 +526,62 @@ export class CodeMirrorEditor {
561
526
  * @param text The text to be inserted.
562
527
  */
563
528
  replaceSelection(text) {
564
- this.doc.replaceSelection(text);
529
+ this.editor.dispatch(this.state.replaceSelection(text));
565
530
  }
566
531
  /**
567
532
  * Get a list of tokens for the current editor text content.
568
533
  */
569
534
  getTokens() {
570
- let tokens = [];
571
- for (let i = 0; i < this.lineCount; ++i) {
572
- const lineTokens = this.editor.getLineTokens(i).map(t => ({
573
- offset: this.getOffsetAt({ column: t.start, line: i }),
574
- value: t.string,
575
- type: t.type || ''
576
- }));
577
- tokens = tokens.concat(lineTokens);
535
+ const tokens = [];
536
+ const tree = ensureSyntaxTree(this.state, this.doc.length);
537
+ if (tree) {
538
+ tree.iterate({
539
+ enter: (node) => {
540
+ tokens.push({
541
+ value: this.state.sliceDoc(node.from, node.to),
542
+ offset: node.from,
543
+ type: node.name
544
+ });
545
+ return true;
546
+ }
547
+ });
578
548
  }
579
549
  return tokens;
580
550
  }
581
551
  /**
582
552
  * Get the token at a given editor position.
583
553
  */
584
- getTokenForPosition(position) {
585
- var _a;
586
- const cursor = this._toCodeMirrorPosition(position);
587
- const token = this.editor.getTokenAt(cursor);
588
- return {
589
- offset: this.getOffsetAt({ column: token.start, line: cursor.line }),
590
- value: token.string,
591
- type: (_a = token.type) !== null && _a !== void 0 ? _a : undefined
592
- };
554
+ getTokenAt(offset) {
555
+ const tree = ensureSyntaxTree(this.state, offset);
556
+ if (tree) {
557
+ const node = tree.resolveInner(offset);
558
+ return {
559
+ value: this.state.sliceDoc(node.from, node.to),
560
+ offset: node.from,
561
+ type: node.name
562
+ };
563
+ }
564
+ else {
565
+ return {
566
+ value: '',
567
+ offset: offset
568
+ };
569
+ }
570
+ }
571
+ /**
572
+ * Get the token a the cursor position.
573
+ */
574
+ getTokenAtCursor() {
575
+ return this.getTokenAt(this.state.selection.main.head);
593
576
  }
594
577
  /**
595
578
  * Insert a new indented line at the current cursor position.
596
579
  */
597
580
  newIndentedLine() {
598
- this.execCommand('newlineAndIndent');
581
+ insertNewlineAndIndent({
582
+ state: this.state,
583
+ dispatch: this.editor.dispatch
584
+ });
599
585
  }
600
586
  /**
601
587
  * Execute a codemirror command on the editor.
@@ -603,31 +589,28 @@ export class CodeMirrorEditor {
603
589
  * @param command - The name of the command to execute.
604
590
  */
605
591
  execCommand(command) {
606
- this._editor.execCommand(command);
592
+ command(this.editor);
607
593
  }
608
594
  /**
609
595
  * Handle keydown events from the editor.
610
596
  */
611
597
  onKeydown(event) {
612
- const position = this.getCursorPosition();
613
- const { line, column } = position;
614
- if (line === 0 && column === 0 && event.keyCode === UP_ARROW) {
598
+ const position = this.state.selection.main.head;
599
+ if (position === 0 && event.keyCode === UP_ARROW) {
615
600
  if (!event.shiftKey) {
616
601
  this.edgeRequested.emit('top');
617
602
  }
618
603
  return false;
619
604
  }
620
- if (line === 0 && event.keyCode === UP_ARROW) {
605
+ const line = this.doc.lineAt(position).number;
606
+ if (line === 1 && event.keyCode === UP_ARROW) {
621
607
  if (!event.shiftKey) {
622
608
  this.edgeRequested.emit('topLine');
623
609
  }
624
610
  return false;
625
611
  }
626
- const lastLine = this.lineCount - 1;
627
- const lastCh = this.getLine(lastLine).length;
628
- if (line === lastLine &&
629
- column === lastCh &&
630
- event.keyCode === DOWN_ARROW) {
612
+ const length = this.doc.length;
613
+ if (position === length && event.keyCode === DOWN_ARROW) {
631
614
  if (!event.shiftKey) {
632
615
  this.edgeRequested.emit('bottom');
633
616
  }
@@ -635,36 +618,16 @@ export class CodeMirrorEditor {
635
618
  }
636
619
  return false;
637
620
  }
638
- /**
639
- * Converts selections to code mirror selections.
640
- */
641
- _toCodeMirrorSelections(selections) {
642
- if (selections.length > 0) {
643
- return selections.map(selection => this._toCodeMirrorSelection(selection));
644
- }
645
- const position = { line: 0, ch: 0 };
646
- return [{ anchor: position, head: position }];
647
- }
648
621
  /**
649
622
  * Handles a mime type change.
650
623
  */
651
624
  _onMimeTypeChanged() {
652
625
  const mime = this._model.mimeType;
653
- const editor = this._editor;
654
- const extraKeys = (editor.getOption('extraKeys') ||
655
- {});
656
- const isCode = mime !== 'text/plain' && mime !== 'text/x-ipythongfm';
657
- if (isCode) {
658
- extraKeys['Backspace'] = 'delSpaceToPrevTabStop';
659
- }
660
- else {
661
- delete extraKeys['Backspace'];
662
- }
663
- this.setOption('extraKeys', extraKeys);
664
626
  // TODO: should we provide a hook for when the mode is done being set?
665
627
  void Mode.ensure(mime).then(spec => {
666
- var _a;
667
- this.setOption('mode', (_a = spec === null || spec === void 0 ? void 0 : spec.mime) !== null && _a !== void 0 ? _a : 'null');
628
+ if (spec) {
629
+ this._editorConfig.reconfigureExtension(this._editor, 'language', spec.support);
630
+ }
668
631
  });
669
632
  }
670
633
  /**
@@ -683,19 +646,15 @@ export class CodeMirrorEditor {
683
646
  * Clean selections for the given uuid.
684
647
  */
685
648
  _cleanSelections(uuid) {
686
- const markers = this.selectionMarkers[uuid];
687
- if (markers) {
688
- markers.forEach(marker => {
689
- marker.clear();
690
- });
691
- }
692
- delete this.selectionMarkers[uuid];
649
+ this.editor.dispatch({
650
+ effects: this._removeMark.of({
651
+ uuid: uuid,
652
+ decorations: this._selectionMarkers[uuid]
653
+ })
654
+ });
693
655
  }
694
- /**
695
- * Marks selections.
696
- */
697
- _markSelections(uuid, selections) {
698
- const markers = [];
656
+ _buildMarkDecoration(uuid, selections) {
657
+ const decorations = [];
699
658
  // If we are marking selections corresponding to an active hover,
700
659
  // remove it.
701
660
  if (uuid === this._hoverId) {
@@ -709,34 +668,73 @@ export class CodeMirrorEditor {
709
668
  }
710
669
  // Style each selection for the uuid.
711
670
  selections.forEach(selection => {
671
+ const from = selection.from;
672
+ const to = selection.to;
712
673
  // Only render selections if the start is not equal to the end.
713
674
  // In that case, we don't need to render the cursor.
714
- if (!JSONExt.deepEqual(selection.start, selection.end)) {
715
- // Selections only appear to render correctly if the anchor
716
- // is before the head in the document. That is, reverse selections
717
- // do not appear as intended.
718
- const forward = selection.start.line < selection.end.line ||
719
- (selection.start.line === selection.end.line &&
720
- selection.start.column <= selection.end.column);
721
- const anchor = this._toCodeMirrorPosition(forward ? selection.start : selection.end);
722
- const head = this._toCodeMirrorPosition(forward ? selection.end : selection.start);
723
- let markerOptions;
724
- if (collaborator) {
725
- markerOptions = this._toTextMarkerOptions(Object.assign(Object.assign({}, selection.style), { color: collaborator.color }));
726
- }
727
- else {
728
- markerOptions = this._toTextMarkerOptions(selection.style);
729
- }
730
- markers.push(this.doc.markText(anchor, head, markerOptions));
675
+ if (from !== to) {
676
+ const style = collaborator
677
+ ? Object.assign(Object.assign({}, selection.style), { color: collaborator.color }) : selection.style;
678
+ const decoration = Decoration.mark({
679
+ attributes: this._toMarkSpec(style)
680
+ });
681
+ decorations.push(from > to ? decoration.range(to, from) : decoration.range(to, from));
731
682
  }
732
683
  else if (collaborator) {
733
- const caret = this._getCaret(collaborator);
734
- markers.push(this.doc.setBookmark(this._toCodeMirrorPosition(selection.end), {
735
- widget: caret
736
- }));
684
+ const caret = Decoration.widget({
685
+ widget: this._getCaret(collaborator)
686
+ });
687
+ decorations.push(caret.range(from));
737
688
  }
738
689
  });
739
- this.selectionMarkers[uuid] = markers;
690
+ return decorations;
691
+ }
692
+ /**
693
+ * Converts the selection style to a text marker options.
694
+ */
695
+ _toMarkSpec(style) {
696
+ const r = parseInt(style.color.slice(1, 3), 16);
697
+ const g = parseInt(style.color.slice(3, 5), 16);
698
+ const b = parseInt(style.color.slice(5, 7), 16);
699
+ const css = `background-color: rgba( ${r}, ${g}, ${b}, 0.15)`;
700
+ return {
701
+ className: style.className,
702
+ title: style.displayName,
703
+ css
704
+ };
705
+ }
706
+ /**
707
+ * Construct a caret element representing the position
708
+ * of a collaborator's cursor.
709
+ */
710
+ _getCaret(collaborator) {
711
+ return new Private.CaretWidget(collaborator, {
712
+ setHoverId: (sessionId) => {
713
+ this._clearHover();
714
+ this._hoverId = sessionId;
715
+ },
716
+ setHoverTimeout: () => {
717
+ this._hoverTimeout = window.setTimeout(() => {
718
+ this._clearHover();
719
+ }, HOVER_TIMEOUT);
720
+ },
721
+ clearHoverTimeout: () => {
722
+ window.clearTimeout(this._hoverTimeout);
723
+ }
724
+ });
725
+ }
726
+ /**
727
+ * Marks selections.
728
+ */
729
+ _markSelections(uuid, selections) {
730
+ const sel = selections.map(selection => ({
731
+ from: this.getOffsetAt(selection.start),
732
+ to: this.getOffsetAt(selection.end),
733
+ style: selection.style
734
+ }));
735
+ this.editor.dispatch({
736
+ effects: this._addMark.of({ uuid: uuid, selections: sel })
737
+ });
740
738
  }
741
739
  /**
742
740
  * Handles a cursor activity event.
@@ -744,7 +742,7 @@ export class CodeMirrorEditor {
744
742
  _onCursorActivity() {
745
743
  // Only add selections if the editor has focus. This avoids unwanted
746
744
  // triggering of cursor activity due to collaborator actions.
747
- if (this._editor.hasFocus()) {
745
+ if (this._editor.hasFocus) {
748
746
  const selections = this.getSelections();
749
747
  this.model.selections.set(this.uuid, selections);
750
748
  }
@@ -760,29 +758,6 @@ export class CodeMirrorEditor {
760
758
  style: this.selectionStyle
761
759
  };
762
760
  }
763
- /**
764
- * Converts the selection style to a text marker options.
765
- */
766
- _toTextMarkerOptions(style) {
767
- const r = parseInt(style.color.slice(1, 3), 16);
768
- const g = parseInt(style.color.slice(3, 5), 16);
769
- const b = parseInt(style.color.slice(5, 7), 16);
770
- const css = `background-color: rgba( ${r}, ${g}, ${b}, 0.15)`;
771
- return {
772
- className: style.className,
773
- title: style.displayName,
774
- css
775
- };
776
- }
777
- /**
778
- * Converts an editor selection to a code mirror selection.
779
- */
780
- _toCodeMirrorSelection(selection) {
781
- return {
782
- anchor: this._toCodeMirrorPosition(selection.start),
783
- head: this._toCodeMirrorPosition(selection.end)
784
- };
785
- }
786
761
  /**
787
762
  * Convert a code mirror position to an editor position.
788
763
  */
@@ -801,60 +776,16 @@ export class CodeMirrorEditor {
801
776
  ch: position.column
802
777
  };
803
778
  }
804
- /**
805
- * Handle model value changes.
806
- */
807
- _onValueChanged(value, args) {
808
- if (this._changeGuard) {
809
- return;
810
- }
811
- this._changeGuard = true;
812
- const doc = this.doc;
813
- switch (args.type) {
814
- case 'insert': {
815
- const pos = doc.posFromIndex(args.start);
816
- // Replace the range, including a '+input' origin,
817
- // which indicates that CodeMirror may merge changes
818
- // for undo/redo purposes.
819
- doc.replaceRange(args.value, pos, pos, '+input');
820
- break;
821
- }
822
- case 'remove': {
823
- const from = doc.posFromIndex(args.start);
824
- const to = doc.posFromIndex(args.end);
825
- // Replace the range, including a '+input' origin,
826
- // which indicates that CodeMirror may merge changes
827
- // for undo/redo purposes.
828
- doc.replaceRange('', from, to, '+input');
829
- break;
830
- }
831
- case 'set':
832
- doc.setValue(args.value);
833
- break;
834
- default:
835
- break;
836
- }
837
- this._changeGuard = false;
838
- }
839
779
  /**
840
780
  * Handles document changes.
841
781
  */
842
- _beforeDocChanged(doc, change) {
843
- if (this._changeGuard) {
844
- return;
845
- }
846
- this._changeGuard = true;
847
- const value = this._model.value;
848
- const start = doc.indexFromPos(change.from);
849
- const end = doc.indexFromPos(change.to);
850
- const inserted = change.text.join('\n');
851
- if (end !== start) {
852
- value.remove(start, end);
782
+ _onDocChanged(update) {
783
+ if (update.transactions.length && update.transactions[0].selection) {
784
+ this._onCursorActivity();
853
785
  }
854
- if (inserted) {
855
- value.insert(start, inserted);
786
+ if (update.docChanged) {
787
+ this._lastChange = update.changes;
856
788
  }
857
- this._changeGuard = false;
858
789
  }
859
790
  /**
860
791
  * Handle the DOM events for the editor.
@@ -885,9 +816,6 @@ export class CodeMirrorEditor {
885
816
  * Handle `focus` events for the editor.
886
817
  */
887
818
  _evtFocus(event) {
888
- if (this._needsRefresh) {
889
- this.refresh();
890
- }
891
819
  this.host.classList.add('jp-mod-focused');
892
820
  // Update the selections on editor gaining focus because
893
821
  // the onCursorActivity function filters usual cursor events
@@ -919,49 +847,6 @@ export class CodeMirrorEditor {
919
847
  this._caretHover = null;
920
848
  }
921
849
  }
922
- /**
923
- * Construct a caret element representing the position
924
- * of a collaborator's cursor.
925
- */
926
- _getCaret(collaborator) {
927
- // FIXME-TRANS: Is this localizable?
928
- const name = collaborator ? collaborator.displayName : 'Anonymous';
929
- const color = collaborator
930
- ? collaborator.color
931
- : this._selectionStyle.color;
932
- const caret = document.createElement('span');
933
- caret.className = COLLABORATOR_CURSOR_CLASS;
934
- caret.style.borderBottomColor = color;
935
- caret.onmouseenter = () => {
936
- this._clearHover();
937
- this._hoverId = collaborator.sessionId;
938
- const rect = caret.getBoundingClientRect();
939
- // Construct and place the hover box.
940
- const hover = document.createElement('div');
941
- hover.className = COLLABORATOR_HOVER_CLASS;
942
- hover.style.left = String(rect.left) + 'px';
943
- hover.style.top = String(rect.bottom) + 'px';
944
- hover.textContent = name;
945
- hover.style.backgroundColor = color;
946
- // If the user mouses over the hover, take over the timer.
947
- hover.onmouseenter = () => {
948
- window.clearTimeout(this._hoverTimeout);
949
- };
950
- hover.onmouseleave = () => {
951
- this._hoverTimeout = window.setTimeout(() => {
952
- this._clearHover();
953
- }, HOVER_TIMEOUT);
954
- };
955
- this._caretHover = hover;
956
- document.body.appendChild(hover);
957
- };
958
- caret.onmouseleave = () => {
959
- this._hoverTimeout = window.setTimeout(() => {
960
- this._clearHover();
961
- }, HOVER_TIMEOUT);
962
- };
963
- return caret;
964
- }
965
850
  /**
966
851
  * Check for an out of sync editor.
967
852
  */
@@ -971,9 +856,8 @@ export class CodeMirrorEditor {
971
856
  return;
972
857
  }
973
858
  this._lastChange = null;
974
- const editor = this._editor;
975
- const doc = editor.getDoc();
976
- if (doc.getValue() === this._model.value.text) {
859
+ const doc = this.doc;
860
+ if (doc.toString() === this._model.value.text) {
977
861
  return;
978
862
  }
979
863
  void showDialog({
@@ -983,11 +867,10 @@ export class CodeMirrorEditor {
983
867
  console.warn('If you are able and willing to publicly share the text or code in your editor, you can help us debug the "Code Editor out of Sync" message by pasting the following to the public issue at https://github.com/jupyterlab/jupyterlab/issues/2951. Please note that the data below includes the text/code in your editor.');
984
868
  console.warn(JSON.stringify({
985
869
  model: this._model.value.text,
986
- view: doc.getValue(),
870
+ view: doc.toString(),
987
871
  selections: this.getSelections(),
988
872
  cursor: this.getCursorPosition(),
989
- lineSep: editor.getOption('lineSeparator'),
990
- mode: editor.getOption('mode'),
873
+ lineSep: this.state.facet(EditorState.lineSeparator),
991
874
  change
992
875
  }));
993
876
  }
@@ -999,222 +882,98 @@ export class CodeMirrorEditor {
999
882
  /**
1000
883
  * The default configuration options for an editor.
1001
884
  */
1002
- CodeMirrorEditor.defaultConfig = Object.assign(Object.assign({}, CodeEditor.defaultConfig), { mode: 'null', theme: 'jupyter', smartIndent: true, electricChars: true, keyMap: 'default', extraKeys: null, gutters: [], fixedGutter: true, showCursorWhenSelecting: false, coverGutterNextToScrollbar: false, dragDrop: true, lineSeparator: null, scrollbarStyle: 'native', lineWiseCopyCut: true, scrollPastEnd: false, styleActiveLine: false, styleSelectedText: true, selectionPointer: false, rulers: [], foldGutter: false, handlePaste: true });
885
+ CodeMirrorEditor.defaultConfig = Object.assign(Object.assign({}, CodeEditor.defaultConfig), { mode: 'null', theme: 'jupyter', smartIndent: true, electricChars: true, keyMap: 'default', extraKeys: null, gutters: [], fixedGutter: true, showCursorWhenSelecting: false, coverGutterNextToScrollbar: false, dragDrop: true, lineSeparator: null, scrollbarStyle: 'native', lineWiseCopyCut: true, scrollPastEnd: false, styleActiveLine: false, styleSelectedText: true, selectionPointer: false, handlePaste: true });
1003
886
  /**
1004
- * Add a command to CodeMirror.
1005
- *
1006
- * @param name - The name of the command to add.
1007
- *
1008
- * @param command - The command function.
887
+ * Indent or insert a tab as appropriate.
1009
888
  */
1010
- function addCommand(name, command) {
1011
- CodeMirror.commands[name] = command;
889
+ function indentMoreOrInsertTab(target) {
890
+ const arg = { state: target.state, dispatch: target.dispatch };
891
+ const from = target.state.selection.main.from;
892
+ const to = target.state.selection.main.to;
893
+ if (from != to) {
894
+ return indentMore(arg);
895
+ }
896
+ const line = target.state.doc.lineAt(from);
897
+ const before = target.state.doc.slice(line.from, from).toString();
898
+ if (/^\s*$/.test(before)) {
899
+ return indentMore(arg);
900
+ }
901
+ else {
902
+ return insertTab(arg);
903
+ }
1012
904
  }
1013
- CodeMirrorEditor.addCommand = addCommand;
905
+ CodeMirrorEditor.indentMoreOrInsertTab = indentMoreOrInsertTab;
1014
906
  })(CodeMirrorEditor || (CodeMirrorEditor = {}));
1015
907
  /**
1016
908
  * The namespace for module private data.
1017
909
  */
1018
910
  var Private;
1019
911
  (function (Private) {
1020
- function createEditor(host, config) {
1021
- const { autoClosingBrackets, fontFamily, fontSize, insertSpaces, lineHeight, lineWrap, wordWrapColumn, tabSize, readOnly } = config, otherOptions = __rest(config, ["autoClosingBrackets", "fontFamily", "fontSize", "insertSpaces", "lineHeight", "lineWrap", "wordWrapColumn", "tabSize", "readOnly"]);
1022
- const bareConfig = Object.assign({ autoCloseBrackets: autoClosingBrackets ? {} : false, indentUnit: tabSize, indentWithTabs: !insertSpaces, lineWrapping: lineWrap === 'off' ? false : true, readOnly }, otherOptions);
1023
- return CodeMirror(el => {
1024
- if (fontFamily) {
1025
- el.style.fontFamily = fontFamily;
1026
- }
1027
- if (fontSize) {
1028
- el.style.fontSize = fontSize + 'px';
1029
- }
1030
- if (lineHeight) {
1031
- el.style.lineHeight = lineHeight.toString();
1032
- }
1033
- if (readOnly) {
1034
- el.classList.add(READ_ONLY_CLASS);
1035
- }
1036
- if (lineWrap === 'wordWrapColumn') {
1037
- const lines = el.querySelector('.CodeMirror-lines');
1038
- lines.style.width = `${wordWrapColumn}ch`;
1039
- }
1040
- if (lineWrap === 'bounded') {
1041
- const lines = el.querySelector('.CodeMirror-lines');
1042
- lines.style.maxWidth = `${wordWrapColumn}ch`;
1043
- }
1044
- host.appendChild(el);
1045
- }, bareConfig);
1046
- }
1047
- Private.createEditor = createEditor;
1048
- /**
1049
- * Indent or insert a tab as appropriate.
1050
- */
1051
- function indentMoreOrinsertTab(cm) {
1052
- const doc = cm.getDoc();
1053
- const from = doc.getCursor('from');
1054
- const to = doc.getCursor('to');
1055
- const sel = !posEq(from, to);
1056
- if (sel) {
1057
- CodeMirror.commands['indentMore'](cm);
1058
- return;
1059
- }
1060
- // Check for start of line.
1061
- const line = doc.getLine(from.line);
1062
- const before = line.slice(0, from.ch);
1063
- if (/^\s*$/.test(before)) {
1064
- CodeMirror.commands['indentMore'](cm);
912
+ function createEditor(host, config, ybinding, editorConfig, additionalExtensions) {
913
+ var _a;
914
+ const extensions = editorConfig.getInitialExtensions(config);
915
+ if (ybinding) {
916
+ extensions.push(yCollab(ybinding.text, ybinding.awareness, {
917
+ undoManager: (_a = ybinding.undoManager) !== null && _a !== void 0 ? _a : false
918
+ }));
1065
919
  }
1066
- else {
1067
- if (cm.getOption('indentWithTabs')) {
1068
- CodeMirror.commands['insertTab'](cm);
1069
- }
1070
- else {
1071
- CodeMirror.commands['insertSoftTab'](cm);
1072
- }
920
+ extensions.push(...additionalExtensions);
921
+ const doc = ybinding === null || ybinding === void 0 ? void 0 : ybinding.text.toString();
922
+ const view = new EditorView({
923
+ state: EditorState.create({
924
+ doc,
925
+ extensions
926
+ }),
927
+ parent: host
928
+ });
929
+ if (config.readOnly) {
930
+ view.dom.classList.add(READ_ONLY_CLASS);
1073
931
  }
932
+ return view;
1074
933
  }
1075
- Private.indentMoreOrinsertTab = indentMoreOrinsertTab;
1076
- /**
1077
- * Delete spaces to the previous tab stop in a codemirror editor.
1078
- */
1079
- function delSpaceToPrevTabStop(cm) {
1080
- var _a;
1081
- const doc = cm.getDoc();
1082
- // default tabsize is 2, according to codemirror docs: https://codemirror.net/doc/manual.html#config
1083
- const tabSize = (_a = cm.getOption('indentUnit')) !== null && _a !== void 0 ? _a : 2;
1084
- const ranges = doc.listSelections(); // handle multicursor
1085
- for (let i = ranges.length - 1; i >= 0; i--) {
1086
- // iterate reverse so any deletions don't overlap
1087
- const head = ranges[i].head;
1088
- const anchor = ranges[i].anchor;
1089
- const isSelection = !posEq(head, anchor);
1090
- if (isSelection) {
1091
- doc.replaceRange('', anchor, head);
1092
- }
1093
- else {
1094
- const line = doc.getLine(head.line).substring(0, head.ch);
1095
- if (line.match(/^\ +$/) !== null) {
1096
- // delete tabs
1097
- const prevTabStop = (Math.ceil(head.ch / tabSize) - 1) * tabSize;
1098
- const from = CodeMirror.Pos(head.line, prevTabStop);
1099
- doc.replaceRange('', from, head);
1100
- }
1101
- else {
1102
- // delete non-tabs
1103
- const from = cm.findPosH(head, -1, 'char', false);
1104
- doc.replaceRange('', from, head);
1105
- }
1106
- }
934
+ Private.createEditor = createEditor;
935
+ class CaretWidget extends WidgetType {
936
+ constructor(collaborator, callbacks) {
937
+ super();
938
+ this.collaborator = collaborator;
939
+ this.callbacks = callbacks;
940
+ }
941
+ eq(other) {
942
+ return this.collaborator.sessionId == other.collaborator.sessionId;
943
+ }
944
+ toDOM() {
945
+ const caret = document.createElement('span');
946
+ caret.className = COLLABORATOR_CURSOR_CLASS;
947
+ caret.style.borderBottomColor = this.collaborator.color;
948
+ // TODO: check if this should be replaced with
949
+ // a global event handler
950
+ caret.onmouseenter = () => {
951
+ this.callbacks.setHoverId(this.collaborator.sessionId);
952
+ const rect = caret.getBoundingClientRect();
953
+ // Construct and place the hover box.
954
+ const hover = document.createElement('div');
955
+ hover.className = COLLABORATOR_HOVER_CLASS;
956
+ hover.style.left = `${rect.left}px`;
957
+ hover.style.top = `${rect.bottom}px`;
958
+ hover.textContent = this.collaborator.displayName;
959
+ hover.style.backgroundColor = this.collaborator.color;
960
+ // If the user mouses over the hover, take over the timer.
961
+ hover.onmouseenter = () => {
962
+ this.callbacks.clearHoverTimeout();
963
+ };
964
+ hover.onmouseleave = () => {
965
+ this.callbacks.setHoverTimeout();
966
+ };
967
+ };
968
+ caret.onmouseleave = () => {
969
+ this.callbacks.setHoverTimeout();
970
+ };
971
+ return caret;
1107
972
  }
1108
- }
1109
- Private.delSpaceToPrevTabStop = delSpaceToPrevTabStop;
1110
- /**
1111
- * Test whether two CodeMirror positions are equal.
1112
- */
1113
- function posEq(a, b) {
1114
- return a.line === b.line && a.ch === b.ch;
1115
- }
1116
- Private.posEq = posEq;
1117
- /**
1118
- * Get the list of active gutters
1119
- *
1120
- * @param config Editor configuration
1121
- */
1122
- function getActiveGutters(config) {
1123
- // The order of the classes will be the gutters order
1124
- const classToSwitch = {
1125
- 'CodeMirror-linenumbers': 'lineNumbers',
1126
- 'CodeMirror-foldgutter': 'codeFolding'
1127
- };
1128
- return Object.keys(classToSwitch).filter(gutter => config[classToSwitch[gutter]]);
1129
- }
1130
- /**
1131
- * Set a config option for the editor.
1132
- */
1133
- function setOption(editor, option, value, config) {
1134
- const el = editor.getWrapperElement();
1135
- switch (option) {
1136
- case 'cursorBlinkRate':
1137
- editor.setOption(option, value);
1138
- break;
1139
- case 'lineWrap': {
1140
- const lineWrapping = value === 'off' ? false : true;
1141
- const lines = el.querySelector('.CodeMirror-lines');
1142
- const maxWidth = value === 'bounded' ? `${config.wordWrapColumn}ch` : null;
1143
- const width = value === 'wordWrapColumn' ? `${config.wordWrapColumn}ch` : null;
1144
- lines.style.setProperty('max-width', maxWidth);
1145
- lines.style.setProperty('width', width);
1146
- editor.setOption('lineWrapping', lineWrapping);
1147
- break;
1148
- }
1149
- case 'wordWrapColumn': {
1150
- const { lineWrap } = config;
1151
- if (lineWrap === 'wordWrapColumn' || lineWrap === 'bounded') {
1152
- const lines = el.querySelector('.CodeMirror-lines');
1153
- const prop = lineWrap === 'wordWrapColumn' ? 'width' : 'maxWidth';
1154
- lines.style[prop] = `${value}ch`;
1155
- }
1156
- break;
1157
- }
1158
- case 'tabSize':
1159
- editor.setOption('indentUnit', value);
1160
- break;
1161
- case 'insertSpaces':
1162
- editor.setOption('indentWithTabs', !value);
1163
- break;
1164
- case 'autoClosingBrackets':
1165
- editor.setOption('autoCloseBrackets', value);
1166
- break;
1167
- case 'rulers': {
1168
- const rulers = value;
1169
- editor.setOption('rulers', rulers.map(column => {
1170
- return {
1171
- column,
1172
- className: 'jp-CodeMirror-ruler'
1173
- };
1174
- }));
1175
- break;
1176
- }
1177
- case 'readOnly':
1178
- el.classList.toggle(READ_ONLY_CLASS, value);
1179
- editor.setOption(option, value);
1180
- break;
1181
- case 'fontFamily':
1182
- el.style.fontFamily = value;
1183
- break;
1184
- case 'fontSize':
1185
- el.style.setProperty('font-size', value ? value + 'px' : null);
1186
- break;
1187
- case 'lineHeight':
1188
- el.style.lineHeight = (value ? value.toString() : null);
1189
- break;
1190
- case 'gutters':
1191
- editor.setOption(option, getActiveGutters(config));
1192
- break;
1193
- case 'lineNumbers':
1194
- editor.setOption(option, value);
1195
- editor.setOption('gutters', getActiveGutters(config));
1196
- break;
1197
- case 'codeFolding':
1198
- editor.setOption('foldGutter', value);
1199
- editor.setOption('gutters', getActiveGutters(config));
1200
- break;
1201
- case 'showTrailingSpace':
1202
- editor.setOption(option, value);
1203
- break;
1204
- default:
1205
- editor.setOption(option, value);
1206
- break;
973
+ ignoreEvent() {
974
+ return false;
1207
975
  }
1208
976
  }
1209
- Private.setOption = setOption;
977
+ Private.CaretWidget = CaretWidget;
1210
978
  })(Private || (Private = {}));
1211
- /**
1212
- * Add a CodeMirror command to delete until previous non blanking space
1213
- * character or first multiple of tabsize tabstop.
1214
- */
1215
- CodeMirrorEditor.addCommand('delSpaceToPrevTabStop', Private.delSpaceToPrevTabStop);
1216
- /**
1217
- * Add a CodeMirror command to indent or insert a tab as appropriate.
1218
- */
1219
- CodeMirrorEditor.addCommand('indentMoreOrinsertTab', Private.indentMoreOrinsertTab);
1220
979
  //# sourceMappingURL=editor.js.map