Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/interactive/browser/interactiveEditor.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 './media/interactive.css';
7
import * as DOM from '../../../../base/browser/dom.js';
8
import * as domStylesheets from '../../../../base/browser/domStylesheets.js';
9
import { CancellationToken } from '../../../../base/common/cancellation.js';
10
import { Emitter, Event } from '../../../../base/common/event.js';
11
import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js';
12
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
13
import { CodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js';
14
import { ICodeEditorViewState, ICompositeCodeEditor } from '../../../../editor/common/editorCommon.js';
15
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
16
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
17
import { IStorageService } from '../../../../platform/storage/common/storage.js';
18
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
19
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
20
import { EditorPane } from '../../../browser/parts/editor/editorPane.js';
21
import { EditorPaneSelectionChangeReason, IEditorMemento, IEditorOpenContext, IEditorPaneScrollPosition, IEditorPaneSelectionChangeEvent, IEditorPaneWithScrolling } from '../../../common/editor.js';
22
import { getSimpleEditorOptions } from '../../codeEditor/browser/simpleEditorOptions.js';
23
import { InteractiveEditorInput } from './interactiveEditorInput.js';
24
import { ICellViewModel, INotebookEditorOptions, INotebookEditorViewState } from '../../notebook/browser/notebookBrowser.js';
25
import { NotebookEditorExtensionsRegistry } from '../../notebook/browser/notebookEditorExtensions.js';
26
import { IBorrowValue, INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js';
27
import { NotebookEditorWidget } from '../../notebook/browser/notebookEditorWidget.js';
28
import { GroupsOrder, IEditorGroup, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js';
29
import { ExecutionStateCellStatusBarContrib, TimerCellStatusBarContrib } from '../../notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.js';
30
import { INotebookKernelService } from '../../notebook/common/notebookKernelService.js';
31
import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js';
32
import { ILanguageService } from '../../../../editor/common/languages/language.js';
33
import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js';
34
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
35
import { ReplEditorSettings, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from './interactiveCommon.js';
36
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
37
import { NotebookOptions } from '../../notebook/browser/notebookOptions.js';
38
import { ToolBar } from '../../../../base/browser/ui/toolbar/toolbar.js';
39
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
40
import { createActionViewItem, getActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';
41
import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js';
42
import { ParameterHintsController } from '../../../../editor/contrib/parameterHints/browser/parameterHints.js';
43
import { MenuPreventer } from '../../codeEditor/browser/menuPreventer.js';
44
import { SelectionClipboardContributionID } from '../../codeEditor/browser/selectionClipboard.js';
45
import { ContextMenuController } from '../../../../editor/contrib/contextmenu/browser/contextmenu.js';
46
import { SuggestController } from '../../../../editor/contrib/suggest/browser/suggestController.js';
47
import { SnippetController2 } from '../../../../editor/contrib/snippet/browser/snippetController2.js';
48
import { TabCompletionController } from '../../snippets/browser/tabCompletion.js';
49
import { MarkerController } from '../../../../editor/contrib/gotoError/browser/gotoError.js';
50
import { EditorInput } from '../../../common/editor/editorInput.js';
51
import { ITextResourceConfigurationService } from '../../../../editor/common/services/textResourceConfiguration.js';
52
import { ITextEditorOptions, TextEditorSelectionSource } from '../../../../platform/editor/common/editor.js';
53
import { INotebookExecutionStateService, NotebookExecutionType } from '../../notebook/common/notebookExecutionStateService.js';
54
import { NOTEBOOK_KERNEL } from '../../notebook/common/notebookContextKeys.js';
55
import { ICursorPositionChangedEvent } from '../../../../editor/common/cursorEvents.js';
56
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
57
import { isEqual } from '../../../../base/common/resources.js';
58
import { NotebookFindContrib } from '../../notebook/browser/contrib/find/notebookFindWidget.js';
59
import { INTERACTIVE_WINDOW_EDITOR_ID } from '../../notebook/common/notebookCommon.js';
60
import './interactiveEditor.css';
61
import { IEditorOptions } from '../../../../editor/common/config/editorOptions.js';
62
import { deepClone } from '../../../../base/common/objects.js';
63
import { ContentHoverController } from '../../../../editor/contrib/hover/browser/contentHoverController.js';
64
import { GlyphHoverController } from '../../../../editor/contrib/hover/browser/glyphHoverController.js';
65
import { ReplInputHintContentWidget } from './replInputHintContentWidget.js';
66
import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js';
67
import { INLINE_CHAT_ID } from '../../inlineChat/common/inlineChat.js';
68
import { ReplEditorControl } from '../../replNotebook/browser/replEditor.js';
69
70
const DECORATION_KEY = 'interactiveInputDecoration';
71
const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState';
72
73
const INPUT_CELL_VERTICAL_PADDING = 8;
74
const INPUT_CELL_HORIZONTAL_PADDING_RIGHT = 10;
75
const INPUT_EDITOR_PADDING = 8;
76
77
78
export interface InteractiveEditorViewState {
79
readonly notebook?: INotebookEditorViewState;
80
readonly input?: ICodeEditorViewState | null;
81
}
82
83
export interface InteractiveEditorOptions extends ITextEditorOptions {
84
readonly viewState?: InteractiveEditorViewState;
85
}
86
87
export class InteractiveEditor extends EditorPane implements IEditorPaneWithScrolling {
88
private _rootElement!: HTMLElement;
89
private _styleElement!: HTMLStyleElement;
90
private _notebookEditorContainer!: HTMLElement;
91
private _notebookWidget: IBorrowValue<NotebookEditorWidget> = { value: undefined };
92
private _inputCellContainer!: HTMLElement;
93
private _inputFocusIndicator!: HTMLElement;
94
private _inputRunButtonContainer!: HTMLElement;
95
private _inputEditorContainer!: HTMLElement;
96
private _codeEditorWidget!: CodeEditorWidget;
97
private _notebookWidgetService: INotebookEditorService;
98
private _instantiationService: IInstantiationService;
99
private _languageService: ILanguageService;
100
private _contextKeyService: IContextKeyService;
101
private _configurationService: IConfigurationService;
102
private _notebookKernelService: INotebookKernelService;
103
private _keybindingService: IKeybindingService;
104
private _menuService: IMenuService;
105
private _contextMenuService: IContextMenuService;
106
private _editorGroupService: IEditorGroupsService;
107
private _notebookExecutionStateService: INotebookExecutionStateService;
108
private _extensionService: IExtensionService;
109
private readonly _widgetDisposableStore: DisposableStore = this._register(new DisposableStore());
110
private _lastLayoutDimensions?: { readonly dimension: DOM.Dimension; readonly position: DOM.IDomPosition };
111
private _editorOptions: IEditorOptions;
112
private _notebookOptions: NotebookOptions;
113
private _editorMemento: IEditorMemento<InteractiveEditorViewState>;
114
private readonly _groupListener = this._register(new MutableDisposable());
115
private _runbuttonToolbar: ToolBar | undefined;
116
private _hintElement: ReplInputHintContentWidget | undefined;
117
118
private _onDidFocusWidget = this._register(new Emitter<void>());
119
override get onDidFocus(): Event<void> { return this._onDidFocusWidget.event; }
120
private _onDidChangeSelection = this._register(new Emitter<IEditorPaneSelectionChangeEvent>());
121
readonly onDidChangeSelection = this._onDidChangeSelection.event;
122
private _onDidChangeScroll = this._register(new Emitter<void>());
123
readonly onDidChangeScroll = this._onDidChangeScroll.event;
124
125
constructor(
126
group: IEditorGroup,
127
@ITelemetryService telemetryService: ITelemetryService,
128
@IThemeService themeService: IThemeService,
129
@IStorageService storageService: IStorageService,
130
@IInstantiationService instantiationService: IInstantiationService,
131
@INotebookEditorService notebookWidgetService: INotebookEditorService,
132
@IContextKeyService contextKeyService: IContextKeyService,
133
@ICodeEditorService codeEditorService: ICodeEditorService,
134
@INotebookKernelService notebookKernelService: INotebookKernelService,
135
@ILanguageService languageService: ILanguageService,
136
@IKeybindingService keybindingService: IKeybindingService,
137
@IConfigurationService configurationService: IConfigurationService,
138
@IMenuService menuService: IMenuService,
139
@IContextMenuService contextMenuService: IContextMenuService,
140
@IEditorGroupsService editorGroupService: IEditorGroupsService,
141
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
142
@INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService,
143
@IExtensionService extensionService: IExtensionService,
144
) {
145
super(
146
INTERACTIVE_WINDOW_EDITOR_ID,
147
group,
148
telemetryService,
149
themeService,
150
storageService
151
);
152
this._notebookWidgetService = notebookWidgetService;
153
this._configurationService = configurationService;
154
this._notebookKernelService = notebookKernelService;
155
this._languageService = languageService;
156
this._keybindingService = keybindingService;
157
this._menuService = menuService;
158
this._contextMenuService = contextMenuService;
159
this._editorGroupService = editorGroupService;
160
this._notebookExecutionStateService = notebookExecutionStateService;
161
this._extensionService = extensionService;
162
163
this._rootElement = DOM.$('.interactive-editor');
164
this._contextKeyService = this._register(contextKeyService.createScoped(this._rootElement));
165
this._contextKeyService.createKey('isCompositeNotebook', true);
166
this._instantiationService = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this._contextKeyService])));
167
168
this._editorOptions = this._computeEditorOptions();
169
this._register(this._configurationService.onDidChangeConfiguration(e => {
170
if (e.affectsConfiguration('editor') || e.affectsConfiguration('notebook')) {
171
this._editorOptions = this._computeEditorOptions();
172
}
173
}));
174
this._notebookOptions = instantiationService.createInstance(NotebookOptions, this.window, true, { cellToolbarInteraction: 'hover', globalToolbar: true, stickyScrollEnabled: false, dragAndDropEnabled: false, disableRulers: true });
175
this._editorMemento = this.getEditorMemento<InteractiveEditorViewState>(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY);
176
177
codeEditorService.registerDecorationType('interactive-decoration', DECORATION_KEY, {});
178
this._register(this._keybindingService.onDidUpdateKeybindings(this._updateInputHint, this));
179
this._register(this._notebookExecutionStateService.onDidChangeExecution((e) => {
180
if (e.type === NotebookExecutionType.cell && isEqual(e.notebook, this._notebookWidget.value?.viewModel?.notebookDocument.uri)) {
181
const cell = this._notebookWidget.value?.getCellByHandle(e.cellHandle);
182
if (cell && e.changed?.state) {
183
this._scrollIfNecessary(cell);
184
}
185
}
186
}));
187
}
188
189
private get inputCellContainerHeight() {
190
return 19 + 2 + INPUT_CELL_VERTICAL_PADDING * 2 + INPUT_EDITOR_PADDING * 2;
191
}
192
193
private get inputCellEditorHeight() {
194
return 19 + INPUT_EDITOR_PADDING * 2;
195
}
196
197
protected createEditor(parent: HTMLElement): void {
198
DOM.append(parent, this._rootElement);
199
this._rootElement.style.position = 'relative';
200
this._notebookEditorContainer = DOM.append(this._rootElement, DOM.$('.notebook-editor-container'));
201
this._inputCellContainer = DOM.append(this._rootElement, DOM.$('.input-cell-container'));
202
this._inputCellContainer.style.position = 'absolute';
203
this._inputCellContainer.style.height = `${this.inputCellContainerHeight}px`;
204
this._inputFocusIndicator = DOM.append(this._inputCellContainer, DOM.$('.input-focus-indicator'));
205
this._inputRunButtonContainer = DOM.append(this._inputCellContainer, DOM.$('.run-button-container'));
206
this._setupRunButtonToolbar(this._inputRunButtonContainer);
207
this._inputEditorContainer = DOM.append(this._inputCellContainer, DOM.$('.input-editor-container'));
208
this._createLayoutStyles();
209
}
210
211
private _setupRunButtonToolbar(runButtonContainer: HTMLElement) {
212
const menu = this._register(this._menuService.createMenu(MenuId.InteractiveInputExecute, this._contextKeyService));
213
this._runbuttonToolbar = this._register(new ToolBar(runButtonContainer, this._contextMenuService, {
214
getKeyBinding: action => this._keybindingService.lookupKeybinding(action.id),
215
actionViewItemProvider: (action, options) => {
216
return createActionViewItem(this._instantiationService, action, options);
217
},
218
renderDropdownAsChildElement: true
219
}));
220
221
const { primary, secondary } = getActionBarActions(menu.getActions({ shouldForwardArgs: true }));
222
this._runbuttonToolbar.setActions([...primary, ...secondary]);
223
}
224
225
private _createLayoutStyles(): void {
226
this._styleElement = domStylesheets.createStyleSheet(this._rootElement);
227
const styleSheets: string[] = [];
228
229
const {
230
codeCellLeftMargin,
231
cellRunGutter
232
} = this._notebookOptions.getLayoutConfiguration();
233
const {
234
focusIndicator
235
} = this._notebookOptions.getDisplayOptions();
236
const leftMargin = this._notebookOptions.getCellEditorContainerLeftMargin();
237
238
styleSheets.push(`
239
.interactive-editor .input-cell-container {
240
padding: ${INPUT_CELL_VERTICAL_PADDING}px ${INPUT_CELL_HORIZONTAL_PADDING_RIGHT}px ${INPUT_CELL_VERTICAL_PADDING}px ${leftMargin}px;
241
}
242
`);
243
if (focusIndicator === 'gutter') {
244
styleSheets.push(`
245
.interactive-editor .input-cell-container:focus-within .input-focus-indicator::before {
246
border-color: var(--vscode-notebook-focusedCellBorder) !important;
247
}
248
.interactive-editor .input-focus-indicator::before {
249
border-color: var(--vscode-notebook-inactiveFocusedCellBorder) !important;
250
}
251
.interactive-editor .input-cell-container .input-focus-indicator {
252
display: block;
253
top: ${INPUT_CELL_VERTICAL_PADDING}px;
254
}
255
.interactive-editor .input-cell-container {
256
border-top: 1px solid var(--vscode-notebook-inactiveFocusedCellBorder);
257
}
258
`);
259
} else {
260
// border
261
styleSheets.push(`
262
.interactive-editor .input-cell-container {
263
border-top: 1px solid var(--vscode-notebook-inactiveFocusedCellBorder);
264
}
265
.interactive-editor .input-cell-container .input-focus-indicator {
266
display: none;
267
}
268
`);
269
}
270
271
styleSheets.push(`
272
.interactive-editor .input-cell-container .run-button-container {
273
width: ${cellRunGutter}px;
274
left: ${codeCellLeftMargin}px;
275
margin-top: ${INPUT_EDITOR_PADDING - 2}px;
276
}
277
`);
278
279
this._styleElement.textContent = styleSheets.join('\n');
280
}
281
282
private _computeEditorOptions(): IEditorOptions {
283
let overrideIdentifier: string | undefined = undefined;
284
if (this._codeEditorWidget) {
285
overrideIdentifier = this._codeEditorWidget.getModel()?.getLanguageId();
286
}
287
const editorOptions = deepClone(this._configurationService.getValue<IEditorOptions>('editor', { overrideIdentifier }));
288
const editorOptionsOverride = getSimpleEditorOptions(this._configurationService);
289
const computed = Object.freeze({
290
...editorOptions,
291
...editorOptionsOverride,
292
...{
293
glyphMargin: true,
294
padding: {
295
top: INPUT_EDITOR_PADDING,
296
bottom: INPUT_EDITOR_PADDING
297
},
298
hover: {
299
enabled: true
300
},
301
rulers: []
302
}
303
});
304
305
return computed;
306
}
307
308
protected override saveState(): void {
309
this._saveEditorViewState(this.input);
310
super.saveState();
311
}
312
313
override getViewState(): InteractiveEditorViewState | undefined {
314
const input = this.input;
315
if (!(input instanceof InteractiveEditorInput)) {
316
return undefined;
317
}
318
319
this._saveEditorViewState(input);
320
return this._loadNotebookEditorViewState(input);
321
}
322
323
private _saveEditorViewState(input: EditorInput | undefined): void {
324
if (this._notebookWidget.value && input instanceof InteractiveEditorInput) {
325
if (this._notebookWidget.value.isDisposed) {
326
return;
327
}
328
329
const state = this._notebookWidget.value.getEditorViewState();
330
const editorState = this._codeEditorWidget.saveViewState();
331
this._editorMemento.saveEditorState(this.group, input.notebookEditorInput.resource, {
332
notebook: state,
333
input: editorState
334
});
335
}
336
}
337
338
private _loadNotebookEditorViewState(input: InteractiveEditorInput): InteractiveEditorViewState | undefined {
339
const result = this._editorMemento.loadEditorState(this.group, input.notebookEditorInput.resource);
340
if (result) {
341
return result;
342
}
343
// when we don't have a view state for the group/input-tuple then we try to use an existing
344
// editor for the same resource.
345
for (const group of this._editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) {
346
if (group.activeEditorPane !== this && group.activeEditorPane === this && group.activeEditor?.matches(input)) {
347
const notebook = this._notebookWidget.value?.getEditorViewState();
348
const input = this._codeEditorWidget.saveViewState();
349
return {
350
notebook,
351
input
352
};
353
}
354
}
355
return;
356
}
357
358
override async setInput(input: InteractiveEditorInput, options: InteractiveEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
359
const notebookInput = input.notebookEditorInput;
360
361
// there currently is a widget which we still own so
362
// we need to hide it before getting a new widget
363
this._notebookWidget.value?.onWillHide();
364
365
this._codeEditorWidget?.dispose();
366
367
this._widgetDisposableStore.clear();
368
369
this._notebookWidget = <IBorrowValue<NotebookEditorWidget>>this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, this.group.id, notebookInput, {
370
isReplHistory: true,
371
isReadOnly: true,
372
contributions: NotebookEditorExtensionsRegistry.getSomeEditorContributions([
373
ExecutionStateCellStatusBarContrib.id,
374
TimerCellStatusBarContrib.id,
375
NotebookFindContrib.id
376
]),
377
menuIds: {
378
notebookToolbar: MenuId.InteractiveToolbar,
379
cellTitleToolbar: MenuId.InteractiveCellTitle,
380
cellDeleteToolbar: MenuId.InteractiveCellDelete,
381
cellInsertToolbar: MenuId.NotebookCellBetween,
382
cellTopInsertToolbar: MenuId.NotebookCellListTop,
383
cellExecuteToolbar: MenuId.InteractiveCellExecute,
384
cellExecutePrimary: undefined
385
},
386
cellEditorContributions: EditorExtensionsRegistry.getSomeEditorContributions([
387
SelectionClipboardContributionID,
388
ContextMenuController.ID,
389
ContentHoverController.ID,
390
GlyphHoverController.ID,
391
MarkerController.ID
392
]),
393
options: this._notebookOptions,
394
codeWindow: this.window
395
}, undefined, this.window);
396
397
this._codeEditorWidget = this._instantiationService.createInstance(CodeEditorWidget, this._inputEditorContainer, this._editorOptions, {
398
...{
399
isSimpleWidget: false,
400
contributions: EditorExtensionsRegistry.getSomeEditorContributions([
401
MenuPreventer.ID,
402
SelectionClipboardContributionID,
403
ContextMenuController.ID,
404
SuggestController.ID,
405
ParameterHintsController.ID,
406
SnippetController2.ID,
407
TabCompletionController.ID,
408
ContentHoverController.ID,
409
GlyphHoverController.ID,
410
MarkerController.ID,
411
INLINE_CHAT_ID,
412
])
413
}
414
});
415
416
if (this._lastLayoutDimensions) {
417
this._notebookEditorContainer.style.height = `${this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight}px`;
418
this._notebookWidget.value!.layout(new DOM.Dimension(this._lastLayoutDimensions.dimension.width, this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight), this._notebookEditorContainer);
419
const leftMargin = this._notebookOptions.getCellEditorContainerLeftMargin();
420
const maxHeight = Math.min(this._lastLayoutDimensions.dimension.height / 2, this.inputCellEditorHeight);
421
this._codeEditorWidget.layout(this._validateDimension(this._lastLayoutDimensions.dimension.width - leftMargin - INPUT_CELL_HORIZONTAL_PADDING_RIGHT, maxHeight));
422
this._inputFocusIndicator.style.height = `${this.inputCellEditorHeight}px`;
423
this._inputCellContainer.style.top = `${this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight}px`;
424
this._inputCellContainer.style.width = `${this._lastLayoutDimensions.dimension.width}px`;
425
}
426
427
await super.setInput(input, options, context, token);
428
const model = await input.resolve();
429
if (this._runbuttonToolbar) {
430
this._runbuttonToolbar.context = input.resource;
431
}
432
433
if (model === null) {
434
throw new Error('The Interactive Window model could not be resolved');
435
}
436
437
this._notebookWidget.value?.setParentContextKeyService(this._contextKeyService);
438
439
const viewState = options?.viewState ?? this._loadNotebookEditorViewState(input);
440
await this._extensionService.whenInstalledExtensionsRegistered();
441
await this._notebookWidget.value!.setModel(model.notebook, viewState?.notebook);
442
model.notebook.setCellCollapseDefault(this._notebookOptions.getCellCollapseDefault());
443
this._notebookWidget.value!.setOptions({
444
isReadOnly: true
445
});
446
this._widgetDisposableStore.add(this._notebookWidget.value!.onDidResizeOutput((cvm) => {
447
this._scrollIfNecessary(cvm);
448
}));
449
this._widgetDisposableStore.add(this._notebookWidget.value!.onDidFocusWidget(() => this._onDidFocusWidget.fire()));
450
this._widgetDisposableStore.add(this._notebookOptions.onDidChangeOptions(e => {
451
if (e.compactView || e.focusIndicator) {
452
// update the styling
453
this._styleElement?.remove();
454
this._createLayoutStyles();
455
}
456
457
if (this._lastLayoutDimensions && this.isVisible()) {
458
this.layout(this._lastLayoutDimensions.dimension, this._lastLayoutDimensions.position);
459
}
460
461
if (e.interactiveWindowCollapseCodeCells) {
462
model.notebook.setCellCollapseDefault(this._notebookOptions.getCellCollapseDefault());
463
}
464
}));
465
466
const languageId = this._notebookWidget.value?.activeKernel?.supportedLanguages[0] ?? input.language ?? PLAINTEXT_LANGUAGE_ID;
467
const editorModel = await input.resolveInput(languageId);
468
editorModel.setLanguage(languageId);
469
this._codeEditorWidget.setModel(editorModel);
470
if (viewState?.input) {
471
this._codeEditorWidget.restoreViewState(viewState.input);
472
}
473
this._editorOptions = this._computeEditorOptions();
474
this._codeEditorWidget.updateOptions(this._editorOptions);
475
476
this._widgetDisposableStore.add(this._codeEditorWidget.onDidFocusEditorWidget(() => this._onDidFocusWidget.fire()));
477
this._widgetDisposableStore.add(this._codeEditorWidget.onDidContentSizeChange(e => {
478
if (!e.contentHeightChanged) {
479
return;
480
}
481
482
if (this._lastLayoutDimensions) {
483
this._layoutWidgets(this._lastLayoutDimensions.dimension, this._lastLayoutDimensions.position);
484
}
485
}));
486
487
this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeCursorPosition(e => this._onDidChangeSelection.fire({ reason: this._toEditorPaneSelectionChangeReason(e) })));
488
this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeModelContent(() => this._onDidChangeSelection.fire({ reason: EditorPaneSelectionChangeReason.EDIT })));
489
490
491
this._widgetDisposableStore.add(this._notebookKernelService.onDidChangeNotebookAffinity(this._syncWithKernel, this));
492
this._widgetDisposableStore.add(this._notebookKernelService.onDidChangeSelectedNotebooks(this._syncWithKernel, this));
493
494
this._widgetDisposableStore.add(this.themeService.onDidColorThemeChange(() => {
495
if (this.isVisible()) {
496
this._updateInputHint();
497
}
498
}));
499
500
this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeModelContent(() => {
501
if (this.isVisible()) {
502
this._updateInputHint();
503
}
504
}));
505
506
this._codeEditorWidget.onDidChangeModelDecorations(() => {
507
if (this.isVisible()) {
508
this._updateInputHint();
509
}
510
});
511
512
this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeModel(() => {
513
this._updateInputHint();
514
}));
515
516
this._configurationService.onDidChangeConfiguration(e => {
517
if (e.affectsConfiguration(ReplEditorSettings.showExecutionHint)) {
518
this._updateInputHint();
519
}
520
});
521
522
const cursorAtBoundaryContext = INTERACTIVE_INPUT_CURSOR_BOUNDARY.bindTo(this._contextKeyService);
523
if (input.resource && input.historyService.has(input.resource)) {
524
cursorAtBoundaryContext.set('top');
525
} else {
526
cursorAtBoundaryContext.set('none');
527
}
528
529
this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeCursorPosition(({ position }) => {
530
const viewModel = this._codeEditorWidget._getViewModel()!;
531
const lastLineNumber = viewModel.getLineCount();
532
const lastLineCol = viewModel.getLineLength(lastLineNumber) + 1;
533
const viewPosition = viewModel.coordinatesConverter.convertModelPositionToViewPosition(position);
534
const firstLine = viewPosition.lineNumber === 1 && viewPosition.column === 1;
535
const lastLine = viewPosition.lineNumber === lastLineNumber && viewPosition.column === lastLineCol;
536
537
if (firstLine) {
538
if (lastLine) {
539
cursorAtBoundaryContext.set('both');
540
} else {
541
cursorAtBoundaryContext.set('top');
542
}
543
} else {
544
if (lastLine) {
545
cursorAtBoundaryContext.set('bottom');
546
} else {
547
cursorAtBoundaryContext.set('none');
548
}
549
}
550
}));
551
552
this._widgetDisposableStore.add(editorModel.onDidChangeContent(() => {
553
const value = editorModel.getValue();
554
if (this.input?.resource) {
555
const historyService = (this.input as InteractiveEditorInput).historyService;
556
if (!historyService.matchesCurrent(this.input.resource, value)) {
557
historyService.replaceLast(this.input.resource, value);
558
}
559
}
560
}));
561
562
this._widgetDisposableStore.add(this._notebookWidget.value!.onDidScroll(() => this._onDidChangeScroll.fire()));
563
564
this._syncWithKernel();
565
566
this._updateInputHint();
567
}
568
569
override setOptions(options: INotebookEditorOptions | undefined): void {
570
this._notebookWidget.value?.setOptions(options);
571
super.setOptions(options);
572
}
573
574
private _toEditorPaneSelectionChangeReason(e: ICursorPositionChangedEvent): EditorPaneSelectionChangeReason {
575
switch (e.source) {
576
case TextEditorSelectionSource.PROGRAMMATIC: return EditorPaneSelectionChangeReason.PROGRAMMATIC;
577
case TextEditorSelectionSource.NAVIGATION: return EditorPaneSelectionChangeReason.NAVIGATION;
578
case TextEditorSelectionSource.JUMP: return EditorPaneSelectionChangeReason.JUMP;
579
default: return EditorPaneSelectionChangeReason.USER;
580
}
581
}
582
583
private _cellAtBottom(cell: ICellViewModel): boolean {
584
const visibleRanges = this._notebookWidget.value?.visibleRanges || [];
585
const cellIndex = this._notebookWidget.value?.getCellIndex(cell);
586
if (cellIndex === Math.max(...visibleRanges.map(range => range.end - 1))) {
587
return true;
588
}
589
return false;
590
}
591
592
private _scrollIfNecessary(cvm: ICellViewModel) {
593
const index = this._notebookWidget.value!.getCellIndex(cvm);
594
if (index === this._notebookWidget.value!.getLength() - 1) {
595
// If we're already at the bottom or auto scroll is enabled, scroll to the bottom
596
if (this._configurationService.getValue<boolean>(ReplEditorSettings.interactiveWindowAlwaysScrollOnNewCell) || this._cellAtBottom(cvm)) {
597
this._notebookWidget.value!.scrollToBottom();
598
}
599
}
600
}
601
602
private _syncWithKernel() {
603
const notebook = this._notebookWidget.value?.textModel;
604
const textModel = this._codeEditorWidget.getModel();
605
606
if (notebook && textModel) {
607
const info = this._notebookKernelService.getMatchingKernel(notebook);
608
const selectedOrSuggested = info.selected
609
?? (info.suggestions.length === 1 ? info.suggestions[0] : undefined)
610
?? (info.all.length === 1 ? info.all[0] : undefined);
611
612
if (selectedOrSuggested) {
613
const language = selectedOrSuggested.supportedLanguages[0];
614
// All kernels will initially list plaintext as the supported language before they properly initialized.
615
if (language && language !== 'plaintext') {
616
const newMode = this._languageService.createById(language).languageId;
617
textModel.setLanguage(newMode);
618
}
619
620
NOTEBOOK_KERNEL.bindTo(this._contextKeyService).set(selectedOrSuggested.id);
621
}
622
}
623
}
624
625
layout(dimension: DOM.Dimension, position: DOM.IDomPosition): void {
626
this._rootElement.classList.toggle('mid-width', dimension.width < 1000 && dimension.width >= 600);
627
this._rootElement.classList.toggle('narrow-width', dimension.width < 600);
628
const editorHeightChanged = dimension.height !== this._lastLayoutDimensions?.dimension.height;
629
this._lastLayoutDimensions = { dimension, position };
630
631
if (!this._notebookWidget.value) {
632
return;
633
}
634
635
if (editorHeightChanged && this._codeEditorWidget) {
636
SuggestController.get(this._codeEditorWidget)?.cancelSuggestWidget();
637
}
638
639
this._notebookEditorContainer.style.height = `${this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight}px`;
640
this._layoutWidgets(dimension, position);
641
}
642
643
private _layoutWidgets(dimension: DOM.Dimension, position: DOM.IDomPosition) {
644
const contentHeight = this._codeEditorWidget.hasModel() ? this._codeEditorWidget.getContentHeight() : this.inputCellEditorHeight;
645
const maxHeight = Math.min(dimension.height / 2, contentHeight);
646
const leftMargin = this._notebookOptions.getCellEditorContainerLeftMargin();
647
648
const inputCellContainerHeight = maxHeight + INPUT_CELL_VERTICAL_PADDING * 2;
649
this._notebookEditorContainer.style.height = `${dimension.height - inputCellContainerHeight}px`;
650
651
this._notebookWidget.value!.layout(dimension.with(dimension.width, dimension.height - inputCellContainerHeight), this._notebookEditorContainer, position);
652
this._codeEditorWidget.layout(this._validateDimension(dimension.width - leftMargin - INPUT_CELL_HORIZONTAL_PADDING_RIGHT, maxHeight));
653
this._inputFocusIndicator.style.height = `${contentHeight}px`;
654
this._inputCellContainer.style.top = `${dimension.height - inputCellContainerHeight}px`;
655
this._inputCellContainer.style.width = `${dimension.width}px`;
656
}
657
658
private _validateDimension(width: number, height: number) {
659
return new DOM.Dimension(Math.max(0, width), Math.max(0, height));
660
}
661
662
private _hasConflictingDecoration() {
663
return Boolean(this._codeEditorWidget.getLineDecorations(1)?.find((d) =>
664
d.options.beforeContentClassName
665
|| d.options.afterContentClassName
666
|| d.options.before?.content
667
|| d.options.after?.content
668
));
669
}
670
671
private _updateInputHint(): void {
672
if (!this._codeEditorWidget) {
673
return;
674
}
675
676
const shouldHide =
677
!this._codeEditorWidget.hasModel() ||
678
this._configurationService.getValue<boolean>(ReplEditorSettings.showExecutionHint) === false ||
679
this._codeEditorWidget.getModel()!.getValueLength() !== 0 ||
680
this._hasConflictingDecoration();
681
682
if (!this._hintElement && !shouldHide) {
683
this._hintElement = this._instantiationService.createInstance(ReplInputHintContentWidget, this._codeEditorWidget);
684
} else if (this._hintElement && shouldHide) {
685
this._hintElement.dispose();
686
this._hintElement = undefined;
687
}
688
}
689
690
getScrollPosition(): IEditorPaneScrollPosition {
691
return {
692
scrollTop: this._notebookWidget.value?.scrollTop ?? 0,
693
scrollLeft: 0
694
};
695
}
696
697
setScrollPosition(position: IEditorPaneScrollPosition): void {
698
this._notebookWidget.value?.setScrollTop(position.scrollTop);
699
}
700
701
override focus() {
702
super.focus();
703
704
this._notebookWidget.value?.onShow();
705
this._codeEditorWidget.focus();
706
}
707
708
focusHistory() {
709
this._notebookWidget.value!.focus();
710
}
711
712
protected override setEditorVisible(visible: boolean): void {
713
super.setEditorVisible(visible);
714
this._groupListener.value = this.group.onWillCloseEditor(e => this._saveEditorViewState(e.editor));
715
716
if (!visible) {
717
this._saveEditorViewState(this.input);
718
if (this.input && this._notebookWidget.value) {
719
this._notebookWidget.value.onWillHide();
720
}
721
}
722
723
this._updateInputHint();
724
}
725
726
override clearInput() {
727
if (this._notebookWidget.value) {
728
this._saveEditorViewState(this.input);
729
this._notebookWidget.value.onWillHide();
730
}
731
732
this._codeEditorWidget?.dispose();
733
734
this._notebookWidget = { value: undefined };
735
this._widgetDisposableStore.clear();
736
737
super.clearInput();
738
}
739
740
override getControl(): ReplEditorControl & ICompositeCodeEditor {
741
return {
742
notebookEditor: this._notebookWidget.value,
743
activeCodeEditor: this._codeEditorWidget,
744
onDidChangeActiveEditor: Event.None
745
};
746
}
747
}
748
749