Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/api/browser/mainThreadNotebookDocuments.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 { DisposableStore, dispose } from '../../../base/common/lifecycle.js';
8
import { ResourceMap } from '../../../base/common/map.js';
9
import { URI, UriComponents } from '../../../base/common/uri.js';
10
import { BoundModelReferenceCollection } from './mainThreadDocuments.js';
11
import { NotebookTextModel } from '../../contrib/notebook/common/model/notebookTextModel.js';
12
import { NotebookCellsChangeType } from '../../contrib/notebook/common/notebookCommon.js';
13
import { INotebookEditorModelResolverService } from '../../contrib/notebook/common/notebookEditorModelResolverService.js';
14
import { IUriIdentityService } from '../../../platform/uriIdentity/common/uriIdentity.js';
15
import { ExtHostContext, ExtHostNotebookDocumentsShape, MainThreadNotebookDocumentsShape, NotebookCellDto, NotebookCellsChangedEventDto, NotebookDataDto } from '../common/extHost.protocol.js';
16
import { NotebookDto } from './mainThreadNotebookDto.js';
17
import { SerializableObjectWithBuffers } from '../../services/extensions/common/proxyIdentifier.js';
18
import { IExtHostContext } from '../../services/extensions/common/extHostCustomers.js';
19
20
export class MainThreadNotebookDocuments implements MainThreadNotebookDocumentsShape {
21
22
private readonly _disposables = new DisposableStore();
23
24
private readonly _proxy: ExtHostNotebookDocumentsShape;
25
private readonly _documentEventListenersMapping = new ResourceMap<DisposableStore>();
26
private readonly _modelReferenceCollection: BoundModelReferenceCollection;
27
28
constructor(
29
extHostContext: IExtHostContext,
30
@INotebookEditorModelResolverService private readonly _notebookEditorModelResolverService: INotebookEditorModelResolverService,
31
@IUriIdentityService private readonly _uriIdentityService: IUriIdentityService
32
) {
33
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebookDocuments);
34
this._modelReferenceCollection = new BoundModelReferenceCollection(this._uriIdentityService.extUri);
35
36
// forward dirty and save events
37
this._disposables.add(this._notebookEditorModelResolverService.onDidChangeDirty(model => this._proxy.$acceptDirtyStateChanged(model.resource, model.isDirty())));
38
this._disposables.add(this._notebookEditorModelResolverService.onDidSaveNotebook(e => this._proxy.$acceptModelSaved(e)));
39
40
// when a conflict is going to happen RELEASE references that are held by extensions
41
this._disposables.add(_notebookEditorModelResolverService.onWillFailWithConflict(e => {
42
this._modelReferenceCollection.remove(e.resource);
43
}));
44
}
45
46
dispose(): void {
47
this._disposables.dispose();
48
this._modelReferenceCollection.dispose();
49
dispose(this._documentEventListenersMapping.values());
50
}
51
52
handleNotebooksAdded(notebooks: readonly NotebookTextModel[]): void {
53
54
for (const textModel of notebooks) {
55
const disposableStore = new DisposableStore();
56
disposableStore.add(textModel.onDidChangeContent(event => {
57
58
const eventDto: NotebookCellsChangedEventDto = {
59
versionId: event.versionId,
60
rawEvents: []
61
};
62
63
for (const e of event.rawEvents) {
64
65
switch (e.kind) {
66
case NotebookCellsChangeType.ModelChange:
67
eventDto.rawEvents.push({
68
kind: e.kind,
69
changes: e.changes.map(diff => [diff[0], diff[1], diff[2].map(cell => NotebookDto.toNotebookCellDto(cell))] as [number, number, NotebookCellDto[]])
70
});
71
break;
72
case NotebookCellsChangeType.Move:
73
eventDto.rawEvents.push({
74
kind: e.kind,
75
index: e.index,
76
length: e.length,
77
newIdx: e.newIdx,
78
});
79
break;
80
case NotebookCellsChangeType.Output:
81
eventDto.rawEvents.push({
82
kind: e.kind,
83
index: e.index,
84
outputs: e.outputs.map(NotebookDto.toNotebookOutputDto)
85
});
86
break;
87
case NotebookCellsChangeType.OutputItem:
88
eventDto.rawEvents.push({
89
kind: e.kind,
90
index: e.index,
91
outputId: e.outputId,
92
outputItems: e.outputItems.map(NotebookDto.toNotebookOutputItemDto),
93
append: e.append
94
});
95
break;
96
case NotebookCellsChangeType.ChangeCellLanguage:
97
case NotebookCellsChangeType.ChangeCellContent:
98
case NotebookCellsChangeType.ChangeCellMetadata:
99
case NotebookCellsChangeType.ChangeCellInternalMetadata:
100
eventDto.rawEvents.push(e);
101
break;
102
}
103
}
104
105
const hasDocumentMetadataChangeEvent = event.rawEvents.find(e => e.kind === NotebookCellsChangeType.ChangeDocumentMetadata);
106
107
// using the model resolver service to know if the model is dirty or not.
108
// assuming this is the first listener it can mean that at first the model
109
// is marked as dirty and that another event is fired
110
this._proxy.$acceptModelChanged(
111
textModel.uri,
112
new SerializableObjectWithBuffers(eventDto),
113
this._notebookEditorModelResolverService.isDirty(textModel.uri),
114
hasDocumentMetadataChangeEvent ? textModel.metadata : undefined
115
);
116
}));
117
118
this._documentEventListenersMapping.set(textModel.uri, disposableStore);
119
}
120
}
121
122
handleNotebooksRemoved(uris: URI[]): void {
123
for (const uri of uris) {
124
this._documentEventListenersMapping.get(uri)?.dispose();
125
this._documentEventListenersMapping.delete(uri);
126
}
127
}
128
129
async $tryCreateNotebook(options: { viewType: string; content?: NotebookDataDto }): Promise<UriComponents> {
130
if (options.content) {
131
const ref = await this._notebookEditorModelResolverService.resolve({ untitledResource: undefined }, options.viewType);
132
133
// untitled notebooks are disposed when they get saved. we should not hold a reference
134
// to such a disposed notebook and therefore dispose the reference as well
135
Event.once(ref.object.notebook.onWillDispose)(() => {
136
ref.dispose();
137
});
138
139
// untitled notebooks with content are dirty by default
140
this._proxy.$acceptDirtyStateChanged(ref.object.resource, true);
141
142
// apply content changes... slightly HACKY -> this triggers a change event
143
if (options.content) {
144
const data = NotebookDto.fromNotebookDataDto(options.content);
145
ref.object.notebook.reset(data.cells, data.metadata, ref.object.notebook.transientOptions);
146
}
147
return ref.object.notebook.uri;
148
} else {
149
// If we aren't adding content, we don't need to resolve the full editor model yet.
150
// This will allow us to adjust settings when the editor is opened, e.g. scratchpad
151
const notebook = await this._notebookEditorModelResolverService.createUntitledNotebookTextModel(options.viewType);
152
return notebook.uri;
153
}
154
}
155
156
async $tryOpenNotebook(uriComponents: UriComponents): Promise<URI> {
157
const uri = URI.revive(uriComponents);
158
const ref = await this._notebookEditorModelResolverService.resolve(uri, undefined);
159
160
if (uriComponents.scheme === 'untitled') {
161
// untitled notebooks are disposed when they get saved. we should not hold a reference
162
// to such a disposed notebook and therefore dispose the reference as well
163
ref.object.notebook.onWillDispose(() => {
164
ref.dispose();
165
});
166
}
167
168
this._modelReferenceCollection.add(uri, ref);
169
return uri;
170
}
171
172
async $trySaveNotebook(uriComponents: UriComponents) {
173
const uri = URI.revive(uriComponents);
174
175
const ref = await this._notebookEditorModelResolverService.resolve(uri);
176
const saveResult = await ref.object.save();
177
ref.dispose();
178
return saveResult;
179
}
180
}
181
182