Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Iterable } from '../../../../base/common/iterator.js';
7
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
8
import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
9
import { parse } from '../../../../base/common/marshalling.js';
10
import { Schemas } from '../../../../base/common/network.js';
11
import { extname, isEqual } from '../../../../base/common/resources.js';
12
import { isFalsyOrWhitespace } from '../../../../base/common/strings.js';
13
import { URI, UriComponents } from '../../../../base/common/uri.js';
14
import { IBulkEditService } from '../../../../editor/browser/services/bulkEditService.js';
15
import { EditOperation } from '../../../../editor/common/core/editOperation.js';
16
import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js';
17
import { ITextModel } from '../../../../editor/common/model.js';
18
import { IModelService } from '../../../../editor/common/services/model.js';
19
import { ITextModelContentProvider, ITextModelService } from '../../../../editor/common/services/resolverService.js';
20
import { peekViewBorder } from '../../../../editor/contrib/peekView/browser/peekView.js';
21
import { Context as SuggestContext } from '../../../../editor/contrib/suggest/browser/suggest.js';
22
import { localize, localize2 } from '../../../../nls.js';
23
import { ILocalizedString } from '../../../../platform/action/common/action.js';
24
import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';
25
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
26
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from '../../../../platform/configuration/common/configurationRegistry.js';
27
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
28
import { EditorActivation, ITextResourceEditorInput } from '../../../../platform/editor/common/editor.js';
29
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
30
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
31
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
32
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
33
import { ILogService } from '../../../../platform/log/common/log.js';
34
import { Registry } from '../../../../platform/registry/common/platform.js';
35
import { contrastBorder, ifDefinedThenElse, listInactiveSelectionBackground, registerColor } from '../../../../platform/theme/common/colorRegistry.js';
36
import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js';
37
import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js';
38
import { EditorExtensions, EditorsOrder, IEditorControl, IEditorFactoryRegistry, IEditorSerializer, IUntypedEditorInput } from '../../../common/editor.js';
39
import { EditorInput } from '../../../common/editor/editorInput.js';
40
import { PANEL_BORDER } from '../../../common/theme.js';
41
import { ResourceNotebookCellEdit } from '../../bulkEdit/browser/bulkCellEdits.js';
42
import { ReplEditorSettings, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from './interactiveCommon.js';
43
import { IInteractiveDocumentService, InteractiveDocumentService } from './interactiveDocumentService.js';
44
import { InteractiveEditor } from './interactiveEditor.js';
45
import { InteractiveEditorInput } from './interactiveEditorInput.js';
46
import { IInteractiveHistoryService, InteractiveHistoryService } from './interactiveHistoryService.js';
47
import { NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from '../../notebook/browser/controller/coreActions.js';
48
import { INotebookEditorOptions } from '../../notebook/browser/notebookBrowser.js';
49
import * as icons from '../../notebook/browser/notebookIcons.js';
50
import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js';
51
import { CellEditType, CellKind, CellUri, INTERACTIVE_WINDOW_EDITOR_ID, NotebookSetting, NotebookWorkingCopyTypeIdentifier } from '../../notebook/common/notebookCommon.js';
52
import { InteractiveWindowOpen, IS_COMPOSITE_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED } from '../../notebook/common/notebookContextKeys.js';
53
import { INotebookKernelService } from '../../notebook/common/notebookKernelService.js';
54
import { INotebookService } from '../../notebook/common/notebookService.js';
55
import { columnToEditorGroup } from '../../../services/editor/common/editorGroupColumn.js';
56
import { IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js';
57
import { IEditorResolverService, RegisteredEditorPriority } from '../../../services/editor/common/editorResolverService.js';
58
import { IEditorService } from '../../../services/editor/common/editorService.js';
59
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
60
import { IWorkingCopyIdentifier } from '../../../services/workingCopy/common/workingCopy.js';
61
import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from '../../../services/workingCopy/common/workingCopyEditorService.js';
62
import { isReplEditorControl, ReplEditorControl } from '../../replNotebook/browser/replEditor.js';
63
import { InlineChatController } from '../../inlineChat/browser/inlineChatController.js';
64
import { IsLinuxContext, IsWindowsContext } from '../../../../platform/contextkey/common/contextkeys.js';
65
66
const interactiveWindowCategory: ILocalizedString = localize2('interactiveWindow', "Interactive Window");
67
68
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
69
EditorPaneDescriptor.create(
70
InteractiveEditor,
71
INTERACTIVE_WINDOW_EDITOR_ID,
72
'Interactive Window'
73
),
74
[
75
new SyncDescriptor(InteractiveEditorInput)
76
]
77
);
78
79
export class InteractiveDocumentContribution extends Disposable implements IWorkbenchContribution {
80
81
static readonly ID = 'workbench.contrib.interactiveDocument';
82
83
constructor(
84
@INotebookService notebookService: INotebookService,
85
@IEditorResolverService editorResolverService: IEditorResolverService,
86
@IEditorService editorService: IEditorService,
87
@IInstantiationService private readonly instantiationService: IInstantiationService
88
) {
89
super();
90
91
const info = notebookService.getContributedNotebookType('interactive');
92
93
// We need to contribute a notebook type for the Interactive Window to provide notebook models.
94
if (!info) {
95
this._register(notebookService.registerContributedNotebookType('interactive', {
96
providerDisplayName: 'Interactive Notebook',
97
displayName: 'Interactive',
98
filenamePattern: ['*.interactive'],
99
priority: RegisteredEditorPriority.builtin
100
}));
101
}
102
103
editorResolverService.registerEditor(
104
`${Schemas.vscodeInteractiveInput}:/**`,
105
{
106
id: 'vscode-interactive-input',
107
label: 'Interactive Editor',
108
priority: RegisteredEditorPriority.exclusive
109
},
110
{
111
canSupportResource: uri => uri.scheme === Schemas.vscodeInteractiveInput,
112
singlePerResource: true
113
},
114
{
115
createEditorInput: ({ resource }) => {
116
const editorInput = editorService.findEditors({
117
resource,
118
editorId: 'interactive',
119
typeId: InteractiveEditorInput.ID
120
}, { order: EditorsOrder.SEQUENTIAL }).at(0);
121
return editorInput!;
122
}
123
}
124
);
125
126
editorResolverService.registerEditor(
127
`*.interactive`,
128
{
129
id: 'interactive',
130
label: 'Interactive Editor',
131
priority: RegisteredEditorPriority.exclusive
132
},
133
{
134
canSupportResource: uri =>
135
(uri.scheme === Schemas.untitled && extname(uri) === '.interactive') ||
136
(uri.scheme === Schemas.vscodeNotebookCell && extname(uri) === '.interactive'),
137
singlePerResource: true
138
},
139
{
140
createEditorInput: ({ resource, options }) => {
141
const data = CellUri.parse(resource);
142
let cellOptions: ITextResourceEditorInput | undefined;
143
let iwResource = resource;
144
145
if (data) {
146
cellOptions = { resource, options };
147
iwResource = data.notebook;
148
}
149
150
const notebookOptions: INotebookEditorOptions | undefined = {
151
...options,
152
cellOptions,
153
cellRevealType: undefined,
154
cellSelections: undefined,
155
isReadOnly: undefined,
156
viewState: undefined,
157
indexedCellOptions: undefined
158
};
159
160
const editorInput = createEditor(iwResource, this.instantiationService);
161
return {
162
editor: editorInput,
163
options: notebookOptions
164
};
165
},
166
createUntitledEditorInput: ({ resource, options }) => {
167
if (!resource) {
168
throw new Error('Interactive window editors must have a resource name');
169
}
170
const data = CellUri.parse(resource);
171
let cellOptions: ITextResourceEditorInput | undefined;
172
173
if (data) {
174
cellOptions = { resource, options };
175
}
176
177
const notebookOptions: INotebookEditorOptions = {
178
...options,
179
cellOptions,
180
cellRevealType: undefined,
181
cellSelections: undefined,
182
isReadOnly: undefined,
183
viewState: undefined,
184
indexedCellOptions: undefined
185
};
186
187
const editorInput = createEditor(resource, this.instantiationService);
188
return {
189
editor: editorInput,
190
options: notebookOptions
191
};
192
}
193
}
194
);
195
}
196
}
197
198
class InteractiveInputContentProvider implements ITextModelContentProvider {
199
200
static readonly ID = 'workbench.contrib.interactiveInputContentProvider';
201
202
private readonly _registration: IDisposable;
203
204
constructor(
205
@ITextModelService textModelService: ITextModelService,
206
@IModelService private readonly _modelService: IModelService,
207
) {
208
this._registration = textModelService.registerTextModelContentProvider(Schemas.vscodeInteractiveInput, this);
209
}
210
211
dispose(): void {
212
this._registration.dispose();
213
}
214
215
async provideTextContent(resource: URI): Promise<ITextModel | null> {
216
const existing = this._modelService.getModel(resource);
217
if (existing) {
218
return existing;
219
}
220
const result: ITextModel | null = this._modelService.createModel('', null, resource, false);
221
return result;
222
}
223
}
224
225
function createEditor(resource: URI, instantiationService: IInstantiationService): EditorInput {
226
const counter = /\/Interactive-(\d+)/.exec(resource.path);
227
const inputBoxPath = counter && counter[1] ? `/InteractiveInput-${counter[1]}` : 'InteractiveInput';
228
const inputUri = URI.from({ scheme: Schemas.vscodeInteractiveInput, path: inputBoxPath });
229
const editorInput = InteractiveEditorInput.create(instantiationService, resource, inputUri);
230
231
return editorInput;
232
}
233
234
class InteractiveWindowWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution, IWorkingCopyEditorHandler {
235
236
static readonly ID = 'workbench.contrib.interactiveWindowWorkingCopyEditorHandler';
237
238
constructor(
239
@IInstantiationService private readonly _instantiationService: IInstantiationService,
240
@IWorkingCopyEditorService private readonly _workingCopyEditorService: IWorkingCopyEditorService,
241
@IExtensionService private readonly _extensionService: IExtensionService,
242
) {
243
super();
244
245
this._installHandler();
246
}
247
248
handles(workingCopy: IWorkingCopyIdentifier): boolean {
249
const viewType = this._getViewType(workingCopy);
250
return !!viewType && viewType === 'interactive';
251
252
}
253
254
isOpen(workingCopy: IWorkingCopyIdentifier, editor: EditorInput): boolean {
255
if (!this.handles(workingCopy)) {
256
return false;
257
}
258
259
return editor instanceof InteractiveEditorInput && isEqual(workingCopy.resource, editor.resource);
260
}
261
262
createEditor(workingCopy: IWorkingCopyIdentifier): EditorInput {
263
return createEditor(workingCopy.resource, this._instantiationService);
264
}
265
266
private async _installHandler(): Promise<void> {
267
await this._extensionService.whenInstalledExtensionsRegistered();
268
269
this._register(this._workingCopyEditorService.registerHandler(this));
270
}
271
272
private _getViewType(workingCopy: IWorkingCopyIdentifier): string | undefined {
273
return NotebookWorkingCopyTypeIdentifier.parse(workingCopy.typeId)?.viewType;
274
}
275
}
276
277
registerWorkbenchContribution2(InteractiveDocumentContribution.ID, InteractiveDocumentContribution, WorkbenchPhase.BlockRestore);
278
registerWorkbenchContribution2(InteractiveInputContentProvider.ID, InteractiveInputContentProvider, {
279
editorTypeId: INTERACTIVE_WINDOW_EDITOR_ID
280
});
281
registerWorkbenchContribution2(InteractiveWindowWorkingCopyEditorHandler.ID, InteractiveWindowWorkingCopyEditorHandler, {
282
editorTypeId: INTERACTIVE_WINDOW_EDITOR_ID
283
});
284
285
type interactiveEditorInputData = { resource: URI; inputResource: URI; name: string; language: string };
286
287
export class InteractiveEditorSerializer implements IEditorSerializer {
288
public static readonly ID = InteractiveEditorInput.ID;
289
290
canSerialize(editor: EditorInput): editor is InteractiveEditorInput {
291
if (!(editor instanceof InteractiveEditorInput)) {
292
return false;
293
}
294
295
return URI.isUri(editor.primary.resource) && URI.isUri(editor.inputResource);
296
}
297
298
serialize(input: EditorInput): string | undefined {
299
if (!this.canSerialize(input)) {
300
return undefined;
301
}
302
303
return JSON.stringify({
304
resource: input.primary.resource,
305
inputResource: input.inputResource,
306
name: input.getName(),
307
language: input.language
308
});
309
}
310
311
deserialize(instantiationService: IInstantiationService, raw: string) {
312
const data = <interactiveEditorInputData>parse(raw);
313
if (!data) {
314
return undefined;
315
}
316
const { resource, inputResource, name, language } = data;
317
if (!URI.isUri(resource) || !URI.isUri(inputResource)) {
318
return undefined;
319
}
320
321
const input = InteractiveEditorInput.create(instantiationService, resource, inputResource, name, language);
322
return input;
323
}
324
}
325
326
Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory)
327
.registerEditorSerializer(
328
InteractiveEditorSerializer.ID,
329
InteractiveEditorSerializer);
330
331
registerSingleton(IInteractiveHistoryService, InteractiveHistoryService, InstantiationType.Delayed);
332
registerSingleton(IInteractiveDocumentService, InteractiveDocumentService, InstantiationType.Delayed);
333
334
registerAction2(class extends Action2 {
335
constructor() {
336
super({
337
id: '_interactive.open',
338
title: localize2('interactive.open', 'Open Interactive Window'),
339
f1: false,
340
category: interactiveWindowCategory,
341
metadata: {
342
description: localize('interactive.open', 'Open Interactive Window'),
343
args: [
344
{
345
name: 'showOptions',
346
description: 'Show Options',
347
schema: {
348
type: 'object',
349
properties: {
350
'viewColumn': {
351
type: 'number',
352
default: -1
353
},
354
'preserveFocus': {
355
type: 'boolean',
356
default: true
357
}
358
},
359
}
360
},
361
{
362
name: 'resource',
363
description: 'Interactive resource Uri',
364
isOptional: true
365
},
366
{
367
name: 'controllerId',
368
description: 'Notebook controller Id',
369
isOptional: true
370
},
371
{
372
name: 'title',
373
description: 'Notebook editor title',
374
isOptional: true
375
}
376
]
377
}
378
379
});
380
}
381
382
async run(accessor: ServicesAccessor, showOptions?: number | { viewColumn?: number; preserveFocus?: boolean }, resource?: URI, id?: string, title?: string): Promise<{ notebookUri: URI; inputUri: URI; notebookEditorId?: string }> {
383
const editorService = accessor.get(IEditorService);
384
const editorGroupService = accessor.get(IEditorGroupsService);
385
const historyService = accessor.get(IInteractiveHistoryService);
386
const kernelService = accessor.get(INotebookKernelService);
387
const logService = accessor.get(ILogService);
388
const configurationService = accessor.get(IConfigurationService);
389
const group = columnToEditorGroup(editorGroupService, configurationService, typeof showOptions === 'number' ? showOptions : showOptions?.viewColumn);
390
const editorOptions = {
391
activation: EditorActivation.PRESERVE,
392
preserveFocus: typeof showOptions !== 'number' ? (showOptions?.preserveFocus ?? false) : false
393
};
394
395
if (resource && extname(resource) === '.interactive') {
396
logService.debug('Open interactive window from resource:', resource.toString());
397
const resourceUri = URI.revive(resource);
398
const editors = editorService.findEditors(resourceUri).filter(id => id.editor instanceof InteractiveEditorInput && id.editor.resource?.toString() === resourceUri.toString());
399
if (editors.length) {
400
logService.debug('Find existing interactive window:', resource.toString());
401
const editorInput = editors[0].editor as InteractiveEditorInput;
402
const currentGroup = editors[0].groupId;
403
const editor = await editorService.openEditor(editorInput, editorOptions, currentGroup);
404
const editorControl = editor?.getControl() as ReplEditorControl;
405
406
return {
407
notebookUri: editorInput.resource,
408
inputUri: editorInput.inputResource,
409
notebookEditorId: editorControl?.notebookEditor?.getId()
410
};
411
}
412
}
413
414
const existingNotebookDocument = new Set<string>();
415
editorService.getEditors(EditorsOrder.SEQUENTIAL).forEach(editor => {
416
if (editor.editor.resource) {
417
existingNotebookDocument.add(editor.editor.resource.toString());
418
}
419
});
420
421
let notebookUri: URI | undefined = undefined;
422
let inputUri: URI | undefined = undefined;
423
let counter = 1;
424
do {
425
notebookUri = URI.from({ scheme: Schemas.untitled, path: `/Interactive-${counter}.interactive` });
426
inputUri = URI.from({ scheme: Schemas.vscodeInteractiveInput, path: `/InteractiveInput-${counter}` });
427
428
counter++;
429
} while (existingNotebookDocument.has(notebookUri.toString()));
430
InteractiveEditorInput.setName(notebookUri, title);
431
432
logService.debug('Open new interactive window:', notebookUri.toString(), inputUri.toString());
433
434
if (id) {
435
const allKernels = kernelService.getMatchingKernel({ uri: notebookUri, notebookType: 'interactive' }).all;
436
const preferredKernel = allKernels.find(kernel => kernel.id === id);
437
if (preferredKernel) {
438
kernelService.preselectKernelForNotebook(preferredKernel, { uri: notebookUri, notebookType: 'interactive' });
439
}
440
}
441
442
historyService.clearHistory(notebookUri);
443
const editorInput: IUntypedEditorInput = { resource: notebookUri, options: editorOptions };
444
const editorPane = await editorService.openEditor(editorInput, group);
445
const editorControl = editorPane?.getControl() as ReplEditorControl;
446
// Extensions must retain references to these URIs to manipulate the interactive editor
447
logService.debug('New interactive window opened. Notebook editor id', editorControl?.notebookEditor?.getId());
448
return { notebookUri, inputUri, notebookEditorId: editorControl?.notebookEditor?.getId() };
449
}
450
});
451
452
registerAction2(class extends Action2 {
453
constructor() {
454
super({
455
id: 'interactive.execute',
456
title: localize2('interactive.execute', 'Execute Code'),
457
category: interactiveWindowCategory,
458
keybinding: [{
459
// when: NOTEBOOK_CELL_LIST_FOCUSED,
460
when: ContextKeyExpr.and(
461
IS_COMPOSITE_NOTEBOOK,
462
ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive')
463
),
464
primary: KeyMod.CtrlCmd | KeyCode.Enter,
465
weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT
466
}, {
467
when: ContextKeyExpr.and(
468
IS_COMPOSITE_NOTEBOOK,
469
ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'),
470
ContextKeyExpr.equals('config.interactiveWindow.executeWithShiftEnter', true)
471
),
472
primary: KeyMod.Shift | KeyCode.Enter,
473
weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT
474
}, {
475
when: ContextKeyExpr.and(
476
IS_COMPOSITE_NOTEBOOK,
477
ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'),
478
ContextKeyExpr.equals('config.interactiveWindow.executeWithShiftEnter', false)
479
),
480
primary: KeyCode.Enter,
481
weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT
482
}],
483
menu: [
484
{
485
id: MenuId.InteractiveInputExecute
486
},
487
],
488
icon: icons.executeIcon,
489
f1: false,
490
metadata: {
491
description: 'Execute the Contents of the Input Box',
492
args: [
493
{
494
name: 'resource',
495
description: 'Interactive resource Uri',
496
isOptional: true
497
}
498
]
499
}
500
});
501
}
502
503
async run(accessor: ServicesAccessor, context?: UriComponents): Promise<void> {
504
const editorService = accessor.get(IEditorService);
505
const bulkEditService = accessor.get(IBulkEditService);
506
const historyService = accessor.get(IInteractiveHistoryService);
507
const notebookEditorService = accessor.get(INotebookEditorService);
508
let editorControl: IEditorControl | undefined;
509
if (context) {
510
const resourceUri = URI.revive(context);
511
const editors = editorService.findEditors(resourceUri);
512
for (const found of editors) {
513
if (found.editor.typeId === InteractiveEditorInput.ID) {
514
const editor = await editorService.openEditor(found.editor, found.groupId);
515
editorControl = editor?.getControl();
516
break;
517
}
518
}
519
}
520
else {
521
editorControl = editorService.activeEditorPane?.getControl();
522
}
523
524
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
525
const notebookDocument = editorControl.notebookEditor.textModel;
526
const textModel = editorControl.activeCodeEditor?.getModel();
527
const activeKernel = editorControl.notebookEditor.activeKernel;
528
const language = activeKernel?.supportedLanguages[0] ?? PLAINTEXT_LANGUAGE_ID;
529
530
if (notebookDocument && textModel && editorControl.activeCodeEditor) {
531
const index = notebookDocument.length;
532
const value = textModel.getValue();
533
534
if (isFalsyOrWhitespace(value)) {
535
return;
536
}
537
538
const ctrl = InlineChatController.get(editorControl.activeCodeEditor);
539
if (ctrl) {
540
ctrl.acceptSession();
541
}
542
543
historyService.replaceLast(notebookDocument.uri, value);
544
historyService.addToHistory(notebookDocument.uri, '');
545
textModel.setValue('');
546
547
const collapseState = editorControl.notebookEditor.notebookOptions.getDisplayOptions().interactiveWindowCollapseCodeCells === 'fromEditor' ?
548
{
549
inputCollapsed: false,
550
outputCollapsed: false
551
} :
552
undefined;
553
554
await bulkEditService.apply([
555
new ResourceNotebookCellEdit(notebookDocument.uri,
556
{
557
editType: CellEditType.Replace,
558
index: index,
559
count: 0,
560
cells: [{
561
cellKind: CellKind.Code,
562
mime: undefined,
563
language,
564
source: value,
565
outputs: [],
566
metadata: {},
567
collapseState
568
}]
569
}
570
)
571
]);
572
573
// reveal the cell into view first
574
const range = { start: index, end: index + 1 };
575
editorControl.notebookEditor.revealCellRangeInView(range);
576
await editorControl.notebookEditor.executeNotebookCells(editorControl.notebookEditor.getCellsInRange({ start: index, end: index + 1 }));
577
578
// update the selection and focus in the extension host model
579
const editor = notebookEditorService.getNotebookEditor(editorControl.notebookEditor.getId());
580
if (editor) {
581
editor.setSelections([range]);
582
editor.setFocus(range);
583
}
584
}
585
}
586
}
587
});
588
589
registerAction2(class extends Action2 {
590
constructor() {
591
super({
592
id: 'interactive.input.clear',
593
title: localize2('interactive.input.clear', 'Clear the interactive window input editor contents'),
594
category: interactiveWindowCategory,
595
f1: false
596
});
597
}
598
599
async run(accessor: ServicesAccessor): Promise<void> {
600
const editorService = accessor.get(IEditorService);
601
const editorControl = editorService.activeEditorPane?.getControl();
602
603
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
604
const notebookDocument = editorControl.notebookEditor.textModel;
605
const editor = editorControl.activeCodeEditor;
606
const range = editor?.getModel()?.getFullModelRange();
607
608
609
if (notebookDocument && editor && range) {
610
editor.executeEdits('', [EditOperation.replace(range, null)]);
611
}
612
}
613
}
614
});
615
616
registerAction2(class extends Action2 {
617
constructor() {
618
super({
619
id: 'interactive.history.previous',
620
title: localize2('interactive.history.previous', 'Previous value in history'),
621
category: interactiveWindowCategory,
622
f1: false,
623
keybinding: {
624
when: ContextKeyExpr.and(
625
INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('bottom'),
626
INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('none'),
627
SuggestContext.Visible.toNegated()
628
),
629
primary: KeyCode.UpArrow,
630
weight: KeybindingWeight.WorkbenchContrib
631
},
632
precondition: ContextKeyExpr.and(IS_COMPOSITE_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED.negate())
633
});
634
}
635
636
async run(accessor: ServicesAccessor): Promise<void> {
637
const editorService = accessor.get(IEditorService);
638
const historyService = accessor.get(IInteractiveHistoryService);
639
const editorControl = editorService.activeEditorPane?.getControl();
640
641
642
643
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
644
const notebookDocument = editorControl.notebookEditor.textModel;
645
const textModel = editorControl.activeCodeEditor?.getModel();
646
647
if (notebookDocument && textModel) {
648
const previousValue = historyService.getPreviousValue(notebookDocument.uri);
649
if (previousValue) {
650
textModel.setValue(previousValue);
651
}
652
}
653
}
654
}
655
});
656
657
registerAction2(class extends Action2 {
658
constructor() {
659
super({
660
id: 'interactive.history.next',
661
title: localize2('interactive.history.next', 'Next value in history'),
662
category: interactiveWindowCategory,
663
f1: false,
664
keybinding: {
665
when: ContextKeyExpr.and(
666
INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('top'),
667
INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('none'),
668
SuggestContext.Visible.toNegated()
669
),
670
primary: KeyCode.DownArrow,
671
weight: KeybindingWeight.WorkbenchContrib
672
},
673
precondition: ContextKeyExpr.and(IS_COMPOSITE_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED.negate())
674
});
675
}
676
677
async run(accessor: ServicesAccessor): Promise<void> {
678
const editorService = accessor.get(IEditorService);
679
const historyService = accessor.get(IInteractiveHistoryService);
680
const editorControl = editorService.activeEditorPane?.getControl();
681
682
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
683
const notebookDocument = editorControl.notebookEditor.textModel;
684
const textModel = editorControl.activeCodeEditor?.getModel();
685
686
if (notebookDocument && textModel) {
687
const nextValue = historyService.getNextValue(notebookDocument.uri);
688
if (nextValue !== null) {
689
textModel.setValue(nextValue);
690
}
691
}
692
}
693
}
694
});
695
696
697
registerAction2(class extends Action2 {
698
constructor() {
699
super({
700
id: 'interactive.scrollToTop',
701
title: localize('interactiveScrollToTop', 'Scroll to Top'),
702
keybinding: {
703
when: ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'),
704
primary: KeyMod.CtrlCmd | KeyCode.Home,
705
mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow },
706
weight: KeybindingWeight.WorkbenchContrib
707
},
708
category: interactiveWindowCategory,
709
});
710
}
711
712
async run(accessor: ServicesAccessor): Promise<void> {
713
const editorService = accessor.get(IEditorService);
714
const editorControl = editorService.activeEditorPane?.getControl();
715
716
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
717
if (editorControl.notebookEditor.getLength() === 0) {
718
return;
719
}
720
721
editorControl.notebookEditor.revealCellRangeInView({ start: 0, end: 1 });
722
}
723
}
724
});
725
726
registerAction2(class extends Action2 {
727
constructor() {
728
super({
729
id: 'interactive.scrollToBottom',
730
title: localize('interactiveScrollToBottom', 'Scroll to Bottom'),
731
keybinding: {
732
when: ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'),
733
primary: KeyMod.CtrlCmd | KeyCode.End,
734
mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow },
735
weight: KeybindingWeight.WorkbenchContrib
736
},
737
category: interactiveWindowCategory,
738
});
739
}
740
741
async run(accessor: ServicesAccessor): Promise<void> {
742
const editorService = accessor.get(IEditorService);
743
const editorControl = editorService.activeEditorPane?.getControl();
744
745
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
746
if (editorControl.notebookEditor.getLength() === 0) {
747
return;
748
}
749
750
const len = editorControl.notebookEditor.getLength();
751
editorControl.notebookEditor.revealCellRangeInView({ start: len - 1, end: len });
752
}
753
}
754
});
755
756
registerAction2(class extends Action2 {
757
constructor() {
758
super({
759
id: 'interactive.input.focus',
760
title: localize2('interactive.input.focus', 'Focus Input Editor'),
761
category: interactiveWindowCategory,
762
menu: {
763
id: MenuId.CommandPalette,
764
when: InteractiveWindowOpen
765
},
766
});
767
}
768
769
async run(accessor: ServicesAccessor): Promise<void> {
770
const editorService = accessor.get(IEditorService);
771
const editorControl = editorService.activeEditorPane?.getControl();
772
773
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
774
editorService.activeEditorPane?.focus();
775
}
776
else {
777
// find and open the most recent interactive window
778
const openEditors = editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE);
779
const interactiveWindow = Iterable.find(openEditors, identifier => { return identifier.editor.typeId === InteractiveEditorInput.ID; });
780
if (interactiveWindow) {
781
const editorInput = interactiveWindow.editor as InteractiveEditorInput;
782
const currentGroup = interactiveWindow.groupId;
783
const editor = await editorService.openEditor(editorInput, currentGroup);
784
const editorControl = editor?.getControl();
785
786
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
787
editorService.activeEditorPane?.focus();
788
}
789
}
790
}
791
}
792
});
793
794
registerAction2(class extends Action2 {
795
constructor() {
796
super({
797
id: 'interactive.history.focus',
798
title: localize2('interactive.history.focus', 'Focus History'),
799
category: interactiveWindowCategory,
800
menu: {
801
id: MenuId.CommandPalette,
802
when: ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'),
803
},
804
keybinding: [{
805
// On mac, require that the cursor is at the top of the input, to avoid stealing cmd+up to move the cursor to the top
806
when: ContextKeyExpr.and(
807
INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('bottom'),
808
INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('none')),
809
weight: KeybindingWeight.WorkbenchContrib + 5,
810
primary: KeyMod.CtrlCmd | KeyCode.UpArrow
811
},
812
{
813
when: ContextKeyExpr.or(IsWindowsContext, IsLinuxContext),
814
weight: KeybindingWeight.WorkbenchContrib,
815
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
816
}],
817
precondition: ContextKeyExpr.and(IS_COMPOSITE_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED.negate())
818
});
819
}
820
821
async run(accessor: ServicesAccessor): Promise<void> {
822
const editorService = accessor.get(IEditorService);
823
const editorControl = editorService.activeEditorPane?.getControl();
824
825
if (editorControl && isReplEditorControl(editorControl) && editorControl.notebookEditor) {
826
editorControl.notebookEditor.focus();
827
}
828
}
829
});
830
831
registerColor('interactive.activeCodeBorder', {
832
dark: ifDefinedThenElse(peekViewBorder, peekViewBorder, '#007acc'),
833
light: ifDefinedThenElse(peekViewBorder, peekViewBorder, '#007acc'),
834
hcDark: contrastBorder,
835
hcLight: contrastBorder
836
}, localize('interactive.activeCodeBorder', 'The border color for the current interactive code cell when the editor has focus.'));
837
838
registerColor('interactive.inactiveCodeBorder', {
839
//dark: theme.getColor(listInactiveSelectionBackground) ?? transparent(listInactiveSelectionBackground, 1),
840
dark: ifDefinedThenElse(listInactiveSelectionBackground, listInactiveSelectionBackground, '#37373D'),
841
light: ifDefinedThenElse(listInactiveSelectionBackground, listInactiveSelectionBackground, '#E4E6F1'),
842
hcDark: PANEL_BORDER,
843
hcLight: PANEL_BORDER
844
}, localize('interactive.inactiveCodeBorder', 'The border color for the current interactive code cell when the editor does not have focus.'));
845
846
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
847
id: 'interactiveWindow',
848
order: 100,
849
type: 'object',
850
'properties': {
851
[ReplEditorSettings.interactiveWindowAlwaysScrollOnNewCell]: {
852
type: 'boolean',
853
default: true,
854
markdownDescription: localize('interactiveWindow.alwaysScrollOnNewCell', "Automatically scroll the interactive window to show the output of the last statement executed. If this value is false, the window will only scroll if the last cell was already the one scrolled to.")
855
},
856
[NotebookSetting.InteractiveWindowPromptToSave]: {
857
type: 'boolean',
858
default: false,
859
markdownDescription: localize('interactiveWindow.promptToSaveOnClose', "Prompt to save the interactive window when it is closed. Only new interactive windows will be affected by this setting change.")
860
},
861
[ReplEditorSettings.executeWithShiftEnter]: {
862
type: 'boolean',
863
default: false,
864
markdownDescription: localize('interactiveWindow.executeWithShiftEnter', "Execute the Interactive Window (REPL) input box with shift+enter, so that enter can be used to create a newline."),
865
tags: ['replExecute']
866
},
867
[ReplEditorSettings.showExecutionHint]: {
868
type: 'boolean',
869
default: true,
870
markdownDescription: localize('interactiveWindow.showExecutionHint', "Display a hint in the Interactive Window (REPL) input box to indicate how to execute code."),
871
tags: ['replExecute']
872
}
873
}
874
});
875
876