diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 427e98ba553..419e9d8a30f 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -66,6 +66,7 @@ import './mainThreadNotebook'; import './mainThreadNotebookKernels'; import './mainThreadNotebookDocumentsAndEditors'; import './mainThreadNotebookRenderers'; +import './mainThreadInteractive'; import './mainThreadTask'; import './mainThreadLabelService'; import './mainThreadTunnelService'; diff --git a/src/vs/workbench/api/browser/mainThreadInteractive.ts b/src/vs/workbench/api/browser/mainThreadInteractive.ts new file mode 100644 index 00000000000..51d2a90a410 --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadInteractive.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ExtHostContext, ExtHostInteractiveShape, IExtHostContext, MainContext, MainThreadInteractiveShape } from 'vs/workbench/api/common/extHost.protocol'; +import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { IInteractiveDocumentService } from 'vs/workbench/contrib/interactive/browser/interactiveDocumentService'; + +@extHostNamedCustomer(MainContext.MainThreadInteractive) +export class MainThreadInteractive implements MainThreadInteractiveShape { + private readonly _proxy: ExtHostInteractiveShape; + + private readonly _disposables = new DisposableStore(); + + constructor( + extHostContext: IExtHostContext, + @IInteractiveDocumentService interactiveDocumentService: IInteractiveDocumentService + ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostInteractive); + + this._disposables.add(interactiveDocumentService.onWillAddInteractiveDocument((e) => { + this._proxy.$acceptInputDocument(e.inputUri, '\n', 'plaintext', e.notebookUri); + })); + } + + dispose(): void { + this._disposables.dispose(); + + } +} diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 4c2b5d1f832..3f24a552f3b 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -90,6 +90,7 @@ import { Schemas } from 'vs/base/common/network'; import { matchesScheme } from 'vs/platform/opener/common/opener'; import { ExtHostNotebookEditors } from 'vs/workbench/api/common/extHostNotebookEditors'; import { ExtHostNotebookDocuments } from 'vs/workbench/api/common/extHostNotebookDocuments'; +import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -173,6 +174,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostWebviewViews = rpcProtocol.set(ExtHostContext.ExtHostWebviewViews, new ExtHostWebviewViews(rpcProtocol, extHostWebviews)); const extHostTesting = rpcProtocol.set(ExtHostContext.ExtHostTesting, new ExtHostTesting(rpcProtocol, extHostCommands)); const extHostUriOpeners = rpcProtocol.set(ExtHostContext.ExtHostUriOpeners, new ExtHostUriOpeners(rpcProtocol)); + rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors)); // Check that no named customers are missing const expected: ProxyIdentifier[] = values(ExtHostContext); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 821e3d9aa90..7d6886457e1 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -44,6 +44,7 @@ import { ThemeColor, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IExtensionIdWithVersion } from 'vs/platform/userDataSync/common/extensionsStorageSync'; import { WorkspaceTrustRequestOptions } from 'vs/platform/workspace/common/workspaceTrust'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; +import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive'; import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { DebugConfigurationProviderTriggerKind, TestResultState } from 'vs/workbench/api/common/extHostTypes'; import * as tasks from 'vs/workbench/api/common/shared/tasks'; @@ -929,6 +930,9 @@ export interface MainThreadNotebookRenderersShape extends IDisposable { $postMessage(editorId: string, rendererId: string, message: unknown): void; } +export interface MainThreadInteractiveShape extends IDisposable { +} + export interface MainThreadUrlsShape extends IDisposable { $registerUriHandler(handle: number, extensionId: ExtensionIdentifier): Promise; $unregisterUriHandler(handle: number): Promise; @@ -2033,6 +2037,10 @@ export interface ExtHostNotebookKernelsShape { $acceptKernelMessageFromRenderer(handle: number, editorId: string, message: any): void; } +export interface ExtHostInteractiveShape { + $acceptInputDocument(uri: UriComponents, eol: string, modeId: string, notebookUri: UriComponents): void; +} + export interface ExtHostStorageShape { $acceptValue(shared: boolean, key: string, value: object | undefined): void; } @@ -2173,6 +2181,7 @@ export const MainContext = { MainThreadNotebookEditors: createMainId('MainThreadNotebookEditorsShape'), MainThreadNotebookKernels: createMainId('MainThreadNotebookKernels'), MainThreadNotebookRenderers: createMainId('MainThreadNotebookRenderers'), + MainThreadInteractive: createMainId('MainThreadInteractive'), MainThreadTheming: createMainId('MainThreadTheming'), MainThreadTunnelService: createMainId('MainThreadTunnelService'), MainThreadTimeline: createMainId('MainThreadTimeline'), @@ -2223,6 +2232,7 @@ export const ExtHostContext = { ExtHostNotebookEditors: createMainId('ExtHostNotebookEditors'), ExtHostNotebookKernels: createMainId('ExtHostNotebookKernels'), ExtHostNotebookRenderers: createMainId('ExtHostNotebookRenderers'), + ExtHostInteractive: createMainId('ExtHostInteractive'), ExtHostTheming: createMainId('ExtHostTheming'), ExtHostTunnelService: createMainId('ExtHostTunnelService'), ExtHostAuthentication: createMainId('ExtHostAuthentication'), diff --git a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts index bd80952ee53..bc5c7b3d19e 100644 --- a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts +++ b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts @@ -92,7 +92,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha // double check -> only notebook cell documents should be // referenced/opened more than once... if (ref) { - if (resource.scheme !== Schemas.vscodeNotebookCell) { + if (resource.scheme !== Schemas.vscodeNotebookCell && resource.scheme !== Schemas.vscodeInteractiveInput) { throw new Error(`document '${resource} already exists!'`); } } diff --git a/src/vs/workbench/api/common/extHostInteractive.ts b/src/vs/workbench/api/common/extHostInteractive.ts new file mode 100644 index 00000000000..0cc338e927c --- /dev/null +++ b/src/vs/workbench/api/common/extHostInteractive.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI, UriComponents } from 'vs/base/common/uri'; +import { ExtHostInteractiveShape, IMainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; + +export class ExtHostInteractive implements ExtHostInteractiveShape { + constructor( + mainContext: IMainContext, + private _extHostNotebooks: ExtHostNotebookController, + private _textDocumentsAndEditors: ExtHostDocumentsAndEditors, + ) { + } + + $acceptInputDocument(uri: UriComponents, eol: string, modeId: string, notebookUri: UriComponents) { + this._textDocumentsAndEditors.acceptDocumentsAndEditorsDelta({ + addedDocuments: [{ + EOL: eol, + lines: [''], + modeId: modeId, + uri: uri, + isDirty: false, + versionId: 1, + notebook: this._extHostNotebooks.getNotebookDocument(URI.revive(notebookUri))?.apiNotebook + }] + }); + } +} diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index a4849f0b95a..849158e2ebf 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -39,6 +39,7 @@ import { IInteractiveHistoryService, InteractiveHistoryService } from 'vs/workbe import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { IInteractiveDocumentService, InteractiveDocumentService } from 'vs/workbench/contrib/interactive/browser/interactiveDocumentService'; Registry.as(EditorExtensions.Editors).registerEditor( @@ -205,6 +206,7 @@ Registry.as(EditorExtensions.EditorInputFactories). ); registerSingleton(IInteractiveHistoryService, InteractiveHistoryService); +registerSingleton(IInteractiveDocumentService, InteractiveDocumentService); registerAction2(class extends Action2 { constructor() { diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveDocumentService.ts b/src/vs/workbench/contrib/interactive/browser/interactiveDocumentService.ts new file mode 100644 index 00000000000..e96c8734bba --- /dev/null +++ b/src/vs/workbench/contrib/interactive/browser/interactiveDocumentService.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * 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 } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IInteractiveDocumentService = createDecorator('IInteractiveDocumentService'); + +export interface IInteractiveDocumentService { + readonly _serviceBrand: undefined; + onWillAddInteractiveDocument: Event<{ notebookUri: URI; inputUri: URI; languageId: string; }>; + willCreateInteractiveDocument(notebookUri: URI, inputUri: URI, languageId: string): void; +} + +export class InteractiveDocumentService extends Disposable { + declare readonly _serviceBrand: undefined; + private readonly _onWillAddInteractiveDocument = this._register(new Emitter<{ notebookUri: URI; inputUri: URI; languageId: string; }>()); + onWillAddInteractiveDocument = this._onWillAddInteractiveDocument.event; + + constructor() { + super(); + } + + willCreateInteractiveDocument(notebookUri: URI, inputUri: URI, languageId: string) { + this._onWillAddInteractiveDocument.fire({ + notebookUri, + inputUri, + languageId + }); + } + +} diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 8497bd08bb0..2a7e52a96ee 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -35,6 +35,7 @@ import { MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon'; import { IInteractiveHistoryService } from 'vs/workbench/contrib/interactive/browser/interactiveHistoryService'; +import { IInteractiveDocumentService } from 'vs/workbench/contrib/interactive/browser/interactiveDocumentService'; const DECORATION_KEY = 'interactiveInputDecoration'; @@ -56,6 +57,7 @@ export class InteractiveEditor extends EditorPane { #notebookKernelService: INotebookKernelService; #keybindingService: IKeybindingService; #historyService: IInteractiveHistoryService; + #interactiveDocumentService: IInteractiveDocumentService; #widgetDisposableStore: DisposableStore = this._register(new DisposableStore()); #dimension?: DOM.Dimension; @@ -71,7 +73,8 @@ export class InteractiveEditor extends EditorPane { @INotebookKernelService notebookKernelService: INotebookKernelService, @IModeService modeService: IModeService, @IKeybindingService keybindingService: IKeybindingService, - @IInteractiveHistoryService historyService: IInteractiveHistoryService + @IInteractiveHistoryService historyService: IInteractiveHistoryService, + @IInteractiveDocumentService interactiveDocumentService: IInteractiveDocumentService ) { super( InteractiveEditor.ID, @@ -87,6 +90,7 @@ export class InteractiveEditor extends EditorPane { this.#modeService = modeService; this.#keybindingService = keybindingService; this.#historyService = historyService; + this.#interactiveDocumentService = interactiveDocumentService; codeEditorService.registerDecorationType('interactive-decoration', DECORATION_KEY, {}); } @@ -159,7 +163,13 @@ export class InteractiveEditor extends EditorPane { isReadOnly: true }); - const editorModel = this.#modelService.getModel(input.inputResource) || this.#modelService.createModel('', null, input.inputResource, false); + let editorModel = this.#modelService.getModel(input.inputResource); + + if (!editorModel) { + this.#interactiveDocumentService.willCreateInteractiveDocument(input.resource!, input.inputResource, this.#notebookWidget.value?.activeKernel?.supportedLanguages[0] ?? 'plaintext'); + editorModel = this.#modelService.createModel('', null, input.inputResource, false); + } + this.#widgetDisposableStore.add(editorModel); this.#codeEditorWidget.setModel(editorModel); this.#widgetDisposableStore.add(this.#codeEditorWidget.onDidContentSizeChange(e => { @@ -220,7 +230,7 @@ export class InteractiveEditor extends EditorPane { this.#widgetDisposableStore.add(editorModel.onDidChangeContent(() => { if (this.input?.resource) { - this.#historyService.replaceLast(this.input.resource, editorModel.getValue()); + this.#historyService.replaceLast(this.input.resource, editorModel!.getValue()); } }));