diff --git a/src/tree/README.md b/src/tree/README.md index cd437d3..4224a99 100644 --- a/src/tree/README.md +++ b/src/tree/README.md @@ -68,6 +68,9 @@ export class TreeDataProvider implements CDTTreeDataProvider { // Handle toggle + }), + webview.onDidSearchChange(event => { + // Handle search input changes }) ); } diff --git a/src/tree/browser/tree.tsx b/src/tree/browser/tree.tsx index d2d5798..3bd441c 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'; @@ -57,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. */ @@ -215,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 ==== @@ -238,12 +254,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(600, (text: string) => { + setGlobalSearchText(text); + notifySearchChanged(text); + }), + [notifySearchChanged] + ); // ==== Selection ==== @@ -263,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) => { 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..25f2d08 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 onDidSearchChangeEvent = new vscode.EventEmitter>(); + public readonly onDidSearchChange = this.onDidSearchChangeEvent.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.onDidSearchChangeEvent.fire(event), { + sender: participant }) ];