diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index ab7e3a18dc56b..339f94e8b37ac 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -60,7 +60,6 @@ export namespace Schemas { export const vscodeNotebookCell = 'vscode-notebook-cell'; export const vscodeNotebookCellMetadata = 'vscode-notebook-cell-metadata'; export const vscodeNotebookCellOutput = 'vscode-notebook-cell-output'; - export const vscodeInteractive = 'vscode-interactive'; export const vscodeInteractiveInput = 'vscode-interactive-input'; export const vscodeSettings = 'vscode-settings'; diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index d09a99cc19e24..f214a38eb45c6 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -51,7 +51,6 @@ 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'; @@ -96,9 +95,6 @@ export class InteractiveDocumentContribution extends Disposable implements IWork cellContentMetadata: {} }; - const interactiveWindowFS = new InteractiveWindowFileSystem(); - this._register(fileService.registerProvider(Schemas.vscodeInteractive, interactiveWindowFS)); - const serializer: INotebookSerializer = { options: contentOptions, dataToNotebook: async (data: VSBuffer): Promise => { @@ -205,7 +201,9 @@ export class InteractiveDocumentContribution extends Disposable implements IWork priority: RegisteredEditorPriority.exclusive }, { - canSupportResource: uri => uri.scheme === Schemas.vscodeInteractive || (uri.scheme === Schemas.vscodeNotebookCell && extname(uri) === '.interactive'), + canSupportResource: uri => + (uri.scheme === Schemas.untitled && extname(uri) === '.interactive') || + (uri.scheme === Schemas.vscodeNotebookCell && extname(uri) === '.interactive'), singlePerResource: true }, { @@ -371,7 +369,7 @@ registerAction2(class extends Action2 { preserveFocus: typeof showOptions !== 'number' ? (showOptions?.preserveFocus ?? false) : false }; - if (resource && resource.scheme === Schemas.vscodeInteractive) { + if (resource && extname(resource) === '.interactive') { logService.debug('Open interactive window from resource:', resource.toString()); const resourceUri = URI.revive(resource); const editors = editorService.findEditors(resourceUri).filter(id => id.editor instanceof InteractiveEditorInput && id.editor.resource?.toString() === resourceUri.toString()); @@ -401,7 +399,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.untitled, path: `/Interactive-${counter}.interactive` }); inputUri = URI.from({ scheme: Schemas.vscodeInteractiveInput, path: `/InteractiveInput-${counter}` }); counter++; @@ -435,7 +433,7 @@ registerAction2(class extends Action2 { category: interactiveWindowCategory, keybinding: { // when: NOTEBOOK_CELL_LIST_FOCUSED, - when: ContextKeyExpr.equals('resourceScheme', Schemas.vscodeInteractive), + when: ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'), primary: KeyMod.WinCtrl | KeyCode.Enter, win: { primary: KeyMod.CtrlCmd | KeyCode.Enter @@ -469,15 +467,14 @@ registerAction2(class extends Action2 { const notebookEditorService = accessor.get(INotebookEditorService); let editorControl: { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; if (context) { - if (context.scheme === Schemas.vscodeInteractive) { - const resourceUri = URI.revive(context); - const editors = editorService.findEditors(resourceUri).filter(id => id.editor instanceof InteractiveEditorInput && id.editor.resource?.toString() === resourceUri.toString()); - if (editors.length) { - const editorInput = editors[0].editor as InteractiveEditorInput; - const currentGroup = editors[0].groupId; - const editor = await editorService.openEditor(editorInput, currentGroup); - editorControl = editor?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; - } + const resourceUri = URI.revive(context); + const editors = editorService.findEditors(resourceUri) + .filter(id => id.editor instanceof InteractiveEditorInput && id.editor.resource?.toString() === resourceUri.toString()); + if (editors.length) { + const editorInput = editors[0].editor as InteractiveEditorInput; + const currentGroup = editors[0].groupId; + const editor = await editorService.openEditor(editorInput, currentGroup); + editorControl = editor?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined; } } else { @@ -578,7 +575,7 @@ registerAction2(class extends Action2 { f1: false, keybinding: { when: ContextKeyExpr.and( - ContextKeyExpr.equals('resourceScheme', Schemas.vscodeInteractive), + ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'), INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('bottom'), INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('none'), SuggestContext.Visible.toNegated() @@ -617,7 +614,7 @@ registerAction2(class extends Action2 { f1: false, keybinding: { when: ContextKeyExpr.and( - ContextKeyExpr.equals('resourceScheme', Schemas.vscodeInteractive), + ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'), INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('top'), INTERACTIVE_INPUT_CURSOR_BOUNDARY.notEqualsTo('none'), SuggestContext.Visible.toNegated() @@ -654,7 +651,7 @@ registerAction2(class extends Action2 { id: 'interactive.scrollToTop', title: localize('interactiveScrollToTop', 'Scroll to Top'), keybinding: { - when: ContextKeyExpr.equals('resourceScheme', Schemas.vscodeInteractive), + when: ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'), primary: KeyMod.CtrlCmd | KeyCode.Home, mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow }, weight: KeybindingWeight.WorkbenchContrib @@ -683,7 +680,7 @@ registerAction2(class extends Action2 { id: 'interactive.scrollToBottom', title: localize('interactiveScrollToBottom', 'Scroll to Bottom'), keybinding: { - when: ContextKeyExpr.equals('resourceScheme', Schemas.vscodeInteractive), + when: ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'), primary: KeyMod.CtrlCmd | KeyCode.End, mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow }, weight: KeybindingWeight.WorkbenchContrib @@ -750,9 +747,9 @@ registerAction2(class extends Action2 { category: interactiveWindowCategory, menu: { id: MenuId.CommandPalette, - when: ContextKeyExpr.equals('resourceScheme', Schemas.vscodeInteractive), + when: ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'), }, - precondition: ContextKeyExpr.equals('resourceScheme', Schemas.vscodeInteractive), + precondition: ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive'), }); } diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts index e5f2643fdebee..d3ae18ee63d9a 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts @@ -11,7 +11,7 @@ import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IUntypedEditorInput } from 'vs/workbench/common/editor'; +import { EditorInputCapabilities, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IInteractiveDocumentService } from 'vs/workbench/contrib/interactive/browser/interactiveDocumentService'; import { IInteractiveHistoryService } from 'vs/workbench/contrib/interactive/browser/interactiveHistoryService'; @@ -113,8 +113,10 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot this._register(this.primary.onDidChangeCapabilities(() => this._onDidChangeCapabilities.fire())); } - override isDirty() { - return false; + override get capabilities(): EditorInputCapabilities { + return EditorInputCapabilities.Untitled + | EditorInputCapabilities.Readonly + | EditorInputCapabilities.Scratchpad; } private async _resolveEditorModel() { diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveWindowFileSystem.ts b/src/vs/workbench/contrib/interactive/browser/interactiveWindowFileSystem.ts deleted file mode 100644 index e5887e0e02bae..0000000000000 --- a/src/vs/workbench/contrib/interactive/browser/interactiveWindowFileSystem.ts +++ /dev/null @@ -1,60 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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, IFileWriteOptions, 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 writeFile(_resource: URI, _content: Uint8Array, _opts: IFileWriteOptions): Promise { } - - 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(); } -} diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index 64084f3f83827..a573aeae203ae 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -5,7 +5,6 @@ import { Iterable } from 'vs/base/common/iterator'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -598,7 +597,7 @@ registerAction2(class InterruptNotebook extends CancelNotebook { when: ContextKeyExpr.and( NOTEBOOK_HAS_SOMETHING_RUNNING, NOTEBOOK_INTERRUPTIBLE_KERNEL, - ContextKeyExpr.equals('resourceScheme', Schemas.vscodeInteractive) + ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive') ), group: 'navigation/execute' } @@ -641,7 +640,7 @@ registerAction2(class RevealRunningCellAction extends NotebookAction { id: MenuId.InteractiveToolbar, when: ContextKeyExpr.and( NOTEBOOK_HAS_RUNNING_CELL, - ContextKeyExpr.equals('resourceScheme', Schemas.vscodeInteractive) + ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive') ), group: 'navigation', order: 10 diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 07b03a206436a..ed15677163a6e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -6,7 +6,7 @@ import { Schemas } from 'vs/base/common/network'; import { IDisposable, Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { parse } from 'vs/base/common/marshalling'; -import { isEqual } from 'vs/base/common/resources'; +import { extname, isEqual } from 'vs/base/common/resources'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { toFormattedString } from 'vs/base/common/jsonFormatter'; @@ -580,7 +580,7 @@ class NotebookEditorManager implements IWorkbenchContribution { private _openMissingDirtyNotebookEditors(models: IResolvedNotebookEditorModel[]): void { const result: IResourceEditorInput[] = []; for (const model of models) { - if (model.isDirty() && !this._editorService.isOpened({ resource: model.resource, typeId: NotebookEditorInput.ID, editorId: model.viewType }) && model.resource.scheme !== Schemas.vscodeInteractive) { + if (model.isDirty() && !this._editorService.isOpened({ resource: model.resource, typeId: NotebookEditorInput.ID, editorId: model.viewType }) && extname(model.resource) !== '.interactive') { result.push({ resource: model.resource, options: { inactive: true, preserveFocus: true, pinned: true, override: model.viewType } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 22786c7704a61..05c0ec041a0f4 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -13,7 +13,7 @@ import { getExtensionForMimeType } from 'vs/base/common/mime'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { equals } from 'vs/base/common/objects'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; -import { dirname, isEqual, joinPath } from 'vs/base/common/resources'; +import { dirname, extname, isEqual, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; import { TokenizationRegistry } from 'vs/editor/common/languages'; @@ -513,7 +513,7 @@ export class BackLayerWebView extends Themable { } private getNotebookBaseUri() { - if (this.documentUri.scheme === Schemas.untitled || this.documentUri.scheme === Schemas.vscodeInteractive) { + if (this.documentUri.scheme === Schemas.untitled) { const folder = this.workspaceContextService.getWorkspaceFolder(this.documentUri); if (folder) { return folder.uri; @@ -1031,7 +1031,7 @@ export class BackLayerWebView extends Themable { return; } - const defaultDir = this.documentUri.scheme === Schemas.vscodeInteractive ? + const defaultDir = extname(this.documentUri) === '.interactive' ? this.workspaceContextService.getWorkspace().folders[0]?.uri ?? await this.fileDialogService.defaultFilePath() : dirname(this.documentUri); let defaultName: string; diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 49e39bdc34c49..80d9281b68453 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -10,6 +10,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { filter } from 'vs/base/common/objects'; +import { extname } from 'vs/base/common/resources'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { IRevertOptions, ISaveOptions, IUntypedEditorInput } from 'vs/workbench/common/editor'; @@ -40,6 +41,7 @@ export class SimpleNotebookEditorModel extends EditorModel implements INotebookE private _workingCopy?: IStoredFileWorkingCopy | IUntitledFileWorkingCopy; private readonly _workingCopyListeners = this._register(new DisposableStore()); + private readonly scratchPad: boolean; constructor( readonly resource: URI, @@ -51,16 +53,7 @@ export class SimpleNotebookEditorModel extends EditorModel implements INotebookE ) { super(); - if (this.viewType === 'interactive') { - lifecycleService.onBeforeShutdown(async e => e.veto(this.onBeforeShutdown(), 'veto.InteractiveWindow')); - } - } - - private async onBeforeShutdown() { - if (this._workingCopy?.isDirty()) { - await this._workingCopy.save(); - } - return false; + this.scratchPad = viewType === 'interactive'; } override dispose(): void { @@ -126,7 +119,7 @@ export class SimpleNotebookEditorModel extends EditorModel implements INotebookE if (this._hasAssociatedFilePath) { this._workingCopy = await this._workingCopyManager.resolve({ associatedResource: this.resource }); } else { - this._workingCopy = await this._workingCopyManager.resolve({ untitledResource: this.resource }); + this._workingCopy = await this._workingCopyManager.resolve({ untitledResource: this.resource, isScratchpad: this.scratchPad }); } } else { this._workingCopy = await this._workingCopyManager.resolve(this.resource, options?.forceReadFromFile ? { reload: { async: false, force: true } } : undefined); @@ -291,7 +284,7 @@ export class NotebookFileWorkingCopyModelFactory implements IStoredFileWorkingCo metadata: {}, cells: [] }; - if (resource.scheme !== Schemas.vscodeInteractive) { + if (extname(resource) !== '.interactive') { const bytes = await streamToBuffer(stream); data = await info.serializer.dataToNotebook(bytes); }