Skip to content

Commit

Permalink
Add search capabilities to Peripheral Inspector view (#34)
Browse files Browse the repository at this point in the history
* Add search capabilities to Peripheral Inspector view

- Provide custom SearchOverlay component
- Add search overlay to filter tree and tree table

Closes #23

* Include children if search matches a particular item

* Add 'Search' icon to trigger search from view title bar

* Consider PR feedback
  • Loading branch information
martin-fleck-at authored Jan 13, 2025
1 parent 89663e2 commit 484775e
Show file tree
Hide file tree
Showing 16 changed files with 812 additions and 354 deletions.
24 changes: 19 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@
"title": "Set Value Format",
"icon": "$(symbol-unit)"
},
{
"command": "peripheral-inspector.svd.find",
"title": "Find",
"icon": "$(search)"
},
{
"command": "peripheral-inspector.svd.refreshAll",
"title": "Refresh All",
Expand Down Expand Up @@ -178,6 +183,10 @@
"command": "peripheral-inspector.svd.setFormat",
"when": "false"
},
{
"command": "peripheral-inspector.svd.find",
"when": "false"
},
{
"command": "peripheral-inspector.svd.refreshAll",
"when": "false"
Expand Down Expand Up @@ -231,19 +240,24 @@
],
"view/title": [
{
"command": "peripheral-inspector.svd.refreshAll",
"command": "peripheral-inspector.svd.collapseAll",
"when": "view =~ /peripheral-inspector.peripheral-*/ && debugState == stopped",
"group": "navigation"
"group": "navigation@1"
},
{
"command": "peripheral-inspector.svd.collapseAll",
"command": "peripheral-inspector.svd.find",
"when": "view =~ /peripheral-inspector.peripheral-*/",
"group": "navigation@2"
},
{
"command": "peripheral-inspector.svd.refreshAll",
"when": "view =~ /peripheral-inspector.peripheral-*/ && debugState == stopped",
"group": "navigation"
"group": "navigation@3"
},
{
"command": "peripheral-inspector.svd.exportAll",
"when": "view =~ /peripheral-inspector.peripheral-*/ && debugState == stopped",
"group": "navigation"
"group": "navigation@4"
}
],
"webview/context": [
Expand Down
11 changes: 9 additions & 2 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ import * as vscode from 'vscode';
import { NumberFormat } from './common/format';
import { TreeNotificationContext } from './common/notification';
import { PERIPHERAL_ID_SEP } from './common/peripheral-dto';
import { CTDTreeWebviewContext } from './components/tree/types';
import { CTDTreeMessengerType, CTDTreeWebviewContext } from './components/tree/types';
import { Commands } from './manifest';
import { PeripheralBaseNode } from './plugin/peripheral/nodes';
import { PeripheralDataTracker } from './plugin/peripheral/tree/peripheral-data-tracker';
import { PeripheralsTreeTableWebView } from './plugin/peripheral/webview/peripheral-tree-webview-main';
import { getFilePath } from './fileUtils';

export class PeripheralCommands {
public constructor(
protected readonly dataTracker: PeripheralDataTracker) {
protected readonly dataTracker: PeripheralDataTracker,
protected readonly webview: PeripheralsTreeTableWebView) {
}

public async activate(context: vscode.ExtensionContext): Promise<void> {
Expand All @@ -29,6 +31,7 @@ export class PeripheralCommands {
vscode.commands.registerCommand(Commands.FORCE_REFRESH_COMMAND.commandId, (node) => this.peripheralsForceRefresh(node)),
vscode.commands.registerCommand(Commands.PIN_COMMAND.commandId, (node, _, context) => this.peripheralsTogglePin(node, context)),
vscode.commands.registerCommand(Commands.UNPIN_COMMAND.commandId, (node, _, context) => this.peripheralsTogglePin(node, context)),
vscode.commands.registerCommand(Commands.FIND_COMMAND.commandId, () => this.find()),
vscode.commands.registerCommand(Commands.REFRESH_ALL_COMMAND.commandId, () => this.peripheralsForceRefresh()),
vscode.commands.registerCommand(Commands.COLLAPSE_ALL_COMMAND.commandId, () => this.collapseAll()),
vscode.commands.registerCommand(Commands.EXPORT_ALL_COMMAND.commandId, () => this.peripheralsExportAll()),
Expand Down Expand Up @@ -100,6 +103,10 @@ export class PeripheralCommands {
this.dataTracker.fireOnDidChange();
}

private async find(): Promise<void> {
this.webview.sendNotification(CTDTreeMessengerType.openSearch);
}

private async peripheralsForceRefresh(node?: PeripheralBaseNode): Promise<void> {
if (node) {
const p = node.getPeripheral();
Expand Down
43 changes: 43 additions & 0 deletions src/components/tree/components/expand-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/********************************************************************************
* Copyright (C) 2024 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the MIT License as outlined in the LICENSE File
********************************************************************************/
import { CDTTreeItem } from '../types';
import { classNames } from './utils';
import React from 'react';

export interface RenderExpandIconProps<RecordType> {
prefixCls: string;
expanded: boolean;
record: RecordType;
expandable: boolean;
onExpand: TriggerEventHandler<RecordType>;
}

export type TriggerEventHandler<RecordType> = (record: RecordType, event: React.MouseEvent<HTMLElement>) => void;

export function ExpandIcon({ expanded, onExpand, record, expandable }: RenderExpandIconProps<CDTTreeItem>): React.ReactElement {
if (!expandable) {
// simulate spacing to the left that we gain through expand icon so that leaf items look correctly intended
return <span className='leaf-item-spacer' />;
}

const doExpand = (event: React.MouseEvent<HTMLElement>) => {
event.stopPropagation();
onExpand(record, event);
};

const iconClass = expanded ? 'codicon-chevron-down' : 'codicon-chevron-right';
return (
<div
className={classNames('tree-toggler-container', 'codicon', iconClass)}
onClick={doExpand}
role="button"
tabIndex={0}
aria-label={expanded ? 'Collapse row' : 'Expand row'}
onKeyDown={event => { if (event.key === 'Enter' || event.key === ' ') doExpand(event as unknown as React.MouseEvent<HTMLElement>); }}
></div>
);
}
88 changes: 88 additions & 0 deletions src/components/tree/components/search-overlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*********************************************************************
* Copyright (c) 2024 Arm Limited and others
*
* This program and the accompanying materials are made available under the
* terms of the MIT License as outlined in the LICENSE File
********************************************************************************/

import { VSCodeButton } from '@vscode/webview-ui-toolkit/react';
import React from 'react';
import './search.css';

export interface SearchOverlayProps {
onChange?: (text: string) => void;
onShow?: () => void;
onHide?: () => void;
}

export interface SearchOverlay {
input: () => HTMLInputElement | null;
focus: () => void;
value(): string;
setValue: (value: string) => void;
show: () => void;
hide: () => void;
}

export const SearchOverlay = React.forwardRef<SearchOverlay, SearchOverlayProps>((props, ref) => {
const [showSearch, setShowSearch] = React.useState(false);
const searchTextRef = React.useRef<HTMLInputElement>(null);
const previousFocusedElementRef = React.useRef<HTMLElement | null>(null);

const show = () => {
previousFocusedElementRef.current = document.activeElement as HTMLElement;
setShowSearch(true);
setTimeout(() => searchTextRef.current?.select(), 100);
props.onShow?.();
};

const hide = () => {
setShowSearch(false);
props.onHide?.();
if (previousFocusedElementRef.current) {
previousFocusedElementRef.current.focus();
}
};

const onTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
props.onChange?.(value);
};

const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.ctrlKey && e.key === 'f') {
e.preventDefault();
e.stopPropagation();
show();
} else if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
hide();
}
};

const onFocus = (e: React.FocusEvent<HTMLInputElement>) => {
if (e.relatedTarget) {
previousFocusedElementRef.current = e.relatedTarget as HTMLElement;
}
};

React.useImperativeHandle(ref, () => ({
input: () => searchTextRef.current,
focus: () => searchTextRef.current?.focus(),
value: () => searchTextRef.current?.value ?? '',
setValue: (newValue: string) => {
if (searchTextRef.current) {
searchTextRef.current.value = newValue;
}
},
show: () => show(),
hide: () => hide()
}));

return (<div className={showSearch ? 'search-overlay visible' : 'search-overlay'} onKeyDown={onKeyDown}>
<input ref={searchTextRef} onChange={onTextChange} onFocus={onFocus} placeholder="Find" className="search-input" />
<VSCodeButton title='Close (Escape)' appearance='icon' aria-label='Close (Escape)'><span className='codicon codicon-close' onClick={() => hide()} /></VSCodeButton>
</div>
);
});
84 changes: 84 additions & 0 deletions src/components/tree/components/search.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/********************************************************************************
* Copyright (C) 2024 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the MIT License as outlined in the LICENSE File
********************************************************************************/

.search-overlay {
position: fixed;
top: -33px;
opacity: 0;
right: 20px;
background-color: var(--vscode-editorWidget-background);
box-shadow: 0 0 4px 1px var(--vscode-widget-shadow);
color: var(--vscode-editorWidget-foreground);
border-bottom: 1px solid var(--vscode-widget-border);
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-left: 1px solid var(--vscode-widget-border);
border-right: 1px solid var(--vscode-widget-border);
box-sizing: border-box;
height: 33px;
line-height: 19px;
overflow: hidden;
padding: 4px;
z-index: 35;
display: flex;
flex-direction: row;
gap: 5px;

-webkit-transition: top 0.2s ease, opacity 0.2s ease;
-moz-transition: top 0.2s ease, opacity 0.2s ease;
-ms-transition: top 0.2s ease, opacity 0.2s ease;
-o-transition: top 0.2s ease, opacity 0.2s ease;
transition: top 0.2s ease, opacity 0.2s ease;
}

.search-overlay.visible {
top: 5px;
opacity: 1;
}

body.has-scrollbar .search-overlay {
right: 5px;
}

.search-overlay .search-input {
color: var(--vscode-input-foreground);
background-color: var(--vscode-input-background);
outline: none;
scrollbar-width: none;
border: none;
box-sizing: border-box;
display: inline-block;
font-family: inherit;
font-size: inherit;
height: 100%;
line-height: inherit;
resize: none;
width: 100%;
padding: 4px 6px;
margin: 0;
}

.search-overlay input.search-input:focus {
outline: 1px solid var(--vscode-focusBorder)
}


.search-input::placeholder {
color: var(--vscode-input-placeholderForeground);
}

.search-input::-moz-placeholder {
color: var(--vscode-input-placeholderForeground);
}

.search-input:-ms-input-placeholder {
color: var(--vscode-input-placeholderForeground);
}

.search-input:-webkit-input-placeholder {
color: var(--vscode-input-placeholderForeground);
}
Loading

0 comments on commit 484775e

Please sign in to comment.