Skip to content

Commit f649124

Browse files
authored
Add hot exit support for interactive window (microsoft#154974)
* Export Interactive Window tab input. * Update inputBoxUri. * remove inputBoxUri from API * Hot exit * Expose inputBoxUri and make IW Tab ctor private * disable hot exit by default
1 parent 3e18c49 commit f649124

File tree

5 files changed

+162
-21
lines changed

5 files changed

+162
-21
lines changed

src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { peekViewBorder /*, peekViewEditorBackground, peekViewResultsBackground
2424
import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/suggest';
2525
import { localize } from 'vs/nls';
2626
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
27+
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
2728
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
2829
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
2930
import { EditorActivation, IResourceEditorInput } from 'vs/platform/editor/common/editor';
@@ -38,12 +39,12 @@ import { contrastBorder, listInactiveSelectionBackground, registerColor, transpa
3839
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
3940
import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor';
4041
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
41-
import { EditorExtensions, EditorsOrder, IEditorSerializer } from 'vs/workbench/common/editor';
42+
import { EditorExtensions, EditorsOrder, IEditorFactoryRegistry, IEditorSerializer } from 'vs/workbench/common/editor';
4243
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
4344
// import { Color } from 'vs/base/common/color';
4445
import { PANEL_BORDER } from 'vs/workbench/common/theme';
4546
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
46-
import { INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon';
47+
import { InteractiveWindowSetting, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon';
4748
import { IInteractiveDocumentService, InteractiveDocumentService } from 'vs/workbench/contrib/interactive/browser/interactiveDocumentService';
4849
import { InteractiveEditor } from 'vs/workbench/contrib/interactive/browser/interactiveEditor';
4950
import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser/interactiveEditorInput';
@@ -52,7 +53,7 @@ import { NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/noteb
5253
import { INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
5354
import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
5455
import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons';
55-
import { CellEditType, CellKind, CellUri, ICellOutput, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
56+
import { CellEditType, CellKind, CellUri, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
5657
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
5758
import { INotebookContentProvider, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
5859
import { columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn';
@@ -194,7 +195,7 @@ export class InteractiveDocumentContribution extends Disposable implements IWork
194195
editorResolverService.registerEditor(
195196
`${Schemas.vscodeInteractiveInput}:/**`,
196197
{
197-
id: InteractiveEditorInput.ID,
198+
id: 'vscode-interactive-input',
198199
label: 'Interactive Editor',
199200
priority: RegisteredEditorPriority.exclusive
200201
},
@@ -211,7 +212,7 @@ export class InteractiveDocumentContribution extends Disposable implements IWork
211212
editorResolverService.registerEditor(
212213
`*.interactive`,
213214
{
214-
id: InteractiveEditorInput.ID,
215+
id: 'interactive',
215216
label: 'Interactive Editor',
216217
priority: RegisteredEditorPriority.exclusive
217218
},
@@ -272,20 +273,30 @@ workbenchContributionsRegistry.registerWorkbenchContribution(InteractiveDocument
272273
workbenchContributionsRegistry.registerWorkbenchContribution(InteractiveInputContentProvider, LifecyclePhase.Starting);
273274

274275
export class InteractiveEditorSerializer implements IEditorSerializer {
276+
public static readonly ID = InteractiveEditorInput.ID;
277+
278+
constructor(@IConfigurationService private configurationService: IConfigurationService) {
279+
}
280+
275281
canSerialize(): boolean {
276-
return true;
282+
return this.configurationService.getValue<boolean>(InteractiveWindowSetting.interactiveWindowHotExit);
277283
}
278284

279285
serialize(input: EditorInput): string {
280286
assertType(input instanceof InteractiveEditorInput);
281287
return JSON.stringify({
282288
resource: input.primary.resource,
283289
inputResource: input.inputResource,
290+
name: input.getName(),
291+
data: input.getSerialization()
284292
});
285293
}
286294

287295
deserialize(instantiationService: IInstantiationService, raw: string) {
288-
type Data = { resource: URI; inputResource: URI };
296+
if (!this.canSerialize()) {
297+
return undefined;
298+
}
299+
type Data = { resource: URI; inputResource: URI; data: any };
289300
const data = <Data>parse(raw);
290301
if (!data) {
291302
return undefined;
@@ -296,14 +307,15 @@ export class InteractiveEditorSerializer implements IEditorSerializer {
296307
}
297308

298309
const input = InteractiveEditorInput.create(instantiationService, resource, inputResource);
310+
input.restoreSerialization(data.data);
299311
return input;
300312
}
301313
}
302314

303-
// Registry.as<EditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(
304-
// InteractiveEditorInput.ID,
305-
// InteractiveEditorSerializer
306-
// );
315+
Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory)
316+
.registerEditorSerializer(
317+
InteractiveEditorSerializer.ID,
318+
InteractiveEditorSerializer);
307319

308320
registerSingleton(IInteractiveHistoryService, InteractiveHistoryService);
309321
registerSingleton(IInteractiveDocumentService, InteractiveDocumentService);
@@ -738,15 +750,20 @@ registerThemingParticipant((theme) => {
738750
});
739751

740752
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
741-
id: 'notebook',
753+
id: 'interactiveWindow',
742754
order: 100,
743755
type: 'object',
744756
'properties': {
745-
[NotebookSetting.interactiveWindowAlwaysScrollOnNewCell]: {
757+
[InteractiveWindowSetting.interactiveWindowAlwaysScrollOnNewCell]: {
746758
type: 'boolean',
747759
default: true,
748760
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.")
749761
},
762+
[InteractiveWindowSetting.interactiveWindowHotExit]: {
763+
type: 'boolean',
764+
default: false,
765+
markdownDescription: localize('interactiveWindow.hotExit', "Controls whether the interactive window sessions should be restored when the workspace reloads.")
766+
}
750767
}
751768
});
752769

src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,8 @@
66
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
77

88
export const INTERACTIVE_INPUT_CURSOR_BOUNDARY = new RawContextKey<'none' | 'top' | 'bottom' | 'both'>('interactiveInputCursorAtBoundary', 'none');
9+
10+
export const InteractiveWindowSetting = {
11+
interactiveWindowAlwaysScrollOnNewCell: 'interactiveWindow.alwaysScrollOnNewCell',
12+
interactiveWindowHotExit: 'interactiveWindow.hotExit'
13+
};

src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,8 @@ import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'
3333
import { ILanguageService } from 'vs/editor/common/languages/language';
3434
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
3535
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
36-
import { INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon';
36+
import { InteractiveWindowSetting, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon';
3737
import { ComplexNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel';
38-
import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
3938
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
4039
import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions';
4140
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
@@ -57,6 +56,7 @@ import { ITextEditorOptions, TextEditorSelectionSource } from 'vs/platform/edito
5756
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
5857
import { NOTEBOOK_KERNEL } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
5958
import { ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents';
59+
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
6060

6161
const DECORATION_KEY = 'interactiveInputDecoration';
6262
const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState';
@@ -97,6 +97,7 @@ export class InteractiveEditor extends EditorPane {
9797
#contextMenuService: IContextMenuService;
9898
#editorGroupService: IEditorGroupsService;
9999
#notebookExecutionStateService: INotebookExecutionStateService;
100+
#extensionService: IExtensionService;
100101
#widgetDisposableStore: DisposableStore = this._register(new DisposableStore());
101102
#dimension?: DOM.Dimension;
102103
#notebookOptions: NotebookOptions;
@@ -124,7 +125,8 @@ export class InteractiveEditor extends EditorPane {
124125
@IContextMenuService contextMenuService: IContextMenuService,
125126
@IEditorGroupsService editorGroupService: IEditorGroupsService,
126127
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
127-
@INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService
128+
@INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService,
129+
@IExtensionService extensionService: IExtensionService,
128130
) {
129131
super(
130132
InteractiveEditor.ID,
@@ -142,6 +144,7 @@ export class InteractiveEditor extends EditorPane {
142144
this.#contextMenuService = contextMenuService;
143145
this.#editorGroupService = editorGroupService;
144146
this.#notebookExecutionStateService = notebookExecutionStateService;
147+
this.#extensionService = extensionService;
145148

146149
this.#notebookOptions = new NotebookOptions(configurationService, notebookExecutionStateService, { cellToolbarInteraction: 'hover', globalToolbar: true, defaultCellCollapseConfig: { codeCell: { inputCollapsed: true } } });
147150
this.#editorMemento = this.getEditorMemento<InteractiveEditorViewState>(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY);
@@ -397,6 +400,7 @@ export class InteractiveEditor extends EditorPane {
397400
this.#notebookWidget.value?.setParentContextKeyService(this.#contextKeyService);
398401

399402
const viewState = options?.viewState ?? this.#loadNotebookEditorViewState(input);
403+
await this.#extensionService.whenInstalledExtensionsRegistered();
400404
await this.#notebookWidget.value!.setModel(model.notebook, viewState?.notebook);
401405
model.notebook.setCellCollapseDefault(this.#notebookOptions.getCellCollapseDefault());
402406
this.#notebookWidget.value!.setOptions({
@@ -528,7 +532,7 @@ export class InteractiveEditor extends EditorPane {
528532
const index = this.#notebookWidget.value!.getCellIndex(cvm);
529533
if (index === this.#notebookWidget.value!.getLength() - 1) {
530534
// If we're already at the bottom or auto scroll is enabled, scroll to the bottom
531-
if (this.configurationService.getValue<boolean>(NotebookSetting.interactiveWindowAlwaysScrollOnNewCell) || this.#cellAtBottom(cvm)) {
535+
if (this.configurationService.getValue<boolean>(InteractiveWindowSetting.interactiveWindowAlwaysScrollOnNewCell) || this.#cellAtBottom(cvm)) {
532536
this.#notebookWidget.value!.scrollToBottom();
533537
}
534538
}

src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { VSBuffer } from 'vs/base/common/buffer';
67
import { Event } from 'vs/base/common/event';
78
import { IReference } from 'vs/base/common/lifecycle';
89
import * as paths from 'vs/base/common/path';
@@ -14,7 +15,9 @@ import { IUntypedEditorInput } from 'vs/workbench/common/editor';
1415
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
1516
import { IInteractiveDocumentService } from 'vs/workbench/contrib/interactive/browser/interactiveDocumentService';
1617
import { IInteractiveHistoryService } from 'vs/workbench/contrib/interactive/browser/interactiveHistoryService';
17-
import { IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
18+
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
19+
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
20+
import { CellKind, ICellDto2, IOutputDto, IResolvedNotebookEditorModel, NotebookCellCollapseState, NotebookCellInternalMetadata, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
1821
import { ICompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
1922

2023
export class InteractiveEditorInput extends EditorInput implements ICompositeNotebookEditorInput {
@@ -131,7 +134,14 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
131134
return this._inputResolver;
132135
}
133136

134-
this._inputResolver = this._resolveEditorModel();
137+
this._inputResolver = this._resolveEditorModel().then(editorModel => {
138+
if (this._data) {
139+
editorModel?.notebook.reset(this._data.notebookData.cells.map((cell: ISerializedCell) => deserializeCell(cell)), this._data.notebookData.metadata, this._data.notebookData.transientOptions);
140+
}
141+
142+
return editorModel;
143+
});
144+
135145
return this._inputResolver;
136146
}
137147

@@ -143,6 +153,10 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
143153
this._interactiveDocumentService.willCreateInteractiveDocument(this.resource!, this.inputResource, language);
144154
this._inputModelRef = await this._textModelService.createModelReference(this.inputResource);
145155

156+
if (this._data && this._data.inputData) {
157+
this._inputModelRef.object.textEditorModel.setValue(this._data.inputData.value);
158+
}
159+
146160
return this._inputModelRef.object.textEditorModel;
147161
}
148162

@@ -167,6 +181,37 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
167181
return basename.substr(0, basename.length - paths.extname(p).length);
168182
}
169183

184+
getSerialization(): { notebookData: any | undefined; inputData: any | undefined } {
185+
return {
186+
notebookData: this._serializeNotebook(this._editorModelReference?.notebook),
187+
inputData: this._inputModelRef ? {
188+
value: this._inputModelRef.object.textEditorModel.getValue(),
189+
language: this._inputModelRef.object.textEditorModel.getLanguageId()
190+
} : undefined
191+
};
192+
}
193+
194+
private _data: { notebookData: any | undefined; inputData: any | undefined } | undefined;
195+
196+
async restoreSerialization(data: { notebookData: any | undefined; inputData: any | undefined } | undefined) {
197+
this._data = data;
198+
}
199+
200+
private _serializeNotebook(notebook?: NotebookTextModel) {
201+
if (!notebook) {
202+
return undefined;
203+
}
204+
205+
const cells = notebook.cells.map(cell => serializeCell(cell));
206+
207+
return {
208+
cells: cells,
209+
metadata: notebook.metadata,
210+
transientOptions: notebook.transientOptions
211+
};
212+
}
213+
214+
170215
override dispose() {
171216
// we support closing the interactive window without prompt, so the editor model should not be dirty
172217
this._editorModelReference?.revert({ soft: true });
@@ -184,3 +229,74 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
184229
return this._historyService;
185230
}
186231
}
232+
233+
/**
234+
* Serialization of interactive notebook.
235+
* This is not placed in notebook land as regular notebooks are handled by file service directly.
236+
*/
237+
238+
interface ISerializedOutputItem {
239+
readonly mime: string;
240+
readonly data: number[];
241+
}
242+
243+
interface ISerializedCellOutput {
244+
outputs: ISerializedOutputItem[];
245+
metadata?: Record<string, any>;
246+
outputId: string;
247+
}
248+
249+
export interface ISerializedCell {
250+
source: string;
251+
language: string;
252+
mime: string | undefined;
253+
cellKind: CellKind;
254+
outputs: ISerializedCellOutput[];
255+
metadata?: NotebookCellMetadata;
256+
internalMetadata?: NotebookCellInternalMetadata;
257+
collapseState?: NotebookCellCollapseState;
258+
}
259+
260+
function serializeCell(cell: NotebookCellTextModel): ISerializedCell {
261+
return {
262+
cellKind: cell.cellKind,
263+
language: cell.language,
264+
metadata: cell.metadata,
265+
mime: cell.mime,
266+
outputs: cell.outputs.map(output => serializeCellOutput(output)),
267+
source: cell.getValue()
268+
};
269+
}
270+
271+
function deserializeCell(cell: ISerializedCell): ICellDto2 {
272+
return {
273+
cellKind: cell.cellKind,
274+
source: cell.source,
275+
language: cell.language,
276+
metadata: cell.metadata,
277+
mime: cell.mime,
278+
outputs: cell.outputs.map((output) => deserializeCellOutput(output))
279+
};
280+
}
281+
282+
function serializeCellOutput(output: IOutputDto): ISerializedCellOutput {
283+
return {
284+
outputId: output.outputId,
285+
outputs: output.outputs.map(ot => ({
286+
mime: ot.mime,
287+
data: ot.data.buffer ? Array.from(ot.data.buffer) : []
288+
})),
289+
metadata: output.metadata
290+
};
291+
}
292+
293+
function deserializeCellOutput(output: ISerializedCellOutput): IOutputDto {
294+
return {
295+
outputId: output.outputId,
296+
outputs: output.outputs.map(ot => ({
297+
mime: ot.mime,
298+
data: VSBuffer.fromByteArray(ot.data)
299+
})),
300+
metadata: output.metadata
301+
};
302+
}

src/vs/workbench/contrib/notebook/common/notebookCommon.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -923,8 +923,7 @@ export const NotebookSetting = {
923923
interactiveWindowCollapseCodeCells: 'interactiveWindow.collapseCellInputCode',
924924
outputLineHeight: 'notebook.outputLineHeight',
925925
outputFontSize: 'notebook.outputFontSize',
926-
outputFontFamily: 'notebook.outputFontFamily',
927-
interactiveWindowAlwaysScrollOnNewCell: 'interactiveWindow.alwaysScrollOnNewCell'
926+
outputFontFamily: 'notebook.outputFontFamily'
928927
} as const;
929928

930929
export const enum CellStatusbarAlignment {

0 commit comments

Comments
 (0)