Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/inlineEdits/common/rejectionCollector.ts
13399 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 { DocumentId } from '../../../platform/inlineEdits/common/dataTypes/documentId';
7
import { IObservableDocument, ObservableWorkspace } from '../../../platform/inlineEdits/common/observableWorkspace';
8
import { autorunWithChanges } from '../../../platform/inlineEdits/common/utils/observable';
9
import { ILogger, ILogService } from '../../../platform/log/common/logService';
10
import { Disposable, IDisposable, toDisposable } from '../../../util/vs/base/common/lifecycle';
11
import { mapObservableArrayCached } from '../../../util/vs/base/common/observable';
12
import { StringEdit, StringReplacement } from '../../../util/vs/editor/common/core/edits/stringEdit';
13
import { StringText } from '../../../util/vs/editor/common/core/text/abstractText';
14
15
export class RejectionCollector extends Disposable {
16
private readonly _garbageCollector = this._register(new LRUGarbageCollector(20));
17
private readonly _documentCaches = new Map<DocumentId, DocumentRejectionTracker>();
18
private readonly _logger: ILogger;
19
20
constructor(
21
public readonly workspace: ObservableWorkspace,
22
logService: ILogService,
23
) {
24
super();
25
26
this._logger = logService.createSubLogger(['NES', 'RejectionCollector']);
27
28
mapObservableArrayCached(this, workspace.openDocuments, (doc, store) => {
29
const state = new DocumentRejectionTracker(doc, this._garbageCollector, this._logger);
30
this._documentCaches.set(state.doc.id, state);
31
32
store.add(autorunWithChanges(this, {
33
value: doc.value,
34
selection: doc.selection,
35
languageId: doc.languageId,
36
}, (data) => {
37
for (const edit of data.value.changes) {
38
state.handleEdit(edit, data.value.value);
39
}
40
}));
41
42
store.add(toDisposable(() => {
43
this._documentCaches.delete(doc.id);
44
}));
45
}).recomputeInitiallyAndOnChange(this._store);
46
}
47
48
public reject(docId: DocumentId, edit: StringReplacement): void {
49
const docCache = this._documentCaches.get(docId);
50
if (!docCache) {
51
this._logger.trace(`Rejecting, no document cache: ${edit}`);
52
return;
53
}
54
const e = edit.removeCommonSuffixAndPrefix(docCache.doc.value.get().value);
55
this._logger.trace(`Rejecting: ${e}`);
56
docCache.reject(e);
57
}
58
59
public isRejected(docId: DocumentId, edit: StringReplacement): boolean {
60
const docCache = this._documentCaches.get(docId);
61
if (!docCache) {
62
this._logger.trace(`Checking rejection, no document cache: ${edit}`);
63
return false;
64
}
65
const e = edit.removeCommonSuffixAndPrefix(docCache.doc.value.get().value);
66
const isRejected = docCache.isRejected(e);
67
this._logger.trace(`Checking rejection, ${isRejected ? 'rejected' : 'not rejected'}: ${e}`);
68
return isRejected;
69
}
70
71
public clear() {
72
this._garbageCollector.clear();
73
}
74
}
75
76
class DocumentRejectionTracker {
77
private readonly _rejectedEdits = new Set<RejectedEdit>();
78
79
constructor(
80
public readonly doc: IObservableDocument,
81
private readonly _garbageCollector: LRUGarbageCollector,
82
private readonly _logger: ILogger,
83
) {
84
}
85
86
public handleEdit(edit: StringEdit, currentContent: StringText): void {
87
for (const r of [...this._rejectedEdits]) {
88
r.handleEdit(edit, currentContent); // this can remove the rejected edit from the set
89
}
90
}
91
92
public reject(edit: StringReplacement): void {
93
if (this.isRejected(edit)) {
94
// already tracked
95
return;
96
}
97
const r = new RejectedEdit(edit.toEdit(), () => {
98
this._logger.trace(`Evicting: ${edit}`);
99
this._rejectedEdits.delete(r);
100
});
101
this._rejectedEdits.add(r);
102
this._garbageCollector.put(r);
103
}
104
105
public isRejected(edit: StringReplacement): boolean {
106
for (const r of this._rejectedEdits) {
107
if (r.isRejected(edit)) {
108
return true;
109
}
110
}
111
return false;
112
}
113
}
114
115
class RejectedEdit implements IDisposable {
116
constructor(
117
private _edit: StringEdit,
118
private readonly _onDispose: () => void,
119
) { }
120
121
public handleEdit(edit: StringEdit, currentContent: StringText): void {
122
const d = this._edit.tryRebase(edit);
123
if (d) {
124
this._edit = d.removeCommonSuffixAndPrefix(currentContent.value);
125
} else {
126
this.dispose();
127
}
128
}
129
130
public isRejected(edit: StringReplacement): boolean {
131
return this._edit.equals(edit.toEdit());
132
}
133
134
public dispose(): void {
135
this._onDispose();
136
}
137
}
138
139
class LRUGarbageCollector implements IDisposable {
140
private _disposables: IDisposable[] = [];
141
142
constructor(
143
private _maxSize: number,
144
) {
145
}
146
147
put(disposable: IDisposable): void {
148
this._disposables.push(disposable);
149
if (this._disposables.length > this._maxSize) {
150
this._disposables.shift()!.dispose();
151
}
152
}
153
154
public clear(): void {
155
for (const d of this._disposables) {
156
d.dispose();
157
}
158
this._disposables = [];
159
}
160
161
public dispose(): void {
162
this.clear();
163
}
164
}
165
166