Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/common/externalEditTracker.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 type * as vscode from 'vscode';
7
import { DeferredPromise } from '../../../util/vs/base/common/async';
8
import { CancellationToken } from '../../../util/vs/base/common/cancellation';
9
import { IDisposable } from '../../../util/vs/base/common/lifecycle';
10
import { isEqualOrParent } from '../../../util/vs/base/common/resources';
11
import { URI } from '../../../util/vs/base/common/uri';
12
13
/**
14
* Tracks ongoing external edit operations for agent tools.
15
* Manages the lifecycle of external edits by coordinating with VS Code's
16
* externalEdit API to ensure proper tracking and attribution of file changes.
17
*/
18
export class ExternalEditTracker {
19
private _ongoingEdits = new Map<string, { complete: () => void; onDidComplete: Thenable<string> }>();
20
21
/**
22
* Creates a new ExternalEditTracker.
23
* @param ignoreDirectories Optional list of directory URIs to ignore when tracking edits
24
*/
25
constructor(
26
private readonly ignoreDirectories: URI[] = []
27
) { }
28
29
/**
30
* Starts tracking an external edit operation.
31
*
32
* @param editKey Unique identifier for this edit operation
33
* @param uris URIs that will be affected by the edit
34
* @param stream The chat response stream to call externalEdit on
35
* @param token Optional cancellation token to handle cancellation
36
* @returns Promise that resolves when the edit can proceed, or void if no URIs provided
37
*/
38
public async trackEdit(
39
editKey: string,
40
uris: vscode.Uri[],
41
stream: vscode.ChatResponseStream,
42
token?: CancellationToken
43
): Promise<void> {
44
// Filter out URIs that are within ignored directories
45
const filteredUris = uris.filter(uri => {
46
const uriAsURI = URI.isUri(uri) ? uri : URI.from(uri);
47
return !this.ignoreDirectories.some(ignoreDir => isEqualOrParent(uriAsURI, ignoreDir));
48
});
49
50
if (!filteredUris.length || token?.isCancellationRequested) {
51
return;
52
}
53
54
return new Promise(proceedWithEdit => {
55
const deferred = new DeferredPromise<void>();
56
let cancelListen: IDisposable | undefined;
57
58
// Handle cancellation if token provided
59
if (token) {
60
cancelListen = token.onCancellationRequested(() => {
61
this._ongoingEdits.delete(editKey);
62
deferred.complete();
63
});
64
}
65
66
const onDidComplete = stream.externalEdit(filteredUris, async () => {
67
proceedWithEdit();
68
await deferred.p;
69
cancelListen?.dispose();
70
});
71
72
this._ongoingEdits.set(editKey, {
73
onDidComplete,
74
complete: () => deferred.complete()
75
});
76
});
77
}
78
79
/**
80
* Completes tracking of an external edit operation.
81
* @param editKey Unique identifier for the edit operation to complete
82
* @returns Promise that resolves when VS Code has finished tracking the edit
83
*/
84
public async completeEdit(editKey: string): Promise<string | undefined> {
85
const ongoingEdit = this._ongoingEdits.get(editKey);
86
if (ongoingEdit) {
87
this._ongoingEdits.delete(editKey);
88
ongoingEdit.complete();
89
return await ongoingEdit.onDidComplete;
90
}
91
}
92
}
93
94