@jupyterlab/codemirror 4.0.0-alpha.8 → 4.0.0-beta.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/lib/codemirror-ipythongfm.d.ts +9 -3
- package/lib/codemirror-ipythongfm.js +52 -30
- package/lib/codemirror-ipythongfm.js.map +1 -1
- package/lib/commands.d.ts +13 -0
- package/lib/commands.js +32 -0
- package/lib/commands.js.map +1 -0
- package/lib/editor.d.ts +48 -273
- package/lib/editor.js +220 -819
- package/lib/editor.js.map +1 -1
- package/lib/extension.d.ts +236 -0
- package/lib/extension.js +696 -0
- package/lib/extension.js.map +1 -0
- package/lib/extensions/customStyle.d.ts +25 -0
- package/lib/extensions/customStyle.js +51 -0
- package/lib/extensions/customStyle.js.map +1 -0
- package/lib/extensions/index.d.ts +3 -0
- package/lib/extensions/index.js +8 -0
- package/lib/extensions/index.js.map +1 -0
- package/lib/extensions/rulers.d.ts +8 -0
- package/lib/extensions/rulers.js +85 -0
- package/lib/extensions/rulers.js.map +1 -0
- package/lib/extensions/ybinding.d.ts +129 -0
- package/lib/extensions/ybinding.js +229 -0
- package/lib/extensions/ybinding.js.map +1 -0
- package/lib/factory.d.ts +15 -5
- package/lib/factory.js +53 -24
- package/lib/factory.js.map +1 -1
- package/lib/index.d.ts +7 -19
- package/lib/index.js +7 -23
- package/lib/index.js.map +1 -1
- package/lib/language.d.ts +96 -0
- package/lib/language.js +1687 -0
- package/lib/language.js.map +1 -0
- package/lib/mimetype.d.ts +3 -0
- package/lib/mimetype.js +16 -5
- package/lib/mimetype.js.map +1 -1
- package/lib/searchprovider.d.ts +219 -0
- package/lib/searchprovider.js +640 -0
- package/lib/searchprovider.js.map +1 -0
- package/lib/theme.d.ts +57 -0
- package/lib/theme.js +194 -0
- package/lib/theme.js.map +1 -0
- package/lib/token.d.ts +375 -0
- package/lib/token.js +18 -0
- package/lib/token.js.map +1 -0
- package/package.json +45 -41
- package/src/codemirror-ipythongfm.ts +109 -0
- package/src/commands.ts +34 -0
- package/src/editor.ts +783 -0
- package/src/extension.ts +890 -0
- package/src/extensions/customStyle.ts +79 -0
- package/src/extensions/index.ts +8 -0
- package/src/extensions/rulers.ts +112 -0
- package/src/extensions/ybinding.ts +295 -0
- package/src/factory.ts +100 -0
- package/src/index.ts +17 -0
- package/src/language.ts +1726 -0
- package/src/mimetype.ts +54 -0
- package/src/searchprovider.ts +813 -0
- package/src/theme.ts +219 -0
- package/src/token.ts +436 -0
- package/src/typings.d.ts +13 -0
- package/style/base.css +33 -187
- package/style/index.css +1 -7
- package/style/index.js +1 -7
- package/lib/codemirror-ipython.d.ts +0 -2
- package/lib/codemirror-ipython.js +0 -30
- package/lib/codemirror-ipython.js.map +0 -1
- package/lib/mode.d.ts +0 -82
- package/lib/mode.js +0 -150
- package/lib/mode.js.map +0 -1
- package/lib/syntaxstatus.d.ts +0 -67
- package/lib/syntaxstatus.js +0 -140
- package/lib/syntaxstatus.js.map +0 -1
- package/lib/tokens.d.ts +0 -18
- package/lib/tokens.js +0 -8
- package/lib/tokens.js.map +0 -1
- package/typings/codemirror/codemirror.d.ts +0 -227
package/src/extension.ts
ADDED
|
@@ -0,0 +1,890 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
|
|
4
|
+
import { closeBrackets } from '@codemirror/autocomplete';
|
|
5
|
+
import { defaultKeymap, indentLess } from '@codemirror/commands';
|
|
6
|
+
import {
|
|
7
|
+
bracketMatching,
|
|
8
|
+
foldGutter,
|
|
9
|
+
indentOnInput,
|
|
10
|
+
indentUnit
|
|
11
|
+
} from '@codemirror/language';
|
|
12
|
+
import {
|
|
13
|
+
Compartment,
|
|
14
|
+
EditorState,
|
|
15
|
+
Extension,
|
|
16
|
+
StateEffect
|
|
17
|
+
} from '@codemirror/state';
|
|
18
|
+
import {
|
|
19
|
+
drawSelection,
|
|
20
|
+
EditorView,
|
|
21
|
+
highlightActiveLine,
|
|
22
|
+
highlightTrailingWhitespace,
|
|
23
|
+
highlightWhitespace,
|
|
24
|
+
KeyBinding,
|
|
25
|
+
keymap,
|
|
26
|
+
lineNumbers,
|
|
27
|
+
scrollPastEnd
|
|
28
|
+
} from '@codemirror/view';
|
|
29
|
+
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
30
|
+
import { JSONExt, ReadonlyJSONObject } from '@lumino/coreutils';
|
|
31
|
+
import { IObservableDisposable } from '@lumino/disposable';
|
|
32
|
+
import { ISignal, Signal } from '@lumino/signaling';
|
|
33
|
+
import { StateCommands } from './commands';
|
|
34
|
+
import { customTheme, CustomTheme, rulers } from './extensions';
|
|
35
|
+
import {
|
|
36
|
+
IConfigurableExtension,
|
|
37
|
+
IEditorExtensionFactory,
|
|
38
|
+
IEditorExtensionRegistry,
|
|
39
|
+
IEditorThemeRegistry,
|
|
40
|
+
IExtensionsHandler
|
|
41
|
+
} from './token';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The class name added to read only editor widgets.
|
|
45
|
+
*/
|
|
46
|
+
const READ_ONLY_CLASS = 'jp-mod-readOnly';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Editor configuration handler options
|
|
50
|
+
*/
|
|
51
|
+
export interface IEditorHandlerOptions {
|
|
52
|
+
/**
|
|
53
|
+
* The base configuration options for all editor.
|
|
54
|
+
*/
|
|
55
|
+
baseConfiguration?: Record<string, any>;
|
|
56
|
+
/**
|
|
57
|
+
* The configuration options for the editor.
|
|
58
|
+
*
|
|
59
|
+
* They take precendence over the base configuration.
|
|
60
|
+
*/
|
|
61
|
+
config?: Record<string, any>;
|
|
62
|
+
/**
|
|
63
|
+
* Editor default extensions.
|
|
64
|
+
*
|
|
65
|
+
* Extension defined in the mapping without a base configuration
|
|
66
|
+
* will not be configurable.
|
|
67
|
+
*/
|
|
68
|
+
defaultExtensions?: [string, IConfigurableExtension<any>][];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Editor configuration handler
|
|
73
|
+
*
|
|
74
|
+
* It stores the editor configuration and the editor extensions.
|
|
75
|
+
* It also allows to inject new extensions into an editor.
|
|
76
|
+
*/
|
|
77
|
+
export class ExtensionsHandler
|
|
78
|
+
implements IExtensionsHandler, IObservableDisposable
|
|
79
|
+
{
|
|
80
|
+
constructor({
|
|
81
|
+
baseConfiguration,
|
|
82
|
+
config,
|
|
83
|
+
defaultExtensions
|
|
84
|
+
}: IEditorHandlerOptions = {}) {
|
|
85
|
+
this._baseConfig = baseConfiguration ?? {};
|
|
86
|
+
this._config = config ?? {};
|
|
87
|
+
|
|
88
|
+
this._configurableBuilderMap = new Map<string, IConfigurableExtension<any>>(
|
|
89
|
+
defaultExtensions
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const configurables = Object.keys(this._config).concat(
|
|
93
|
+
Object.keys(this._baseConfig)
|
|
94
|
+
);
|
|
95
|
+
this._immutables = new Set(
|
|
96
|
+
[...this._configurableBuilderMap.keys()].filter(
|
|
97
|
+
key => !configurables.includes(key)
|
|
98
|
+
)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Signal triggered when the editor configuration changes.
|
|
104
|
+
* It provides the mapping of the new configuration (only those that changed).
|
|
105
|
+
*
|
|
106
|
+
* It should result in a call to `IExtensionsHandler.reconfigureExtensions`.
|
|
107
|
+
*/
|
|
108
|
+
get configChanged(): ISignal<this, Record<string, any>> {
|
|
109
|
+
return this._configChanged;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* A signal emitted when the object is disposed.
|
|
114
|
+
*/
|
|
115
|
+
get disposed(): ISignal<this, void> {
|
|
116
|
+
return this._disposed;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Tests whether the object is disposed.
|
|
121
|
+
*/
|
|
122
|
+
get isDisposed(): boolean {
|
|
123
|
+
return this._isDisposed;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Dispose of the resources held by the object.
|
|
128
|
+
*/
|
|
129
|
+
dispose(): void {
|
|
130
|
+
if (this.isDisposed) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this._isDisposed = true;
|
|
134
|
+
this._disposed.emit();
|
|
135
|
+
Signal.clearData(this);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get a config option for the editor.
|
|
140
|
+
*/
|
|
141
|
+
getOption(option: string): unknown {
|
|
142
|
+
return this._config[option] ?? this._baseConfig[option];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Whether the option exists or not.
|
|
147
|
+
*/
|
|
148
|
+
hasOption(option: string): boolean {
|
|
149
|
+
return (
|
|
150
|
+
Object.keys(this._config).includes(option) ||
|
|
151
|
+
Object.keys(this._baseConfig).includes(option)
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Set a config option for the editor.
|
|
157
|
+
*
|
|
158
|
+
* You will need to reconfigure the editor extensions by listening
|
|
159
|
+
* to `IExtensionsHandler.configChanged`.
|
|
160
|
+
*/
|
|
161
|
+
setOption(option: string, value: unknown): void {
|
|
162
|
+
// Don't bother setting the option if it is already the same.
|
|
163
|
+
if (this._config[option] !== value) {
|
|
164
|
+
this._config[option] = value;
|
|
165
|
+
this._configChanged.emit({ [option]: value });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Set a base config option for the editor.
|
|
171
|
+
*
|
|
172
|
+
* You will need to reconfigure the editor extensions by listening
|
|
173
|
+
* to `IExtensionsHandler.configChanged`.
|
|
174
|
+
*/
|
|
175
|
+
setBaseOptions(options: Record<string, any>): void {
|
|
176
|
+
const changed = this._getChangedOptions(options, this._baseConfig);
|
|
177
|
+
if (changed.length > 0) {
|
|
178
|
+
this._baseConfig = options;
|
|
179
|
+
const customizedKeys = Object.keys(this._config);
|
|
180
|
+
const notOverridden = changed.filter(k => !customizedKeys.includes(k));
|
|
181
|
+
if (notOverridden.length > 0) {
|
|
182
|
+
this._configChanged.emit(
|
|
183
|
+
notOverridden.reduce<Record<string, any>>((agg, key) => {
|
|
184
|
+
agg[key] = this._baseConfig[key];
|
|
185
|
+
return agg;
|
|
186
|
+
}, {})
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Set config options for the editor.
|
|
194
|
+
*
|
|
195
|
+
* You will need to reconfigure the editor extensions by listening
|
|
196
|
+
* to `EditorHandler.configChanged`.
|
|
197
|
+
*
|
|
198
|
+
* This method is preferred when setting several options. The
|
|
199
|
+
* options are set within an operation, which only performs
|
|
200
|
+
* the costly update at the end, and not after every option
|
|
201
|
+
* is set.
|
|
202
|
+
*/
|
|
203
|
+
setOptions(options: Record<string, any>): void {
|
|
204
|
+
const changed = this._getChangedOptions(options, this._config);
|
|
205
|
+
if (changed.length > 0) {
|
|
206
|
+
this._config = { ...options };
|
|
207
|
+
|
|
208
|
+
this._configChanged.emit(
|
|
209
|
+
changed.reduce<Record<string, any>>((agg, key) => {
|
|
210
|
+
agg[key] = this._config[key] ?? this._baseConfig[key];
|
|
211
|
+
return agg;
|
|
212
|
+
}, {})
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Reconfigures the extension mapped with key with the provided value.
|
|
219
|
+
*
|
|
220
|
+
* @param view Editor view
|
|
221
|
+
* @param key Parameter unique key
|
|
222
|
+
* @param value Parameter value to be applied
|
|
223
|
+
*/
|
|
224
|
+
reconfigureExtension<T>(view: EditorView, key: string, value: T): void {
|
|
225
|
+
const effect = this.getEffect<T>(view.state, key, value);
|
|
226
|
+
|
|
227
|
+
if (effect) {
|
|
228
|
+
view.dispatch({
|
|
229
|
+
effects: [effect]
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Reconfigures all the extensions mapped with the options from the
|
|
236
|
+
* provided partial configuration.
|
|
237
|
+
*
|
|
238
|
+
* @param view Editor view
|
|
239
|
+
* @param configuration Editor configuration
|
|
240
|
+
*/
|
|
241
|
+
reconfigureExtensions(
|
|
242
|
+
view: EditorView,
|
|
243
|
+
configuration: Record<string, any>
|
|
244
|
+
): void {
|
|
245
|
+
const effects = Object.keys(configuration)
|
|
246
|
+
.filter(key => this.has(key))
|
|
247
|
+
.map(key => this.getEffect(view.state, key, configuration[key]));
|
|
248
|
+
|
|
249
|
+
view.dispatch({
|
|
250
|
+
effects: effects.filter(
|
|
251
|
+
effect => effect !== null
|
|
252
|
+
) as StateEffect<unknown>[]
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Appends extensions to the top-level configuration of the
|
|
258
|
+
* editor.
|
|
259
|
+
*
|
|
260
|
+
* Injected extension cannot be removed.
|
|
261
|
+
*
|
|
262
|
+
* ### Notes
|
|
263
|
+
* You should prefer registering a IEditorExtensionFactory instead
|
|
264
|
+
* of this feature.
|
|
265
|
+
*
|
|
266
|
+
* @alpha
|
|
267
|
+
* @param view Editor view
|
|
268
|
+
* @param extension Editor extension to inject
|
|
269
|
+
*/
|
|
270
|
+
injectExtension(view: EditorView, extension: Extension): void {
|
|
271
|
+
view.dispatch({
|
|
272
|
+
effects: StateEffect.appendConfig.of(extension)
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Returns the list of initial extensions of an editor
|
|
278
|
+
* based on the configuration.
|
|
279
|
+
*
|
|
280
|
+
* @returns The initial editor extensions
|
|
281
|
+
*/
|
|
282
|
+
getInitialExtensions(): Extension[] {
|
|
283
|
+
const configuration = { ...this._baseConfig, ...this._config };
|
|
284
|
+
|
|
285
|
+
const extensions = [...this._immutables]
|
|
286
|
+
.map(key => this.get(key)?.instance(undefined))
|
|
287
|
+
.filter(ext => ext) as Extension[];
|
|
288
|
+
for (const k of Object.keys(configuration)) {
|
|
289
|
+
const builder = this.get(k);
|
|
290
|
+
if (builder) {
|
|
291
|
+
const value = configuration[k];
|
|
292
|
+
extensions.push(builder.instance(value));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return extensions;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get a extension builder
|
|
301
|
+
* @param key Extension unique identifier
|
|
302
|
+
* @returns The extension builder
|
|
303
|
+
*/
|
|
304
|
+
protected get(key: string): IConfigurableExtension<any> | undefined {
|
|
305
|
+
return this._configurableBuilderMap.get(key);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Whether the editor has an extension for the identifier.
|
|
310
|
+
*
|
|
311
|
+
* @param key Extension unique identifier
|
|
312
|
+
* @returns Extension existence
|
|
313
|
+
*/
|
|
314
|
+
protected has(key: string): boolean {
|
|
315
|
+
return this._configurableBuilderMap.has(key);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
protected getEffect<T>(
|
|
319
|
+
state: EditorState,
|
|
320
|
+
key: string,
|
|
321
|
+
value: T
|
|
322
|
+
): StateEffect<unknown> | null {
|
|
323
|
+
const builder = this.get(key);
|
|
324
|
+
return builder?.reconfigure(value) ?? null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private _getChangedOptions(
|
|
328
|
+
newConfig: Record<string, any>,
|
|
329
|
+
oldConfig: Record<string, any>
|
|
330
|
+
): string[] {
|
|
331
|
+
const changed = new Array<string>();
|
|
332
|
+
const newKeys = new Array<string>();
|
|
333
|
+
for (const [key, value] of Object.entries(newConfig)) {
|
|
334
|
+
newKeys.push(key);
|
|
335
|
+
if (oldConfig[key] !== value) {
|
|
336
|
+
changed.push(key);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// Add removed old keys
|
|
340
|
+
changed.push(...Object.keys(oldConfig).filter(k => !newKeys.includes(k)));
|
|
341
|
+
|
|
342
|
+
return changed;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private _baseConfig: Record<string, any>;
|
|
346
|
+
private _config: Record<string, any>;
|
|
347
|
+
private _configChanged = new Signal<this, Record<string, any>>(this);
|
|
348
|
+
private _configurableBuilderMap: Map<string, IConfigurableExtension<any>>;
|
|
349
|
+
private _disposed = new Signal<this, void>(this);
|
|
350
|
+
private _isDisposed = false;
|
|
351
|
+
private _immutables = new Set<string>();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* CodeMirror extensions registry
|
|
356
|
+
*/
|
|
357
|
+
export class EditorExtensionRegistry implements IEditorExtensionRegistry {
|
|
358
|
+
/**
|
|
359
|
+
* Base editor configuration
|
|
360
|
+
*
|
|
361
|
+
* This is the default configuration optionally modified by the user;
|
|
362
|
+
* e.g. through user settings.
|
|
363
|
+
*/
|
|
364
|
+
get baseConfiguration(): Record<string, any> {
|
|
365
|
+
return { ...this.defaultOptions, ...this._baseConfiguration };
|
|
366
|
+
}
|
|
367
|
+
set baseConfiguration(v: Record<string, any>) {
|
|
368
|
+
if (!JSONExt.deepEqual(v, this._baseConfiguration)) {
|
|
369
|
+
this._baseConfiguration = v;
|
|
370
|
+
for (const handler of this.handlers) {
|
|
371
|
+
handler.setBaseOptions(this.baseConfiguration);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Default editor configuration
|
|
378
|
+
*
|
|
379
|
+
* This is the default configuration as defined when extensions
|
|
380
|
+
* are registered.
|
|
381
|
+
*/
|
|
382
|
+
get defaultConfiguration(): Record<string, any> {
|
|
383
|
+
// Only options with schema should be JSON serializable
|
|
384
|
+
// So we cannot use `JSONExt.deepCopy` on the default options.
|
|
385
|
+
return Object.freeze({ ...this.defaultOptions });
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Editor configuration JSON schema
|
|
390
|
+
*/
|
|
391
|
+
get settingsSchema(): ReadonlyJSONObject {
|
|
392
|
+
return Object.freeze(JSONExt.deepCopy(this.configurationSchema));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Add a default editor extension
|
|
397
|
+
*
|
|
398
|
+
* @template T Extension parameter type
|
|
399
|
+
* @param factory Extension factory
|
|
400
|
+
*/
|
|
401
|
+
addExtension<T>(factory: IEditorExtensionFactory<T>): void {
|
|
402
|
+
if (this.configurationBuilder.has(factory.name)) {
|
|
403
|
+
throw new Error(`Extension named ${factory.name} is already registered.`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
this.configurationBuilder.set(factory.name, factory);
|
|
407
|
+
if (typeof factory.default != 'undefined') {
|
|
408
|
+
this.defaultOptions[factory.name] = factory.default;
|
|
409
|
+
}
|
|
410
|
+
if (factory.schema) {
|
|
411
|
+
this.configurationSchema[factory.name] = {
|
|
412
|
+
default: factory.default ?? null,
|
|
413
|
+
...factory.schema
|
|
414
|
+
};
|
|
415
|
+
this.defaultOptions[factory.name] =
|
|
416
|
+
this.configurationSchema[factory.name].default;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Create a new extensions handler for an editor
|
|
422
|
+
*
|
|
423
|
+
* @param options Extensions options and initial editor configuration
|
|
424
|
+
*/
|
|
425
|
+
createNew(
|
|
426
|
+
options: IEditorExtensionFactory.IOptions & {
|
|
427
|
+
/**
|
|
428
|
+
* The configuration options for the editor.
|
|
429
|
+
*/
|
|
430
|
+
config?: Record<string, any>;
|
|
431
|
+
}
|
|
432
|
+
): IExtensionsHandler {
|
|
433
|
+
const configuration = new Array<[string, IConfigurableExtension<any>]>();
|
|
434
|
+
for (const [key, builder] of this.configurationBuilder.entries()) {
|
|
435
|
+
const extension = builder.factory(options);
|
|
436
|
+
if (extension) {
|
|
437
|
+
configuration.push([key, extension]);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const handler = new ExtensionsHandler({
|
|
442
|
+
baseConfiguration: this.baseConfiguration,
|
|
443
|
+
config: options.config,
|
|
444
|
+
defaultExtensions: configuration
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
this.handlers.add(handler);
|
|
448
|
+
handler.disposed.connect(() => {
|
|
449
|
+
this.handlers.delete(handler);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
return handler;
|
|
453
|
+
}
|
|
454
|
+
protected configurationBuilder = new Map<
|
|
455
|
+
string,
|
|
456
|
+
IEditorExtensionFactory<any>
|
|
457
|
+
>();
|
|
458
|
+
protected configurationSchema: Record<string, any> = {};
|
|
459
|
+
|
|
460
|
+
protected defaultOptions: Record<string, any> = {};
|
|
461
|
+
protected handlers = new Set<ExtensionsHandler>();
|
|
462
|
+
protected immutableExtensions = new Set<string>();
|
|
463
|
+
private _baseConfiguration: Record<string, any> = {};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Editor extension registry namespace
|
|
468
|
+
*/
|
|
469
|
+
export namespace EditorExtensionRegistry {
|
|
470
|
+
/**
|
|
471
|
+
* Dynamically configurable editor extension.
|
|
472
|
+
*/
|
|
473
|
+
class ConfigurableExtension<T> implements IConfigurableExtension<T> {
|
|
474
|
+
/**
|
|
475
|
+
* Create a dynamic editor extension.
|
|
476
|
+
*
|
|
477
|
+
* @param builder Extension builder
|
|
478
|
+
*/
|
|
479
|
+
constructor(builder: (value: T) => Extension) {
|
|
480
|
+
this._compartment = new Compartment();
|
|
481
|
+
this._builder = builder;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Create an editor extension for the provided value.
|
|
486
|
+
*
|
|
487
|
+
* @param value Editor extension parameter value
|
|
488
|
+
* @returns The editor extension
|
|
489
|
+
*/
|
|
490
|
+
instance(value: T): Extension {
|
|
491
|
+
return this._compartment.of(this._builder(value));
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Reconfigure an editor extension.
|
|
496
|
+
*
|
|
497
|
+
* @param value Editor extension value
|
|
498
|
+
* @returns Editor state effect
|
|
499
|
+
*/
|
|
500
|
+
reconfigure(value: T): StateEffect<T> {
|
|
501
|
+
return this._compartment.reconfigure(
|
|
502
|
+
this._builder(value)
|
|
503
|
+
) as StateEffect<T>;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
private _compartment: Compartment;
|
|
507
|
+
private _builder: (value: T) => Extension;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Immutable editor extension class
|
|
512
|
+
*/
|
|
513
|
+
class ImmutableExtension implements IConfigurableExtension<undefined> {
|
|
514
|
+
/**
|
|
515
|
+
* Create an immutable editor extension.
|
|
516
|
+
*
|
|
517
|
+
* @param extension Extension
|
|
518
|
+
*/
|
|
519
|
+
constructor(extension: Extension) {
|
|
520
|
+
this._extension = extension;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Create an editor extension.
|
|
525
|
+
*
|
|
526
|
+
* @returns The editor extension
|
|
527
|
+
*/
|
|
528
|
+
instance(): Extension {
|
|
529
|
+
return this._extension;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Reconfigure an editor extension.
|
|
534
|
+
*
|
|
535
|
+
* This is a no-op
|
|
536
|
+
*/
|
|
537
|
+
reconfigure(): null {
|
|
538
|
+
// This is a no-op
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
private _extension: Extension;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Creates a dynamically configurable editor extension.
|
|
547
|
+
*
|
|
548
|
+
* @param builder Extension builder
|
|
549
|
+
* @return The extension
|
|
550
|
+
*/
|
|
551
|
+
export function createConfigurableExtension<T>(
|
|
552
|
+
builder: (value: T) => Extension
|
|
553
|
+
): IConfigurableExtension<T> {
|
|
554
|
+
return new ConfigurableExtension<T>(builder);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Creates a configurable extension returning
|
|
559
|
+
* one of two extensions depending on a boolean value.
|
|
560
|
+
*
|
|
561
|
+
* @param truthy Extension to apply when the parameter is true
|
|
562
|
+
* @param falsy Extension to apply when the parameter is false
|
|
563
|
+
* @return The extension
|
|
564
|
+
*/
|
|
565
|
+
export function createConditionalExtension(
|
|
566
|
+
truthy: Extension,
|
|
567
|
+
falsy: Extension = []
|
|
568
|
+
): IConfigurableExtension<boolean> {
|
|
569
|
+
return new ConfigurableExtension<boolean>(value =>
|
|
570
|
+
value ? truthy : falsy
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Creates an immutable extension.
|
|
576
|
+
*
|
|
577
|
+
* @param extension Immutable extension
|
|
578
|
+
* @return The extension
|
|
579
|
+
*/
|
|
580
|
+
export function createImmutableExtension(
|
|
581
|
+
extension: Extension
|
|
582
|
+
): IConfigurableExtension<undefined> {
|
|
583
|
+
return new ImmutableExtension(extension);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Get the default editor extensions.
|
|
588
|
+
*
|
|
589
|
+
* @returns CodeMirror 6 extension factories
|
|
590
|
+
*/
|
|
591
|
+
export function getDefaultExtensions(
|
|
592
|
+
options: {
|
|
593
|
+
themes?: IEditorThemeRegistry;
|
|
594
|
+
translator?: ITranslator | null;
|
|
595
|
+
} = {}
|
|
596
|
+
): ReadonlyArray<Readonly<IEditorExtensionFactory<any>>> {
|
|
597
|
+
const { themes, translator } = options;
|
|
598
|
+
const trans = (translator ?? nullTranslator).load('jupyter');
|
|
599
|
+
const extensions: IEditorExtensionFactory<any>[] = [
|
|
600
|
+
Object.freeze({
|
|
601
|
+
name: 'autoClosingBrackets',
|
|
602
|
+
default: false,
|
|
603
|
+
factory: () => createConditionalExtension(closeBrackets()),
|
|
604
|
+
schema: {
|
|
605
|
+
type: 'boolean',
|
|
606
|
+
title: trans.__('Auto Closing Brackets')
|
|
607
|
+
}
|
|
608
|
+
}),
|
|
609
|
+
Object.freeze({
|
|
610
|
+
name: 'codeFolding',
|
|
611
|
+
default: false,
|
|
612
|
+
factory: () => createConditionalExtension(foldGutter()),
|
|
613
|
+
schema: {
|
|
614
|
+
type: 'boolean',
|
|
615
|
+
title: trans.__('Code Folding')
|
|
616
|
+
}
|
|
617
|
+
}),
|
|
618
|
+
Object.freeze({
|
|
619
|
+
name: 'cursorBlinkRate',
|
|
620
|
+
default: 1200,
|
|
621
|
+
factory: () =>
|
|
622
|
+
createConfigurableExtension((value: number) =>
|
|
623
|
+
drawSelection({ cursorBlinkRate: value })
|
|
624
|
+
),
|
|
625
|
+
schema: {
|
|
626
|
+
type: 'number',
|
|
627
|
+
title: trans.__('Cursor blinking rate'),
|
|
628
|
+
description: trans.__(
|
|
629
|
+
'Half-period in milliseconds used for cursor blinking. The default blink rate is 1200ms. By setting this to zero, blinking can be disabled.'
|
|
630
|
+
)
|
|
631
|
+
}
|
|
632
|
+
}),
|
|
633
|
+
Object.freeze({
|
|
634
|
+
name: 'highlightActiveLine',
|
|
635
|
+
default: false,
|
|
636
|
+
factory: () => createConditionalExtension(highlightActiveLine()),
|
|
637
|
+
schema: {
|
|
638
|
+
type: 'boolean',
|
|
639
|
+
title: trans.__('Highlight the active line')
|
|
640
|
+
}
|
|
641
|
+
}),
|
|
642
|
+
Object.freeze({
|
|
643
|
+
name: 'highlightTrailingWhitespace',
|
|
644
|
+
default: false,
|
|
645
|
+
factory: () =>
|
|
646
|
+
createConditionalExtension(highlightTrailingWhitespace()),
|
|
647
|
+
schema: {
|
|
648
|
+
type: 'boolean',
|
|
649
|
+
title: trans.__('Highlight trailing white spaces')
|
|
650
|
+
}
|
|
651
|
+
}),
|
|
652
|
+
Object.freeze({
|
|
653
|
+
name: 'highlightWhitespace',
|
|
654
|
+
default: false,
|
|
655
|
+
factory: () => createConditionalExtension(highlightWhitespace()),
|
|
656
|
+
schema: {
|
|
657
|
+
type: 'boolean',
|
|
658
|
+
title: trans.__('Highlight white spaces')
|
|
659
|
+
}
|
|
660
|
+
}),
|
|
661
|
+
Object.freeze({
|
|
662
|
+
name: 'indentUnit',
|
|
663
|
+
default: '2',
|
|
664
|
+
factory: () =>
|
|
665
|
+
createConfigurableExtension<string>((value: string) =>
|
|
666
|
+
value == 'Tab'
|
|
667
|
+
? indentUnit.of('\t')
|
|
668
|
+
: indentUnit.of(' '.repeat(parseInt(value, 10)))
|
|
669
|
+
),
|
|
670
|
+
schema: {
|
|
671
|
+
type: 'string',
|
|
672
|
+
title: trans.__('Indentation unit'),
|
|
673
|
+
description: trans.__(
|
|
674
|
+
'The indentation is a `Tab` or the number of spaces. This defaults to 2 spaces.'
|
|
675
|
+
),
|
|
676
|
+
enum: ['Tab', '1', '2', '4', '8']
|
|
677
|
+
}
|
|
678
|
+
}),
|
|
679
|
+
// Default keyboard shortcuts
|
|
680
|
+
// TODO at some point we may want to get this configurable
|
|
681
|
+
Object.freeze({
|
|
682
|
+
name: 'keymap',
|
|
683
|
+
default: [
|
|
684
|
+
...defaultKeymap,
|
|
685
|
+
{
|
|
686
|
+
key: 'Tab',
|
|
687
|
+
run: StateCommands.indentMoreOrInsertTab,
|
|
688
|
+
shift: indentLess
|
|
689
|
+
}
|
|
690
|
+
],
|
|
691
|
+
factory: () =>
|
|
692
|
+
createConfigurableExtension<KeyBinding[]>(value => keymap.of(value))
|
|
693
|
+
}),
|
|
694
|
+
Object.freeze({
|
|
695
|
+
name: 'lineNumbers',
|
|
696
|
+
default: true,
|
|
697
|
+
factory: () => createConditionalExtension(lineNumbers()),
|
|
698
|
+
schema: {
|
|
699
|
+
type: 'boolean',
|
|
700
|
+
title: trans.__('Line Numbers')
|
|
701
|
+
}
|
|
702
|
+
}),
|
|
703
|
+
Object.freeze({
|
|
704
|
+
name: 'lineWrap',
|
|
705
|
+
factory: () => createConditionalExtension(EditorView.lineWrapping),
|
|
706
|
+
default: true,
|
|
707
|
+
schema: {
|
|
708
|
+
type: 'boolean',
|
|
709
|
+
title: trans.__('Line Wrap')
|
|
710
|
+
}
|
|
711
|
+
}),
|
|
712
|
+
Object.freeze({
|
|
713
|
+
name: 'matchBrackets',
|
|
714
|
+
default: true,
|
|
715
|
+
factory: () => createConditionalExtension(bracketMatching()),
|
|
716
|
+
schema: {
|
|
717
|
+
type: 'boolean',
|
|
718
|
+
title: trans.__('Match Brackets')
|
|
719
|
+
}
|
|
720
|
+
}),
|
|
721
|
+
Object.freeze({
|
|
722
|
+
name: 'readOnly',
|
|
723
|
+
default: false,
|
|
724
|
+
factory: () =>
|
|
725
|
+
createConfigurableExtension((value: boolean) => [
|
|
726
|
+
EditorState.readOnly.of(value),
|
|
727
|
+
value
|
|
728
|
+
? EditorView.editorAttributes.of({ class: READ_ONLY_CLASS })
|
|
729
|
+
: []
|
|
730
|
+
])
|
|
731
|
+
}),
|
|
732
|
+
Object.freeze({
|
|
733
|
+
name: 'rulers',
|
|
734
|
+
default: [],
|
|
735
|
+
factory: () =>
|
|
736
|
+
createConfigurableExtension((value: number[]) =>
|
|
737
|
+
value.length > 0 ? rulers(value) : []
|
|
738
|
+
),
|
|
739
|
+
schema: {
|
|
740
|
+
type: 'array',
|
|
741
|
+
title: trans.__('Rulers'),
|
|
742
|
+
items: {
|
|
743
|
+
type: 'number',
|
|
744
|
+
minimum: 0
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}),
|
|
748
|
+
Object.freeze({
|
|
749
|
+
name: 'scrollPastEnd',
|
|
750
|
+
default: false,
|
|
751
|
+
factory: (options: IEditorExtensionFactory.IOptions) =>
|
|
752
|
+
options.inline ? null : createConditionalExtension(scrollPastEnd())
|
|
753
|
+
}),
|
|
754
|
+
Object.freeze({
|
|
755
|
+
name: 'smartIndent',
|
|
756
|
+
default: true,
|
|
757
|
+
factory: () => createConditionalExtension(indentOnInput()),
|
|
758
|
+
schema: {
|
|
759
|
+
type: 'boolean',
|
|
760
|
+
title: trans.__('Smart Indentation')
|
|
761
|
+
}
|
|
762
|
+
}),
|
|
763
|
+
Object.freeze({
|
|
764
|
+
name: 'tabSize',
|
|
765
|
+
default: 4,
|
|
766
|
+
factory: () =>
|
|
767
|
+
createConfigurableExtension((value: number) =>
|
|
768
|
+
EditorState.tabSize.of(value)
|
|
769
|
+
),
|
|
770
|
+
schema: {
|
|
771
|
+
type: 'number',
|
|
772
|
+
title: trans.__('Tab size')
|
|
773
|
+
}
|
|
774
|
+
}),
|
|
775
|
+
Object.freeze({
|
|
776
|
+
name: 'allowMultipleSelections',
|
|
777
|
+
default: true,
|
|
778
|
+
factory: () =>
|
|
779
|
+
createConfigurableExtension((value: boolean) =>
|
|
780
|
+
EditorState.allowMultipleSelections.of(value)
|
|
781
|
+
),
|
|
782
|
+
schema: {
|
|
783
|
+
type: 'boolean',
|
|
784
|
+
title: trans.__('Multiple selections')
|
|
785
|
+
}
|
|
786
|
+
}),
|
|
787
|
+
Object.freeze({
|
|
788
|
+
name: 'customStyles',
|
|
789
|
+
factory: () =>
|
|
790
|
+
createConfigurableExtension<CustomTheme>(config =>
|
|
791
|
+
customTheme(config)
|
|
792
|
+
),
|
|
793
|
+
default: {
|
|
794
|
+
fontFamily: null,
|
|
795
|
+
fontSize: null,
|
|
796
|
+
lineHeight: null
|
|
797
|
+
},
|
|
798
|
+
schema: {
|
|
799
|
+
title: trans.__('Custom editor styles'),
|
|
800
|
+
type: 'object',
|
|
801
|
+
properties: {
|
|
802
|
+
fontFamily: {
|
|
803
|
+
type: ['string', 'null'],
|
|
804
|
+
title: trans.__('Font Family')
|
|
805
|
+
},
|
|
806
|
+
fontSize: {
|
|
807
|
+
type: ['number', 'null'],
|
|
808
|
+
minimum: 1,
|
|
809
|
+
maximum: 100,
|
|
810
|
+
title: trans.__('Font Size')
|
|
811
|
+
},
|
|
812
|
+
lineHeight: {
|
|
813
|
+
type: ['number', 'null'],
|
|
814
|
+
title: trans.__('Line Height')
|
|
815
|
+
}
|
|
816
|
+
},
|
|
817
|
+
additionalProperties: false
|
|
818
|
+
}
|
|
819
|
+
})
|
|
820
|
+
];
|
|
821
|
+
|
|
822
|
+
if (themes) {
|
|
823
|
+
extensions.push(
|
|
824
|
+
Object.freeze({
|
|
825
|
+
name: 'theme',
|
|
826
|
+
default: 'jupyter',
|
|
827
|
+
factory: () =>
|
|
828
|
+
createConfigurableExtension<string>(value =>
|
|
829
|
+
themes.getTheme(value)
|
|
830
|
+
),
|
|
831
|
+
schema: {
|
|
832
|
+
type: 'string',
|
|
833
|
+
title: trans.__('Theme'),
|
|
834
|
+
description: trans.__('CodeMirror theme')
|
|
835
|
+
}
|
|
836
|
+
})
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (translator) {
|
|
841
|
+
extensions.push(
|
|
842
|
+
Object.freeze({
|
|
843
|
+
name: 'translation',
|
|
844
|
+
// The list of internal strings is available at https://codemirror.net/examples/translate/
|
|
845
|
+
default: {
|
|
846
|
+
// @codemirror/view
|
|
847
|
+
'Control character': trans.__('Control character'),
|
|
848
|
+
// @codemirror/commands
|
|
849
|
+
'Selection deleted': trans.__('Selection deleted'),
|
|
850
|
+
// @codemirror/language
|
|
851
|
+
'Folded lines': trans.__('Folded lines'),
|
|
852
|
+
'Unfolded lines': trans.__('Unfolded lines'),
|
|
853
|
+
to: trans.__('to'),
|
|
854
|
+
'folded code': trans.__('folded code'),
|
|
855
|
+
unfold: trans.__('unfold'),
|
|
856
|
+
'Fold line': trans.__('Fold line'),
|
|
857
|
+
'Unfold line': trans.__('Unfold line'),
|
|
858
|
+
// @codemirror/search
|
|
859
|
+
'Go to line': trans.__('Go to line'),
|
|
860
|
+
go: trans.__('go'),
|
|
861
|
+
Find: trans.__('Find'),
|
|
862
|
+
Replace: trans.__('Replace'),
|
|
863
|
+
next: trans.__('next'),
|
|
864
|
+
previous: trans.__('previous'),
|
|
865
|
+
all: trans.__('all'),
|
|
866
|
+
'match case': trans.__('match case'),
|
|
867
|
+
replace: trans.__('replace'),
|
|
868
|
+
'replace all': trans.__('replace all'),
|
|
869
|
+
close: trans.__('close'),
|
|
870
|
+
'current match': trans.__('current match'),
|
|
871
|
+
'replaced $ matches': trans.__('replaced $ matches'),
|
|
872
|
+
'replaced match on line $': trans.__('replaced match on line $'),
|
|
873
|
+
'on line': trans.__('on line'),
|
|
874
|
+
// @codemirror/autocomplete
|
|
875
|
+
Completions: trans.__('Completions'),
|
|
876
|
+
// @codemirror/lint
|
|
877
|
+
Diagnostics: trans.__('Diagnostics'),
|
|
878
|
+
'No diagnostics': trans.__('No diagnostics')
|
|
879
|
+
},
|
|
880
|
+
factory: () =>
|
|
881
|
+
createConfigurableExtension<Record<string, string>>(value =>
|
|
882
|
+
EditorState.phrases.of(value)
|
|
883
|
+
)
|
|
884
|
+
})
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
return extensions;
|
|
889
|
+
}
|
|
890
|
+
}
|