Path: blob/main/extensions/copilot/src/extension/inlineEdits/common/rejectionCollector.ts
13399 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 { DocumentId } from '../../../platform/inlineEdits/common/dataTypes/documentId';6import { IObservableDocument, ObservableWorkspace } from '../../../platform/inlineEdits/common/observableWorkspace';7import { autorunWithChanges } from '../../../platform/inlineEdits/common/utils/observable';8import { ILogger, ILogService } from '../../../platform/log/common/logService';9import { Disposable, IDisposable, toDisposable } from '../../../util/vs/base/common/lifecycle';10import { mapObservableArrayCached } from '../../../util/vs/base/common/observable';11import { StringEdit, StringReplacement } from '../../../util/vs/editor/common/core/edits/stringEdit';12import { StringText } from '../../../util/vs/editor/common/core/text/abstractText';1314export class RejectionCollector extends Disposable {15private readonly _garbageCollector = this._register(new LRUGarbageCollector(20));16private readonly _documentCaches = new Map<DocumentId, DocumentRejectionTracker>();17private readonly _logger: ILogger;1819constructor(20public readonly workspace: ObservableWorkspace,21logService: ILogService,22) {23super();2425this._logger = logService.createSubLogger(['NES', 'RejectionCollector']);2627mapObservableArrayCached(this, workspace.openDocuments, (doc, store) => {28const state = new DocumentRejectionTracker(doc, this._garbageCollector, this._logger);29this._documentCaches.set(state.doc.id, state);3031store.add(autorunWithChanges(this, {32value: doc.value,33selection: doc.selection,34languageId: doc.languageId,35}, (data) => {36for (const edit of data.value.changes) {37state.handleEdit(edit, data.value.value);38}39}));4041store.add(toDisposable(() => {42this._documentCaches.delete(doc.id);43}));44}).recomputeInitiallyAndOnChange(this._store);45}4647public reject(docId: DocumentId, edit: StringReplacement): void {48const docCache = this._documentCaches.get(docId);49if (!docCache) {50this._logger.trace(`Rejecting, no document cache: ${edit}`);51return;52}53const e = edit.removeCommonSuffixAndPrefix(docCache.doc.value.get().value);54this._logger.trace(`Rejecting: ${e}`);55docCache.reject(e);56}5758public isRejected(docId: DocumentId, edit: StringReplacement): boolean {59const docCache = this._documentCaches.get(docId);60if (!docCache) {61this._logger.trace(`Checking rejection, no document cache: ${edit}`);62return false;63}64const e = edit.removeCommonSuffixAndPrefix(docCache.doc.value.get().value);65const isRejected = docCache.isRejected(e);66this._logger.trace(`Checking rejection, ${isRejected ? 'rejected' : 'not rejected'}: ${e}`);67return isRejected;68}6970public clear() {71this._garbageCollector.clear();72}73}7475class DocumentRejectionTracker {76private readonly _rejectedEdits = new Set<RejectedEdit>();7778constructor(79public readonly doc: IObservableDocument,80private readonly _garbageCollector: LRUGarbageCollector,81private readonly _logger: ILogger,82) {83}8485public handleEdit(edit: StringEdit, currentContent: StringText): void {86for (const r of [...this._rejectedEdits]) {87r.handleEdit(edit, currentContent); // this can remove the rejected edit from the set88}89}9091public reject(edit: StringReplacement): void {92if (this.isRejected(edit)) {93// already tracked94return;95}96const r = new RejectedEdit(edit.toEdit(), () => {97this._logger.trace(`Evicting: ${edit}`);98this._rejectedEdits.delete(r);99});100this._rejectedEdits.add(r);101this._garbageCollector.put(r);102}103104public isRejected(edit: StringReplacement): boolean {105for (const r of this._rejectedEdits) {106if (r.isRejected(edit)) {107return true;108}109}110return false;111}112}113114class RejectedEdit implements IDisposable {115constructor(116private _edit: StringEdit,117private readonly _onDispose: () => void,118) { }119120public handleEdit(edit: StringEdit, currentContent: StringText): void {121const d = this._edit.tryRebase(edit);122if (d) {123this._edit = d.removeCommonSuffixAndPrefix(currentContent.value);124} else {125this.dispose();126}127}128129public isRejected(edit: StringReplacement): boolean {130return this._edit.equals(edit.toEdit());131}132133public dispose(): void {134this._onDispose();135}136}137138class LRUGarbageCollector implements IDisposable {139private _disposables: IDisposable[] = [];140141constructor(142private _maxSize: number,143) {144}145146put(disposable: IDisposable): void {147this._disposables.push(disposable);148if (this._disposables.length > this._maxSize) {149this._disposables.shift()!.dispose();150}151}152153public clear(): void {154for (const d of this._disposables) {155d.dispose();156}157this._disposables = [];158}159160public dispose(): void {161this.clear();162}163}164165166