Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/inlineEdits/common/observableWorkspace.ts
13400 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 { assert } from '../../../util/vs/base/common/assert';
7
import { Disposable, toDisposable } from '../../../util/vs/base/common/lifecycle';
8
import { autorunWithStore, derivedHandleChanges, derivedWithStore, IObservable, IObservableWithChange, ISettableObservable, ITransaction, observableValue, runOnChange, subtransaction } from '../../../util/vs/base/common/observableInternal';
9
import { URI } from '../../../util/vs/base/common/uri';
10
import { StringEdit, StringReplacement } from '../../../util/vs/editor/common/core/edits/stringEdit';
11
import { OffsetRange } from '../../../util/vs/editor/common/core/ranges/offsetRange';
12
import { StringText } from '../../../util/vs/editor/common/core/text/abstractText';
13
import { DiagnosticData } from './dataTypes/diagnosticData';
14
import { DocumentId } from './dataTypes/documentId';
15
import { LanguageId } from './dataTypes/languageId';
16
import { EditReason } from './editReason';
17
18
export abstract class ObservableWorkspace {
19
abstract get openDocuments(): IObservableWithChange<readonly IObservableDocument[], { added: readonly IObservableDocument[]; removed: readonly IObservableDocument[] }>;
20
21
abstract getWorkspaceRoot(documentId: DocumentId): URI | undefined;
22
23
getFirstOpenDocument(): IObservableDocument | undefined {
24
return this.openDocuments.get()[0];
25
}
26
27
getDocument(documentId: DocumentId): IObservableDocument | undefined {
28
return this.openDocuments.get().find(d => d.id === documentId);
29
}
30
31
private _version = 0;
32
33
/**
34
* Is fired when any open document changes.
35
*/
36
public readonly onDidOpenDocumentChange = derivedHandleChanges({
37
owner: this,
38
changeTracker: {
39
createChangeSummary: () => ({ didChange: false }),
40
handleChange: (ctx, changeSummary) => {
41
if (!ctx.didChange(this.openDocuments)) {
42
changeSummary.didChange = true; // A document changed
43
}
44
return true;
45
}
46
}
47
}, (reader, changeSummary) => {
48
const docs = this.openDocuments.read(reader);
49
for (const d of docs) {
50
d.value.read(reader); // add dependency
51
}
52
if (changeSummary.didChange) {
53
this._version++; // to force a change
54
}
55
return this._version;
56
57
// TODO@hediet make this work:
58
/*
59
const docs = this.openDocuments.read(reader);
60
for (const d of docs) {
61
if (reader.readChangesSinceLastRun(d.value).length > 0) {
62
reader.reportChange(d);
63
}
64
}
65
return undefined;
66
*/
67
});
68
69
public readonly lastActiveDocument = derivedWithStore((_reader, store) => {
70
const obs = observableValue('lastActiveDocument', undefined as IObservableDocument | undefined);
71
store.add(autorunWithStore((reader, store) => {
72
const docs = this.openDocuments.read(reader);
73
for (const d of docs) {
74
store.add(runOnChange(d.value, () => {
75
obs.set(d, undefined);
76
}));
77
}
78
}));
79
return obs;
80
}).flatten();
81
}
82
83
export interface IObservableDocument {
84
readonly id: DocumentId;
85
readonly value: IObservableWithChange<StringText, StringEditWithReason>;
86
87
/**
88
* Increases whenever the value changes. Is also used to reference document states from the past.
89
*/
90
readonly version: IObservable<number>;
91
92
/**
93
* `selection` is an array because of `multi-cursor` support.
94
*/
95
readonly selection: IObservable<readonly OffsetRange[]>;
96
/**
97
* 0-based line number of the primary cursor.
98
*/
99
readonly primarySelectionLine: IObservable<number | undefined>;
100
readonly visibleRanges: IObservable<readonly OffsetRange[]>;
101
readonly languageId: IObservable<LanguageId>;
102
readonly diagnostics: IObservable<readonly DiagnosticData[]>;
103
}
104
105
export class StringEditWithReason extends StringEdit {
106
constructor(
107
replacements: StringEdit['replacements'],
108
public readonly reason: EditReason,
109
) {
110
super(replacements);
111
}
112
}
113
114
export class MutableObservableWorkspace extends ObservableWorkspace {
115
private readonly _openDocuments = observableValue<readonly IObservableDocument[], { added: readonly IObservableDocument[]; removed: readonly IObservableDocument[] }>(this, []);
116
public readonly openDocuments = this._openDocuments;
117
118
private readonly _documents = new Map<DocumentId, MutableObservableDocument>();
119
120
/**
121
* Dispose to remove.
122
*/
123
public addDocument(options: { id: DocumentId; workspaceRoot?: URI; initialValue?: string; initialVersionId?: number; languageId?: LanguageId }, tx: ITransaction | undefined = undefined): MutableObservableDocument {
124
assert(!this._documents.has(options.id));
125
126
const document = new MutableObservableDocument(
127
options.id,
128
new StringText(options.initialValue ?? ''),
129
[],
130
options.languageId ?? LanguageId.PlainText,
131
() => {
132
this._documents.delete(options.id);
133
const docs = this._openDocuments.get();
134
const filteredDocs = docs.filter(d => d.id !== document.id);
135
if (filteredDocs.length !== docs.length) {
136
this._openDocuments.set(filteredDocs, tx, { added: [], removed: [document] });
137
}
138
},
139
options.initialVersionId ?? 0,
140
options.workspaceRoot,
141
);
142
143
this._documents.set(options.id, document);
144
this._openDocuments.set([...this._openDocuments.get(), document], tx, { added: [document], removed: [] });
145
146
return document;
147
}
148
149
public override getDocument(id: DocumentId): MutableObservableDocument | undefined {
150
return this._documents.get(id);
151
}
152
153
public clear(): void {
154
this._openDocuments.set([], undefined, { added: [], removed: this._openDocuments.get() });
155
for (const doc of this._documents.values()) {
156
doc.dispose();
157
}
158
this._documents.clear();
159
}
160
161
getWorkspaceRoot(documentId: DocumentId): URI | undefined {
162
return this._documents.get(documentId)?.workspaceRoot;
163
}
164
}
165
166
export class MutableObservableDocument extends Disposable implements IObservableDocument {
167
private readonly _value: ISettableObservable<StringText, StringEditWithReason>;
168
public get value(): IObservableWithChange<StringText, StringEditWithReason> { return this._value; }
169
170
private readonly _selection: ISettableObservable<readonly OffsetRange[]>;
171
public get selection(): IObservable<readonly OffsetRange[]> { return this._selection; }
172
173
private readonly _primarySelectionLine: ISettableObservable<number | undefined>;
174
public get primarySelectionLine(): IObservable<number | undefined> { return this._primarySelectionLine; }
175
176
private readonly _visibleRanges: ISettableObservable<readonly OffsetRange[]>;
177
public get visibleRanges(): IObservable<readonly OffsetRange[]> { return this._visibleRanges; }
178
179
private readonly _languageId: ISettableObservable<LanguageId>;
180
public get languageId(): IObservable<LanguageId> { return this._languageId; }
181
182
private readonly _version: ISettableObservable<number>;
183
public get version(): IObservable<number> { return this._version; }
184
185
private readonly _diagnostics: ISettableObservable<readonly DiagnosticData[]>;
186
public get diagnostics(): IObservable<readonly DiagnosticData[]> { return this._diagnostics; }
187
188
constructor(
189
public readonly id: DocumentId,
190
value: StringText,
191
selection: readonly OffsetRange[],
192
languageId: LanguageId,
193
onDispose: () => void,
194
versionId: number,
195
public readonly workspaceRoot: URI | undefined,
196
) {
197
super();
198
199
this._value = observableValue(this, value);
200
this._selection = observableValue(this, selection);
201
this._primarySelectionLine = observableValue(this, undefined);
202
this._visibleRanges = observableValue(this, []);
203
this._languageId = observableValue(this, languageId);
204
this._version = observableValue(this, versionId);
205
this._diagnostics = observableValue(this, []);
206
207
this._register(toDisposable(onDispose));
208
}
209
210
setSelection(selection: readonly OffsetRange[], tx: ITransaction | undefined = undefined, primaryLine?: number): void {
211
this._selection.set(selection, tx);
212
this._primarySelectionLine.set(primaryLine, tx);
213
}
214
215
setVisibleRange(visibleRanges: readonly OffsetRange[], tx: ITransaction | undefined = undefined): void {
216
this._visibleRanges.set(visibleRanges, tx);
217
}
218
219
applyEdit(edit: StringEdit | StringEditWithReason, tx: ITransaction | undefined = undefined, newVersion: number | undefined = undefined): void {
220
const newValue = edit.applyOnText(this.value.get());
221
const e = edit instanceof StringEditWithReason ? edit : new StringEditWithReason(edit.replacements, EditReason.unknown);
222
subtransaction(tx, tx => {
223
this._value.set(newValue, tx, e);
224
this._version.set(newVersion ?? this._version.get() + 1, tx);
225
});
226
}
227
228
updateSelection(selection: readonly OffsetRange[], tx: ITransaction | undefined = undefined, primaryLine?: number): void {
229
this._selection.set(selection, tx);
230
this._primarySelectionLine.set(primaryLine, tx);
231
}
232
233
setValue(value: StringText, tx: ITransaction | undefined = undefined, newVersion: number | undefined = undefined): void {
234
const reason = EditReason.unknown;
235
const e = new StringEditWithReason([StringReplacement.replace(new OffsetRange(0, this.value.get().value.length), value.value)], reason);
236
subtransaction(tx, tx => {
237
this._value.set(value, tx, e);
238
this._version.set(newVersion ?? this._version.get() + 1, tx);
239
});
240
}
241
242
updateDiagnostics(diagnostics: readonly DiagnosticData[], tx: ITransaction | undefined = undefined): void {
243
this._diagnostics.set(diagnostics, tx);
244
}
245
}
246
247