Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Event } from '../../../../base/common/event.js';
7
import { IReference } from '../../../../base/common/lifecycle.js';
8
import * as paths from '../../../../base/common/path.js';
9
import { isEqual, joinPath } from '../../../../base/common/resources.js';
10
import { URI } from '../../../../base/common/uri.js';
11
import { PLAINTEXT_LANGUAGE_ID } from '../../../../editor/common/languages/modesRegistry.js';
12
import { IResolvedTextEditorModel, ITextModelService } from '../../../../editor/common/services/resolverService.js';
13
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
14
import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js';
15
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
16
import { EditorInputCapabilities, GroupIdentifier, IRevertOptions, ISaveOptions, IUntypedEditorInput } from '../../../common/editor.js';
17
import { EditorInput } from '../../../common/editor/editorInput.js';
18
import { IInteractiveDocumentService } from './interactiveDocumentService.js';
19
import { IInteractiveHistoryService } from './interactiveHistoryService.js';
20
import { IResolvedNotebookEditorModel, NotebookSetting } from '../../notebook/common/notebookCommon.js';
21
import { ICompositeNotebookEditorInput, NotebookEditorInput } from '../../notebook/common/notebookEditorInput.js';
22
import { INotebookService } from '../../notebook/common/notebookService.js';
23
24
export class InteractiveEditorInput extends EditorInput implements ICompositeNotebookEditorInput {
25
static create(instantiationService: IInstantiationService, resource: URI, inputResource: URI, title?: string, language?: string) {
26
return instantiationService.createInstance(InteractiveEditorInput, resource, inputResource, title, language);
27
}
28
29
private static windowNames: Record<string, string> = {};
30
31
static setName(notebookUri: URI, title: string | undefined) {
32
if (title) {
33
this.windowNames[notebookUri.path] = title;
34
}
35
}
36
37
static readonly ID: string = 'workbench.input.interactive';
38
39
public override get editorId(): string {
40
return 'interactive';
41
}
42
43
override get typeId(): string {
44
return InteractiveEditorInput.ID;
45
}
46
47
private name: string;
48
private readonly isScratchpad: boolean;
49
50
get language() {
51
return this._inputModelRef?.object.textEditorModel.getLanguageId() ?? this._initLanguage;
52
}
53
private _initLanguage?: string;
54
55
private _notebookEditorInput: NotebookEditorInput;
56
get notebookEditorInput() {
57
return this._notebookEditorInput;
58
}
59
60
get editorInputs() {
61
return [this._notebookEditorInput];
62
}
63
64
private _resource: URI;
65
66
override get resource(): URI {
67
return this._resource;
68
}
69
70
private _inputResource: URI;
71
72
get inputResource() {
73
return this._inputResource;
74
}
75
private _inputResolver: Promise<IResolvedNotebookEditorModel | null> | null;
76
private _editorModelReference: IResolvedNotebookEditorModel | null;
77
78
private _inputModelRef: IReference<IResolvedTextEditorModel> | null;
79
80
get primary(): EditorInput {
81
return this._notebookEditorInput;
82
}
83
private _textModelService: ITextModelService;
84
private _interactiveDocumentService: IInteractiveDocumentService;
85
private _historyService: IInteractiveHistoryService;
86
87
88
constructor(
89
resource: URI,
90
inputResource: URI,
91
title: string | undefined,
92
languageId: string | undefined,
93
@IInstantiationService instantiationService: IInstantiationService,
94
@ITextModelService textModelService: ITextModelService,
95
@IInteractiveDocumentService interactiveDocumentService: IInteractiveDocumentService,
96
@IInteractiveHistoryService historyService: IInteractiveHistoryService,
97
@INotebookService private readonly _notebookService: INotebookService,
98
@IFileDialogService private readonly _fileDialogService: IFileDialogService,
99
@IConfigurationService configurationService: IConfigurationService
100
) {
101
const input = NotebookEditorInput.getOrCreate(instantiationService, resource, undefined, 'interactive', {});
102
super();
103
this.isScratchpad = configurationService.getValue<boolean>(NotebookSetting.InteractiveWindowPromptToSave) !== true;
104
this._notebookEditorInput = input;
105
this._register(this._notebookEditorInput);
106
this.name = title ?? InteractiveEditorInput.windowNames[resource.path] ?? paths.basename(resource.path, paths.extname(resource.path));
107
this._initLanguage = languageId;
108
this._resource = resource;
109
this._inputResource = inputResource;
110
this._inputResolver = null;
111
this._editorModelReference = null;
112
this._inputModelRef = null;
113
this._textModelService = textModelService;
114
this._interactiveDocumentService = interactiveDocumentService;
115
this._historyService = historyService;
116
117
this._registerListeners();
118
}
119
120
private _registerListeners(): void {
121
const oncePrimaryDisposed = Event.once(this.primary.onWillDispose);
122
this._register(oncePrimaryDisposed(() => {
123
if (!this.isDisposed()) {
124
this.dispose();
125
}
126
}));
127
128
// Re-emit some events from the primary side to the outside
129
this._register(this.primary.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
130
this._register(this.primary.onDidChangeLabel(() => this._onDidChangeLabel.fire()));
131
132
// Re-emit some events from both sides to the outside
133
this._register(this.primary.onDidChangeCapabilities(() => this._onDidChangeCapabilities.fire()));
134
}
135
136
override get capabilities(): EditorInputCapabilities {
137
const scratchPad = this.isScratchpad ? EditorInputCapabilities.Scratchpad : 0;
138
139
return EditorInputCapabilities.Untitled
140
| EditorInputCapabilities.Readonly
141
| scratchPad;
142
}
143
144
private async _resolveEditorModel() {
145
if (!this._editorModelReference) {
146
this._editorModelReference = await this._notebookEditorInput.resolve();
147
}
148
149
return this._editorModelReference;
150
}
151
152
override async resolve(): Promise<IResolvedNotebookEditorModel | null> {
153
if (this._editorModelReference) {
154
return this._editorModelReference;
155
}
156
157
if (this._inputResolver) {
158
return this._inputResolver;
159
}
160
161
this._inputResolver = this._resolveEditorModel();
162
163
return this._inputResolver;
164
}
165
166
async resolveInput(language?: string) {
167
if (this._inputModelRef) {
168
return this._inputModelRef.object.textEditorModel;
169
}
170
171
const resolvedLanguage = language ?? this._initLanguage ?? PLAINTEXT_LANGUAGE_ID;
172
this._interactiveDocumentService.willCreateInteractiveDocument(this.resource, this.inputResource, resolvedLanguage);
173
this._inputModelRef = await this._textModelService.createModelReference(this.inputResource);
174
175
return this._inputModelRef.object.textEditorModel;
176
}
177
178
override async save(group: GroupIdentifier, options?: ISaveOptions): Promise<EditorInput | IUntypedEditorInput | undefined> {
179
if (this._editorModelReference) {
180
181
if (this.hasCapability(EditorInputCapabilities.Untitled)) {
182
return this.saveAs(group, options);
183
} else {
184
await this._editorModelReference.save(options);
185
}
186
187
return this;
188
}
189
190
return undefined;
191
}
192
193
override async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise<IUntypedEditorInput | undefined> {
194
if (!this._editorModelReference) {
195
return undefined;
196
}
197
198
const provider = this._notebookService.getContributedNotebookType('interactive');
199
200
if (!provider) {
201
return undefined;
202
}
203
204
const filename = this.getName() + '.ipynb';
205
const pathCandidate = joinPath(await this._fileDialogService.defaultFilePath(), filename);
206
207
const target = await this._fileDialogService.pickFileToSave(pathCandidate, options?.availableFileSystems);
208
if (!target) {
209
return undefined; // save cancelled
210
}
211
212
const saved = await this._editorModelReference.saveAs(target);
213
if (saved && 'resource' in saved && saved.resource) {
214
this._notebookService.getNotebookTextModel(saved.resource)?.dispose();
215
}
216
return saved;
217
}
218
219
override matches(otherInput: EditorInput | IUntypedEditorInput): boolean {
220
if (super.matches(otherInput)) {
221
return true;
222
}
223
if (otherInput instanceof InteractiveEditorInput) {
224
return isEqual(this.resource, otherInput.resource) && isEqual(this.inputResource, otherInput.inputResource);
225
}
226
return false;
227
}
228
229
override getName() {
230
return this.name;
231
}
232
233
override isDirty(): boolean {
234
if (this.isScratchpad) {
235
return false;
236
}
237
238
return this._editorModelReference?.isDirty() ?? false;
239
}
240
241
override isModified() {
242
return this._editorModelReference?.isModified() ?? false;
243
}
244
245
override async revert(_group: GroupIdentifier, options?: IRevertOptions): Promise<void> {
246
if (this._editorModelReference && this._editorModelReference.isDirty()) {
247
await this._editorModelReference.revert(options);
248
}
249
}
250
251
override dispose() {
252
// we support closing the interactive window without prompt, so the editor model should not be dirty
253
this._editorModelReference?.revert({ soft: true });
254
255
this._notebookEditorInput?.dispose();
256
this._editorModelReference?.dispose();
257
this._editorModelReference = null;
258
this._interactiveDocumentService.willRemoveInteractiveDocument(this.resource, this.inputResource);
259
this._inputModelRef?.dispose();
260
this._inputModelRef = null;
261
super.dispose();
262
}
263
264
get historyService() {
265
return this._historyService;
266
}
267
}
268
269