Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/tree/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ export class TreeDataProvider implements CDTTreeDataProvider<Example, ExampleDTO
}),
webview.onDidToggleNode(event => {
// Handle toggle
}),
webview.onDidSearchChange(event => {
// Handle search input changes
})
);
}
Expand Down
51 changes: 44 additions & 7 deletions src/tree/browser/tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';
Expand Down Expand Up @@ -57,6 +60,17 @@ export type CDTTreeProps<T extends CDTTreeItemResource = CDTTreeItemResource> =
* Function to sort the data source.
*/
dataSourceSorter?: (dataSource: CDTTreeItem<T>[]) => CDTTreeItem<T>[];
/**
* 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.
*/
Expand Down Expand Up @@ -215,18 +229,20 @@ export const CDTTree = <T extends CDTTreeItemResource>(props: CDTTreeProps<T>) =
const ref = React.useRef<HTMLDivElement | null>(null);
const tblRef: Parameters<typeof Table>[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 ====

Expand All @@ -238,12 +254,31 @@ export const CDTTree = <T extends CDTTreeItemResource>(props: CDTTreeProps<T>) =
}
}, []);

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 ====

Expand All @@ -263,18 +298,20 @@ export const CDTTree = <T extends CDTTreeItemResource>(props: CDTTreeProps<T>) =

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<T>) => {
Expand Down
1 change: 1 addition & 0 deletions src/tree/common/tree-messenger-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@ export namespace CDTTreeMessengerType {
export const toggleNode: NotificationType<CDTTreeNotification<string>> = { method: 'toggleNode' };
export const clickNode: NotificationType<CDTTreeNotification<string>> = { method: 'clickNode' };
export const openSearch: NotificationType<void> = { method: 'openSearch' };
export const searchChanged: NotificationType<CDTTreeNotification<{ text: string }>> = { method: 'searchChanged' };
}
5 changes: 5 additions & 0 deletions src/tree/vscode/tree-webview-view-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export abstract class CDTTreeWebviewViewProvider<TNode> implements vscode.Webvie
public readonly onDidExecuteCommand = this.onDidExecuteCommandEvent.event;
protected onDidClickNodeEvent = new vscode.EventEmitter<CDTTreeNotification<string>>();
public readonly onDidClickNode = this.onDidClickNodeEvent.event;
protected onDidSearchChangeEvent = new vscode.EventEmitter<CDTTreeNotification<{ text: string }>>();
public readonly onDidSearchChange = this.onDidSearchChangeEvent.event;

protected get extensionUri(): vscode.Uri {
return this.context.extensionUri;
Expand Down Expand Up @@ -119,6 +121,9 @@ export abstract class CDTTreeWebviewViewProvider<TNode> 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
})
];

Expand Down
Loading