From 0a07bc30c80c6f074768e8990981722455c8ed4c Mon Sep 17 00:00:00 2001 From: Trinh Van Phong Date: Thu, 5 Mar 2026 16:24:04 +0700 Subject: [PATCH 1/3] tree: forward searchChanged event to extension backend Expose search text updates through vscode-messenger so extensions can implement backend search for large trees. Also document the new onDidSearchChanged event in the tree README. --- src/tree/README.md | 3 ++ src/tree/browser/tree.tsx | 28 +++++++++++++++++-- src/tree/common/tree-messenger-types.ts | 1 + src/tree/vscode/tree-webview-view-provider.ts | 5 ++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/tree/README.md b/src/tree/README.md index cd437d3..7f825c1 100644 --- a/src/tree/README.md +++ b/src/tree/README.md @@ -68,6 +68,9 @@ export class TreeDataProvider implements CDTTreeDataProvider { // Handle toggle + }), + webview.onDidSearchChanged(event => { + // Handle search input changes }) ); } diff --git a/src/tree/browser/tree.tsx b/src/tree/browser/tree.tsx index d2d5798..3ab6318 100644 --- a/src/tree/browser/tree.tsx +++ b/src/tree/browser/tree.tsx @@ -14,7 +14,9 @@ import classNames from 'classnames'; import { Resizable } from 're-resizable'; import { default as React, useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react'; import { debounce } from 'throttle-debounce'; +import { HOST_EXTENSION } from 'vscode-messenger-common'; import { findNestedValue } from '../../base'; +import { messenger } from '../../browser-types'; import { CommandDefinition } from '../../vscode/webview-types'; import { CDTTreeItem, @@ -25,6 +27,7 @@ import { CDTTreeTableStringColumn, CDTTreeWebviewContext } from '../common/index'; +import { CDTTreeMessengerType } from '../common/tree-messenger-types'; import ActionCell from './components/cells/ActionCell'; import StringCell from './components/cells/StringCell'; import { ExpandIcon } from './components/expand-icon'; @@ -238,12 +241,31 @@ export const CDTTree = (props: CDTTreeProps) = } }, []); - const onSearchShow = useCallback(() => setGlobalSearchText(globalSearchRef.current?.value()), []); + const notifySearchChanged = useCallback((text: string) => { + messenger.sendNotification(CDTTreeMessengerType.searchChanged, HOST_EXTENSION, { data: { text } }); + }, []); + + const onSearchShow = useCallback(() => { + const text = globalSearchRef.current?.value() ?? ''; + setGlobalSearchText(text); + notifySearchChanged(text); + }, [notifySearchChanged]); + const onSearchHide = useCallback(() => { setGlobalSearchText(undefined); autoSelectRowRef.current = true; - }, [autoSelectRowRef]); - const onSearchChange = useMemo(() => debounce(300, (text: string) => setGlobalSearchText(text)), []); + + notifySearchChanged(''); + }, [notifySearchChanged]); + + const onSearchChange = useMemo( + () => + debounce(300, (text: string) => { + setGlobalSearchText(text); + notifySearchChanged(text); + }), + [notifySearchChanged] + ); // ==== Selection ==== diff --git a/src/tree/common/tree-messenger-types.ts b/src/tree/common/tree-messenger-types.ts index 061e927..b3b5c7e 100644 --- a/src/tree/common/tree-messenger-types.ts +++ b/src/tree/common/tree-messenger-types.ts @@ -45,4 +45,5 @@ export namespace CDTTreeMessengerType { export const toggleNode: NotificationType> = { method: 'toggleNode' }; export const clickNode: NotificationType> = { method: 'clickNode' }; export const openSearch: NotificationType = { method: 'openSearch' }; + export const searchChanged: NotificationType> = { method: 'searchChanged' }; } diff --git a/src/tree/vscode/tree-webview-view-provider.ts b/src/tree/vscode/tree-webview-view-provider.ts index 91cbd66..c3b4f39 100644 --- a/src/tree/vscode/tree-webview-view-provider.ts +++ b/src/tree/vscode/tree-webview-view-provider.ts @@ -18,6 +18,8 @@ export abstract class CDTTreeWebviewViewProvider implements vscode.Webvie public readonly onDidExecuteCommand = this.onDidExecuteCommandEvent.event; protected onDidClickNodeEvent = new vscode.EventEmitter>(); public readonly onDidClickNode = this.onDidClickNodeEvent.event; + protected onDidSearchChangedEvent = new vscode.EventEmitter>(); + public readonly onDidSearchChanged = this.onDidSearchChangedEvent.event; protected get extensionUri(): vscode.Uri { return this.context.extensionUri; @@ -119,6 +121,9 @@ export abstract class CDTTreeWebviewViewProvider implements vscode.Webvie }), this.messenger.onNotification(CDTTreeMessengerType.clickNode, event => this.onDidClickNodeEvent.fire(event), { sender: participant + }), + this.messenger.onNotification(CDTTreeMessengerType.searchChanged, event => this.onDidSearchChangedEvent.fire(event), { + sender: participant }) ]; From 4155a34ac4e928bde6db45215ceecafbe5492e04 Mon Sep 17 00:00:00 2001 From: Trinh Van Phong Date: Mon, 9 Mar 2026 15:24:02 +0700 Subject: [PATCH 2/3] Rename onDidSearchChanged to onDidSearchChange --- src/tree/README.md | 2 +- src/tree/vscode/tree-webview-view-provider.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tree/README.md b/src/tree/README.md index 7f825c1..4224a99 100644 --- a/src/tree/README.md +++ b/src/tree/README.md @@ -69,7 +69,7 @@ export class TreeDataProvider implements CDTTreeDataProvider { // Handle toggle }), - webview.onDidSearchChanged(event => { + webview.onDidSearchChange(event => { // Handle search input changes }) ); diff --git a/src/tree/vscode/tree-webview-view-provider.ts b/src/tree/vscode/tree-webview-view-provider.ts index c3b4f39..25f2d08 100644 --- a/src/tree/vscode/tree-webview-view-provider.ts +++ b/src/tree/vscode/tree-webview-view-provider.ts @@ -18,8 +18,8 @@ export abstract class CDTTreeWebviewViewProvider implements vscode.Webvie public readonly onDidExecuteCommand = this.onDidExecuteCommandEvent.event; protected onDidClickNodeEvent = new vscode.EventEmitter>(); public readonly onDidClickNode = this.onDidClickNodeEvent.event; - protected onDidSearchChangedEvent = new vscode.EventEmitter>(); - public readonly onDidSearchChanged = this.onDidSearchChangedEvent.event; + protected onDidSearchChangeEvent = new vscode.EventEmitter>(); + public readonly onDidSearchChange = this.onDidSearchChangeEvent.event; protected get extensionUri(): vscode.Uri { return this.context.extensionUri; @@ -122,7 +122,7 @@ export abstract class CDTTreeWebviewViewProvider implements vscode.Webvie this.messenger.onNotification(CDTTreeMessengerType.clickNode, event => this.onDidClickNodeEvent.fire(event), { sender: participant }), - this.messenger.onNotification(CDTTreeMessengerType.searchChanged, event => this.onDidSearchChangedEvent.fire(event), { + this.messenger.onNotification(CDTTreeMessengerType.searchChanged, event => this.onDidSearchChangeEvent.fire(event), { sender: participant }) ]; From ddd3117dd1284fd82087b3a9872ccf1e9cd549fb Mon Sep 17 00:00:00 2001 From: Trinh Van Phong Date: Mon, 16 Mar 2026 14:49:17 +0700 Subject: [PATCH 3/3] Add backend search configuration and increase search debounce --- src/tree/browser/tree.tsx | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/tree/browser/tree.tsx b/src/tree/browser/tree.tsx index 3ab6318..3bd441c 100644 --- a/src/tree/browser/tree.tsx +++ b/src/tree/browser/tree.tsx @@ -60,6 +60,17 @@ export type CDTTreeProps = * Function to sort the data source. */ dataSourceSorter?: (dataSource: CDTTreeItem[]) => CDTTreeItem[]; + /** + * Configuration for search behavior. + */ + search?: { + /** + * Search mode. + * - 'client': filter locally in the webview (default) + * - 'backend': keep current content until backend returns updated data + */ + mode?: 'client' | 'backend'; + }; /** * Configuration for the expansion of the tree table. */ @@ -218,18 +229,20 @@ export const CDTTree = (props: CDTTreeProps) = const ref = React.useRef(null); const tblRef: Parameters[0]['ref'] = React.useRef(null); + const isBackendSearch = props.search?.mode === 'backend'; + // ==== Data ==== const filteredData = useMemo(() => { let data = props.dataSource ?? []; - if (globalSearchText) { + if (globalSearchText && !isBackendSearch) { data = filterTree(data, globalSearchText); } if (props.dataSourceSorter) { data = props.dataSourceSorter([...data]); } return data; - }, [props.dataSource, props.dataSourceSorter, globalSearchText]); + }, [props.dataSource, props.dataSourceSorter, globalSearchText, isBackendSearch]); // ==== Search ==== @@ -260,7 +273,7 @@ export const CDTTree = (props: CDTTreeProps) = const onSearchChange = useMemo( () => - debounce(300, (text: string) => { + debounce(600, (text: string) => { setGlobalSearchText(text); notifySearchChanged(text); }), @@ -285,18 +298,20 @@ export const CDTTree = (props: CDTTreeProps) = const expandedRowKeys = useMemo(() => { const expanded = new Set(props.expansion?.expandedRowKeys ?? []); - if (globalSearchText) { + if (globalSearchText && !isBackendSearch) { + // client-side search: // on search expand all nodes that match the search const matchingExpansion = traverseTree(filteredData, { predicate: item => item.matching ?? false, mapper: getAncestors }); matchingExpansion.forEach(ancestorHierarchy => ancestorHierarchy.forEach(ancestor => expanded.add(ancestor.key))); } else { + // normal mode or backend-search mode: // otherwise use the expandedRowKeys from the props but ensure that the selected element is also expanded if (autoSelectRowRef.current && selection) { getAncestors(selection).forEach(ancestor => expanded.add(ancestor.key)); } } return Array.from(expanded); - }, [filteredData, globalSearchText, props.expansion?.expandedRowKeys, selection, autoSelectRowRef.current]); + }, [filteredData, globalSearchText, isBackendSearch, props.expansion?.expandedRowKeys, selection, autoSelectRowRef.current]); const handleExpand = useCallback( (expanded: boolean, record: CDTTreeItem) => {