Skip to content

Commit d2b3e0a

Browse files
authored
Merge pull request microsoft#254981 from microsoft/osortega/fix-cancel-search
Fix for canceling pending copilot search request
2 parents 73fbb0c + 32e71a8 commit d2b3e0a

File tree

2 files changed

+38
-28
lines changed

2 files changed

+38
-28
lines changed

src/vs/base/browser/ui/tree/asyncDataTree.ts

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ interface IAsyncDataTreeNode<TInput, T> {
3232
readonly parent: IAsyncDataTreeNode<TInput, T> | null;
3333
readonly children: IAsyncDataTreeNode<TInput, T>[];
3434
readonly id?: string | null;
35-
refreshPromise: Promise<void> | undefined;
35+
refreshPromise: CancelablePromise<void> | undefined;
3636
hasChildren: boolean;
3737
stale: boolean;
3838
slow: boolean;
@@ -528,7 +528,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
528528
private readonly findController?: AsyncFindController<TInput, T, TFilterData>;
529529
private readonly getDefaultCollapseState: { (e: T): undefined | ObjectTreeElementCollapseState.PreserveOrCollapsed | ObjectTreeElementCollapseState.PreserveOrExpanded };
530530

531-
private readonly subTreeRefreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, Promise<void>>();
531+
private readonly subTreeRefreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, CancelablePromise<void>>();
532532
private readonly refreshPromises = new Map<IAsyncDataTreeNode<TInput, T>, CancelablePromise<Iterable<T>>>();
533533

534534
protected readonly identityProvider?: IIdentityProvider<T>;
@@ -769,8 +769,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
769769
}
770770

771771
async setInput(input: TInput, viewState?: IAsyncDataTreeViewState): Promise<void> {
772-
this.refreshPromises.forEach(promise => promise.cancel());
773-
this.refreshPromises.clear();
772+
this.cancelAllRefreshPromises();
774773

775774
this.root.element = input!;
776775

@@ -792,6 +791,14 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
792791
await this._updateChildren(element, recursive, rerender, undefined, options);
793792
}
794793

794+
cancelAllRefreshPromises(): void {
795+
this.refreshPromises.forEach(promise => promise.cancel());
796+
this.refreshPromises.clear();
797+
798+
this.subTreeRefreshPromises.forEach(promise => promise.cancel());
799+
this.subTreeRefreshPromises.clear();
800+
}
801+
795802
private async _updateChildren(element: TInput | T = this.root.element, recursive = true, rerender = false, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>, options?: IAsyncDataTreeUpdateChildrenOptions<T>): Promise<void> {
796803
if (typeof this.root.element === 'undefined') {
797804
throw new TreeError(this.user, 'Tree input not set');
@@ -875,7 +882,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
875882
}
876883

877884
if (node.refreshPromise) {
878-
await this.root.refreshPromise;
885+
await node.refreshPromise;
879886
await Event.toPromise(this._onDidRender.event);
880887
}
881888

@@ -886,7 +893,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
886893
const result = this.tree.expand(node === this.root ? null : node, recursive);
887894

888895
if (node.refreshPromise) {
889-
await this.root.refreshPromise;
896+
await node.refreshPromise;
890897
await Event.toPromise(this._onDidRender.event);
891898
}
892899

@@ -1088,28 +1095,26 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
10881095
return;
10891096
}
10901097
}
1091-
10921098
return this.doRefreshSubTree(node, recursive, viewStateContext);
10931099
}
10941100

10951101
private async doRefreshSubTree(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<void> {
1096-
let done: () => void;
1097-
node.refreshPromise = new Promise(c => done = c);
1098-
this.subTreeRefreshPromises.set(node, node.refreshPromise);
1102+
const cancelablePromise = createCancelablePromise(async () => {
1103+
const childrenToRefresh = await this.doRefreshNode(node, recursive, viewStateContext);
1104+
node.stale = false;
10991105

1100-
node.refreshPromise.finally(() => {
1106+
await Promises.settled(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext)));
1107+
});
1108+
1109+
node.refreshPromise = cancelablePromise;
1110+
this.subTreeRefreshPromises.set(node, cancelablePromise);
1111+
1112+
cancelablePromise.finally(() => {
11011113
node.refreshPromise = undefined;
11021114
this.subTreeRefreshPromises.delete(node);
11031115
});
11041116

1105-
try {
1106-
const childrenToRefresh = await this.doRefreshNode(node, recursive, viewStateContext);
1107-
node.stale = false;
1108-
1109-
await Promises.settled(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext)));
1110-
} finally {
1111-
done!();
1112-
}
1117+
return cancelablePromise;
11131118
}
11141119

11151120
private async doRefreshNode(node: IAsyncDataTreeNode<TInput, T>, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): Promise<IAsyncDataTreeNode<TInput, T>[]> {

src/vs/workbench/contrib/search/browser/searchView.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -543,9 +543,9 @@ export class SearchView extends ViewPane {
543543
// Subscribe to AI search result changes and update the tree when new AI results are reported
544544
this._onAIResultChangedDisposable?.dispose();
545545
this._onAIResultChangedDisposable = this._register(
546-
this.viewModel.searchResult.aiTextSearchResult.onChange(() => {
546+
this.viewModel.searchResult.aiTextSearchResult.onChange((e) => {
547547
// Only refresh the AI node, not the whole tree
548-
if (this.tree && this.tree.hasNode(this.searchResult.aiTextSearchResult)) {
548+
if (this.tree && this.tree.hasNode(this.searchResult.aiTextSearchResult) && !e.removed) {
549549
this.tree.updateChildren(this.searchResult.aiTextSearchResult);
550550
}
551551
})
@@ -1349,6 +1349,7 @@ export class SearchView extends ViewPane {
13491349
this.searchWidget.clear();
13501350
}
13511351
this.viewModel.cancelSearch();
1352+
this.viewModel.cancelAISearch();
13521353
this.tree.ariaLabel = nls.localize('emptySearch', "Empty Search");
13531354

13541355
this.accessibilitySignalService.playSignal(AccessibilitySignal.clear);
@@ -1858,17 +1859,17 @@ export class SearchView extends ViewPane {
18581859

18591860
public clearAIResults() {
18601861
this.model.searchResult.aiTextSearchResult.hidden = true;
1861-
if (!this._pendingSemanticSearchPromise) {
1862-
this._cachedResults = undefined;
1863-
this._cachedKeywords = [];
1864-
this.model.cancelAISearch(true);
1865-
this.model.clearAiSearchResults();
1866-
}
1862+
this.refreshTreeController.clearAllPending();
1863+
this._pendingSemanticSearchPromise = undefined;
1864+
this._cachedResults = undefined;
1865+
this._cachedKeywords = [];
1866+
this.model.cancelAISearch(true);
1867+
this.model.clearAiSearchResults();
18671868
}
18681869

18691870
public async requestAIResults() {
18701871
this.logService.info(`SearchView: Requesting semantic results from keybinding. Cached: ${!!this.cachedResults}`);
1871-
if (!this.cachedResults || this.cachedResults.results.length === 0) {
1872+
if ((!this.cachedResults || this.cachedResults.results.length === 0) && !this._pendingSemanticSearchPromise) {
18721873
this.clearAIResults();
18731874
}
18741875
this.model.searchResult.aiTextSearchResult.hidden = false;
@@ -2646,6 +2647,10 @@ class RefreshTreeController extends Disposable {
26462647

26472648
private queuedIChangeEvents: IChangeEvent[] = [];
26482649

2650+
public clearAllPending(): void {
2651+
this.searchView.getControl().cancelAllRefreshPromises();
2652+
}
2653+
26492654
public async queue(e?: IChangeEvent): Promise<void> {
26502655
if (e) {
26512656
this.queuedIChangeEvents.push(e);

0 commit comments

Comments
 (0)