Skip to content

Commit 1a3cc15

Browse files
authored
Merge pull request microsoft#184757 from microsoft/aamunger/fileBackedIW
use scratchpad for IW working copy
2 parents 07faf57 + c5d0233 commit 1a3cc15

File tree

15 files changed

+234
-353
lines changed

15 files changed

+234
-353
lines changed

extensions/ipynb/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"dropMetadata"
1515
],
1616
"activationEvents": [
17-
"onNotebook:jupyter-notebook"
17+
"onNotebook:jupyter-notebook",
18+
"onNotebookSerializer:interactive"
1819
],
1920
"extensionKind": [
2021
"workspace",

extensions/ipynb/src/ipynbMain.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ export function activate(context: vscode.ExtensionContext) {
4343
}
4444
} as vscode.NotebookDocumentContentOptions));
4545

46+
context.subscriptions.push(vscode.workspace.registerNotebookSerializer('interactive', serializer, {
47+
transientOutputs: false,
48+
transientCellMetadata: {
49+
breakpointMargin: true,
50+
custom: false,
51+
attachments: false
52+
},
53+
cellContentMetadata: {
54+
attachments: true
55+
}
56+
} as vscode.NotebookDocumentContentOptions));
57+
4658
vscode.languages.registerCodeLensProvider({ pattern: '**/*.ipynb' }, {
4759
provideCodeLenses: (document) => {
4860
if (

src/vs/base/common/network.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ export namespace Schemas {
6262
export const vscodeNotebookCell = 'vscode-notebook-cell';
6363
export const vscodeNotebookCellMetadata = 'vscode-notebook-cell-metadata';
6464
export const vscodeNotebookCellOutput = 'vscode-notebook-cell-output';
65-
export const vscodeInteractive = 'vscode-interactive';
6665
export const vscodeInteractiveInput = 'vscode-interactive-input';
6766

6867
export const vscodeSettings = 'vscode-settings';

src/vs/workbench/api/browser/mainThreadNotebook.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,14 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
5050
options,
5151
dataToNotebook: async (data: VSBuffer): Promise<NotebookData> => {
5252
const sw = new StopWatch();
53-
const dto = await this._proxy.$dataToNotebook(handle, data, CancellationToken.None);
54-
const result = NotebookDto.fromNotebookDataDto(dto.value);
53+
let result: NotebookData;
54+
if (data.byteLength === 0 && viewType === 'interactive') {
55+
// we don't want any starting cells for an empty interactive window.
56+
result = NotebookDto.fromNotebookDataDto({ cells: [], metadata: {} });
57+
} else {
58+
const dto = await this._proxy.$dataToNotebook(handle, data, CancellationToken.None);
59+
result = NotebookDto.fromNotebookDataDto(dto.value);
60+
}
5561
this._logService.trace(`[NotebookSerializer] dataToNotebook DONE after ${sw.elapsed()}ms`, {
5662
viewType,
5763
extensionId: extension.id.value,

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

Lines changed: 116 additions & 128 deletions
Large diffs are not rendered by default.

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ export class InteractiveEditor extends EditorPane {
425425
}
426426

427427
if (model === null) {
428-
throw new Error('?');
428+
throw new Error('The Interactive Window model could not be resolved');
429429
}
430430

431431
this.#notebookWidget.value?.setParentContextKeyService(this.#contextKeyService);
@@ -457,7 +457,9 @@ export class InteractiveEditor extends EditorPane {
457457
}
458458
}));
459459

460-
const editorModel = await input.resolveInput(this.#notebookWidget.value?.activeKernel?.supportedLanguages[0] ?? PLAINTEXT_LANGUAGE_ID);
460+
const languageId = this.#notebookWidget.value?.activeKernel?.supportedLanguages[0] ?? input.language ?? PLAINTEXT_LANGUAGE_ID;
461+
const editorModel = await input.resolveInput(languageId);
462+
editorModel.setLanguage(languageId);
461463
this.#codeEditorWidget.setModel(editorModel);
462464
if (viewState?.input) {
463465
this.#codeEditorWidget.restoreViewState(viewState.input);
@@ -568,7 +570,6 @@ export class InteractiveEditor extends EditorPane {
568570
}
569571
}
570572

571-
572573
#syncWithKernel() {
573574
const notebook = this.#notebookWidget.value?.textModel;
574575
const textModel = this.#codeEditorWidget.getModel();
@@ -581,8 +582,11 @@ export class InteractiveEditor extends EditorPane {
581582

582583
if (selectedOrSuggested) {
583584
const language = selectedOrSuggested.supportedLanguages[0];
584-
const newMode = language ? this.#languageService.createById(language).languageId : PLAINTEXT_LANGUAGE_ID;
585-
textModel.setLanguage(newMode);
585+
// All kernels will initially list plaintext as the supported language before they properly initialized.
586+
if (language && language !== 'plaintext') {
587+
const newMode = this.#languageService.createById(language).languageId;
588+
textModel.setLanguage(newMode);
589+
}
586590

587591
NOTEBOOK_KERNEL.bindTo(this.#contextKeyService).set(selectedOrSuggested.id);
588592
}

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

Lines changed: 62 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,26 @@
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';
76
import { Event } from 'vs/base/common/event';
87
import { IReference } from 'vs/base/common/lifecycle';
98
import * as paths from 'vs/base/common/path';
10-
import { isEqual } from 'vs/base/common/resources';
9+
import { isEqual, joinPath } from 'vs/base/common/resources';
1110
import { URI } from 'vs/base/common/uri';
11+
import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry';
1212
import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService';
13+
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
1314
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
14-
import { IUntypedEditorInput } from 'vs/workbench/common/editor';
15+
import { EditorInputCapabilities, GroupIdentifier, ISaveOptions, IUntypedEditorInput } from 'vs/workbench/common/editor';
1516
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
1617
import { IInteractiveDocumentService } from 'vs/workbench/contrib/interactive/browser/interactiveDocumentService';
1718
import { IInteractiveHistoryService } from 'vs/workbench/contrib/interactive/browser/interactiveHistoryService';
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';
19+
import { IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
2120
import { ICompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
21+
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
2222

2323
export class InteractiveEditorInput extends EditorInput implements ICompositeNotebookEditorInput {
24-
static create(instantiationService: IInstantiationService, resource: URI, inputResource: URI, title?: string) {
25-
return instantiationService.createInstance(InteractiveEditorInput, resource, inputResource, title);
24+
static create(instantiationService: IInstantiationService, resource: URI, inputResource: URI, title?: string, language?: string) {
25+
return instantiationService.createInstance(InteractiveEditorInput, resource, inputResource, title, language);
2626
}
2727

2828
static readonly ID: string = 'workbench.input.interactive';
@@ -37,6 +37,11 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
3737

3838
private _initTitle?: string;
3939

40+
get language() {
41+
return this._inputModelRef?.object.textEditorModel.getLanguageId() ?? this._initLanguage;
42+
}
43+
private _initLanguage?: string;
44+
4045
private _notebookEditorInput: NotebookEditorInput;
4146
get notebookEditorInput() {
4247
return this._notebookEditorInput;
@@ -74,17 +79,20 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
7479
resource: URI,
7580
inputResource: URI,
7681
title: string | undefined,
82+
languageId: string | undefined,
7783
@IInstantiationService instantiationService: IInstantiationService,
7884
@ITextModelService textModelService: ITextModelService,
79-
8085
@IInteractiveDocumentService interactiveDocumentService: IInteractiveDocumentService,
81-
@IInteractiveHistoryService historyService: IInteractiveHistoryService
86+
@IInteractiveHistoryService historyService: IInteractiveHistoryService,
87+
@INotebookService private readonly _notebookService: INotebookService,
88+
@IFileDialogService private readonly _fileDialogService: IFileDialogService
8289
) {
8390
const input = NotebookEditorInput.create(instantiationService, resource, 'interactive', {});
8491
super();
8592
this._notebookEditorInput = input;
8693
this._register(this._notebookEditorInput);
8794
this._initTitle = title;
95+
this._initLanguage = languageId;
8896
this._resource = resource;
8997
this._inputResource = inputResource;
9098
this._inputResolver = null;
@@ -113,8 +121,10 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
113121
this._register(this.primary.onDidChangeCapabilities(() => this._onDidChangeCapabilities.fire()));
114122
}
115123

116-
override isDirty() {
117-
return false;
124+
override get capabilities(): EditorInputCapabilities {
125+
return EditorInputCapabilities.Untitled
126+
| EditorInputCapabilities.Readonly
127+
| EditorInputCapabilities.Scratchpad;
118128
}
119129

120130
private async _resolveEditorModel() {
@@ -134,30 +144,58 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
134144
return this._inputResolver;
135145
}
136146

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-
});
147+
this._inputResolver = this._resolveEditorModel();
144148

145149
return this._inputResolver;
146150
}
147151

148-
async resolveInput(language: string) {
152+
async resolveInput(language?: string) {
149153
if (this._inputModelRef) {
150154
return this._inputModelRef.object.textEditorModel;
151155
}
152156

153-
this._interactiveDocumentService.willCreateInteractiveDocument(this.resource!, this.inputResource, language);
157+
const resolvedLanguage = language ?? this._initLanguage ?? PLAINTEXT_LANGUAGE_ID;
158+
this._interactiveDocumentService.willCreateInteractiveDocument(this.resource!, this.inputResource, resolvedLanguage);
154159
this._inputModelRef = await this._textModelService.createModelReference(this.inputResource);
155160

156-
if (this._data && this._data.inputData) {
157-
this._inputModelRef.object.textEditorModel.setValue(this._data.inputData.value);
161+
return this._inputModelRef.object.textEditorModel;
162+
}
163+
164+
override async save(group: GroupIdentifier, options?: ISaveOptions): Promise<EditorInput | IUntypedEditorInput | undefined> {
165+
if (this._editorModelReference) {
166+
167+
if (this.hasCapability(EditorInputCapabilities.Untitled)) {
168+
return this.saveAs(group, options);
169+
} else {
170+
await this._editorModelReference.save(options);
171+
}
172+
173+
return this;
158174
}
159175

160-
return this._inputModelRef.object.textEditorModel;
176+
return undefined;
177+
}
178+
179+
override async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise<IUntypedEditorInput | undefined> {
180+
if (!this._editorModelReference) {
181+
return undefined;
182+
}
183+
184+
const provider = this._notebookService.getContributedNotebookType('interactive');
185+
186+
if (!provider) {
187+
return undefined;
188+
}
189+
190+
const filename = this.getName() + '.ipynb';
191+
const pathCandidate = joinPath(await this._fileDialogService.defaultFilePath(), filename);
192+
193+
const target = await this._fileDialogService.pickFileToSave(pathCandidate, options?.availableFileSystems);
194+
if (!target) {
195+
return undefined; // save cancelled
196+
}
197+
198+
return await this._editorModelReference.saveAs(target);
161199
}
162200

163201
override matches(otherInput: EditorInput | IUntypedEditorInput): boolean {
@@ -181,37 +219,6 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
181219
return basename.substr(0, basename.length - paths.extname(p).length);
182220
}
183221

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-
215222
override dispose() {
216223
// we support closing the interactive window without prompt, so the editor model should not be dirty
217224
this._editorModelReference?.revert({ soft: true });
@@ -229,74 +236,3 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
229236
return this._historyService;
230237
}
231238
}
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-
}

0 commit comments

Comments
 (0)