Path: blob/main/extensions/copilot/src/extension/chatSessions/common/externalEditTracker.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 type * as vscode from 'vscode';6import { DeferredPromise } from '../../../util/vs/base/common/async';7import { CancellationToken } from '../../../util/vs/base/common/cancellation';8import { IDisposable } from '../../../util/vs/base/common/lifecycle';9import { isEqualOrParent } from '../../../util/vs/base/common/resources';10import { URI } from '../../../util/vs/base/common/uri';1112/**13* Tracks ongoing external edit operations for agent tools.14* Manages the lifecycle of external edits by coordinating with VS Code's15* externalEdit API to ensure proper tracking and attribution of file changes.16*/17export class ExternalEditTracker {18private _ongoingEdits = new Map<string, { complete: () => void; onDidComplete: Thenable<string> }>();1920/**21* Creates a new ExternalEditTracker.22* @param ignoreDirectories Optional list of directory URIs to ignore when tracking edits23*/24constructor(25private readonly ignoreDirectories: URI[] = []26) { }2728/**29* Starts tracking an external edit operation.30*31* @param editKey Unique identifier for this edit operation32* @param uris URIs that will be affected by the edit33* @param stream The chat response stream to call externalEdit on34* @param token Optional cancellation token to handle cancellation35* @returns Promise that resolves when the edit can proceed, or void if no URIs provided36*/37public async trackEdit(38editKey: string,39uris: vscode.Uri[],40stream: vscode.ChatResponseStream,41token?: CancellationToken42): Promise<void> {43// Filter out URIs that are within ignored directories44const filteredUris = uris.filter(uri => {45const uriAsURI = URI.isUri(uri) ? uri : URI.from(uri);46return !this.ignoreDirectories.some(ignoreDir => isEqualOrParent(uriAsURI, ignoreDir));47});4849if (!filteredUris.length || token?.isCancellationRequested) {50return;51}5253return new Promise(proceedWithEdit => {54const deferred = new DeferredPromise<void>();55let cancelListen: IDisposable | undefined;5657// Handle cancellation if token provided58if (token) {59cancelListen = token.onCancellationRequested(() => {60this._ongoingEdits.delete(editKey);61deferred.complete();62});63}6465const onDidComplete = stream.externalEdit(filteredUris, async () => {66proceedWithEdit();67await deferred.p;68cancelListen?.dispose();69});7071this._ongoingEdits.set(editKey, {72onDidComplete,73complete: () => deferred.complete()74});75});76}7778/**79* Completes tracking of an external edit operation.80* @param editKey Unique identifier for the edit operation to complete81* @returns Promise that resolves when VS Code has finished tracking the edit82*/83public async completeEdit(editKey: string): Promise<string | undefined> {84const ongoingEdit = this._ongoingEdits.get(editKey);85if (ongoingEdit) {86this._ongoingEdits.delete(editKey);87ongoingEdit.complete();88return await ongoingEdit.onDidComplete;89}90}91}929394