@jupyterlab/codemirror 4.0.0-alpha.19 → 4.0.0-alpha.20

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.
Files changed (78) hide show
  1. package/lib/codemirror-ipythongfm.d.ts +0 -1
  2. package/lib/codemirror-ipythongfm.js +0 -8
  3. package/lib/codemirror-ipythongfm.js.map +1 -1
  4. package/lib/commands.d.ts +13 -0
  5. package/lib/commands.js +32 -0
  6. package/lib/commands.js.map +1 -0
  7. package/lib/editor.d.ts +29 -68
  8. package/lib/editor.js +66 -212
  9. package/lib/editor.js.map +1 -1
  10. package/lib/extension.d.ts +236 -0
  11. package/lib/extension.js +687 -0
  12. package/lib/extension.js.map +1 -0
  13. package/lib/extensions/customStyle.d.ts +25 -0
  14. package/lib/extensions/customStyle.js +51 -0
  15. package/lib/extensions/customStyle.js.map +1 -0
  16. package/lib/extensions/index.d.ts +3 -0
  17. package/lib/extensions/index.js +8 -0
  18. package/lib/extensions/index.js.map +1 -0
  19. package/lib/extensions/rulers.d.ts +8 -0
  20. package/lib/extensions/rulers.js +85 -0
  21. package/lib/extensions/rulers.js.map +1 -0
  22. package/lib/extensions/ybinding.d.ts +125 -0
  23. package/lib/extensions/ybinding.js +213 -0
  24. package/lib/extensions/ybinding.js.map +1 -0
  25. package/lib/factory.d.ts +15 -5
  26. package/lib/factory.js +45 -37
  27. package/lib/factory.js.map +1 -1
  28. package/lib/index.d.ts +7 -20
  29. package/lib/index.js +7 -24
  30. package/lib/index.js.map +1 -1
  31. package/lib/language.d.ts +96 -0
  32. package/lib/language.js +1690 -0
  33. package/lib/language.js.map +1 -0
  34. package/lib/mimetype.d.ts +3 -0
  35. package/lib/mimetype.js +9 -4
  36. package/lib/mimetype.js.map +1 -1
  37. package/lib/searchprovider.d.ts +90 -80
  38. package/lib/searchprovider.js +221 -352
  39. package/lib/searchprovider.js.map +1 -1
  40. package/lib/theme.d.ts +57 -0
  41. package/lib/{editortheme.js → theme.js} +57 -30
  42. package/lib/theme.js.map +1 -0
  43. package/lib/token.d.ts +375 -0
  44. package/lib/token.js +18 -0
  45. package/lib/token.js.map +1 -0
  46. package/package.json +28 -36
  47. package/src/codemirror-ipythongfm.ts +109 -0
  48. package/src/commands.ts +34 -0
  49. package/src/editor.ts +783 -0
  50. package/src/extension.ts +878 -0
  51. package/src/extensions/customStyle.ts +79 -0
  52. package/src/extensions/index.ts +8 -0
  53. package/src/extensions/rulers.ts +112 -0
  54. package/src/extensions/ybinding.ts +267 -0
  55. package/src/factory.ts +100 -0
  56. package/src/index.ts +17 -0
  57. package/src/language.ts +1729 -0
  58. package/src/mimetype.ts +54 -0
  59. package/src/searchprovider.ts +700 -0
  60. package/src/theme.ts +219 -0
  61. package/src/token.ts +436 -0
  62. package/src/typings.d.ts +13 -0
  63. package/style/index.css +0 -3
  64. package/style/index.js +0 -3
  65. package/lib/codemirror-ipython.d.ts +0 -1
  66. package/lib/codemirror-ipython.js +0 -12
  67. package/lib/codemirror-ipython.js.map +0 -1
  68. package/lib/editorconfiguration.d.ts +0 -149
  69. package/lib/editorconfiguration.js +0 -289
  70. package/lib/editorconfiguration.js.map +0 -1
  71. package/lib/editortheme.d.ts +0 -41
  72. package/lib/editortheme.js.map +0 -1
  73. package/lib/mode.d.ts +0 -88
  74. package/lib/mode.js +0 -1518
  75. package/lib/mode.js.map +0 -1
  76. package/lib/syntaxstatus.d.ts +0 -67
  77. package/lib/syntaxstatus.js +0 -146
  78. package/lib/syntaxstatus.js.map +0 -1
package/src/editor.ts ADDED
@@ -0,0 +1,783 @@
1
+ /* eslint-disable @typescript-eslint/ban-types */
2
+ // Copyright (c) Jupyter Development Team.
3
+ // Distributed under the terms of the Modified BSD License.
4
+
5
+ import { insertNewlineAndIndent } from '@codemirror/commands';
6
+ import { ensureSyntaxTree } from '@codemirror/language';
7
+ import {
8
+ Compartment,
9
+ EditorSelection,
10
+ EditorState,
11
+ Extension,
12
+ Prec,
13
+ StateCommand,
14
+ Text
15
+ } from '@codemirror/state';
16
+ import { Command, EditorView, ViewUpdate } from '@codemirror/view';
17
+ import { CodeEditor } from '@jupyterlab/codeeditor';
18
+ import { SyntaxNodeRef } from '@lezer/common';
19
+ import { UUID } from '@lumino/coreutils';
20
+ import { Signal } from '@lumino/signaling';
21
+ import './codemirror-ipythongfm';
22
+ import { ExtensionsHandler } from './extension';
23
+ import { EditorLanguageRegistry } from './language';
24
+ import {
25
+ IEditorExtensionRegistry,
26
+ IEditorLanguageRegistry,
27
+ IExtensionsHandler
28
+ } from './token';
29
+
30
+ /**
31
+ * The class name added to CodeMirrorWidget instances.
32
+ */
33
+ const EDITOR_CLASS = 'jp-CodeMirrorEditor';
34
+
35
+ /**
36
+ * The key code for the up arrow key.
37
+ */
38
+ const UP_ARROW = 38;
39
+
40
+ /**
41
+ * The key code for the down arrow key.
42
+ */
43
+ const DOWN_ARROW = 40;
44
+
45
+ /**
46
+ * CodeMirror editor.
47
+ */
48
+ export class CodeMirrorEditor implements CodeEditor.IEditor {
49
+ /**
50
+ * Construct a CodeMirror editor.
51
+ */
52
+ constructor(options: CodeMirrorEditor.IOptions) {
53
+ this._languages = options.languages ?? new EditorLanguageRegistry();
54
+ this._configurator =
55
+ options.extensionsRegistry?.createNew({
56
+ ...options,
57
+ inline: options.inline ?? false
58
+ }) ?? new ExtensionsHandler();
59
+ const host = (this.host = options.host);
60
+
61
+ host.classList.add(EDITOR_CLASS);
62
+ host.classList.add('jp-Editor');
63
+ host.addEventListener('focus', this, true);
64
+ host.addEventListener('blur', this, true);
65
+ host.addEventListener('scroll', this, true);
66
+
67
+ this._uuid = options.uuid ?? UUID.uuid4();
68
+
69
+ const model = (this._model = options.model);
70
+
71
+ // Default keydown handler - it will have low priority
72
+ const onKeyDown = EditorView.domEventHandlers({
73
+ keydown: (event: KeyboardEvent, view: EditorView) => {
74
+ return this.onKeydown(event);
75
+ }
76
+ });
77
+
78
+ const updateListener = EditorView.updateListener.of(
79
+ (update: ViewUpdate) => {
80
+ this._onDocChanged(update);
81
+ }
82
+ );
83
+
84
+ this._editor = Private.createEditor(
85
+ host,
86
+ this._configurator,
87
+ [
88
+ // We need to set the order to high, otherwise the keybinding for ArrowUp/ArrowDown
89
+ // will process the event shunting our edge detection code.
90
+ Prec.high(onKeyDown),
91
+ updateListener,
92
+ // Initialize with empty extension
93
+ this._language.of([]),
94
+ ...(options.extensions ?? [])
95
+ ],
96
+ model.sharedModel.source
97
+ );
98
+
99
+ this._onMimeTypeChanged();
100
+ this._onCursorActivity();
101
+
102
+ this._configurator.configChanged.connect(this.onConfigChanged, this);
103
+ model.mimeTypeChanged.connect(this._onMimeTypeChanged, this);
104
+ }
105
+
106
+ /**
107
+ * A signal emitted when either the top or bottom edge is requested.
108
+ */
109
+ readonly edgeRequested = new Signal<this, CodeEditor.EdgeLocation>(this);
110
+
111
+ /**
112
+ * The DOM node that hosts the editor.
113
+ */
114
+ readonly host: HTMLElement;
115
+
116
+ /**
117
+ * The uuid of this editor;
118
+ */
119
+ get uuid(): string {
120
+ return this._uuid;
121
+ }
122
+ set uuid(value: string) {
123
+ this._uuid = value;
124
+ }
125
+
126
+ /**
127
+ * Get the codemirror editor wrapped by the editor.
128
+ */
129
+ get editor(): EditorView {
130
+ return this._editor;
131
+ }
132
+
133
+ /**
134
+ * Get the codemirror doc wrapped by the widget.
135
+ */
136
+ get doc(): Text {
137
+ return this._editor.state.doc;
138
+ }
139
+
140
+ /**
141
+ * Get the number of lines in the editor.
142
+ */
143
+ get lineCount(): number {
144
+ return this.doc.lines;
145
+ }
146
+
147
+ /**
148
+ * Returns a model for this editor.
149
+ */
150
+ get model(): CodeEditor.IModel {
151
+ return this._model;
152
+ }
153
+
154
+ /**
155
+ * The height of a line in the editor in pixels.
156
+ */
157
+ get lineHeight(): number {
158
+ return this._editor.defaultLineHeight;
159
+ }
160
+
161
+ /**
162
+ * The widget of a character in the editor in pixels.
163
+ */
164
+ get charWidth(): number {
165
+ return this._editor.defaultCharacterWidth;
166
+ }
167
+
168
+ /**
169
+ * Tests whether the editor is disposed.
170
+ */
171
+ get isDisposed(): boolean {
172
+ return this._isDisposed;
173
+ }
174
+
175
+ /**
176
+ * Dispose of the resources held by the widget.
177
+ */
178
+ dispose(): void {
179
+ if (this.isDisposed) {
180
+ return;
181
+ }
182
+ this._isDisposed = true;
183
+ this.host.removeEventListener('focus', this, true);
184
+ this.host.removeEventListener('blur', this, true);
185
+ this.host.removeEventListener('scroll', this, true);
186
+ this._configurator.dispose();
187
+ Signal.clearData(this);
188
+ this.editor.destroy();
189
+ }
190
+
191
+ /**
192
+ * Get a config option for the editor.
193
+ */
194
+ getOption(option: string): unknown {
195
+ return this._configurator.getOption(option);
196
+ }
197
+
198
+ /**
199
+ * Whether the option exists or not.
200
+ */
201
+ hasOption(option: string): boolean {
202
+ return this._configurator.hasOption(option);
203
+ }
204
+
205
+ /**
206
+ * Set a config option for the editor.
207
+ */
208
+ setOption(option: string, value: unknown): void {
209
+ this._configurator.setOption(option, value);
210
+ }
211
+
212
+ /**
213
+ * Set config options for the editor.
214
+ *
215
+ * This method is preferred when setting several options. The
216
+ * options are set within an operation, which only performs
217
+ * the costly update at the end, and not after every option
218
+ * is set.
219
+ */
220
+ setOptions(options: Record<string, any>): void {
221
+ this._configurator.setOptions(options);
222
+ }
223
+
224
+ /**
225
+ * Inject an extension into the editor
226
+ *
227
+ * @alpha
228
+ * @experimental
229
+ * @param ext CodeMirror 6 extension
230
+ */
231
+ injectExtension(ext: Extension): void {
232
+ this._configurator.injectExtension(this._editor, ext);
233
+ }
234
+
235
+ /**
236
+ * Returns the content for the given line number.
237
+ */
238
+ getLine(line: number): string | undefined {
239
+ // TODO: CM6 remove +1 when CM6 first line number has propagated
240
+ line = line + 1;
241
+ return line <= this.doc.lines ? this.doc.line(line).text : undefined;
242
+ }
243
+
244
+ /**
245
+ * Find an offset for the given position.
246
+ */
247
+ getOffsetAt(position: CodeEditor.IPosition): number {
248
+ // TODO: CM6 remove +1 when CM6 first line number has propagated
249
+ return this.doc.line(position.line + 1).from + position.column;
250
+ }
251
+
252
+ /**
253
+ * Find a position for the given offset.
254
+ */
255
+ getPositionAt(offset: number): CodeEditor.IPosition {
256
+ // TODO: CM6 remove -1 when CM6 first line number has propagated
257
+ const line = this.doc.lineAt(offset);
258
+ return { line: line.number - 1, column: offset - line.from };
259
+ }
260
+
261
+ /**
262
+ * Undo one edit (if any undo events are stored).
263
+ */
264
+ undo(): void {
265
+ this.model.sharedModel.undo();
266
+ }
267
+
268
+ /**
269
+ * Redo one undone edit.
270
+ */
271
+ redo(): void {
272
+ this.model.sharedModel.redo();
273
+ }
274
+
275
+ /**
276
+ * Clear the undo history.
277
+ */
278
+ clearHistory(): void {
279
+ this.model.sharedModel.clearUndoHistory();
280
+ }
281
+
282
+ /**
283
+ * Brings browser focus to this editor text.
284
+ */
285
+ focus(): void {
286
+ this._editor.focus();
287
+ }
288
+
289
+ /**
290
+ * Test whether the editor has keyboard focus.
291
+ */
292
+ hasFocus(): boolean {
293
+ return this._editor.hasFocus;
294
+ }
295
+
296
+ /**
297
+ * Explicitly blur the editor.
298
+ */
299
+ blur(): void {
300
+ this._editor.contentDOM.blur();
301
+ }
302
+
303
+ get state(): EditorState {
304
+ return this._editor.state;
305
+ }
306
+
307
+ firstLine(): number {
308
+ // TODO: return 1 when CM6 first line number has propagated
309
+ return 0;
310
+ }
311
+
312
+ lastLine(): number {
313
+ return this.doc.lines - 1;
314
+ }
315
+
316
+ cursorCoords(
317
+ where: boolean,
318
+ mode?: 'window' | 'page' | 'local'
319
+ ): { left: number; top: number; bottom: number } {
320
+ const selection = this.state.selection.main;
321
+ const pos = where ? selection.from : selection.to;
322
+ const rect = this.editor.coordsAtPos(pos);
323
+ return rect as { left: number; top: number; bottom: number };
324
+ }
325
+
326
+ getRange(
327
+ from: { line: number; ch: number },
328
+ to: { line: number; ch: number },
329
+ separator?: string
330
+ ): string {
331
+ const fromOffset = this.getOffsetAt(this._toPosition(from));
332
+ const toOffset = this.getOffsetAt(this._toPosition(to));
333
+ return this.state.sliceDoc(fromOffset, toOffset);
334
+ }
335
+
336
+ /**
337
+ * Reveal the given position in the editor.
338
+ */
339
+ revealPosition(position: CodeEditor.IPosition): void {
340
+ const offset = this.getOffsetAt(position);
341
+ this._editor.dispatch({
342
+ effects: EditorView.scrollIntoView(offset)
343
+ });
344
+ }
345
+
346
+ /**
347
+ * Reveal the given selection in the editor.
348
+ */
349
+ revealSelection(selection: CodeEditor.IRange): void {
350
+ const start = this.getOffsetAt(selection.start);
351
+ const end = this.getOffsetAt(selection.end);
352
+ this._editor.dispatch({
353
+ effects: EditorView.scrollIntoView(EditorSelection.range(start, end))
354
+ });
355
+ }
356
+
357
+ /**
358
+ * Get the window coordinates given a cursor position.
359
+ */
360
+ getCoordinateForPosition(
361
+ position: CodeEditor.IPosition
362
+ ): CodeEditor.ICoordinate {
363
+ const offset = this.getOffsetAt(position);
364
+ const rect = this.editor.coordsAtPos(offset);
365
+ return rect as CodeEditor.ICoordinate;
366
+ }
367
+
368
+ /**
369
+ * Get the cursor position given window coordinates.
370
+ *
371
+ * @param coordinate - The desired coordinate.
372
+ *
373
+ * @returns The position of the coordinates, or null if not
374
+ * contained in the editor.
375
+ */
376
+ getPositionForCoordinate(
377
+ coordinate: CodeEditor.ICoordinate
378
+ ): CodeEditor.IPosition | null {
379
+ const offset = this.editor.posAtCoords({
380
+ x: coordinate.left,
381
+ y: coordinate.top
382
+ });
383
+ return this.getPositionAt(offset!) || null;
384
+ }
385
+
386
+ /**
387
+ * Returns the primary position of the cursor, never `null`.
388
+ */
389
+ getCursorPosition(): CodeEditor.IPosition {
390
+ const offset = this.state.selection.main.head;
391
+ return this.getPositionAt(offset);
392
+ }
393
+
394
+ /**
395
+ * Set the primary position of the cursor.
396
+ *
397
+ * #### Notes
398
+ * This will remove any secondary cursors.
399
+ */
400
+ setCursorPosition(
401
+ position: CodeEditor.IPosition,
402
+ options?: { bias?: number; origin?: string; scroll?: boolean }
403
+ ): void {
404
+ const offset = this.getOffsetAt(position);
405
+ this.editor.dispatch({
406
+ selection: { anchor: offset },
407
+ scrollIntoView: true
408
+ });
409
+ // If the editor does not have focus, this cursor change
410
+ // will get screened out in _onCursorsChanged(). Make an
411
+ // exception for this method.
412
+ if (!this.editor.hasFocus) {
413
+ this.model.selections.set(this.uuid, this.getSelections());
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Returns the primary selection, never `null`.
419
+ */
420
+ getSelection(): CodeEditor.ITextSelection {
421
+ return this.getSelections()[0];
422
+ }
423
+
424
+ /**
425
+ * Set the primary selection. This will remove any secondary cursors.
426
+ */
427
+ setSelection(selection: CodeEditor.IRange): void {
428
+ this.setSelections([selection]);
429
+ }
430
+
431
+ /**
432
+ * Gets the selections for all the cursors, never `null` or empty.
433
+ */
434
+ getSelections(): CodeEditor.ITextSelection[] {
435
+ const selections = this.state.selection.ranges; //= [{anchor: number, head: number}]
436
+ if (selections.length > 0) {
437
+ const sel = selections.map(r => ({
438
+ anchor: this._toCodeMirrorPosition(this.getPositionAt(r.from)),
439
+ head: this._toCodeMirrorPosition(this.getPositionAt(r.to))
440
+ }));
441
+ return sel.map(selection => this._toSelection(selection));
442
+ }
443
+ const cursor = this._toCodeMirrorPosition(
444
+ this.getPositionAt(this.state.selection.main.head)
445
+ );
446
+ const selection = this._toSelection({ anchor: cursor, head: cursor });
447
+ return [selection];
448
+ }
449
+
450
+ /**
451
+ * Sets the selections for all the cursors, should not be empty.
452
+ * Cursors will be removed or added, as necessary.
453
+ * Passing an empty array resets a cursor position to the start of a document.
454
+ */
455
+ setSelections(selections: CodeEditor.IRange[]): void {
456
+ const sel = selections.length
457
+ ? selections.map(r =>
458
+ EditorSelection.range(
459
+ this.getOffsetAt(r.start),
460
+ this.getOffsetAt(r.end)
461
+ )
462
+ )
463
+ : [EditorSelection.range(0, 0)];
464
+ this.editor.dispatch({ selection: EditorSelection.create(sel) });
465
+ }
466
+
467
+ /**
468
+ * Replaces the current selection with the given text.
469
+ *
470
+ * Behaviour for multiple selections is undefined.
471
+ *
472
+ * @param text The text to be inserted.
473
+ */
474
+ replaceSelection(text: string): void {
475
+ const firstSelection = this.getSelections()[0];
476
+ this.model.sharedModel.updateSource(
477
+ this.getOffsetAt(firstSelection.start),
478
+ this.getOffsetAt(firstSelection.end),
479
+ text
480
+ );
481
+ const newPosition = this.getPositionAt(
482
+ this.getOffsetAt(firstSelection.start) + text.length
483
+ );
484
+ this.setSelection({ start: newPosition, end: newPosition });
485
+ }
486
+
487
+ /**
488
+ * Get a list of tokens for the current editor text content.
489
+ */
490
+ getTokens(): CodeEditor.IToken[] {
491
+ const tokens: CodeEditor.IToken[] = [];
492
+ const tree = ensureSyntaxTree(this.state, this.doc.length);
493
+ if (tree) {
494
+ tree.iterate({
495
+ enter: (node: SyntaxNodeRef) => {
496
+ // If it has a child, it is not a leaf, but we still want to enter
497
+ if (node.node.firstChild !== null) {
498
+ return true;
499
+ }
500
+ tokens.push({
501
+ value: this.state.sliceDoc(node.from, node.to),
502
+ offset: node.from,
503
+ type: node.name
504
+ });
505
+ return true;
506
+ }
507
+ });
508
+ }
509
+ return tokens;
510
+ }
511
+
512
+ /**
513
+ * Get the token at a given editor position.
514
+ */
515
+ getTokenAt(offset: number): CodeEditor.IToken {
516
+ const tree = ensureSyntaxTree(this.state, offset);
517
+ let token: CodeEditor.IToken | null = null;
518
+ if (tree) {
519
+ tree.iterate({
520
+ enter: (node: SyntaxNodeRef) => {
521
+ // If it has a child, it is not a leaf, but we still want to enter
522
+ if (node.node.firstChild !== null) {
523
+ return true;
524
+ }
525
+ if (offset >= node.from && offset <= node.to) {
526
+ token = {
527
+ value: this.state.sliceDoc(node.from, node.to),
528
+ offset: node.from,
529
+ type: node.name
530
+ };
531
+ // We have just found the relevant leaf token, no need to iterate further
532
+ return false;
533
+ }
534
+ return true;
535
+ }
536
+ });
537
+ }
538
+ if (token !== null) {
539
+ return token;
540
+ } else {
541
+ return {
542
+ value: '',
543
+ offset: offset
544
+ };
545
+ }
546
+ }
547
+
548
+ /**
549
+ * Get the token a the cursor position.
550
+ */
551
+ getTokenAtCursor(): CodeEditor.IToken {
552
+ return this.getTokenAt(this.state.selection.main.head);
553
+ }
554
+
555
+ /**
556
+ * Insert a new indented line at the current cursor position.
557
+ */
558
+ newIndentedLine(): void {
559
+ insertNewlineAndIndent({
560
+ state: this.state,
561
+ dispatch: this.editor.dispatch
562
+ });
563
+ }
564
+
565
+ /**
566
+ * Execute a codemirror command on the editor.
567
+ *
568
+ * @param command - The name of the command to execute.
569
+ */
570
+ execCommand(command: Command | StateCommand): void {
571
+ command(this.editor);
572
+ }
573
+
574
+ protected onConfigChanged(
575
+ configurator: IExtensionsHandler,
576
+ changes: Record<string, any>
577
+ ): void {
578
+ configurator.reconfigureExtensions(this._editor, changes);
579
+ }
580
+
581
+ /**
582
+ * Handle keydown events from the editor.
583
+ */
584
+ protected onKeydown(event: KeyboardEvent): boolean {
585
+ const position = this.state.selection.main.head;
586
+
587
+ if (position === 0 && event.keyCode === UP_ARROW) {
588
+ if (!event.shiftKey) {
589
+ this.edgeRequested.emit('top');
590
+ }
591
+ return false;
592
+ }
593
+
594
+ const line = this.doc.lineAt(position).number;
595
+ if (line === 1 && event.keyCode === UP_ARROW) {
596
+ if (!event.shiftKey) {
597
+ this.edgeRequested.emit('topLine');
598
+ }
599
+ return false;
600
+ }
601
+
602
+ const length = this.doc.length;
603
+ if (position === length && event.keyCode === DOWN_ARROW) {
604
+ if (!event.shiftKey) {
605
+ this.edgeRequested.emit('bottom');
606
+ }
607
+ return false;
608
+ }
609
+
610
+ return false;
611
+ }
612
+
613
+ /**
614
+ * Handles a mime type change.
615
+ */
616
+ private _onMimeTypeChanged(): void {
617
+ // TODO: should we provide a hook for when the mode is done being set?
618
+ this._languages
619
+ .getLanguage(this._model.mimeType)
620
+ .then(language => {
621
+ this._editor.dispatch({
622
+ effects: this._language.reconfigure(language?.support ?? [])
623
+ });
624
+ })
625
+ .catch(reason => {
626
+ console.log(
627
+ `Failed to load language for '${this._model.mimeType}'.`,
628
+ reason
629
+ );
630
+ this._editor.dispatch({
631
+ effects: this._language.reconfigure([])
632
+ });
633
+ });
634
+ }
635
+
636
+ /**
637
+ * Handles a cursor activity event.
638
+ */
639
+ private _onCursorActivity(): void {
640
+ // Only add selections if the editor has focus. This avoids unwanted
641
+ // triggering of cursor activity due to collaborator actions.
642
+ if (this._editor.hasFocus) {
643
+ const selections = this.getSelections();
644
+ this.model.selections.set(this.uuid, selections);
645
+ }
646
+ }
647
+
648
+ /**
649
+ * Converts a code mirror selection to an editor selection.
650
+ */
651
+ private _toSelection(selection: {
652
+ anchor: { line: number; ch: number };
653
+ head: { line: number; ch: number };
654
+ }): CodeEditor.ITextSelection {
655
+ return {
656
+ uuid: this.uuid,
657
+ start: this._toPosition(selection.anchor),
658
+ end: this._toPosition(selection.head)
659
+ };
660
+ }
661
+
662
+ /**
663
+ * Convert a code mirror position to an editor position.
664
+ */
665
+ private _toPosition(position: { line: number; ch: number }) {
666
+ return {
667
+ line: position.line,
668
+ column: position.ch
669
+ };
670
+ }
671
+
672
+ /**
673
+ * Convert an editor position to a code mirror position.
674
+ */
675
+ private _toCodeMirrorPosition(position: CodeEditor.IPosition) {
676
+ return {
677
+ line: position.line,
678
+ ch: position.column
679
+ };
680
+ }
681
+
682
+ /**
683
+ * Handles document changes.
684
+ */
685
+ private _onDocChanged(update: ViewUpdate) {
686
+ if (update.transactions.length && update.transactions[0].selection) {
687
+ this._onCursorActivity();
688
+ }
689
+ }
690
+
691
+ /**
692
+ * Handle the DOM events for the editor.
693
+ *
694
+ * @param event - The DOM event sent to the editor.
695
+ *
696
+ * #### Notes
697
+ * This method implements the DOM `EventListener` interface and is
698
+ * called in response to events on the editor's DOM node. It should
699
+ * not be called directly by user code.
700
+ */
701
+ handleEvent(event: Event): void {
702
+ switch (event.type) {
703
+ case 'focus':
704
+ this._evtFocus(event as FocusEvent);
705
+ break;
706
+ case 'blur':
707
+ this._evtBlur(event as FocusEvent);
708
+ break;
709
+ default:
710
+ break;
711
+ }
712
+ }
713
+
714
+ /**
715
+ * Handle `focus` events for the editor.
716
+ */
717
+ private _evtFocus(event: FocusEvent): void {
718
+ this.host.classList.add('jp-mod-focused');
719
+
720
+ // Update the selections on editor gaining focus because
721
+ // the onCursorActivity function filters usual cursor events
722
+ // based on the editor's focus.
723
+ this._onCursorActivity();
724
+ }
725
+
726
+ /**
727
+ * Handle `blur` events for the editor.
728
+ */
729
+ private _evtBlur(event: FocusEvent): void {
730
+ this.host.classList.remove('jp-mod-focused');
731
+ }
732
+
733
+ private _configurator: IExtensionsHandler;
734
+ private _editor: EditorView;
735
+ private _isDisposed = false;
736
+ private _language = new Compartment();
737
+ private _languages: IEditorLanguageRegistry;
738
+ private _model: CodeEditor.IModel;
739
+ private _uuid = '';
740
+ }
741
+
742
+ /**
743
+ * The namespace for `CodeMirrorEditor` statics.
744
+ */
745
+ export namespace CodeMirrorEditor {
746
+ /**
747
+ * The options used to initialize a code mirror editor.
748
+ */
749
+ export interface IOptions extends CodeEditor.IOptions {
750
+ /**
751
+ * CodeMirror extensions registry
752
+ */
753
+ extensionsRegistry?: IEditorExtensionRegistry;
754
+ /**
755
+ * CodeMirror languages registry
756
+ */
757
+ languages?: IEditorLanguageRegistry;
758
+ }
759
+ }
760
+
761
+ /**
762
+ * The namespace for module private data.
763
+ */
764
+ namespace Private {
765
+ export function createEditor(
766
+ host: HTMLElement,
767
+ editorConfig: IExtensionsHandler,
768
+ additionalExtensions: Extension[],
769
+ doc?: string
770
+ ): EditorView {
771
+ const extensions = editorConfig.getInitialExtensions();
772
+ extensions.push(...additionalExtensions);
773
+ const view = new EditorView({
774
+ state: EditorState.create({
775
+ doc,
776
+ extensions
777
+ }),
778
+ parent: host
779
+ });
780
+
781
+ return view;
782
+ }
783
+ }