diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index f3f03f7ebd5..205639642b7 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { VSBuffer } from 'vs/base/common/buffer'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { Iterable } from 'vs/base/common/iterator'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; @@ -31,6 +30,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'v import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { EditorActivation, IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -51,14 +51,15 @@ import { IInteractiveDocumentService, InteractiveDocumentService } from 'vs/work import { InteractiveEditor } from 'vs/workbench/contrib/interactive/browser/interactiveEditor'; import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser/interactiveEditorInput'; import { IInteractiveHistoryService, InteractiveHistoryService } from 'vs/workbench/contrib/interactive/browser/interactiveHistoryService'; +import { InteractiveWindowFileSystem } from 'vs/workbench/contrib/interactive/browser/interactiveWindowFileSystem'; import { NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; -import { CellEditType, CellKind, CellUri, ICellOutput, INTERACTIVE_WINDOW_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, CellUri, ICellOutput, INTERACTIVE_WINDOW_EDITOR_ID, NotebookData } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; -import { INotebookContentProvider, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { INotebookSerializer, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; @@ -83,6 +84,8 @@ export class InteractiveDocumentContribution extends Disposable implements IWork @INotebookService notebookService: INotebookService, @IEditorResolverService editorResolverService: IEditorResolverService, @IEditorService editorService: IEditorService, + @IFileService fileService: IFileService, + @ILogService logService: ILogService ) { super(); @@ -93,87 +96,73 @@ export class InteractiveDocumentContribution extends Disposable implements IWork cellContentMetadata: {} }; - const controller: INotebookContentProvider = { - get options() { - return contentOptions; - }, - set options(newOptions) { - contentOptions.transientCellMetadata = newOptions.transientCellMetadata; - contentOptions.transientDocumentMetadata = newOptions.transientDocumentMetadata; - contentOptions.transientOutputs = newOptions.transientOutputs; - }, - open: async (_uri: URI, _backupId: string | VSBuffer | undefined, _untitledDocumentData: VSBuffer | undefined, _token: CancellationToken) => { - if (_backupId instanceof VSBuffer) { - const backup = _backupId.toString(); + const interactiveWindowFS = new InteractiveWindowFileSystem(); + this._register(fileService.registerProvider(Schemas.vscodeInteractive, interactiveWindowFS)); + + const serializer: INotebookSerializer = { + options: contentOptions, + dataToNotebook: async (data: VSBuffer): Promise => { + if (data.byteLength > 0) { try { - const document = JSON.parse(backup) as { cells: { kind: CellKind; language: string; metadata: any; mime: string | undefined; content: string; outputs?: ICellOutput[] }[] }; + const document = JSON.parse(data.toString()) as { cells: { kind: CellKind; language: string; metadata: any; mime: string | undefined; content: string; outputs?: ICellOutput[] }[] }; return { - data: { - metadata: {}, - cells: document.cells.map(cell => ({ - source: cell.content, - language: cell.language, - cellKind: cell.kind, - mime: cell.mime, - outputs: cell.outputs - ? cell.outputs.map(output => ({ - outputId: output.outputId, - outputs: output.outputs.map(ot => ({ - mime: ot.mime, - data: ot.data - })) + cells: document.cells.map(cell => ({ + source: cell.content, + language: cell.language, + cellKind: cell.kind, + mime: cell.mime, + outputs: cell.outputs + ? cell.outputs.map(output => ({ + outputId: output.outputId, + outputs: output.outputs.map(ot => ({ + mime: ot.mime, + data: ot.data })) - : [], - metadata: cell.metadata - })) - }, - transientOptions: contentOptions + })) + : [], + metadata: cell.metadata + })), + metadata: {} }; - } catch (_e) { } + } catch (e) { + logService.error(`error when converting data to notebook data object: ${e}`); + } } return { - data: { - metadata: {}, - cells: [] - }, - transientOptions: contentOptions + metadata: {}, + cells: [] }; + }, - backup: async (uri: URI, token: CancellationToken) => { - const doc = notebookService.listNotebookDocuments().find(document => document.uri.toString() === uri.toString()); - if (doc) { - const cells = doc.cells.map(cell => ({ - kind: cell.cellKind, - language: cell.language, - metadata: cell.metadata, - mine: cell.mime, - outputs: cell.outputs.map(output => { - return { - outputId: output.outputId, - outputs: output.outputs.map(ot => ({ - mime: ot.mime, - data: ot.data - })) - }; - }), - content: cell.getValue() - })); + notebookToData: async (data: NotebookData): Promise => { + const cells = data.cells.map(cell => ({ + kind: cell.cellKind, + language: cell.language, + metadata: cell.metadata, + mine: cell.mime, + outputs: cell.outputs.map(output => { + return { + outputId: output.outputId, + outputs: output.outputs.map(ot => ({ + mime: ot.mime, + data: ot.data + })) + }; + }), + content: cell.source + })); - const buffer = VSBuffer.fromString(JSON.stringify({ - cells: cells - })); - - return buffer; - } else { - return ''; - } + return VSBuffer.fromString(JSON.stringify({ + cells: cells + })); } }; - this._register(notebookService.registerNotebookController('interactive', { + + this._register(notebookService.registerNotebookSerializer('interactive', { id: new ExtensionIdentifier('interactive.builtin'), location: undefined - }, controller)); + }, serializer)); const info = notebookService.getContributedNotebookType('interactive'); @@ -412,7 +401,7 @@ registerAction2(class extends Action2 { let inputUri: URI | undefined = undefined; let counter = 1; do { - notebookUri = URI.from({ scheme: Schemas.vscodeInteractive, path: `Interactive-${counter}.interactive` }); + notebookUri = URI.from({ scheme: Schemas.vscodeInteractive, path: `/Interactive-${counter}.interactive` }); inputUri = URI.from({ scheme: Schemas.vscodeInteractiveInput, path: `/InteractiveInput-${counter}` }); counter++; diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 9be9ba01eed..05c72cf677f 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -34,7 +34,6 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { InteractiveWindowSetting, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon'; -import { ComplexNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; @@ -416,9 +415,6 @@ export class InteractiveEditor extends EditorPane { this.#scrollIfNecessary(cvm); })); this.#widgetDisposableStore.add(this.#notebookWidget.value!.onDidFocusWidget(() => this.#onDidFocusWidget.fire())); - this.#widgetDisposableStore.add(model.notebook.onDidChangeContent(() => { - (model as ComplexNotebookEditorModel).setDirty(false); - })); this.#widgetDisposableStore.add(this.#notebookOptions.onDidChangeOptions(e => { if (e.compactView || e.focusIndicator) { // update the styling diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveWindowFileSystem.ts b/src/vs/workbench/contrib/interactive/browser/interactiveWindowFileSystem.ts new file mode 100644 index 00000000000..992a3a6ea88 --- /dev/null +++ b/src/vs/workbench/contrib/interactive/browser/interactiveWindowFileSystem.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { FileSystemProviderCapabilities, FileType, IFileChange, IFileDeleteOptions, IFileOverwriteOptions, IFileSystemProvider, IStat, IWatchOptions } from 'vs/platform/files/common/files'; + +export class InteractiveWindowFileSystem implements IFileSystemProvider { + + capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite; + + constructor(private disposableFactory: () => IDisposable = () => Disposable.None) { } + + async readFile(_resource: URI): Promise { + return new Uint8Array(); + } + + async stat(_resource: URI): Promise { + return { + type: FileType.File, + ctime: 0, + mtime: 0, + size: 0, + }; + } + async mkdir(_resource: URI): Promise { } + + async readdir(_resource: URI): Promise<[string, FileType][]> { + return [['', FileType.Unknown]]; + } + + async delete(_resource: URI, _opts: IFileDeleteOptions): Promise { } + + async rename(_from: URI, _to: URI, _opts: IFileOverwriteOptions): Promise { } + + private readonly _onDidChangeCapabilities = new Emitter(); + readonly onDidChangeCapabilities: Event = this._onDidChangeCapabilities.event; + + private readonly _onDidChangeFile = new Emitter(); + readonly onDidChangeFile: Event = this._onDidChangeFile.event; + + onDidWatchError?: Event | undefined; + + emitFileChangeEvents(changes: IFileChange[]): void { + this._onDidChangeFile.fire(changes); + } + + setCapabilities(capabilities: FileSystemProviderCapabilities): void { + this.capabilities = capabilities; + + this._onDidChangeCapabilities.fire(); + } + + watch(_resource: URI, _opts: IWatchOptions): IDisposable { return this.disposableFactory(); } +}