@difizen/libro-codemirror 0.0.2-alpha.0
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/LICENSE +21 -0
- package/README.md +0 -0
- package/es/auto-complete/closebrackets.d.ts +12 -0
- package/es/auto-complete/closebrackets.d.ts.map +1 -0
- package/es/auto-complete/closebrackets.js +408 -0
- package/es/auto-complete/completion.d.ts +57 -0
- package/es/auto-complete/completion.d.ts.map +1 -0
- package/es/auto-complete/completion.js +265 -0
- package/es/auto-complete/config.d.ts +22 -0
- package/es/auto-complete/config.d.ts.map +1 -0
- package/es/auto-complete/config.js +44 -0
- package/es/auto-complete/filter.d.ts +13 -0
- package/es/auto-complete/filter.d.ts.map +1 -0
- package/es/auto-complete/filter.js +191 -0
- package/es/auto-complete/index.d.ts +17 -0
- package/es/auto-complete/index.d.ts.map +1 -0
- package/es/auto-complete/index.js +107 -0
- package/es/auto-complete/snippet.d.ts +14 -0
- package/es/auto-complete/snippet.d.ts.map +1 -0
- package/es/auto-complete/snippet.js +447 -0
- package/es/auto-complete/state.d.ts +63 -0
- package/es/auto-complete/state.d.ts.map +1 -0
- package/es/auto-complete/state.js +452 -0
- package/es/auto-complete/theme.d.ts +6 -0
- package/es/auto-complete/theme.d.ts.map +1 -0
- package/es/auto-complete/theme.js +151 -0
- package/es/auto-complete/tooltip.d.ts +5 -0
- package/es/auto-complete/tooltip.d.ts.map +1 -0
- package/es/auto-complete/tooltip.js +365 -0
- package/es/auto-complete/view.d.ts +43 -0
- package/es/auto-complete/view.d.ts.map +1 -0
- package/es/auto-complete/view.js +372 -0
- package/es/auto-complete/word.d.ts +3 -0
- package/es/auto-complete/word.d.ts.map +1 -0
- package/es/auto-complete/word.js +119 -0
- package/es/completion.d.ts +6 -0
- package/es/completion.d.ts.map +1 -0
- package/es/completion.js +84 -0
- package/es/config.d.ts +184 -0
- package/es/config.d.ts.map +1 -0
- package/es/config.js +473 -0
- package/es/editor.d.ts +361 -0
- package/es/editor.d.ts.map +1 -0
- package/es/editor.js +1126 -0
- package/es/factory.d.ts +3 -0
- package/es/factory.d.ts.map +1 -0
- package/es/factory.js +12 -0
- package/es/hyperlink.d.ts +15 -0
- package/es/hyperlink.d.ts.map +1 -0
- package/es/hyperlink.js +120 -0
- package/es/indent.d.ts +8 -0
- package/es/indent.d.ts.map +1 -0
- package/es/indent.js +58 -0
- package/es/indentation-markers/config.d.ts +17 -0
- package/es/indentation-markers/config.d.ts.map +1 -0
- package/es/indentation-markers/config.js +10 -0
- package/es/indentation-markers/index.d.ts +3 -0
- package/es/indentation-markers/index.d.ts.map +1 -0
- package/es/indentation-markers/index.js +160 -0
- package/es/indentation-markers/map.d.ts +77 -0
- package/es/indentation-markers/map.d.ts.map +1 -0
- package/es/indentation-markers/map.js +265 -0
- package/es/indentation-markers/utils.d.ts +27 -0
- package/es/indentation-markers/utils.d.ts.map +1 -0
- package/es/indentation-markers/utils.js +91 -0
- package/es/index.d.ts +11 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +10 -0
- package/es/libro-icon.d.ts +3 -0
- package/es/libro-icon.d.ts.map +1 -0
- package/es/libro-icon.js +2 -0
- package/es/mimetype.d.ts +22 -0
- package/es/mimetype.d.ts.map +1 -0
- package/es/mimetype.js +59 -0
- package/es/mode.d.ts +86 -0
- package/es/mode.d.ts.map +1 -0
- package/es/mode.js +284 -0
- package/es/monitor.d.ts +32 -0
- package/es/monitor.d.ts.map +1 -0
- package/es/monitor.js +129 -0
- package/es/python-lang.d.ts +3 -0
- package/es/python-lang.d.ts.map +1 -0
- package/es/python-lang.js +7 -0
- package/es/style/base.css +131 -0
- package/es/style/theme.css +12 -0
- package/es/style/variables.css +403 -0
- package/es/theme.d.ts +35 -0
- package/es/theme.d.ts.map +1 -0
- package/es/theme.js +225 -0
- package/es/tooltip.d.ts +10 -0
- package/es/tooltip.d.ts.map +1 -0
- package/es/tooltip.js +170 -0
- package/package.json +74 -0
- package/src/auto-complete/README.md +71 -0
- package/src/auto-complete/closebrackets.ts +423 -0
- package/src/auto-complete/completion.ts +345 -0
- package/src/auto-complete/config.ts +101 -0
- package/src/auto-complete/filter.ts +215 -0
- package/src/auto-complete/index.ts +112 -0
- package/src/auto-complete/snippet.ts +394 -0
- package/src/auto-complete/state.ts +472 -0
- package/src/auto-complete/theme.ts +126 -0
- package/src/auto-complete/tooltip.ts +386 -0
- package/src/auto-complete/view.ts +343 -0
- package/src/auto-complete/word.ts +118 -0
- package/src/completion.ts +61 -0
- package/src/config.ts +689 -0
- package/src/editor.ts +1078 -0
- package/src/factory.ts +10 -0
- package/src/hyperlink.ts +95 -0
- package/src/indent.ts +69 -0
- package/src/indentation-markers/config.ts +31 -0
- package/src/indentation-markers/index.ts +192 -0
- package/src/indentation-markers/map.ts +273 -0
- package/src/indentation-markers/utils.ts +84 -0
- package/src/index.ts +11 -0
- package/src/libro-icon.ts +4 -0
- package/src/mimetype.ts +49 -0
- package/src/mode.ts +269 -0
- package/src/monitor.ts +105 -0
- package/src/python-lang.ts +7 -0
- package/src/style/base.css +129 -0
- package/src/style/theme.css +12 -0
- package/src/style/variables.css +405 -0
- package/src/theme.ts +231 -0
- package/src/tooltip.ts +145 -0
package/src/editor.ts
ADDED
|
@@ -0,0 +1,1078 @@
|
|
|
1
|
+
import { insertNewlineAndIndent, redo, undo } from '@codemirror/commands';
|
|
2
|
+
import { ensureSyntaxTree } from '@codemirror/language';
|
|
3
|
+
import type {
|
|
4
|
+
ChangeSet,
|
|
5
|
+
Extension,
|
|
6
|
+
Range,
|
|
7
|
+
StateEffectType,
|
|
8
|
+
StateCommand,
|
|
9
|
+
Text,
|
|
10
|
+
} from '@codemirror/state';
|
|
11
|
+
import {
|
|
12
|
+
Prec,
|
|
13
|
+
EditorState,
|
|
14
|
+
EditorSelection,
|
|
15
|
+
StateEffect,
|
|
16
|
+
StateField,
|
|
17
|
+
} from '@codemirror/state';
|
|
18
|
+
import { Decoration, EditorView } from '@codemirror/view';
|
|
19
|
+
import type { Command, DecorationSet, ViewUpdate } from '@codemirror/view';
|
|
20
|
+
import type {
|
|
21
|
+
IEditor,
|
|
22
|
+
IEditorConfig,
|
|
23
|
+
IEditorSelectionStyle,
|
|
24
|
+
KeydownHandler,
|
|
25
|
+
IEditorOptions,
|
|
26
|
+
IModel,
|
|
27
|
+
IPosition,
|
|
28
|
+
IRange,
|
|
29
|
+
ICoordinate,
|
|
30
|
+
ITextSelection,
|
|
31
|
+
IToken,
|
|
32
|
+
} from '@difizen/libro-code-editor';
|
|
33
|
+
import { defaultSelectionStyle, defaultConfig } from '@difizen/libro-code-editor';
|
|
34
|
+
import { findFirstArrayIndex, removeAllWhereFromArray } from '@difizen/libro-common';
|
|
35
|
+
import { Disposable, Emitter } from '@difizen/mana-app';
|
|
36
|
+
import { getOrigin, watch } from '@difizen/mana-app';
|
|
37
|
+
import type { SyntaxNodeRef } from '@lezer/common';
|
|
38
|
+
import { v4 } from 'uuid';
|
|
39
|
+
|
|
40
|
+
import type { CodeMirrorConfig } from './config.js';
|
|
41
|
+
import { EditorConfiguration } from './config.js';
|
|
42
|
+
import { ensure } from './mode.js';
|
|
43
|
+
import { monitorPlugin } from './monitor.js';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The class name added to CodeMirrorWidget instances.
|
|
47
|
+
*/
|
|
48
|
+
const EDITOR_CLASS = 'jp-CodeMirrorEditor';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The class name added to read only cell editor widgets.
|
|
52
|
+
*/
|
|
53
|
+
const READ_ONLY_CLASS = 'jp-mod-readOnly';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The class name for the hover box for collaborator cursors.
|
|
57
|
+
*/
|
|
58
|
+
// const COLLABORATOR_CURSOR_CLASS = 'jp-CollaboratorCursor';
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The class name for the hover box for collaborator cursors.
|
|
62
|
+
*/
|
|
63
|
+
// const COLLABORATOR_HOVER_CLASS = 'jp-CollaboratorCursor-hover';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The key code for the up arrow key.
|
|
67
|
+
*/
|
|
68
|
+
const UP_ARROW = 38;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The key code for the down arrow key.
|
|
72
|
+
*/
|
|
73
|
+
const DOWN_ARROW = 40;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The time that a collaborator name hover persists.
|
|
77
|
+
*/
|
|
78
|
+
// const HOVER_TIMEOUT = 1000;
|
|
79
|
+
/**
|
|
80
|
+
* The default configuration options for an editor.
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
// interface IYCodeMirrorBinding {
|
|
84
|
+
// text: Y.Text;
|
|
85
|
+
// awareness: Awareness | null;
|
|
86
|
+
// undoManager: Y.UndoManager | null;
|
|
87
|
+
// }
|
|
88
|
+
|
|
89
|
+
export const codeMirrorDefaultConfig: Required<CodeMirrorConfig> = {
|
|
90
|
+
...defaultConfig,
|
|
91
|
+
mode: 'null',
|
|
92
|
+
mimetype: 'text/x-python',
|
|
93
|
+
theme: 'jupyter',
|
|
94
|
+
smartIndent: true,
|
|
95
|
+
electricChars: true,
|
|
96
|
+
keyMap: 'default',
|
|
97
|
+
extraKeys: null,
|
|
98
|
+
gutters: [],
|
|
99
|
+
fixedGutter: true,
|
|
100
|
+
showCursorWhenSelecting: false,
|
|
101
|
+
coverGutterNextToScrollbar: false,
|
|
102
|
+
dragDrop: true,
|
|
103
|
+
lineSeparator: null,
|
|
104
|
+
scrollbarStyle: 'native',
|
|
105
|
+
lineWiseCopyCut: true,
|
|
106
|
+
scrollPastEnd: false,
|
|
107
|
+
styleActiveLine: false,
|
|
108
|
+
styleSelectedText: true,
|
|
109
|
+
selectionPointer: false,
|
|
110
|
+
handlePaste: true,
|
|
111
|
+
lineWrap: 'off',
|
|
112
|
+
|
|
113
|
+
//
|
|
114
|
+
highlightActiveLineGutter: false,
|
|
115
|
+
highlightSpecialChars: true,
|
|
116
|
+
history: true,
|
|
117
|
+
drawSelection: true,
|
|
118
|
+
dropCursor: true,
|
|
119
|
+
allowMultipleSelections: true,
|
|
120
|
+
autocompletion: true,
|
|
121
|
+
rectangularSelection: true,
|
|
122
|
+
crosshairCursor: true,
|
|
123
|
+
highlightSelectionMatches: true,
|
|
124
|
+
foldGutter: true,
|
|
125
|
+
syntaxHighlighting: true,
|
|
126
|
+
jupyterKernelCompletion: true,
|
|
127
|
+
indentationMarkers: true,
|
|
128
|
+
hyperLink: true,
|
|
129
|
+
jupyterKernelTooltip: true,
|
|
130
|
+
tabEditorFunction: true,
|
|
131
|
+
lspCompletion: true,
|
|
132
|
+
lspTooltip: true,
|
|
133
|
+
lspLint: true,
|
|
134
|
+
placeholder: '',
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export class CodeMirrorEditor implements IEditor {
|
|
138
|
+
/**
|
|
139
|
+
* Construct a CodeMirror editor.
|
|
140
|
+
*/
|
|
141
|
+
constructor(options: IOptions) {
|
|
142
|
+
this._editorConfig = new EditorConfiguration(options);
|
|
143
|
+
const host = (this.host = options.host);
|
|
144
|
+
|
|
145
|
+
host.classList.add(EDITOR_CLASS);
|
|
146
|
+
host.classList.add('jp-Editor');
|
|
147
|
+
host.addEventListener('focus', this, true);
|
|
148
|
+
host.addEventListener('blur', this, true);
|
|
149
|
+
host.addEventListener('scroll', this, true);
|
|
150
|
+
|
|
151
|
+
this._uuid = options.uuid || v4();
|
|
152
|
+
|
|
153
|
+
// State and effects for handling the selection marks
|
|
154
|
+
this._addMark = StateEffect.define<ICollabSelectionText>();
|
|
155
|
+
this._removeMark = StateEffect.define<ICollabDecorationSet>();
|
|
156
|
+
|
|
157
|
+
this._markField = StateField.define<DecorationSet>({
|
|
158
|
+
create: () => {
|
|
159
|
+
return Decoration.none;
|
|
160
|
+
},
|
|
161
|
+
update: (marks, transaction) => {
|
|
162
|
+
let _marks = marks.map(transaction.changes);
|
|
163
|
+
for (const ef of transaction.effects) {
|
|
164
|
+
if (ef.is(this._addMark)) {
|
|
165
|
+
const e = ef;
|
|
166
|
+
const decorations = this._buildMarkDecoration(
|
|
167
|
+
e.value.uuid,
|
|
168
|
+
e.value.selections,
|
|
169
|
+
);
|
|
170
|
+
_marks = _marks.update({ add: decorations });
|
|
171
|
+
this._selectionMarkers[e.value.uuid] = decorations;
|
|
172
|
+
} else if (ef.is(this._removeMark)) {
|
|
173
|
+
const e = ef;
|
|
174
|
+
for (const rd of ef.value.decorations) {
|
|
175
|
+
_marks = _marks.update({
|
|
176
|
+
filter: (from, to, value) => {
|
|
177
|
+
return !(from === rd.from && to === rd.to && value === rd.value);
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
delete this._selectionMarkers[e.value.uuid];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return _marks;
|
|
185
|
+
},
|
|
186
|
+
provide: (f) => EditorView.decorations.from(f),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Handle selection style.
|
|
190
|
+
const style = options.selectionStyle || {};
|
|
191
|
+
this._selectionStyle = {
|
|
192
|
+
...defaultSelectionStyle,
|
|
193
|
+
...(style as IEditorSelectionStyle),
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const model = (this._model = options.model);
|
|
197
|
+
|
|
198
|
+
const config = options.config || {};
|
|
199
|
+
const fullConfig = (this._config = {
|
|
200
|
+
...codeMirrorDefaultConfig,
|
|
201
|
+
...config,
|
|
202
|
+
mimetype: options.model.mimeType,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// this._initializeEditorBinding();
|
|
206
|
+
|
|
207
|
+
// Extension for handling DOM events
|
|
208
|
+
const domEventHandlers = EditorView.domEventHandlers({
|
|
209
|
+
keydown: (event: KeyboardEvent) => {
|
|
210
|
+
const index = findFirstArrayIndex(this._keydownHandlers, (handler) => {
|
|
211
|
+
if (handler(this, event) === true) {
|
|
212
|
+
event.preventDefault();
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
});
|
|
217
|
+
if (index === -1) {
|
|
218
|
+
return this.onKeydown(event);
|
|
219
|
+
}
|
|
220
|
+
return false;
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const updateListener = EditorView.updateListener.of((update: ViewUpdate) => {
|
|
225
|
+
this._onDocChanged(update);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
this._editor = createEditor(
|
|
229
|
+
host,
|
|
230
|
+
fullConfig,
|
|
231
|
+
this.model.value,
|
|
232
|
+
this._editorConfig,
|
|
233
|
+
[
|
|
234
|
+
this._markField,
|
|
235
|
+
Prec.high(domEventHandlers),
|
|
236
|
+
updateListener,
|
|
237
|
+
|
|
238
|
+
monitorPlugin({ onTooltipChange: this.handleTooltipChange }),
|
|
239
|
+
],
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// every time the model is switched, we need to re-initialize the editor binding
|
|
243
|
+
// this.model.sharedModelSwitched.connect(this._initializeEditorBinding, this);
|
|
244
|
+
|
|
245
|
+
this._onMimeTypeChanged();
|
|
246
|
+
this._onCursorActivity();
|
|
247
|
+
// this._poll = new Poll({
|
|
248
|
+
// factory: async () => {
|
|
249
|
+
// this._checkSync();
|
|
250
|
+
// },
|
|
251
|
+
// frequency: { interval: 3000, backoff: false },
|
|
252
|
+
// standby: () => {
|
|
253
|
+
// // If changed, only stand by when hidden, otherwise always stand by.
|
|
254
|
+
// return this._lastChange ? 'when-hidden' : true;
|
|
255
|
+
// },
|
|
256
|
+
// });
|
|
257
|
+
|
|
258
|
+
watch(model, 'mimeType', this._onMimeTypeChanged);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
handleTooltipChange = (val: boolean) => {
|
|
262
|
+
this.modalChangeEmitter.fire(val);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Initialize the editor binding.
|
|
267
|
+
*/
|
|
268
|
+
// protected _initializeEditorBinding(): void {
|
|
269
|
+
// const sharedModel = this.model.sharedModel as models.IYText;
|
|
270
|
+
// this._yeditorBinding = {
|
|
271
|
+
// text: sharedModel.ysource,
|
|
272
|
+
// awareness: sharedModel.awareness,
|
|
273
|
+
// undoManager: sharedModel.undoManager,
|
|
274
|
+
// };
|
|
275
|
+
// }
|
|
276
|
+
|
|
277
|
+
save: () => void = () => {
|
|
278
|
+
//
|
|
279
|
+
};
|
|
280
|
+
/**
|
|
281
|
+
* A signal emitted when either the top or bottom edge is requested.
|
|
282
|
+
*/
|
|
283
|
+
readonly edgeRequestedEmitter = new Emitter();
|
|
284
|
+
readonly edgeRequested = this.edgeRequestedEmitter.event;
|
|
285
|
+
/**
|
|
286
|
+
* The DOM node that hosts the editor.
|
|
287
|
+
*/
|
|
288
|
+
readonly host: HTMLElement;
|
|
289
|
+
/**
|
|
290
|
+
* The uuid of this editor;
|
|
291
|
+
*/
|
|
292
|
+
get uuid(): string {
|
|
293
|
+
return this._uuid;
|
|
294
|
+
}
|
|
295
|
+
set uuid(value: string) {
|
|
296
|
+
this._uuid = value;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
protected modalChangeEmitter = new Emitter<boolean>();
|
|
300
|
+
|
|
301
|
+
get onModalChange() {
|
|
302
|
+
return this.modalChangeEmitter.event;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* The selection style of this editor.
|
|
307
|
+
*/
|
|
308
|
+
get selectionStyle(): IEditorSelectionStyle {
|
|
309
|
+
return this._selectionStyle;
|
|
310
|
+
}
|
|
311
|
+
set selectionStyle(value: IEditorSelectionStyle) {
|
|
312
|
+
this._selectionStyle = value;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Get the codemirror editor wrapped by the editor.
|
|
317
|
+
*/
|
|
318
|
+
get editor(): EditorView {
|
|
319
|
+
return this._editor;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get the codemirror doc wrapped by the widget.
|
|
324
|
+
*/
|
|
325
|
+
get doc(): Text {
|
|
326
|
+
return this._editor.state.doc;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Get the number of lines in the editor.
|
|
331
|
+
*/
|
|
332
|
+
get lineCount(): number {
|
|
333
|
+
return this.doc.lines;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Returns a model for this editor.
|
|
338
|
+
*/
|
|
339
|
+
get model(): IModel {
|
|
340
|
+
return this._model;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* The height of a line in the editor in pixels.
|
|
345
|
+
*/
|
|
346
|
+
get lineHeight(): number {
|
|
347
|
+
return this._editor.defaultLineHeight;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* The widget of a character in the editor in pixels.
|
|
352
|
+
*/
|
|
353
|
+
get charWidth(): number {
|
|
354
|
+
return this._editor.defaultCharacterWidth;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Tests whether the editor is disposed.
|
|
359
|
+
*/
|
|
360
|
+
get isDisposed(): boolean {
|
|
361
|
+
return this._isDisposed;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Dispose of the resources held by the widget.
|
|
366
|
+
*/
|
|
367
|
+
dispose(): void {
|
|
368
|
+
if (this.isDisposed) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
this._isDisposed = true;
|
|
372
|
+
this.host.removeEventListener('focus', this, true);
|
|
373
|
+
this.host.removeEventListener('blur', this, true);
|
|
374
|
+
this.host.removeEventListener('scroll', this, true);
|
|
375
|
+
this._keydownHandlers.length = 0;
|
|
376
|
+
// this._poll.dispose();
|
|
377
|
+
this.editor.destroy();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Get a config option for the editor.
|
|
382
|
+
*/
|
|
383
|
+
getOption<K extends keyof IConfig>(option: K): IConfig[K] {
|
|
384
|
+
return this._config[option];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Set a config option for the editor.
|
|
389
|
+
*/
|
|
390
|
+
setOption<K extends keyof IEditorConfig>(option: K, value: IEditorConfig[K]): void {
|
|
391
|
+
// Don't bother setting the option if it is already the same.
|
|
392
|
+
if (this._config[option] !== value) {
|
|
393
|
+
this._config[option] = value;
|
|
394
|
+
this._editorConfig.reconfigureExtension(this._editor, option, value);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (option === 'readOnly') {
|
|
398
|
+
if (value === true) {
|
|
399
|
+
getOrigin(this._editor).dom.classList.add(READ_ONLY_CLASS);
|
|
400
|
+
} else {
|
|
401
|
+
getOrigin(this._editor).dom.classList.remove(READ_ONLY_CLASS);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Set config options for the editor.
|
|
407
|
+
*
|
|
408
|
+
* This method is preferred when setting several options. The
|
|
409
|
+
* options are set within an operation, which only performs
|
|
410
|
+
* the costly update at the end, and not after every option
|
|
411
|
+
* is set.
|
|
412
|
+
*/
|
|
413
|
+
setOptions(options: Partial<IEditorConfig>): void {
|
|
414
|
+
this._config = { ...this._config, ...options };
|
|
415
|
+
this._editorConfig.reconfigureExtensions(this._editor, options);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
injectExtension(ext: Extension): void {
|
|
419
|
+
this._editorConfig.injectExtension(this._editor, ext);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Returns the content for the given line number.
|
|
424
|
+
*/
|
|
425
|
+
getLine(line: number): string | undefined {
|
|
426
|
+
// TODO: CM6 remove +1 when CM6 first line number has propagated
|
|
427
|
+
const _line = line + 1;
|
|
428
|
+
return _line <= this.doc.lines ? this.doc.line(_line).text : undefined;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Find an offset for the given position.
|
|
433
|
+
*/
|
|
434
|
+
getOffsetAt(position: IPosition): number {
|
|
435
|
+
// TODO: CM6 remove +1 when CM6 first line number has propagated
|
|
436
|
+
return this.doc.line(position.line + 1).from + position.column;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Find a position for the given offset.
|
|
441
|
+
*/
|
|
442
|
+
getPositionAt(offset: number): IPosition {
|
|
443
|
+
// TODO: CM6 remove -1 when CM6 first line number has propagated
|
|
444
|
+
const line = this.doc.lineAt(offset);
|
|
445
|
+
return { line: line.number - 1, column: offset - line.from };
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Undo one edit (if any undo events are stored).
|
|
450
|
+
*/
|
|
451
|
+
undo(): void {
|
|
452
|
+
undo({
|
|
453
|
+
state: getOrigin(this.state),
|
|
454
|
+
dispatch: this.editor.dispatch,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Redo one undone edit.
|
|
460
|
+
*/
|
|
461
|
+
redo(): void {
|
|
462
|
+
redo({
|
|
463
|
+
state: getOrigin(this.state),
|
|
464
|
+
dispatch: this.editor.dispatch,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Clear the undo history.
|
|
470
|
+
*/
|
|
471
|
+
clearHistory(): void {
|
|
472
|
+
// this._yeditorBinding?.undoManager?.clear();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Brings browser focus to this editor text.
|
|
477
|
+
*/
|
|
478
|
+
focus(): void {
|
|
479
|
+
this._editor.focus();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Test whether the editor has keyboard focus.
|
|
484
|
+
*/
|
|
485
|
+
hasFocus(): boolean {
|
|
486
|
+
return this._editor.hasFocus;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Explicitly blur the editor.
|
|
491
|
+
*/
|
|
492
|
+
blur(): void {
|
|
493
|
+
this._editor.contentDOM.blur();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Refresh the editor if it is focused;
|
|
498
|
+
* otherwise postpone refreshing till focusing.
|
|
499
|
+
*/
|
|
500
|
+
resizeToFit(): void {
|
|
501
|
+
this._clearHover();
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
get state(): EditorState {
|
|
505
|
+
return this._editor.state;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
firstLine(): number {
|
|
509
|
+
// TODO: return 1 when CM6 first line number has propagated
|
|
510
|
+
return 0;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
lastLine(): number {
|
|
514
|
+
return this.doc.lines - 1;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
cursorCoords(
|
|
518
|
+
where: boolean,
|
|
519
|
+
// mode?: 'window' | 'page' | 'local',
|
|
520
|
+
): { left: number; top: number; bottom: number } {
|
|
521
|
+
const selection = this.state.selection.main;
|
|
522
|
+
const pos = where ? selection.from : selection.to;
|
|
523
|
+
const rect = this.editor.coordsAtPos(pos);
|
|
524
|
+
return rect as { left: number; top: number; bottom: number };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
getRange(
|
|
528
|
+
from: { line: number; ch: number },
|
|
529
|
+
to: { line: number; ch: number },
|
|
530
|
+
// separator?: string,
|
|
531
|
+
): string {
|
|
532
|
+
const fromOffset = this.getOffsetAt(this._toPosition(from));
|
|
533
|
+
const toOffset = this.getOffsetAt(this._toPosition(to));
|
|
534
|
+
return this.state.sliceDoc(fromOffset, toOffset);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Add a keydown handler to the editor.
|
|
539
|
+
*
|
|
540
|
+
* @param handler - A keydown handler.
|
|
541
|
+
*
|
|
542
|
+
* @returns A disposable that can be used to remove the handler.
|
|
543
|
+
*/
|
|
544
|
+
addKeydownHandler(handler: KeydownHandler): Disposable {
|
|
545
|
+
this._keydownHandlers.push(handler);
|
|
546
|
+
return Disposable.create(() => {
|
|
547
|
+
removeAllWhereFromArray(this._keydownHandlers, (val) => val === handler);
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Reveal the given position in the editor.
|
|
553
|
+
*/
|
|
554
|
+
revealPosition(position: IPosition): void {
|
|
555
|
+
const offset = this.getOffsetAt(position);
|
|
556
|
+
this._editor.dispatch({
|
|
557
|
+
effects: EditorView.scrollIntoView(offset),
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Reveal the given selection in the editor.
|
|
563
|
+
*/
|
|
564
|
+
revealSelection(selection: IRange): void {
|
|
565
|
+
const start = this.getOffsetAt(selection.start);
|
|
566
|
+
const end = this.getOffsetAt(selection.end);
|
|
567
|
+
this._editor.dispatch({
|
|
568
|
+
effects: EditorView.scrollIntoView(EditorSelection.range(start, end)),
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Get the window coordinates given a cursor position.
|
|
574
|
+
*/
|
|
575
|
+
getCoordinateForPosition(position: IPosition): ICoordinate {
|
|
576
|
+
const offset = this.getOffsetAt(position);
|
|
577
|
+
const rect = this.editor.coordsAtPos(offset);
|
|
578
|
+
return rect as ICoordinate;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Get the cursor position given window coordinates.
|
|
582
|
+
*
|
|
583
|
+
* @param coordinate - The desired coordinate.
|
|
584
|
+
*
|
|
585
|
+
* @returns The position of the coordinates, or null if not
|
|
586
|
+
* contained in the editor.
|
|
587
|
+
*/
|
|
588
|
+
getPositionForCoordinate(coordinate: ICoordinate): IPosition | null {
|
|
589
|
+
const offset = this.editor.posAtCoords({
|
|
590
|
+
x: coordinate.left,
|
|
591
|
+
y: coordinate.top,
|
|
592
|
+
});
|
|
593
|
+
return this.getPositionAt(offset!) || null;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Returns the primary position of the cursor, never `null`.
|
|
598
|
+
*/
|
|
599
|
+
getCursorPosition(): IPosition {
|
|
600
|
+
const offset = this.state.selection.main.head;
|
|
601
|
+
return this.getPositionAt(offset);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Set the primary position of the cursor.
|
|
606
|
+
*
|
|
607
|
+
* #### Notes
|
|
608
|
+
* This will remove any secondary cursors.
|
|
609
|
+
*/
|
|
610
|
+
setCursorPosition(
|
|
611
|
+
position: IPosition,
|
|
612
|
+
// options?: { bias?: number; origin?: string; scroll?: boolean },
|
|
613
|
+
): void {
|
|
614
|
+
const offset = this.getOffsetAt(position);
|
|
615
|
+
this.editor.dispatch({
|
|
616
|
+
selection: { anchor: offset },
|
|
617
|
+
scrollIntoView: true,
|
|
618
|
+
});
|
|
619
|
+
// If the editor does not have focus, this cursor change
|
|
620
|
+
// will get screened out in _onCursorsChanged(). Make an
|
|
621
|
+
// exception for this method.
|
|
622
|
+
if (!this.editor.hasFocus) {
|
|
623
|
+
this.model.selections = this.getSelections();
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Returns the primary selection, never `null`.
|
|
629
|
+
*/
|
|
630
|
+
getSelection(): ITextSelection {
|
|
631
|
+
return this.getSelections()[0];
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Set the primary selection. This will remove any secondary cursors.
|
|
636
|
+
*/
|
|
637
|
+
setSelection(selection: IRange): void {
|
|
638
|
+
this.setSelections([selection]);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Gets the selections for all the cursors, never `null` or empty.
|
|
643
|
+
*/
|
|
644
|
+
getSelections(): ITextSelection[] {
|
|
645
|
+
const selections = this.state.selection.ranges; //= [{anchor: number, head: number}]
|
|
646
|
+
if (selections.length > 0) {
|
|
647
|
+
const sel = selections.map((r) => ({
|
|
648
|
+
anchor: this._toCodeMirrorPosition(this.getPositionAt(r.from)),
|
|
649
|
+
head: this._toCodeMirrorPosition(this.getPositionAt(r.to)),
|
|
650
|
+
}));
|
|
651
|
+
return sel.map((selection) => this._toSelection(selection));
|
|
652
|
+
}
|
|
653
|
+
const cursor = this._toCodeMirrorPosition(
|
|
654
|
+
this.getPositionAt(this.state.selection.main.head),
|
|
655
|
+
);
|
|
656
|
+
const selection = this._toSelection({ anchor: cursor, head: cursor });
|
|
657
|
+
return [selection];
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Sets the selections for all the cursors, should not be empty.
|
|
662
|
+
* Cursors will be removed or added, as necessary.
|
|
663
|
+
* Passing an empty array resets a cursor position to the start of a document.
|
|
664
|
+
*/
|
|
665
|
+
setSelections(selections: IRange[]): void {
|
|
666
|
+
const sel = selections.length
|
|
667
|
+
? selections.map((r) =>
|
|
668
|
+
EditorSelection.range(this.getOffsetAt(r.start), this.getOffsetAt(r.end)),
|
|
669
|
+
)
|
|
670
|
+
: [EditorSelection.range(0, 0)];
|
|
671
|
+
this.editor.dispatch({ selection: EditorSelection.create(sel) });
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Replaces the current selection with the given text.
|
|
676
|
+
*
|
|
677
|
+
* @param text The text to be inserted.
|
|
678
|
+
*/
|
|
679
|
+
replaceSelection(text: string): void {
|
|
680
|
+
this.editor.dispatch(this.state.replaceSelection(text));
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Get a list of tokens for the current editor text content.
|
|
685
|
+
*/
|
|
686
|
+
getTokens(): IToken[] {
|
|
687
|
+
const tokens: IToken[] = [];
|
|
688
|
+
const tree = ensureSyntaxTree(this.state, this.doc.length);
|
|
689
|
+
if (tree) {
|
|
690
|
+
tree.iterate({
|
|
691
|
+
enter: (node: SyntaxNodeRef) => {
|
|
692
|
+
tokens.push({
|
|
693
|
+
value: this.state.sliceDoc(node.from, node.to),
|
|
694
|
+
offset: node.from,
|
|
695
|
+
type: node.name,
|
|
696
|
+
});
|
|
697
|
+
return true;
|
|
698
|
+
},
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
return tokens;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Get the token at a given editor position.
|
|
706
|
+
*/
|
|
707
|
+
getTokenAt(offset: number): IToken {
|
|
708
|
+
const tree = ensureSyntaxTree(this.state, offset);
|
|
709
|
+
if (tree) {
|
|
710
|
+
const node = tree.resolveInner(offset);
|
|
711
|
+
return {
|
|
712
|
+
value: this.state.sliceDoc(node.from, node.to),
|
|
713
|
+
offset: node.from,
|
|
714
|
+
type: node.name,
|
|
715
|
+
};
|
|
716
|
+
} else {
|
|
717
|
+
return {
|
|
718
|
+
value: '',
|
|
719
|
+
offset: offset,
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Get the token a the cursor position.
|
|
726
|
+
*/
|
|
727
|
+
getTokenAtCursor(): IToken {
|
|
728
|
+
return this.getTokenAt(this.state.selection.main.head);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Insert a new indented line at the current cursor position.
|
|
733
|
+
*/
|
|
734
|
+
newIndentedLine(): void {
|
|
735
|
+
insertNewlineAndIndent({
|
|
736
|
+
state: this.state,
|
|
737
|
+
dispatch: this.editor.dispatch,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Execute a codemirror command on the editor.
|
|
743
|
+
*
|
|
744
|
+
* @param command - The name of the command to execute.
|
|
745
|
+
*/
|
|
746
|
+
execCommand(command: Command | StateCommand): void {
|
|
747
|
+
command(this.editor);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Handle keydown events from the editor.
|
|
752
|
+
*/
|
|
753
|
+
protected onKeydown(event: KeyboardEvent): boolean {
|
|
754
|
+
const position = this.state.selection.main.head;
|
|
755
|
+
|
|
756
|
+
if (position === 0 && event.keyCode === UP_ARROW) {
|
|
757
|
+
if (!event.shiftKey) {
|
|
758
|
+
// this.edgeRequested.emit('top');
|
|
759
|
+
this.edgeRequestedEmitter.fire('top');
|
|
760
|
+
}
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const line = this.doc.lineAt(position).number;
|
|
765
|
+
if (line === 1 && event.keyCode === UP_ARROW) {
|
|
766
|
+
if (!event.shiftKey) {
|
|
767
|
+
// this.edgeRequested.emit('topLine');
|
|
768
|
+
this.edgeRequestedEmitter.fire('topLine');
|
|
769
|
+
}
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const length = this.doc.length;
|
|
774
|
+
if (position === length && event.keyCode === DOWN_ARROW) {
|
|
775
|
+
if (!event.shiftKey) {
|
|
776
|
+
// this.edgeRequested.emit('bottom');
|
|
777
|
+
this.edgeRequestedEmitter.fire('bottom');
|
|
778
|
+
}
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Handles a mime type change.
|
|
787
|
+
*/
|
|
788
|
+
protected _onMimeTypeChanged(): void {
|
|
789
|
+
const mime = this._model.mimeType;
|
|
790
|
+
// TODO: should we provide a hook for when the mode is done being set?
|
|
791
|
+
void ensure(mime).then((spec) => {
|
|
792
|
+
if (spec) {
|
|
793
|
+
this._editorConfig.reconfigureExtension(
|
|
794
|
+
this._editor,
|
|
795
|
+
'language',
|
|
796
|
+
spec.support!,
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
return;
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
protected _buildMarkDecoration(
|
|
804
|
+
uuid: string,
|
|
805
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
806
|
+
_selections: ISelectionText[],
|
|
807
|
+
) {
|
|
808
|
+
const decorations: Range<Decoration>[] = [];
|
|
809
|
+
|
|
810
|
+
// If we are marking selections corresponding to an active hover,
|
|
811
|
+
// remove it.
|
|
812
|
+
if (uuid === this._hoverId) {
|
|
813
|
+
this._clearHover();
|
|
814
|
+
}
|
|
815
|
+
// If we can id the selection to a specific collaborator,
|
|
816
|
+
// use that information.
|
|
817
|
+
// let collaborator: ICollaborator | undefined;
|
|
818
|
+
// if (this._model.modelDB.collaborators) {
|
|
819
|
+
// collaborator = this._model.modelDB.collaborators.get(uuid);
|
|
820
|
+
// }
|
|
821
|
+
|
|
822
|
+
// Style each selection for the uuid.
|
|
823
|
+
// selections.forEach(selection => {
|
|
824
|
+
// const from = selection.from;
|
|
825
|
+
// const to = selection.to;
|
|
826
|
+
// // Only render selections if the start is not equal to the end.
|
|
827
|
+
// // In that case, we don't need to render the cursor.
|
|
828
|
+
// if (from !== to) {
|
|
829
|
+
// const style = collaborator
|
|
830
|
+
// ? { ...selection.style, color: collaborator.color }
|
|
831
|
+
// : selection.style;
|
|
832
|
+
// const decoration = Decoration.mark({
|
|
833
|
+
// attributes: this._toMarkSpec(style),
|
|
834
|
+
// });
|
|
835
|
+
// decorations.push(from > to ? decoration.range(to, from) : decoration.range(to, from));
|
|
836
|
+
// } else if (collaborator) {
|
|
837
|
+
// const caret = Decoration.widget({
|
|
838
|
+
// widget: this._getCaret(collaborator),
|
|
839
|
+
// });
|
|
840
|
+
// decorations.push(caret.range(from));
|
|
841
|
+
// }
|
|
842
|
+
// });
|
|
843
|
+
|
|
844
|
+
return decorations;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Converts the selection style to a text marker options.
|
|
849
|
+
*/
|
|
850
|
+
// protected _toMarkSpec(style: IEditorSelectionStyle) {
|
|
851
|
+
// const r = parseInt(style.color.slice(1, 3), 16);
|
|
852
|
+
// const g = parseInt(style.color.slice(3, 5), 16);
|
|
853
|
+
// const b = parseInt(style.color.slice(5, 7), 16);
|
|
854
|
+
// const css = `background-color: rgba( ${r}, ${g}, ${b}, 0.15)`;
|
|
855
|
+
// return {
|
|
856
|
+
// className: style.className,
|
|
857
|
+
// title: style.displayName,
|
|
858
|
+
// css,
|
|
859
|
+
// };
|
|
860
|
+
// }
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Construct a caret element representing the position
|
|
864
|
+
* of a collaborator's cursor.
|
|
865
|
+
*/
|
|
866
|
+
// protected _getCaret(collaborator: ICollaborator): CaretWidget {
|
|
867
|
+
// return new CaretWidget(collaborator, {
|
|
868
|
+
// setHoverId: (sessionId: string) => {
|
|
869
|
+
// this._clearHover();
|
|
870
|
+
// this._hoverId = sessionId;
|
|
871
|
+
// },
|
|
872
|
+
// setHoverTimeout: () => {
|
|
873
|
+
// this._hoverTimeout = window.setTimeout(() => {
|
|
874
|
+
// this._clearHover();
|
|
875
|
+
// }, HOVER_TIMEOUT);
|
|
876
|
+
// },
|
|
877
|
+
// clearHoverTimeout: () => {
|
|
878
|
+
// window.clearTimeout(this._hoverTimeout);
|
|
879
|
+
// },
|
|
880
|
+
// });
|
|
881
|
+
// }
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Handles a cursor activity event.
|
|
885
|
+
*/
|
|
886
|
+
protected _onCursorActivity(): void {
|
|
887
|
+
// Only add selections if the editor has focus. This avoids unwanted
|
|
888
|
+
// triggering of cursor activity due to collaborator actions.
|
|
889
|
+
if (this._editor.hasFocus) {
|
|
890
|
+
const selections = this.getSelections();
|
|
891
|
+
this.model.selections = selections;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* Converts a code mirror selection to an editor selection.
|
|
897
|
+
*/
|
|
898
|
+
protected _toSelection(selection: {
|
|
899
|
+
anchor: { line: number; ch: number };
|
|
900
|
+
head: { line: number; ch: number };
|
|
901
|
+
}): ITextSelection {
|
|
902
|
+
return {
|
|
903
|
+
uuid: this.uuid,
|
|
904
|
+
start: this._toPosition(selection.anchor),
|
|
905
|
+
end: this._toPosition(selection.head),
|
|
906
|
+
style: this.selectionStyle,
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Convert a code mirror position to an editor position.
|
|
911
|
+
*/
|
|
912
|
+
protected _toPosition(position: { line: number; ch: number }) {
|
|
913
|
+
return {
|
|
914
|
+
line: position.line,
|
|
915
|
+
column: position.ch,
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Convert an editor position to a code mirror position.
|
|
921
|
+
*/
|
|
922
|
+
protected _toCodeMirrorPosition(position: IPosition) {
|
|
923
|
+
return {
|
|
924
|
+
line: position.line,
|
|
925
|
+
ch: position.column,
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Handles document changes.
|
|
931
|
+
*/
|
|
932
|
+
protected _onDocChanged(update: ViewUpdate) {
|
|
933
|
+
if (update.transactions.length && update.transactions[0].selection) {
|
|
934
|
+
this._onCursorActivity();
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (update.docChanged) {
|
|
938
|
+
this._lastChange = update.changes;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
this.model.value = update.state.doc.toJSON().join('\n');
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Handle the DOM events for the editor.
|
|
945
|
+
*
|
|
946
|
+
* @param event - The DOM event sent to the editor.
|
|
947
|
+
*
|
|
948
|
+
* #### Notes
|
|
949
|
+
* This method implements the DOM `EventListener` interface and is
|
|
950
|
+
* called in response to events on the editor's DOM node. It should
|
|
951
|
+
* not be called directly by user code.
|
|
952
|
+
*/
|
|
953
|
+
handleEvent(event: Event): void {
|
|
954
|
+
switch (event.type) {
|
|
955
|
+
case 'focus':
|
|
956
|
+
this._evtFocus();
|
|
957
|
+
break;
|
|
958
|
+
case 'blur':
|
|
959
|
+
this._evtBlur();
|
|
960
|
+
break;
|
|
961
|
+
case 'scroll':
|
|
962
|
+
this._evtScroll();
|
|
963
|
+
break;
|
|
964
|
+
default:
|
|
965
|
+
break;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Handle `focus` events for the editor.
|
|
971
|
+
*/
|
|
972
|
+
protected _evtFocus(): // event: FocusEvent
|
|
973
|
+
void {
|
|
974
|
+
this.host.classList.add('jp-mod-focused');
|
|
975
|
+
|
|
976
|
+
// Update the selections on editor gaining focus because
|
|
977
|
+
// the onCursorActivity function filters usual cursor events
|
|
978
|
+
// based on the editor's focus.
|
|
979
|
+
this._onCursorActivity();
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Handle `blur` events for the editor.
|
|
984
|
+
*/
|
|
985
|
+
protected _evtBlur(): // event: FocusEvent
|
|
986
|
+
void {
|
|
987
|
+
this.host.classList.remove('jp-mod-focused');
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Handle `scroll` events for the editor.
|
|
992
|
+
*/
|
|
993
|
+
protected _evtScroll(): void {
|
|
994
|
+
// Remove any active hover.
|
|
995
|
+
this._clearHover();
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* Clear the hover for a caret, due to things like
|
|
1000
|
+
* scrolling, resizing, deactivation, etc, where
|
|
1001
|
+
* the position is no longer valid.
|
|
1002
|
+
*/
|
|
1003
|
+
protected _clearHover(): void {
|
|
1004
|
+
if (this._caretHover) {
|
|
1005
|
+
window.clearTimeout(this._hoverTimeout);
|
|
1006
|
+
document.body.removeChild(this._caretHover);
|
|
1007
|
+
this._caretHover = null;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
protected _model: IModel;
|
|
1011
|
+
protected _editor: EditorView;
|
|
1012
|
+
protected _selectionMarkers: Record<string, Range<Decoration>[]> = {};
|
|
1013
|
+
protected _caretHover: HTMLElement | null = null;
|
|
1014
|
+
protected _config: IConfig;
|
|
1015
|
+
protected _hoverTimeout: number | undefined = undefined;
|
|
1016
|
+
protected _hoverId: string | undefined = undefined;
|
|
1017
|
+
protected _keydownHandlers = new Array<KeydownHandler>();
|
|
1018
|
+
protected _selectionStyle: IEditorSelectionStyle;
|
|
1019
|
+
protected _uuid = '';
|
|
1020
|
+
protected _isDisposed = false;
|
|
1021
|
+
protected _lastChange: ChangeSet | null = null;
|
|
1022
|
+
// protected _poll: Poll;
|
|
1023
|
+
// protected _yeditorBinding: IYCodeMirrorBinding | null;
|
|
1024
|
+
protected _editorConfig: EditorConfiguration;
|
|
1025
|
+
protected _addMark: StateEffectType<ICollabSelectionText>;
|
|
1026
|
+
protected _removeMark: StateEffectType<ICollabDecorationSet>;
|
|
1027
|
+
protected _markField: StateField<DecorationSet>;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
export type IConfig = CodeMirrorConfig;
|
|
1031
|
+
|
|
1032
|
+
export interface IOptions extends IEditorOptions {
|
|
1033
|
+
[x: string]: any;
|
|
1034
|
+
/**
|
|
1035
|
+
* The configuration options for the editor.
|
|
1036
|
+
*/
|
|
1037
|
+
config?: Partial<IConfig>;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
export function createEditor(
|
|
1041
|
+
host: HTMLElement,
|
|
1042
|
+
config: IConfig,
|
|
1043
|
+
value: string,
|
|
1044
|
+
editorConfig: EditorConfiguration,
|
|
1045
|
+
additionalExtensions: Extension[],
|
|
1046
|
+
): EditorView {
|
|
1047
|
+
const extensions = editorConfig.getInitialExtensions(config);
|
|
1048
|
+
|
|
1049
|
+
extensions.push(...additionalExtensions);
|
|
1050
|
+
const view = new EditorView({
|
|
1051
|
+
state: EditorState.create({
|
|
1052
|
+
doc: value,
|
|
1053
|
+
extensions,
|
|
1054
|
+
}),
|
|
1055
|
+
parent: host,
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
if (config.readOnly) {
|
|
1059
|
+
view.dom.classList.add(READ_ONLY_CLASS);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
return view;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
export interface ISelectionText {
|
|
1066
|
+
from: number;
|
|
1067
|
+
to: number;
|
|
1068
|
+
style: IEditorSelectionStyle;
|
|
1069
|
+
}
|
|
1070
|
+
export interface ICollabSelectionText {
|
|
1071
|
+
uuid: string;
|
|
1072
|
+
selections: ISelectionText[];
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
export interface ICollabDecorationSet {
|
|
1076
|
+
uuid: string;
|
|
1077
|
+
decorations: Range<Decoration>[];
|
|
1078
|
+
}
|