Path: blob/main/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Event } from '../../../../base/common/event.js';6import { IReference } from '../../../../base/common/lifecycle.js';7import * as paths from '../../../../base/common/path.js';8import { isEqual, joinPath } from '../../../../base/common/resources.js';9import { URI } from '../../../../base/common/uri.js';10import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js';11import { IResolvedTextEditorModel, ITextModelService } from '../../../../editor/common/services/resolverService.js';12import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';13import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js';14import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';15import { EditorInputCapabilities, GroupIdentifier, IRevertOptions, ISaveOptions, IUntypedEditorInput } from '../../../common/editor.js';16import { EditorInput } from '../../../common/editor/editorInput.js';17import { IInteractiveDocumentService } from './interactiveDocumentService.js';18import { IInteractiveHistoryService } from './interactiveHistoryService.js';19import { IResolvedNotebookEditorModel, NotebookSetting } from '../../notebook/common/notebookCommon.js';20import { ICompositeNotebookEditorInput, NotebookEditorInput } from '../../notebook/common/notebookEditorInput.js';21import { INotebookService } from '../../notebook/common/notebookService.js';2223export class InteractiveEditorInput extends EditorInput implements ICompositeNotebookEditorInput {24static create(instantiationService: IInstantiationService, resource: URI, inputResource: URI, title?: string, language?: string) {25return instantiationService.createInstance(InteractiveEditorInput, resource, inputResource, title, language);26}2728private static windowNames: Record<string, string> = {};2930static setName(notebookUri: URI, title: string | undefined) {31if (title) {32this.windowNames[notebookUri.path] = title;33}34}3536static readonly ID: string = 'workbench.input.interactive';3738public override get editorId(): string {39return 'interactive';40}4142override get typeId(): string {43return InteractiveEditorInput.ID;44}4546private name: string;47private readonly isScratchpad: boolean;4849get language() {50return this._inputModelRef?.object.textEditorModel.getLanguageId() ?? this._initLanguage;51}52private _initLanguage?: string;5354private _notebookEditorInput: NotebookEditorInput;55get notebookEditorInput() {56return this._notebookEditorInput;57}5859get editorInputs() {60return [this._notebookEditorInput];61}6263private _resource: URI;6465override get resource(): URI {66return this._resource;67}6869private _inputResource: URI;7071get inputResource() {72return this._inputResource;73}74private _inputResolver: Promise<IResolvedNotebookEditorModel | null> | null;75private _editorModelReference: IResolvedNotebookEditorModel | null;7677private _inputModelRef: IReference<IResolvedTextEditorModel> | null;7879get primary(): EditorInput {80return this._notebookEditorInput;81}82private _textModelService: ITextModelService;83private _interactiveDocumentService: IInteractiveDocumentService;84private _historyService: IInteractiveHistoryService;858687constructor(88resource: URI,89inputResource: URI,90title: string | undefined,91languageId: string | undefined,92@IInstantiationService instantiationService: IInstantiationService,93@ITextModelService textModelService: ITextModelService,94@IInteractiveDocumentService interactiveDocumentService: IInteractiveDocumentService,95@IInteractiveHistoryService historyService: IInteractiveHistoryService,96@INotebookService private readonly _notebookService: INotebookService,97@IFileDialogService private readonly _fileDialogService: IFileDialogService,98@IConfigurationService configurationService: IConfigurationService99) {100const input = NotebookEditorInput.getOrCreate(instantiationService, resource, undefined, 'interactive', {});101super();102this.isScratchpad = configurationService.getValue<boolean>(NotebookSetting.InteractiveWindowPromptToSave) !== true;103this._notebookEditorInput = input;104this._register(this._notebookEditorInput);105this.name = title ?? InteractiveEditorInput.windowNames[resource.path] ?? paths.basename(resource.path, paths.extname(resource.path));106this._initLanguage = languageId;107this._resource = resource;108this._inputResource = inputResource;109this._inputResolver = null;110this._editorModelReference = null;111this._inputModelRef = null;112this._textModelService = textModelService;113this._interactiveDocumentService = interactiveDocumentService;114this._historyService = historyService;115116this._registerListeners();117}118119private _registerListeners(): void {120const oncePrimaryDisposed = Event.once(this.primary.onWillDispose);121this._register(oncePrimaryDisposed(() => {122if (!this.isDisposed()) {123this.dispose();124}125}));126127// Re-emit some events from the primary side to the outside128this._register(this.primary.onDidChangeDirty(() => this._onDidChangeDirty.fire()));129this._register(this.primary.onDidChangeLabel(() => this._onDidChangeLabel.fire()));130131// Re-emit some events from both sides to the outside132this._register(this.primary.onDidChangeCapabilities(() => this._onDidChangeCapabilities.fire()));133}134135override get capabilities(): EditorInputCapabilities {136const scratchPad = this.isScratchpad ? EditorInputCapabilities.Scratchpad : 0;137138return EditorInputCapabilities.Untitled139| EditorInputCapabilities.Readonly140| scratchPad;141}142143private async _resolveEditorModel() {144if (!this._editorModelReference) {145this._editorModelReference = await this._notebookEditorInput.resolve();146}147148return this._editorModelReference;149}150151override async resolve(): Promise<IResolvedNotebookEditorModel | null> {152if (this._editorModelReference) {153return this._editorModelReference;154}155156if (this._inputResolver) {157return this._inputResolver;158}159160this._inputResolver = this._resolveEditorModel();161162return this._inputResolver;163}164165async resolveInput(language?: string) {166if (this._inputModelRef) {167return this._inputModelRef.object.textEditorModel;168}169170const resolvedLanguage = language ?? this._initLanguage ?? PLAINTEXT_LANGUAGE_ID;171this._interactiveDocumentService.willCreateInteractiveDocument(this.resource, this.inputResource, resolvedLanguage);172this._inputModelRef = await this._textModelService.createModelReference(this.inputResource);173174return this._inputModelRef.object.textEditorModel;175}176177override async save(group: GroupIdentifier, options?: ISaveOptions): Promise<EditorInput | IUntypedEditorInput | undefined> {178if (this._editorModelReference) {179180if (this.hasCapability(EditorInputCapabilities.Untitled)) {181return this.saveAs(group, options);182} else {183await this._editorModelReference.save(options);184}185186return this;187}188189return undefined;190}191192override async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise<IUntypedEditorInput | undefined> {193if (!this._editorModelReference) {194return undefined;195}196197const provider = this._notebookService.getContributedNotebookType('interactive');198199if (!provider) {200return undefined;201}202203const filename = this.getName() + '.ipynb';204const pathCandidate = joinPath(await this._fileDialogService.defaultFilePath(), filename);205206const target = await this._fileDialogService.pickFileToSave(pathCandidate, options?.availableFileSystems);207if (!target) {208return undefined; // save cancelled209}210211const saved = await this._editorModelReference.saveAs(target);212if (saved && 'resource' in saved && saved.resource) {213this._notebookService.getNotebookTextModel(saved.resource)?.dispose();214}215return saved;216}217218override matches(otherInput: EditorInput | IUntypedEditorInput): boolean {219if (super.matches(otherInput)) {220return true;221}222if (otherInput instanceof InteractiveEditorInput) {223return isEqual(this.resource, otherInput.resource) && isEqual(this.inputResource, otherInput.inputResource);224}225return false;226}227228override getName() {229return this.name;230}231232override isDirty(): boolean {233if (this.isScratchpad) {234return false;235}236237return this._editorModelReference?.isDirty() ?? false;238}239240override isModified() {241return this._editorModelReference?.isModified() ?? false;242}243244override async revert(_group: GroupIdentifier, options?: IRevertOptions): Promise<void> {245if (this._editorModelReference && this._editorModelReference.isDirty()) {246await this._editorModelReference.revert(options);247}248}249250override dispose() {251// we support closing the interactive window without prompt, so the editor model should not be dirty252this._editorModelReference?.revert({ soft: true });253254this._notebookEditorInput?.dispose();255this._editorModelReference?.dispose();256this._editorModelReference = null;257this._interactiveDocumentService.willRemoveInteractiveDocument(this.resource, this.inputResource);258this._inputModelRef?.dispose();259this._inputModelRef = null;260super.dispose();261}262263get historyService() {264return this._historyService;265}266}267268269