@jupyterlab/fileeditor-extension 4.0.0-alpha.2 → 4.0.0-alpha.21
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/commands.d.ts +18 -5
- package/lib/commands.js +279 -116
- package/lib/commands.js.map +1 -1
- package/lib/index.js +253 -26
- package/lib/index.js.map +1 -1
- package/lib/syntaxstatus.d.ts +5 -0
- package/lib/syntaxstatus.js +44 -0
- package/lib/syntaxstatus.js.map +1 -0
- package/package.json +33 -22
- package/schema/completer.json +14 -0
- package/schema/plugin.json +48 -103
- package/src/commands.ts +1388 -0
- package/src/index.ts +672 -0
- package/src/syntaxstatus.ts +64 -0
- package/style/index.css +6 -2
- package/style/index.js +6 -2
package/src/commands.ts
ADDED
|
@@ -0,0 +1,1388 @@
|
|
|
1
|
+
// Copyright (c) Jupyter Development Team.
|
|
2
|
+
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
import { selectAll } from '@codemirror/commands';
|
|
4
|
+
import { findNext, gotoLine } from '@codemirror/search';
|
|
5
|
+
import { JupyterFrontEnd } from '@jupyterlab/application';
|
|
6
|
+
import {
|
|
7
|
+
Clipboard,
|
|
8
|
+
ICommandPalette,
|
|
9
|
+
ISessionContextDialogs,
|
|
10
|
+
MainAreaWidget,
|
|
11
|
+
sessionContextDialogs,
|
|
12
|
+
WidgetTracker
|
|
13
|
+
} from '@jupyterlab/apputils';
|
|
14
|
+
import {
|
|
15
|
+
CodeEditor,
|
|
16
|
+
CodeViewerWidget,
|
|
17
|
+
IEditorServices
|
|
18
|
+
} from '@jupyterlab/codeeditor';
|
|
19
|
+
import {
|
|
20
|
+
CodeMirrorEditor,
|
|
21
|
+
IEditorExtensionRegistry,
|
|
22
|
+
IEditorLanguageRegistry,
|
|
23
|
+
IEditorThemeRegistry
|
|
24
|
+
} from '@jupyterlab/codemirror';
|
|
25
|
+
import { ICompletionProviderManager } from '@jupyterlab/completer';
|
|
26
|
+
import { IConsoleTracker } from '@jupyterlab/console';
|
|
27
|
+
import { MarkdownCodeBlocks, PathExt } from '@jupyterlab/coreutils';
|
|
28
|
+
import { IDocumentWidget } from '@jupyterlab/docregistry';
|
|
29
|
+
import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
|
|
30
|
+
import { FileEditor, IEditorTracker } from '@jupyterlab/fileeditor';
|
|
31
|
+
import { ILauncher } from '@jupyterlab/launcher';
|
|
32
|
+
import { IMainMenu } from '@jupyterlab/mainmenu';
|
|
33
|
+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
34
|
+
import {
|
|
35
|
+
ITranslator,
|
|
36
|
+
nullTranslator,
|
|
37
|
+
TranslationBundle
|
|
38
|
+
} from '@jupyterlab/translation';
|
|
39
|
+
import {
|
|
40
|
+
consoleIcon,
|
|
41
|
+
copyIcon,
|
|
42
|
+
cutIcon,
|
|
43
|
+
LabIcon,
|
|
44
|
+
markdownIcon,
|
|
45
|
+
pasteIcon,
|
|
46
|
+
redoIcon,
|
|
47
|
+
textEditorIcon,
|
|
48
|
+
undoIcon
|
|
49
|
+
} from '@jupyterlab/ui-components';
|
|
50
|
+
import { find } from '@lumino/algorithm';
|
|
51
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
52
|
+
import {
|
|
53
|
+
JSONObject,
|
|
54
|
+
ReadonlyJSONObject,
|
|
55
|
+
ReadonlyPartialJSONObject
|
|
56
|
+
} from '@lumino/coreutils';
|
|
57
|
+
|
|
58
|
+
const autoClosingBracketsNotebook = 'notebook:toggle-autoclosing-brackets';
|
|
59
|
+
const autoClosingBracketsConsole = 'console:toggle-autoclosing-brackets';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The command IDs used by the fileeditor plugin.
|
|
63
|
+
*/
|
|
64
|
+
export namespace CommandIDs {
|
|
65
|
+
export const createNew = 'fileeditor:create-new';
|
|
66
|
+
|
|
67
|
+
export const createNewMarkdown = 'fileeditor:create-new-markdown-file';
|
|
68
|
+
|
|
69
|
+
export const changeFontSize = 'fileeditor:change-font-size';
|
|
70
|
+
|
|
71
|
+
export const lineNumbers = 'fileeditor:toggle-line-numbers';
|
|
72
|
+
|
|
73
|
+
export const currentLineNumbers = 'fileeditor:toggle-current-line-numbers';
|
|
74
|
+
|
|
75
|
+
export const lineWrap = 'fileeditor:toggle-line-wrap';
|
|
76
|
+
|
|
77
|
+
export const currentLineWrap = 'fileeditor:toggle-current-line-wrap';
|
|
78
|
+
|
|
79
|
+
export const changeTabs = 'fileeditor:change-tabs';
|
|
80
|
+
|
|
81
|
+
export const matchBrackets = 'fileeditor:toggle-match-brackets';
|
|
82
|
+
|
|
83
|
+
export const currentMatchBrackets =
|
|
84
|
+
'fileeditor:toggle-current-match-brackets';
|
|
85
|
+
|
|
86
|
+
export const autoClosingBrackets = 'fileeditor:toggle-autoclosing-brackets';
|
|
87
|
+
|
|
88
|
+
export const autoClosingBracketsUniversal =
|
|
89
|
+
'fileeditor:toggle-autoclosing-brackets-universal';
|
|
90
|
+
|
|
91
|
+
export const createConsole = 'fileeditor:create-console';
|
|
92
|
+
|
|
93
|
+
export const replaceSelection = 'fileeditor:replace-selection';
|
|
94
|
+
|
|
95
|
+
export const restartConsole = 'fileeditor:restart-console';
|
|
96
|
+
|
|
97
|
+
export const runCode = 'fileeditor:run-code';
|
|
98
|
+
|
|
99
|
+
export const runAllCode = 'fileeditor:run-all';
|
|
100
|
+
|
|
101
|
+
export const markdownPreview = 'fileeditor:markdown-preview';
|
|
102
|
+
|
|
103
|
+
export const undo = 'fileeditor:undo';
|
|
104
|
+
|
|
105
|
+
export const redo = 'fileeditor:redo';
|
|
106
|
+
|
|
107
|
+
export const cut = 'fileeditor:cut';
|
|
108
|
+
|
|
109
|
+
export const copy = 'fileeditor:copy';
|
|
110
|
+
|
|
111
|
+
export const paste = 'fileeditor:paste';
|
|
112
|
+
|
|
113
|
+
export const selectAll = 'fileeditor:select-all';
|
|
114
|
+
|
|
115
|
+
export const invokeCompleter = 'completer:invoke-file';
|
|
116
|
+
|
|
117
|
+
export const selectCompleter = 'completer:select-file';
|
|
118
|
+
|
|
119
|
+
export const openCodeViewer = 'code-viewer:open';
|
|
120
|
+
|
|
121
|
+
export const changeTheme = 'fileeditor:change-theme';
|
|
122
|
+
|
|
123
|
+
export const changeLanguage = 'fileeditor:change-language';
|
|
124
|
+
|
|
125
|
+
export const find = 'fileeditor:find';
|
|
126
|
+
|
|
127
|
+
export const goToLine = 'fileeditor:go-to-line';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface IFileTypeData extends ReadonlyJSONObject {
|
|
131
|
+
fileExt: string;
|
|
132
|
+
iconName: string;
|
|
133
|
+
launcherLabel: string;
|
|
134
|
+
paletteLabel: string;
|
|
135
|
+
caption: string;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The name of the factory that creates editor widgets.
|
|
140
|
+
*/
|
|
141
|
+
export const FACTORY = 'Editor';
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* A utility class for adding commands and menu items,
|
|
145
|
+
* for use by the File Editor extension or other Editor extensions.
|
|
146
|
+
*/
|
|
147
|
+
export namespace Commands {
|
|
148
|
+
let config: Record<string, any> = {};
|
|
149
|
+
let scrollPastEnd = true;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Accessor function that returns the createConsole function for use by Create Console commands
|
|
153
|
+
*/
|
|
154
|
+
function getCreateConsoleFunction(
|
|
155
|
+
commands: CommandRegistry,
|
|
156
|
+
languages: IEditorLanguageRegistry
|
|
157
|
+
): (
|
|
158
|
+
widget: IDocumentWidget<FileEditor>,
|
|
159
|
+
args?: ReadonlyPartialJSONObject
|
|
160
|
+
) => Promise<void> {
|
|
161
|
+
return async function createConsole(
|
|
162
|
+
widget: IDocumentWidget<FileEditor>,
|
|
163
|
+
args?: ReadonlyPartialJSONObject
|
|
164
|
+
): Promise<void> {
|
|
165
|
+
const options = args || {};
|
|
166
|
+
const console = await commands.execute('console:create', {
|
|
167
|
+
activate: options['activate'],
|
|
168
|
+
name: widget.context.contentsModel?.name,
|
|
169
|
+
path: widget.context.path,
|
|
170
|
+
// Default value is an empty string -> using OR operator
|
|
171
|
+
preferredLanguage:
|
|
172
|
+
widget.context.model.defaultKernelLanguage ||
|
|
173
|
+
(languages.findByFileName(widget.context.path)?.name ?? ''),
|
|
174
|
+
ref: widget.id,
|
|
175
|
+
insertMode: 'split-bottom'
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
widget.context.pathChanged.connect((sender, value) => {
|
|
179
|
+
console.session.setPath(value);
|
|
180
|
+
console.session.setName(widget.context.contentsModel?.name);
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Update the setting values.
|
|
187
|
+
*/
|
|
188
|
+
export function updateSettings(
|
|
189
|
+
settings: ISettingRegistry.ISettings,
|
|
190
|
+
commands: CommandRegistry
|
|
191
|
+
): void {
|
|
192
|
+
config =
|
|
193
|
+
(settings.get('editorConfig').composite as Record<string, any>) ?? {};
|
|
194
|
+
scrollPastEnd = settings.get('scrollPasteEnd').composite as boolean;
|
|
195
|
+
|
|
196
|
+
// Trigger a refresh of the rendered commands
|
|
197
|
+
commands.notifyCommandChanged(CommandIDs.lineNumbers);
|
|
198
|
+
commands.notifyCommandChanged(CommandIDs.currentLineNumbers);
|
|
199
|
+
commands.notifyCommandChanged(CommandIDs.lineWrap);
|
|
200
|
+
commands.notifyCommandChanged(CommandIDs.currentLineWrap);
|
|
201
|
+
commands.notifyCommandChanged(CommandIDs.changeTabs);
|
|
202
|
+
commands.notifyCommandChanged(CommandIDs.matchBrackets);
|
|
203
|
+
commands.notifyCommandChanged(CommandIDs.currentMatchBrackets);
|
|
204
|
+
commands.notifyCommandChanged(CommandIDs.autoClosingBrackets);
|
|
205
|
+
commands.notifyCommandChanged(CommandIDs.changeLanguage);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Update the settings of the current tracker instances.
|
|
210
|
+
*/
|
|
211
|
+
export function updateTracker(
|
|
212
|
+
tracker: WidgetTracker<IDocumentWidget<FileEditor>>
|
|
213
|
+
): void {
|
|
214
|
+
tracker.forEach(widget => {
|
|
215
|
+
updateWidget(widget.content);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Update the settings of a widget.
|
|
221
|
+
* Skip global settings for transient editor specific configs.
|
|
222
|
+
*/
|
|
223
|
+
export function updateWidget(widget: FileEditor): void {
|
|
224
|
+
const editor = widget.editor;
|
|
225
|
+
editor.setOptions({ ...config, scrollPastEnd });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Wrapper function for adding the default File Editor commands
|
|
230
|
+
*/
|
|
231
|
+
export function addCommands(
|
|
232
|
+
commands: CommandRegistry,
|
|
233
|
+
settingRegistry: ISettingRegistry,
|
|
234
|
+
trans: TranslationBundle,
|
|
235
|
+
id: string,
|
|
236
|
+
isEnabled: () => boolean,
|
|
237
|
+
tracker: WidgetTracker<IDocumentWidget<FileEditor>>,
|
|
238
|
+
defaultBrowser: IDefaultFileBrowser,
|
|
239
|
+
extensions: IEditorExtensionRegistry,
|
|
240
|
+
languages: IEditorLanguageRegistry,
|
|
241
|
+
themes: IEditorThemeRegistry,
|
|
242
|
+
consoleTracker: IConsoleTracker | null,
|
|
243
|
+
sessionDialogs: ISessionContextDialogs | null,
|
|
244
|
+
mainMenu: IMainMenu | null
|
|
245
|
+
): void {
|
|
246
|
+
/**
|
|
247
|
+
* Add a command to change font size for File Editor
|
|
248
|
+
*/
|
|
249
|
+
commands.addCommand(CommandIDs.changeFontSize, {
|
|
250
|
+
execute: args => {
|
|
251
|
+
const delta = Number(args['delta']);
|
|
252
|
+
if (Number.isNaN(delta)) {
|
|
253
|
+
console.error(
|
|
254
|
+
`${CommandIDs.changeFontSize}: delta arg must be a number`
|
|
255
|
+
);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const style = window.getComputedStyle(document.documentElement);
|
|
259
|
+
const cssSize = parseInt(
|
|
260
|
+
style.getPropertyValue('--jp-code-font-size'),
|
|
261
|
+
10
|
|
262
|
+
);
|
|
263
|
+
const currentSize =
|
|
264
|
+
(config['customStyles']['fontSize'] ??
|
|
265
|
+
extensions.baseConfiguration['customStyles']['fontSize']) ||
|
|
266
|
+
cssSize;
|
|
267
|
+
config.fontSize = currentSize + delta;
|
|
268
|
+
return settingRegistry
|
|
269
|
+
.set(id, 'editorConfig', config)
|
|
270
|
+
.catch((reason: Error) => {
|
|
271
|
+
console.error(`Failed to set ${id}: ${reason.message}`);
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
label: args => {
|
|
275
|
+
const delta = Number(args['delta']);
|
|
276
|
+
if (Number.isNaN(delta)) {
|
|
277
|
+
console.error(
|
|
278
|
+
`${CommandIDs.changeFontSize}: delta arg must be a number`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
if (delta > 0) {
|
|
282
|
+
return args.isMenu
|
|
283
|
+
? trans.__('Increase Text Editor Font Size')
|
|
284
|
+
: trans.__('Increase Font Size');
|
|
285
|
+
} else {
|
|
286
|
+
return args.isMenu
|
|
287
|
+
? trans.__('Decrease Text Editor Font Size')
|
|
288
|
+
: trans.__('Decrease Font Size');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Add the Line Numbers command
|
|
295
|
+
*/
|
|
296
|
+
commands.addCommand(CommandIDs.lineNumbers, {
|
|
297
|
+
execute: async () => {
|
|
298
|
+
config.lineNumbers = !(
|
|
299
|
+
config.lineNumbers ?? extensions.baseConfiguration.lineNumbers
|
|
300
|
+
);
|
|
301
|
+
try {
|
|
302
|
+
return await settingRegistry.set(id, 'editorConfig', config);
|
|
303
|
+
} catch (reason) {
|
|
304
|
+
console.error(`Failed to set ${id}: ${reason.message}`);
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
isEnabled,
|
|
308
|
+
isToggled: () =>
|
|
309
|
+
config.lineNumbers ?? extensions.baseConfiguration.lineNumbers,
|
|
310
|
+
label: trans.__('Show Line Numbers')
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
commands.addCommand(CommandIDs.currentLineNumbers, {
|
|
314
|
+
label: trans.__('Show Line Numbers'),
|
|
315
|
+
caption: trans.__('Show the line numbers for the current file.'),
|
|
316
|
+
execute: () => {
|
|
317
|
+
const widget = tracker.currentWidget;
|
|
318
|
+
if (!widget) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const lineNumbers = !widget.content.editor.getOption('lineNumbers');
|
|
322
|
+
widget.content.editor.setOption('lineNumbers', lineNumbers);
|
|
323
|
+
},
|
|
324
|
+
isEnabled,
|
|
325
|
+
isToggled: () => {
|
|
326
|
+
const widget = tracker.currentWidget;
|
|
327
|
+
return (
|
|
328
|
+
(widget?.content.editor.getOption('lineNumbers') as
|
|
329
|
+
| boolean
|
|
330
|
+
| undefined) ?? false
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Add the Word Wrap command
|
|
337
|
+
*/
|
|
338
|
+
commands.addCommand(CommandIDs.lineWrap, {
|
|
339
|
+
execute: async args => {
|
|
340
|
+
config.lineWrap = (args['mode'] as boolean) ?? false;
|
|
341
|
+
try {
|
|
342
|
+
return await settingRegistry.set(id, 'editorConfig', config);
|
|
343
|
+
} catch (reason) {
|
|
344
|
+
console.error(`Failed to set ${id}: ${reason.message}`);
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
isEnabled,
|
|
348
|
+
isToggled: args => {
|
|
349
|
+
const lineWrap = args['mode'] ?? false;
|
|
350
|
+
return (
|
|
351
|
+
lineWrap ===
|
|
352
|
+
(config.lineWrap ?? extensions.baseConfiguration.lineWrap)
|
|
353
|
+
);
|
|
354
|
+
},
|
|
355
|
+
label: trans.__('Word Wrap')
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
commands.addCommand(CommandIDs.currentLineWrap, {
|
|
359
|
+
label: trans.__('Wrap Words'),
|
|
360
|
+
caption: trans.__('Wrap words for the current file.'),
|
|
361
|
+
execute: () => {
|
|
362
|
+
const widget = tracker.currentWidget;
|
|
363
|
+
if (!widget) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const oldValue = widget.content.editor.getOption('lineWrap');
|
|
367
|
+
widget.content.editor.setOption('lineWrap', !oldValue);
|
|
368
|
+
},
|
|
369
|
+
isEnabled,
|
|
370
|
+
isToggled: () => {
|
|
371
|
+
const widget = tracker.currentWidget;
|
|
372
|
+
return (
|
|
373
|
+
(widget?.content.editor.getOption('lineWrap') as boolean) ?? false
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Add command for changing tabs size or type in File Editor
|
|
380
|
+
*/
|
|
381
|
+
|
|
382
|
+
commands.addCommand(CommandIDs.changeTabs, {
|
|
383
|
+
label: args => {
|
|
384
|
+
if (args.size) {
|
|
385
|
+
return trans.__('Spaces: %1', args.size ?? '');
|
|
386
|
+
} else {
|
|
387
|
+
return trans.__('Indent with Tab');
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
execute: async args => {
|
|
391
|
+
config.indentUnit =
|
|
392
|
+
args['size'] !== undefined
|
|
393
|
+
? ((args['size'] as string) ?? '4').toString()
|
|
394
|
+
: 'Tab';
|
|
395
|
+
try {
|
|
396
|
+
return await settingRegistry.set(id, 'editorConfig', config);
|
|
397
|
+
} catch (reason) {
|
|
398
|
+
console.error(`Failed to set ${id}: ${reason.message}`);
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
isToggled: args => {
|
|
402
|
+
const currentIndentUnit =
|
|
403
|
+
config.indentUnit ?? extensions.baseConfiguration.indentUnit;
|
|
404
|
+
return args['size']
|
|
405
|
+
? args['size'] === currentIndentUnit
|
|
406
|
+
: 'Tab' == currentIndentUnit;
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Add the Match Brackets command
|
|
412
|
+
*/
|
|
413
|
+
commands.addCommand(CommandIDs.matchBrackets, {
|
|
414
|
+
execute: async () => {
|
|
415
|
+
config.matchBrackets = !(
|
|
416
|
+
config.matchBrackets ?? extensions.baseConfiguration.matchBrackets
|
|
417
|
+
);
|
|
418
|
+
try {
|
|
419
|
+
return await settingRegistry.set(id, 'editorConfig', config);
|
|
420
|
+
} catch (reason) {
|
|
421
|
+
console.error(`Failed to set ${id}: ${reason.message}`);
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
label: trans.__('Match Brackets'),
|
|
425
|
+
isEnabled,
|
|
426
|
+
isToggled: () =>
|
|
427
|
+
config.matchBrackets ?? extensions.baseConfiguration.matchBrackets
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
commands.addCommand(CommandIDs.currentMatchBrackets, {
|
|
431
|
+
label: trans.__('Match Brackets'),
|
|
432
|
+
caption: trans.__('Change match brackets for the current file.'),
|
|
433
|
+
execute: () => {
|
|
434
|
+
const widget = tracker.currentWidget;
|
|
435
|
+
if (!widget) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const matchBrackets = !widget.content.editor.getOption('matchBrackets');
|
|
439
|
+
widget.content.editor.setOption('matchBrackets', matchBrackets);
|
|
440
|
+
},
|
|
441
|
+
isEnabled,
|
|
442
|
+
isToggled: () => {
|
|
443
|
+
const widget = tracker.currentWidget;
|
|
444
|
+
return (
|
|
445
|
+
(widget?.content.editor.getOption('matchBrackets') as
|
|
446
|
+
| boolean
|
|
447
|
+
| undefined) ?? false
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Add the Auto Close Brackets for Text Editor command
|
|
454
|
+
*/
|
|
455
|
+
commands.addCommand(CommandIDs.autoClosingBrackets, {
|
|
456
|
+
execute: async args => {
|
|
457
|
+
config.autoClosingBrackets = !!(
|
|
458
|
+
args['force'] ??
|
|
459
|
+
!(
|
|
460
|
+
config.autoClosingBrackets ??
|
|
461
|
+
extensions.baseConfiguration.autoClosingBrackets
|
|
462
|
+
)
|
|
463
|
+
);
|
|
464
|
+
try {
|
|
465
|
+
return await settingRegistry.set(id, 'editorConfig', config);
|
|
466
|
+
} catch (reason) {
|
|
467
|
+
console.error(`Failed to set ${id}: ${reason.message}`);
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
label: trans.__('Auto Close Brackets in Text Editor'),
|
|
471
|
+
isToggled: () =>
|
|
472
|
+
config.autoClosingBrackets ??
|
|
473
|
+
extensions.baseConfiguration.autoClosingBrackets
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
commands.addCommand(CommandIDs.autoClosingBracketsUniversal, {
|
|
477
|
+
execute: () => {
|
|
478
|
+
const anyToggled =
|
|
479
|
+
commands.isToggled(CommandIDs.autoClosingBrackets) ||
|
|
480
|
+
commands.isToggled(autoClosingBracketsNotebook) ||
|
|
481
|
+
commands.isToggled(autoClosingBracketsConsole);
|
|
482
|
+
// if any auto closing brackets options is toggled, toggle both off
|
|
483
|
+
if (anyToggled) {
|
|
484
|
+
void commands.execute(CommandIDs.autoClosingBrackets, {
|
|
485
|
+
force: false
|
|
486
|
+
});
|
|
487
|
+
void commands.execute(autoClosingBracketsNotebook, { force: false });
|
|
488
|
+
void commands.execute(autoClosingBracketsConsole, { force: false });
|
|
489
|
+
} else {
|
|
490
|
+
// both are off, turn them on
|
|
491
|
+
void commands.execute(CommandIDs.autoClosingBrackets, {
|
|
492
|
+
force: true
|
|
493
|
+
});
|
|
494
|
+
void commands.execute(autoClosingBracketsNotebook, { force: true });
|
|
495
|
+
void commands.execute(autoClosingBracketsConsole, { force: true });
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
label: trans.__('Auto Close Brackets'),
|
|
499
|
+
isToggled: () =>
|
|
500
|
+
commands.isToggled(CommandIDs.autoClosingBrackets) ||
|
|
501
|
+
commands.isToggled(autoClosingBracketsNotebook) ||
|
|
502
|
+
commands.isToggled(autoClosingBracketsConsole)
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Create a menu for the editor.
|
|
507
|
+
*/
|
|
508
|
+
commands.addCommand(CommandIDs.changeTheme, {
|
|
509
|
+
label: args =>
|
|
510
|
+
((args.displayName ?? args.theme) as string) ??
|
|
511
|
+
config.theme ??
|
|
512
|
+
extensions.baseConfiguration.theme ??
|
|
513
|
+
trans.__('Editor Theme'),
|
|
514
|
+
execute: async args => {
|
|
515
|
+
config.theme = (args['theme'] as string) ?? config.theme;
|
|
516
|
+
|
|
517
|
+
try {
|
|
518
|
+
return await settingRegistry.set(id, 'editorConfig', config);
|
|
519
|
+
} catch (reason) {
|
|
520
|
+
console.error(`Failed to set theme - ${reason.message}`);
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
isToggled: args =>
|
|
524
|
+
args['theme'] === (config.theme ?? extensions.baseConfiguration.theme)
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
commands.addCommand(CommandIDs.find, {
|
|
528
|
+
label: trans.__('Find…'),
|
|
529
|
+
execute: () => {
|
|
530
|
+
const widget = tracker.currentWidget;
|
|
531
|
+
if (!widget) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const editor = widget.content.editor as CodeMirrorEditor;
|
|
535
|
+
editor.execCommand(findNext);
|
|
536
|
+
},
|
|
537
|
+
isEnabled
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
commands.addCommand(CommandIDs.goToLine, {
|
|
541
|
+
label: trans.__('Go to Line…'),
|
|
542
|
+
execute: args => {
|
|
543
|
+
const widget = tracker.currentWidget;
|
|
544
|
+
if (!widget) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const editor = widget.content.editor as CodeMirrorEditor;
|
|
548
|
+
|
|
549
|
+
const line = args['line'] as number | undefined;
|
|
550
|
+
const column = args['column'] as number | undefined;
|
|
551
|
+
if (line !== undefined || column !== undefined) {
|
|
552
|
+
editor.setCursorPosition({
|
|
553
|
+
line: (line ?? 1) - 1,
|
|
554
|
+
column: (column ?? 1) - 1
|
|
555
|
+
});
|
|
556
|
+
} else {
|
|
557
|
+
editor.execCommand(gotoLine);
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
isEnabled
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
commands.addCommand(CommandIDs.changeLanguage, {
|
|
564
|
+
label: args =>
|
|
565
|
+
((args['displayName'] ?? args['name']) as string) ??
|
|
566
|
+
trans.__('Change editor language.'),
|
|
567
|
+
execute: args => {
|
|
568
|
+
const name = args['name'] as string;
|
|
569
|
+
const widget = tracker.currentWidget;
|
|
570
|
+
if (name && widget) {
|
|
571
|
+
const spec = languages.findByName(name);
|
|
572
|
+
if (spec) {
|
|
573
|
+
widget.content.model.mimeType = spec.mime as string;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
isEnabled,
|
|
578
|
+
isToggled: args => {
|
|
579
|
+
const widget = tracker.currentWidget;
|
|
580
|
+
if (!widget) {
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
const mime = widget.content.model.mimeType;
|
|
584
|
+
const spec = languages.findByMIME(mime);
|
|
585
|
+
const name = spec && spec.name;
|
|
586
|
+
return args['name'] === name;
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Add the replace selection for text editor command
|
|
592
|
+
*/
|
|
593
|
+
|
|
594
|
+
commands.addCommand(CommandIDs.replaceSelection, {
|
|
595
|
+
execute: args => {
|
|
596
|
+
const text: string = (args['text'] as string) || '';
|
|
597
|
+
const widget = tracker.currentWidget;
|
|
598
|
+
if (!widget) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
widget.content.editor.replaceSelection?.(text);
|
|
602
|
+
},
|
|
603
|
+
isEnabled,
|
|
604
|
+
label: trans.__('Replace Selection in Editor')
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Add the Create Console for Editor command
|
|
609
|
+
*/
|
|
610
|
+
commands.addCommand(CommandIDs.createConsole, {
|
|
611
|
+
execute: args => {
|
|
612
|
+
const widget = tracker.currentWidget;
|
|
613
|
+
|
|
614
|
+
if (!widget) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return getCreateConsoleFunction(commands, languages)(widget, args);
|
|
619
|
+
},
|
|
620
|
+
isEnabled,
|
|
621
|
+
icon: consoleIcon,
|
|
622
|
+
label: trans.__('Create Console for Editor')
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Restart the Console Kernel linked to the current Editor
|
|
627
|
+
*/
|
|
628
|
+
commands.addCommand(CommandIDs.restartConsole, {
|
|
629
|
+
execute: async () => {
|
|
630
|
+
const current = tracker.currentWidget?.content;
|
|
631
|
+
|
|
632
|
+
if (!current || consoleTracker === null) {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const widget = consoleTracker.find(
|
|
637
|
+
widget => widget.sessionContext.session?.path === current.context.path
|
|
638
|
+
);
|
|
639
|
+
if (widget) {
|
|
640
|
+
return (sessionDialogs || sessionContextDialogs).restart(
|
|
641
|
+
widget.sessionContext
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
label: trans.__('Restart Kernel'),
|
|
646
|
+
isEnabled: () => consoleTracker !== null && isEnabled()
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Add the Run Code command
|
|
651
|
+
*/
|
|
652
|
+
commands.addCommand(CommandIDs.runCode, {
|
|
653
|
+
execute: () => {
|
|
654
|
+
// Run the appropriate code, taking into account a ```fenced``` code block.
|
|
655
|
+
const widget = tracker.currentWidget?.content;
|
|
656
|
+
|
|
657
|
+
if (!widget) {
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
let code: string | undefined = '';
|
|
662
|
+
const editor = widget.editor;
|
|
663
|
+
const path = widget.context.path;
|
|
664
|
+
const extension = PathExt.extname(path);
|
|
665
|
+
const selection = editor.getSelection();
|
|
666
|
+
const { start, end } = selection;
|
|
667
|
+
let selected = start.column !== end.column || start.line !== end.line;
|
|
668
|
+
|
|
669
|
+
if (selected) {
|
|
670
|
+
// Get the selected code from the editor.
|
|
671
|
+
const start = editor.getOffsetAt(selection.start);
|
|
672
|
+
const end = editor.getOffsetAt(selection.end);
|
|
673
|
+
|
|
674
|
+
code = editor.model.sharedModel.getSource().substring(start, end);
|
|
675
|
+
} else if (MarkdownCodeBlocks.isMarkdown(extension)) {
|
|
676
|
+
const text = editor.model.sharedModel.getSource();
|
|
677
|
+
const blocks = MarkdownCodeBlocks.findMarkdownCodeBlocks(text);
|
|
678
|
+
|
|
679
|
+
for (const block of blocks) {
|
|
680
|
+
if (block.startLine <= start.line && start.line <= block.endLine) {
|
|
681
|
+
code = block.code;
|
|
682
|
+
selected = true;
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (!selected) {
|
|
689
|
+
// no selection, submit whole line and advance
|
|
690
|
+
code = editor.getLine(selection.start.line);
|
|
691
|
+
const cursor = editor.getCursorPosition();
|
|
692
|
+
if (cursor.line + 1 === editor.lineCount) {
|
|
693
|
+
const text = editor.model.sharedModel.getSource();
|
|
694
|
+
editor.model.sharedModel.setSource(text + '\n');
|
|
695
|
+
}
|
|
696
|
+
editor.setCursorPosition({
|
|
697
|
+
line: cursor.line + 1,
|
|
698
|
+
column: cursor.column
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const activate = false;
|
|
703
|
+
if (code) {
|
|
704
|
+
return commands.execute('console:inject', { activate, code, path });
|
|
705
|
+
} else {
|
|
706
|
+
return Promise.resolve(void 0);
|
|
707
|
+
}
|
|
708
|
+
},
|
|
709
|
+
isEnabled,
|
|
710
|
+
label: trans.__('Run Selected Code')
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Add the Run All Code command
|
|
715
|
+
*/
|
|
716
|
+
commands.addCommand(CommandIDs.runAllCode, {
|
|
717
|
+
execute: () => {
|
|
718
|
+
const widget = tracker.currentWidget?.content;
|
|
719
|
+
|
|
720
|
+
if (!widget) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
let code = '';
|
|
725
|
+
const editor = widget.editor;
|
|
726
|
+
const text = editor.model.sharedModel.getSource();
|
|
727
|
+
const path = widget.context.path;
|
|
728
|
+
const extension = PathExt.extname(path);
|
|
729
|
+
|
|
730
|
+
if (MarkdownCodeBlocks.isMarkdown(extension)) {
|
|
731
|
+
// For Markdown files, run only code blocks.
|
|
732
|
+
const blocks = MarkdownCodeBlocks.findMarkdownCodeBlocks(text);
|
|
733
|
+
for (const block of blocks) {
|
|
734
|
+
code += block.code;
|
|
735
|
+
}
|
|
736
|
+
} else {
|
|
737
|
+
code = text;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const activate = false;
|
|
741
|
+
if (code) {
|
|
742
|
+
return commands.execute('console:inject', { activate, code, path });
|
|
743
|
+
} else {
|
|
744
|
+
return Promise.resolve(void 0);
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
isEnabled,
|
|
748
|
+
label: trans.__('Run All Code')
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Add markdown preview command
|
|
753
|
+
*/
|
|
754
|
+
commands.addCommand(CommandIDs.markdownPreview, {
|
|
755
|
+
execute: () => {
|
|
756
|
+
const widget = tracker.currentWidget;
|
|
757
|
+
if (!widget) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const path = widget.context.path;
|
|
761
|
+
return commands.execute('markdownviewer:open', {
|
|
762
|
+
path,
|
|
763
|
+
options: {
|
|
764
|
+
mode: 'split-right'
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
},
|
|
768
|
+
isVisible: () => {
|
|
769
|
+
const widget = tracker.currentWidget;
|
|
770
|
+
return (
|
|
771
|
+
(widget && PathExt.extname(widget.context.path) === '.md') || false
|
|
772
|
+
);
|
|
773
|
+
},
|
|
774
|
+
icon: markdownIcon,
|
|
775
|
+
label: trans.__('Show Markdown Preview')
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Add the New File command
|
|
780
|
+
*
|
|
781
|
+
* Defaults to Text/.txt if file type data is not specified
|
|
782
|
+
*/
|
|
783
|
+
commands.addCommand(CommandIDs.createNew, {
|
|
784
|
+
label: args => {
|
|
785
|
+
if (args.isPalette) {
|
|
786
|
+
return (args.paletteLabel as string) ?? trans.__('New Text File');
|
|
787
|
+
}
|
|
788
|
+
return (args.launcherLabel as string) ?? trans.__('Text File');
|
|
789
|
+
},
|
|
790
|
+
caption: args =>
|
|
791
|
+
(args.caption as string) ?? trans.__('Create a new text file'),
|
|
792
|
+
icon: args =>
|
|
793
|
+
args.isPalette
|
|
794
|
+
? undefined
|
|
795
|
+
: LabIcon.resolve({
|
|
796
|
+
icon: (args.iconName as string) ?? textEditorIcon
|
|
797
|
+
}),
|
|
798
|
+
execute: args => {
|
|
799
|
+
const cwd = args.cwd || defaultBrowser.model.path;
|
|
800
|
+
return createNew(
|
|
801
|
+
commands,
|
|
802
|
+
cwd as string,
|
|
803
|
+
(args.fileExt as string) ?? 'txt'
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Add the New Markdown File command
|
|
810
|
+
*/
|
|
811
|
+
commands.addCommand(CommandIDs.createNewMarkdown, {
|
|
812
|
+
label: args =>
|
|
813
|
+
args['isPalette']
|
|
814
|
+
? trans.__('New Markdown File')
|
|
815
|
+
: trans.__('Markdown File'),
|
|
816
|
+
caption: trans.__('Create a new markdown file'),
|
|
817
|
+
icon: args => (args['isPalette'] ? undefined : markdownIcon),
|
|
818
|
+
execute: args => {
|
|
819
|
+
const cwd = args['cwd'] || defaultBrowser.model.path;
|
|
820
|
+
return createNew(commands, cwd as string, 'md');
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Add undo command
|
|
826
|
+
*/
|
|
827
|
+
commands.addCommand(CommandIDs.undo, {
|
|
828
|
+
execute: () => {
|
|
829
|
+
const widget = tracker.currentWidget?.content;
|
|
830
|
+
|
|
831
|
+
if (!widget) {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
widget.editor.undo();
|
|
836
|
+
},
|
|
837
|
+
isEnabled: () => {
|
|
838
|
+
if (!isEnabled()) {
|
|
839
|
+
return false;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
const widget = tracker.currentWidget?.content;
|
|
843
|
+
|
|
844
|
+
if (!widget) {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
// Ideally enable it when there are undo events stored
|
|
848
|
+
// Reference issue #8590: Code mirror editor could expose the history of undo/redo events
|
|
849
|
+
return true;
|
|
850
|
+
},
|
|
851
|
+
icon: undoIcon.bindprops({ stylesheet: 'menuItem' }),
|
|
852
|
+
label: trans.__('Undo')
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Add redo command
|
|
857
|
+
*/
|
|
858
|
+
commands.addCommand(CommandIDs.redo, {
|
|
859
|
+
execute: () => {
|
|
860
|
+
const widget = tracker.currentWidget?.content;
|
|
861
|
+
|
|
862
|
+
if (!widget) {
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
widget.editor.redo();
|
|
867
|
+
},
|
|
868
|
+
isEnabled: () => {
|
|
869
|
+
if (!isEnabled()) {
|
|
870
|
+
return false;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const widget = tracker.currentWidget?.content;
|
|
874
|
+
|
|
875
|
+
if (!widget) {
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
878
|
+
// Ideally enable it when there are redo events stored
|
|
879
|
+
// Reference issue #8590: Code mirror editor could expose the history of undo/redo events
|
|
880
|
+
return true;
|
|
881
|
+
},
|
|
882
|
+
icon: redoIcon.bindprops({ stylesheet: 'menuItem' }),
|
|
883
|
+
label: trans.__('Redo')
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Add cut command
|
|
888
|
+
*/
|
|
889
|
+
commands.addCommand(CommandIDs.cut, {
|
|
890
|
+
execute: () => {
|
|
891
|
+
const widget = tracker.currentWidget?.content;
|
|
892
|
+
|
|
893
|
+
if (!widget) {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const editor = widget.editor as CodeMirrorEditor;
|
|
898
|
+
const text = getTextSelection(editor);
|
|
899
|
+
|
|
900
|
+
Clipboard.copyToSystem(text);
|
|
901
|
+
editor.replaceSelection && editor.replaceSelection('');
|
|
902
|
+
},
|
|
903
|
+
isEnabled: () => {
|
|
904
|
+
if (!isEnabled()) {
|
|
905
|
+
return false;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const widget = tracker.currentWidget?.content;
|
|
909
|
+
|
|
910
|
+
if (!widget) {
|
|
911
|
+
return false;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Enable command if there is a text selection in the editor
|
|
915
|
+
return isSelected(widget.editor as CodeMirrorEditor);
|
|
916
|
+
},
|
|
917
|
+
icon: cutIcon.bindprops({ stylesheet: 'menuItem' }),
|
|
918
|
+
label: trans.__('Cut')
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Add copy command
|
|
923
|
+
*/
|
|
924
|
+
commands.addCommand(CommandIDs.copy, {
|
|
925
|
+
execute: () => {
|
|
926
|
+
const widget = tracker.currentWidget?.content;
|
|
927
|
+
|
|
928
|
+
if (!widget) {
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
const editor = widget.editor as CodeMirrorEditor;
|
|
933
|
+
const text = getTextSelection(editor);
|
|
934
|
+
|
|
935
|
+
Clipboard.copyToSystem(text);
|
|
936
|
+
},
|
|
937
|
+
isEnabled: () => {
|
|
938
|
+
if (!isEnabled()) {
|
|
939
|
+
return false;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const widget = tracker.currentWidget?.content;
|
|
943
|
+
|
|
944
|
+
if (!widget) {
|
|
945
|
+
return false;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Enable command if there is a text selection in the editor
|
|
949
|
+
return isSelected(widget.editor as CodeMirrorEditor);
|
|
950
|
+
},
|
|
951
|
+
icon: copyIcon.bindprops({ stylesheet: 'menuItem' }),
|
|
952
|
+
label: trans.__('Copy')
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* Add paste command
|
|
957
|
+
*/
|
|
958
|
+
commands.addCommand(CommandIDs.paste, {
|
|
959
|
+
execute: async () => {
|
|
960
|
+
const widget = tracker.currentWidget?.content;
|
|
961
|
+
|
|
962
|
+
if (!widget) {
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
const editor: CodeEditor.IEditor = widget.editor;
|
|
967
|
+
|
|
968
|
+
// Get data from clipboard
|
|
969
|
+
const clipboard = window.navigator.clipboard;
|
|
970
|
+
const clipboardData: string = await clipboard.readText();
|
|
971
|
+
|
|
972
|
+
if (clipboardData) {
|
|
973
|
+
// Paste data to the editor
|
|
974
|
+
editor.replaceSelection && editor.replaceSelection(clipboardData);
|
|
975
|
+
}
|
|
976
|
+
},
|
|
977
|
+
isEnabled: () => Boolean(isEnabled() && tracker.currentWidget?.content),
|
|
978
|
+
icon: pasteIcon.bindprops({ stylesheet: 'menuItem' }),
|
|
979
|
+
label: trans.__('Paste')
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Add select all command
|
|
984
|
+
*/
|
|
985
|
+
commands.addCommand(CommandIDs.selectAll, {
|
|
986
|
+
execute: () => {
|
|
987
|
+
const widget = tracker.currentWidget?.content;
|
|
988
|
+
|
|
989
|
+
if (!widget) {
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
const editor = widget.editor as CodeMirrorEditor;
|
|
994
|
+
editor.execCommand(selectAll);
|
|
995
|
+
},
|
|
996
|
+
isEnabled: () => Boolean(isEnabled() && tracker.currentWidget?.content),
|
|
997
|
+
label: trans.__('Select All')
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
export function addCompleterCommands(
|
|
1002
|
+
commands: CommandRegistry,
|
|
1003
|
+
editorTracker: IEditorTracker,
|
|
1004
|
+
manager: ICompletionProviderManager,
|
|
1005
|
+
translator: ITranslator | null
|
|
1006
|
+
): void {
|
|
1007
|
+
const trans = (translator ?? nullTranslator).load('jupyterlab');
|
|
1008
|
+
|
|
1009
|
+
commands.addCommand(CommandIDs.invokeCompleter, {
|
|
1010
|
+
label: trans.__('Display the completion helper.'),
|
|
1011
|
+
execute: () => {
|
|
1012
|
+
const id =
|
|
1013
|
+
editorTracker.currentWidget && editorTracker.currentWidget.id;
|
|
1014
|
+
if (id) {
|
|
1015
|
+
return manager.invoke(id);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
commands.addCommand(CommandIDs.selectCompleter, {
|
|
1021
|
+
label: trans.__('Select the completion suggestion.'),
|
|
1022
|
+
execute: () => {
|
|
1023
|
+
const id =
|
|
1024
|
+
editorTracker.currentWidget && editorTracker.currentWidget.id;
|
|
1025
|
+
if (id) {
|
|
1026
|
+
return manager.select(id);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
commands.addKeyBinding({
|
|
1032
|
+
command: CommandIDs.selectCompleter,
|
|
1033
|
+
keys: ['Enter'],
|
|
1034
|
+
selector: '.jp-FileEditor .jp-mod-completer-active'
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Helper function to check if there is a text selection in the editor
|
|
1040
|
+
*/
|
|
1041
|
+
function isSelected(editor: CodeMirrorEditor) {
|
|
1042
|
+
const selectionObj = editor.getSelection();
|
|
1043
|
+
const { start, end } = selectionObj;
|
|
1044
|
+
const selected = start.column !== end.column || start.line !== end.line;
|
|
1045
|
+
|
|
1046
|
+
return selected;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* Helper function to get text selection from the editor
|
|
1051
|
+
*/
|
|
1052
|
+
function getTextSelection(editor: CodeMirrorEditor) {
|
|
1053
|
+
const selectionObj = editor.getSelection();
|
|
1054
|
+
const start = editor.getOffsetAt(selectionObj.start);
|
|
1055
|
+
const end = editor.getOffsetAt(selectionObj.end);
|
|
1056
|
+
const text = editor.model.sharedModel.getSource().substring(start, end);
|
|
1057
|
+
|
|
1058
|
+
return text;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Function to create a new untitled text file, given the current working directory.
|
|
1063
|
+
*/
|
|
1064
|
+
async function createNew(
|
|
1065
|
+
commands: CommandRegistry,
|
|
1066
|
+
cwd: string,
|
|
1067
|
+
ext: string = 'txt'
|
|
1068
|
+
) {
|
|
1069
|
+
const model = await commands.execute('docmanager:new-untitled', {
|
|
1070
|
+
path: cwd,
|
|
1071
|
+
type: 'file',
|
|
1072
|
+
ext
|
|
1073
|
+
});
|
|
1074
|
+
if (model != undefined) {
|
|
1075
|
+
const widget = (await commands.execute('docmanager:open', {
|
|
1076
|
+
path: model.path,
|
|
1077
|
+
factory: FACTORY
|
|
1078
|
+
})) as unknown as IDocumentWidget;
|
|
1079
|
+
widget.isUntitled = true;
|
|
1080
|
+
return widget;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Wrapper function for adding the default launcher items for File Editor
|
|
1086
|
+
*/
|
|
1087
|
+
export function addLauncherItems(
|
|
1088
|
+
launcher: ILauncher,
|
|
1089
|
+
trans: TranslationBundle
|
|
1090
|
+
): void {
|
|
1091
|
+
addCreateNewToLauncher(launcher, trans);
|
|
1092
|
+
|
|
1093
|
+
addCreateNewMarkdownToLauncher(launcher, trans);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* Add Create New Text File to the Launcher
|
|
1098
|
+
*/
|
|
1099
|
+
export function addCreateNewToLauncher(
|
|
1100
|
+
launcher: ILauncher,
|
|
1101
|
+
trans: TranslationBundle
|
|
1102
|
+
): void {
|
|
1103
|
+
launcher.add({
|
|
1104
|
+
command: CommandIDs.createNew,
|
|
1105
|
+
category: trans.__('Other'),
|
|
1106
|
+
rank: 1
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/**
|
|
1111
|
+
* Add Create New Markdown to the Launcher
|
|
1112
|
+
*/
|
|
1113
|
+
export function addCreateNewMarkdownToLauncher(
|
|
1114
|
+
launcher: ILauncher,
|
|
1115
|
+
trans: TranslationBundle
|
|
1116
|
+
): void {
|
|
1117
|
+
launcher.add({
|
|
1118
|
+
command: CommandIDs.createNewMarkdown,
|
|
1119
|
+
category: trans.__('Other'),
|
|
1120
|
+
rank: 2
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
/**
|
|
1125
|
+
* Add ___ File items to the Launcher for common file types associated with available kernels
|
|
1126
|
+
*/
|
|
1127
|
+
export function addKernelLanguageLauncherItems(
|
|
1128
|
+
launcher: ILauncher,
|
|
1129
|
+
trans: TranslationBundle,
|
|
1130
|
+
availableKernelFileTypes: Iterable<IFileTypeData>
|
|
1131
|
+
): void {
|
|
1132
|
+
for (let ext of availableKernelFileTypes) {
|
|
1133
|
+
launcher.add({
|
|
1134
|
+
command: CommandIDs.createNew,
|
|
1135
|
+
category: trans.__('Other'),
|
|
1136
|
+
rank: 3,
|
|
1137
|
+
args: ext
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
/**
|
|
1143
|
+
* Wrapper function for adding the default items to the File Editor palette
|
|
1144
|
+
*/
|
|
1145
|
+
export function addPaletteItems(
|
|
1146
|
+
palette: ICommandPalette,
|
|
1147
|
+
trans: TranslationBundle
|
|
1148
|
+
): void {
|
|
1149
|
+
addChangeTabsCommandsToPalette(palette, trans);
|
|
1150
|
+
|
|
1151
|
+
addCreateNewCommandToPalette(palette, trans);
|
|
1152
|
+
|
|
1153
|
+
addCreateNewMarkdownCommandToPalette(palette, trans);
|
|
1154
|
+
|
|
1155
|
+
addChangeFontSizeCommandsToPalette(palette, trans);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* Add commands to change the tab indentation to the File Editor palette
|
|
1160
|
+
*/
|
|
1161
|
+
export function addChangeTabsCommandsToPalette(
|
|
1162
|
+
palette: ICommandPalette,
|
|
1163
|
+
trans: TranslationBundle
|
|
1164
|
+
): void {
|
|
1165
|
+
const paletteCategory = trans.__('Text Editor');
|
|
1166
|
+
const args: JSONObject = {
|
|
1167
|
+
size: 4
|
|
1168
|
+
};
|
|
1169
|
+
const command = CommandIDs.changeTabs;
|
|
1170
|
+
palette.addItem({ command, args, category: paletteCategory });
|
|
1171
|
+
|
|
1172
|
+
for (const size of [1, 2, 4, 8]) {
|
|
1173
|
+
const args: JSONObject = {
|
|
1174
|
+
size
|
|
1175
|
+
};
|
|
1176
|
+
palette.addItem({ command, args, category: paletteCategory });
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
/**
|
|
1181
|
+
* Add a Create New File command to the File Editor palette
|
|
1182
|
+
*/
|
|
1183
|
+
export function addCreateNewCommandToPalette(
|
|
1184
|
+
palette: ICommandPalette,
|
|
1185
|
+
trans: TranslationBundle
|
|
1186
|
+
): void {
|
|
1187
|
+
const paletteCategory = trans.__('Text Editor');
|
|
1188
|
+
palette.addItem({
|
|
1189
|
+
command: CommandIDs.createNew,
|
|
1190
|
+
args: { isPalette: true },
|
|
1191
|
+
category: paletteCategory
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* Add a Create New Markdown command to the File Editor palette
|
|
1197
|
+
*/
|
|
1198
|
+
export function addCreateNewMarkdownCommandToPalette(
|
|
1199
|
+
palette: ICommandPalette,
|
|
1200
|
+
trans: TranslationBundle
|
|
1201
|
+
): void {
|
|
1202
|
+
const paletteCategory = trans.__('Text Editor');
|
|
1203
|
+
palette.addItem({
|
|
1204
|
+
command: CommandIDs.createNewMarkdown,
|
|
1205
|
+
args: { isPalette: true },
|
|
1206
|
+
category: paletteCategory
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
/**
|
|
1211
|
+
* Add commands to change the font size to the File Editor palette
|
|
1212
|
+
*/
|
|
1213
|
+
export function addChangeFontSizeCommandsToPalette(
|
|
1214
|
+
palette: ICommandPalette,
|
|
1215
|
+
trans: TranslationBundle
|
|
1216
|
+
): void {
|
|
1217
|
+
const paletteCategory = trans.__('Text Editor');
|
|
1218
|
+
const command = CommandIDs.changeFontSize;
|
|
1219
|
+
|
|
1220
|
+
let args = { delta: 1 };
|
|
1221
|
+
palette.addItem({ command, args, category: paletteCategory });
|
|
1222
|
+
|
|
1223
|
+
args = { delta: -1 };
|
|
1224
|
+
palette.addItem({ command, args, category: paletteCategory });
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* Add New ___ File commands to the File Editor palette for common file types associated with available kernels
|
|
1229
|
+
*/
|
|
1230
|
+
export function addKernelLanguagePaletteItems(
|
|
1231
|
+
palette: ICommandPalette,
|
|
1232
|
+
trans: TranslationBundle,
|
|
1233
|
+
availableKernelFileTypes: Iterable<IFileTypeData>
|
|
1234
|
+
): void {
|
|
1235
|
+
const paletteCategory = trans.__('Text Editor');
|
|
1236
|
+
for (let ext of availableKernelFileTypes) {
|
|
1237
|
+
palette.addItem({
|
|
1238
|
+
command: CommandIDs.createNew,
|
|
1239
|
+
args: { ...ext, isPalette: true },
|
|
1240
|
+
category: paletteCategory
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* Wrapper function for adding the default menu items for File Editor
|
|
1247
|
+
*/
|
|
1248
|
+
export function addMenuItems(
|
|
1249
|
+
menu: IMainMenu,
|
|
1250
|
+
tracker: WidgetTracker<IDocumentWidget<FileEditor>>,
|
|
1251
|
+
consoleTracker: IConsoleTracker | null,
|
|
1252
|
+
isEnabled: () => boolean
|
|
1253
|
+
): void {
|
|
1254
|
+
// Add undo/redo hooks to the edit menu.
|
|
1255
|
+
menu.editMenu.undoers.redo.add({
|
|
1256
|
+
id: CommandIDs.redo,
|
|
1257
|
+
isEnabled
|
|
1258
|
+
});
|
|
1259
|
+
menu.editMenu.undoers.undo.add({
|
|
1260
|
+
id: CommandIDs.undo,
|
|
1261
|
+
isEnabled
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1264
|
+
// Add editor view options.
|
|
1265
|
+
menu.viewMenu.editorViewers.toggleLineNumbers.add({
|
|
1266
|
+
id: CommandIDs.currentLineNumbers,
|
|
1267
|
+
isEnabled
|
|
1268
|
+
});
|
|
1269
|
+
menu.viewMenu.editorViewers.toggleMatchBrackets.add({
|
|
1270
|
+
id: CommandIDs.currentMatchBrackets,
|
|
1271
|
+
isEnabled
|
|
1272
|
+
});
|
|
1273
|
+
menu.viewMenu.editorViewers.toggleWordWrap.add({
|
|
1274
|
+
id: CommandIDs.currentLineWrap,
|
|
1275
|
+
isEnabled
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
// Add a console creator the the file menu.
|
|
1279
|
+
menu.fileMenu.consoleCreators.add({
|
|
1280
|
+
id: CommandIDs.createConsole,
|
|
1281
|
+
isEnabled
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
// Add a code runner to the run menu.
|
|
1285
|
+
if (consoleTracker) {
|
|
1286
|
+
addCodeRunnersToRunMenu(menu, consoleTracker);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* Add Create New ___ File commands to the File menu for common file types associated with available kernels
|
|
1292
|
+
*/
|
|
1293
|
+
export function addKernelLanguageMenuItems(
|
|
1294
|
+
menu: IMainMenu,
|
|
1295
|
+
availableKernelFileTypes: Iterable<IFileTypeData>
|
|
1296
|
+
): void {
|
|
1297
|
+
for (let ext of availableKernelFileTypes) {
|
|
1298
|
+
menu.fileMenu.newMenu.addItem({
|
|
1299
|
+
command: CommandIDs.createNew,
|
|
1300
|
+
args: ext,
|
|
1301
|
+
rank: 31
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/**
|
|
1307
|
+
* Add a File Editor code runner to the Run menu
|
|
1308
|
+
*/
|
|
1309
|
+
export function addCodeRunnersToRunMenu(
|
|
1310
|
+
menu: IMainMenu,
|
|
1311
|
+
consoleTracker: IConsoleTracker
|
|
1312
|
+
): void {
|
|
1313
|
+
const isEnabled = (current: IDocumentWidget<FileEditor>) =>
|
|
1314
|
+
current.context &&
|
|
1315
|
+
!!consoleTracker.find(
|
|
1316
|
+
widget => widget.sessionContext.session?.path === current.context.path
|
|
1317
|
+
);
|
|
1318
|
+
menu.runMenu.codeRunners.restart.add({
|
|
1319
|
+
id: CommandIDs.restartConsole,
|
|
1320
|
+
isEnabled
|
|
1321
|
+
});
|
|
1322
|
+
menu.runMenu.codeRunners.run.add({
|
|
1323
|
+
id: CommandIDs.runCode,
|
|
1324
|
+
isEnabled
|
|
1325
|
+
});
|
|
1326
|
+
menu.runMenu.codeRunners.runAll.add({
|
|
1327
|
+
id: CommandIDs.runAllCode,
|
|
1328
|
+
isEnabled
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
export function addOpenCodeViewerCommand(
|
|
1333
|
+
app: JupyterFrontEnd,
|
|
1334
|
+
editorServices: IEditorServices,
|
|
1335
|
+
tracker: WidgetTracker<MainAreaWidget<CodeViewerWidget>>,
|
|
1336
|
+
trans: TranslationBundle
|
|
1337
|
+
): void {
|
|
1338
|
+
const openCodeViewer = async (args: {
|
|
1339
|
+
content: string;
|
|
1340
|
+
label?: string;
|
|
1341
|
+
mimeType?: string;
|
|
1342
|
+
extension?: string;
|
|
1343
|
+
widgetId?: string;
|
|
1344
|
+
}): Promise<CodeViewerWidget> => {
|
|
1345
|
+
const func = editorServices.factoryService.newDocumentEditor;
|
|
1346
|
+
const factory: CodeEditor.Factory = options => {
|
|
1347
|
+
return func(options);
|
|
1348
|
+
};
|
|
1349
|
+
|
|
1350
|
+
// Derive mimetype from extension
|
|
1351
|
+
let mimetype = args.mimeType;
|
|
1352
|
+
if (!mimetype && args.extension) {
|
|
1353
|
+
mimetype = editorServices.mimeTypeService.getMimeTypeByFilePath(
|
|
1354
|
+
`temp.${args.extension.replace(/\\.$/, '')}`
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
const widget = CodeViewerWidget.createCodeViewer({
|
|
1359
|
+
factory,
|
|
1360
|
+
content: args.content,
|
|
1361
|
+
mimeType: mimetype
|
|
1362
|
+
});
|
|
1363
|
+
widget.title.label = args.label || trans.__('Code Viewer');
|
|
1364
|
+
widget.title.caption = widget.title.label;
|
|
1365
|
+
|
|
1366
|
+
// Get the fileType based on the mimetype to determine the icon
|
|
1367
|
+
const fileType = find(app.docRegistry.fileTypes(), fileType =>
|
|
1368
|
+
mimetype ? fileType.mimeTypes.includes(mimetype) : false
|
|
1369
|
+
);
|
|
1370
|
+
widget.title.icon = fileType?.icon ?? textEditorIcon;
|
|
1371
|
+
|
|
1372
|
+
if (args.widgetId) {
|
|
1373
|
+
widget.id = args.widgetId;
|
|
1374
|
+
}
|
|
1375
|
+
const main = new MainAreaWidget({ content: widget });
|
|
1376
|
+
await tracker.add(main);
|
|
1377
|
+
app.shell.add(main, 'main');
|
|
1378
|
+
return widget;
|
|
1379
|
+
};
|
|
1380
|
+
|
|
1381
|
+
app.commands.addCommand(CommandIDs.openCodeViewer, {
|
|
1382
|
+
label: trans.__('Open Code Viewer'),
|
|
1383
|
+
execute: (args: any) => {
|
|
1384
|
+
return openCodeViewer(args);
|
|
1385
|
+
}
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
}
|