diff --git a/src/commands.ts b/src/commands.ts index bdf3fea..58bf032 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -81,7 +81,7 @@ export class PeripheralCommands { await p.updateData(); } } else { - this.dataTracker.updateData(); + return this.dataTracker.updateData(); } } diff --git a/src/components/tree/components/Codicon.tsx b/src/components/tree/components/Codicon.tsx new file mode 100644 index 0000000..1f50db2 --- /dev/null +++ b/src/components/tree/components/Codicon.tsx @@ -0,0 +1,21 @@ +/********************************************************************* + * Copyright (c) 2024 Arm Limited and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *********************************************************************/ + +import React from 'react'; + + +export interface CodiconProps { + icon: string; +} + +export const Codicon: React.FC = ({ icon }) => { + return ; + +}; diff --git a/src/components/tree/components/Expander.tsx b/src/components/tree/components/Expander.tsx new file mode 100644 index 0000000..7e9c7b9 --- /dev/null +++ b/src/components/tree/components/Expander.tsx @@ -0,0 +1,32 @@ +/********************************************************************* + * Copyright (c) 2024 Arm Limited and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *********************************************************************/ + +import { classNames } from 'primereact/utils'; +import React from 'react'; + +export interface ToggleItem { + expanded?: boolean | undefined; + leaf?: boolean | undefined; +} + +export interface ExpandToggleProps { + item: ToggleItem; + depth?: number; +} + +export const ExpandToggle: React.FC = ({ item, depth = 0 }) => { + return
; +}; diff --git a/src/components/tree/components/LabelCell.tsx b/src/components/tree/components/LabelCell.tsx new file mode 100644 index 0000000..2eb2433 --- /dev/null +++ b/src/components/tree/components/LabelCell.tsx @@ -0,0 +1,38 @@ +/********************************************************************* + * Copyright (c) 2024 Arm Limited and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *********************************************************************/ + +import React, { HTMLAttributes } from 'react'; +import Markdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; +import { Tooltip, TooltipContent, TooltipTrigger } from '../../tooltip/tooltip'; +import { AsTreeTableCellProps, AsTreeTableCell } from './TreeTableCell'; +import { createHighlightedText } from './utils'; +import { classNames } from 'primereact/utils'; + +export interface LabelProps extends AsTreeTableCellProps, HTMLAttributes { +} + +const Label: React.FC = React.forwardRef(({ row: _r, cell, expander: _e, ...props }, ref) => { + const text = createHighlightedText(cell.value, cell.highlight); + const label =
+ {text} +
; + + if (cell.tooltip === undefined) { + return label; + } + + return + {label} + {cell.tooltip} + ; +}); + +export const LabelCell = AsTreeTableCell(Label); diff --git a/src/components/tree/components/TextFieldCell.tsx b/src/components/tree/components/TextFieldCell.tsx new file mode 100644 index 0000000..2be348d --- /dev/null +++ b/src/components/tree/components/TextFieldCell.tsx @@ -0,0 +1,73 @@ +/********************************************************************* + * Copyright (c) 2024 Arm Limited and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *********************************************************************/ + +import './text-field-cell.css'; + +import { ReactWrapperProps } from '@microsoft/fast-react-wrapper'; +import { TextField } from '@vscode/webview-ui-toolkit'; +import { VSCodeTextField } from '@vscode/webview-ui-toolkit/react'; +import React from 'react'; +import { CDTTreeItem, CDTTreeTableColumn } from '../types'; +import { AsEditable, EditableComponentProps, EditableComponentRef, AsTreeTableCell } from './TreeTableCell'; + +export type VSCodeTextFieldComponent = React.Component, unknown, unknown> & TextField; + +const KEY_CHANGE_VALUE = [ + 'Enter' +]; + +const KEY_UNSELECT = [ + 'ArrowUp', + 'ArrowDown', + 'PageDown', + 'PageUp', + 'Escape' +]; + +export interface TextFielCellProps extends EditableComponentProps { + row: CDTTreeItem; + cell: CDTTreeTableColumn; +} + +const TextFieldComponent = React.forwardRef(({ row, cell, ...props }, ref) => { + const textFieldRef = React.useRef(null); + + React.useImperativeHandle(ref, () => ({ + focus: () => { + textFieldRef.current?.control.select(); + } + })); + + const onKeyDown = (event: React.KeyboardEvent) => { + event.stopPropagation(); + + if (KEY_CHANGE_VALUE.includes(event.key)) { + const element = event.currentTarget as HTMLInputElement; + props.onSubmitValue(element.value); + } + if (KEY_UNSELECT.includes(event.key)) { + props.onCancelEdit(); + } + }; + + + return onKeyDown(event)} + onClick={event => event.stopPropagation()} + onBlur={props.onCancelEdit} + />; +}); + +export const TextFieldCell = AsEditable(AsTreeTableCell(TextFieldComponent)); diff --git a/src/components/tree/components/TreeTableCell.tsx b/src/components/tree/components/TreeTableCell.tsx new file mode 100644 index 0000000..c6e4bdf --- /dev/null +++ b/src/components/tree/components/TreeTableCell.tsx @@ -0,0 +1,98 @@ +/********************************************************************* + * Copyright (c) 2024 Arm Limited and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *********************************************************************/ + +import React, { ComponentType, HTMLAttributes } from 'react'; +import { CDTTreeItem, CDTTreeTableColumn, CTDTreeMessengerType, CTDTreeWebviewContext } from '../types'; +import { Codicon } from './Codicon'; +import { ExpandToggle } from './Expander'; +import { useCDTTreeContext } from '../tree-context'; +import { LabelCell } from './LabelCell'; +import { classNames } from 'primereact/utils'; + +export interface AsTreeTableCellProps { + row: CDTTreeItem; + cell: CDTTreeTableColumn; + expander?: boolean; + cellProps?: HTMLAttributes; +} + +export const AsTreeTableCell =

(Component: ComponentType

) => { + const AsTreeTableCell = React.forwardRef((props, ref) => { + const { cellProps, ...componentProps } = props; + const { row, cell, expander } = componentProps; + return ( +

+ {expander && } + {cell.icon && } + +
+ ); + }); + return AsTreeTableCell; +}; + + +export interface AsEditableTreeTableCellProps extends AsTreeTableCellProps { + field: string; +} + +export interface EditableComponentRef { + focus: () => void; +} + +export interface EditableComponentProps { + onCancelEdit: () => void; + onSubmitValue: (value: string) => void; + labelProps?: HTMLAttributes; +} + +export const AsEditable =

(EditComponent: ComponentType

): React.FC & AsEditableTreeTableCellProps> => { + return (props) => { + const [isEditMode, setEditMode] = React.useState(false); + const editComponent = React.useRef(null); + const treeContext = useCDTTreeContext(); + const { labelProps, cell, row, expander, field } = props; + + React.useEffect(() => { + if (isEditMode && editComponent.current) { + editComponent.current.focus(); + } + }, [isEditMode]); + + const onStartEdit = (event: React.MouseEvent) => { + setEditMode(true); + event.stopPropagation(); + }; + + const onCancelEdit = () => { + setEditMode(false); + }; + + const onSubmitValue = (value: string) => { + treeContext.notify(CTDTreeMessengerType.changeValue, { + field, + item: row, + value + }); + setEditMode(false); + }; + + return isEditMode + ? + : { /* capture so no expansion */ }} />; + }; +}; diff --git a/src/components/tree/components/text-field-cell.css b/src/components/tree/components/text-field-cell.css new file mode 100644 index 0000000..9b19b7e --- /dev/null +++ b/src/components/tree/components/text-field-cell.css @@ -0,0 +1,28 @@ +/********************************************************************* + * Copyright (c) 2024 Arm Limited and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *********************************************************************/ + +.text-field-cell { + display: flex; + align-items: center; + background: var(--vscode-active-background); + color: var(--vscode-foreground); + border: var(--vscode-border); + font-family: var(--vscode-editor-font-family); + font-size: 13px; + height: 22px; + line-height: 22px; + padding: 0; + --input-height: 22px; +} + +.p-treetable .p-treetable-tbody .treetable-cell.editable:hover .tree-label { + text-decoration: underline dotted var(--vscode-foreground); + text-underline-offset: 4px; +} diff --git a/src/components/tree/components/tree.css b/src/components/tree/components/tree.css index 0285d55..d03d5f7 100644 --- a/src/components/tree/components/tree.css +++ b/src/components/tree/components/tree.css @@ -107,3 +107,7 @@ overflow: hidden; align-items: center; } + +.tree-node :first-child { + flex-grow: 1; +} \ No newline at end of file diff --git a/src/components/tree/components/tree.tsx b/src/components/tree/components/tree.tsx index 0d97eed..0ca7d6f 100644 --- a/src/components/tree/components/tree.tsx +++ b/src/components/tree/components/tree.tsx @@ -18,6 +18,7 @@ import React from 'react'; import { useCDTTreeContext } from '../tree-context'; import { CDTTreeItem, CTDTreeMessengerType, CTDTreeWebviewContext } from '../types'; import { createActions, createHighlightedText, createLabelWithTooltip } from './utils'; +import { ExpandToggle } from './Expander'; export type ComponentTreeProps = { nodes?: CDTTreeItem[]; @@ -61,13 +62,7 @@ export const ComponentTree = (props: ComponentTreeProps) => { }; const togglerTemplate = (node: TreeNode) => { - return

-
; + return ; }; return
diff --git a/src/components/tree/components/treetable.tsx b/src/components/tree/components/treetable.tsx index 397a556..67d3620 100644 --- a/src/components/tree/components/treetable.tsx +++ b/src/components/tree/components/treetable.tsx @@ -14,11 +14,12 @@ import './treetable.css'; import { Column } from 'primereact/column'; import { TreeNode } from 'primereact/treenode'; import { TreeTable, TreeTableEvent } from 'primereact/treetable'; -import { classNames } from 'primereact/utils'; import React from 'react'; import { useCDTTreeContext } from '../tree-context'; -import { CDTTreeItem, CDTTreeTableColumnDefinition, CDTTreeTableExpanderColumn, CDTTreeTableStringColumn, CTDTreeMessengerType, CTDTreeWebviewContext } from '../types'; -import { createActions, createHighlightedText, createIcon, createLabelWithTooltip } from './utils'; +import { CDTTreeItem, CDTTreeTableColumnDefinition, CTDTreeMessengerType } from '../types'; +import { LabelCell } from './LabelCell'; +import { TextFieldCell } from './TextFieldCell'; +import { createActions } from './utils'; export type ComponentTreeTableProps = { nodes?: CDTTreeItem[]; @@ -52,55 +53,25 @@ export const ComponentTreeTable = (props: ComponentTreeTableProps) => { }; // Sub Components - const template = (node: TreeNode, field: string) => { + const cellRenderer = (node: TreeNode, field: string, expander: boolean) => { CDTTreeItem.assert(node); const column = node.columns?.[field]; - - if (column?.type === 'expander') { - return expanderTemplate(node, column); - } else if (column?.type === 'string') { - return stringTemplate(node, column); + if (!column) { + return No columns provided for field {field}; } - return No columns provided for field {field}; - }; - - const expanderTemplate = (node: TreeNode, column: CDTTreeTableExpanderColumn) => { - CDTTreeItem.assert(node); - - return
-
-
-
- {createIcon(node)} - {createLabelWithTooltip({column.label}, column.tooltip)} -
-
; - }; - - const stringTemplate = (node: CDTTreeItem, column: CDTTreeTableStringColumn) => { - const text = createHighlightedText(column.label, column.highlight); - - return
- {createLabelWithTooltip(text, column.tooltip)} -
; + if (column.edit?.type === 'text') { + return ; + } + return ; }; const togglerTemplate = () => { return
; }; - const actionsTemplate = (node: TreeNode) => { + const actionsRenderer = (node: TreeNode) => { return
{createActions(treeContext, node)}
; @@ -125,10 +96,10 @@ export const ComponentTreeTable = (props: ComponentTreeTableProps) => { onCollapse={event => onToggle(event)} onRowClick={event => onClick(event)} > - {props.columnDefinitions?.map(c => { - return template(node, c.field)} expander={c.expander} />; + {props.columnDefinitions?.map((column, idx) => { + return cellRenderer(node, column.field, idx === 0)} expander={idx === 0} />; })} - +
; }; diff --git a/src/components/tree/components/utils.tsx b/src/components/tree/components/utils.tsx index d2032e8..7f88ef9 100644 --- a/src/components/tree/components/utils.tsx +++ b/src/components/tree/components/utils.tsx @@ -52,12 +52,8 @@ export function createLabelWithTooltip(child: React.JSX.Element, tooltip?: strin } return - - {label} - - - {tooltip} - + {label} + {tooltip} ; } @@ -71,7 +67,7 @@ export function createActions(context: CDTTreeContext, node: TreeNode): React.JS }; return
- {node.options?.commands?.map(a => onClick(event, a)}>)} + {node.options?.commands?.map(a => onClick(event, a)}>)}
; } diff --git a/src/components/tree/integration/webview/tree-webview-view-provider.ts b/src/components/tree/integration/webview/tree-webview-view-provider.ts index 6ec0cff..618477a 100644 --- a/src/components/tree/integration/webview/tree-webview-view-provider.ts +++ b/src/components/tree/integration/webview/tree-webview-view-provider.ts @@ -11,7 +11,7 @@ import * as vscode from 'vscode'; import { Messenger } from 'vscode-messenger'; import { WebviewIdMessageParticipant } from 'vscode-messenger-common'; -import { CDTTreeExecuteCommand, CDTTreeItem, CDTTreeState, CDTTreeViewType, CTDTreeMessengerType } from '../../types'; +import { CDTTreeExecuteCommand, CDTTreeItem, CDTTreeItemChangeValue, CDTTreeState, CDTTreeViewType, CTDTreeMessengerType } from '../../types'; import { CDTTreeDataProvider } from '../tree-data-provider'; export abstract class CDTTreeWebviewViewProvider implements vscode.WebviewViewProvider { @@ -22,6 +22,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 onDidChangeValueEvent = new vscode.EventEmitter(); + public readonly onDidChangeValue = this.onDidChangeValueEvent.event; abstract readonly type: CDTTreeViewType; @@ -110,6 +112,7 @@ export abstract class CDTTreeWebviewViewProvider implements vscode.Webvie this.messenger.onNotification(CTDTreeMessengerType.executeCommand, (command) => this.onDidExecuteCommandEvent.fire(command), { sender: this.participant }), this.messenger.onNotification(CTDTreeMessengerType.toggleNode, node => this.onDidToggleNodeEvent.fire(node), { sender: this.participant }), this.messenger.onNotification(CTDTreeMessengerType.clickNode, node => this.onDidClickNodeEvent.fire(node), { sender: this.participant }), + this.messenger.onNotification(CTDTreeMessengerType.changeValue, change => this.onDidChangeValueEvent.fire(change), { sender: this.participant }), ]; webview.onDidDispose(() => disposables.forEach(disposible => disposible.dispose())); diff --git a/src/components/tree/types.ts b/src/components/tree/types.ts index da6b119..9f852fe 100644 --- a/src/components/tree/types.ts +++ b/src/components/tree/types.ts @@ -19,18 +19,24 @@ export interface CDTTreeOptions { tooltip?: string, } -export interface CDTTreeTableExpanderColumn { - type: 'expander'; - icon?: string; - label: string; - tooltip?: string; +export interface EditableCellData { + type: string; +} + +export interface NoEditableData extends EditableCellData { + type: 'none'; } -export interface CDTTreeTableStringColumn { - type: 'string'; - label: string; +export interface EditableTextData extends EditableCellData { + type: 'text'; +} + +export interface CDTTreeTableColumn { + value: string; highlight?: [number, number][]; tooltip?: string; + icon?: string; + edit?: EditableTextData | NoEditableData; } export interface CDTTreeItem extends PrimeTreeNode { @@ -40,7 +46,7 @@ export interface CDTTreeItem extends PrimeTreeNode { icon?: string; path: string[]; options?: CDTTreeOptions; - columns?: Record; + columns?: Record; children?: CDTTreeItem[]; } @@ -49,6 +55,11 @@ export namespace CDTTreeItem { return '__type' in item && item.__type === 'CDTTreeItem'; } + export function as(item: PrimeTreeNode): CDTTreeItem { + assert(item); + return item; + } + export function assert(treeNode: PrimeTreeNode): asserts treeNode is CDTTreeItem { if (!is(treeNode)) { throw new Error(`Provided tree item isn't a valid CDTTreeItem: ${treeNode}`); @@ -67,7 +78,6 @@ export type CDTTreeViewType = 'tree' | 'treetable'; export interface CDTTreeTableColumnDefinition { field: string; - expander?: boolean; } export interface CDTTreeState { @@ -86,6 +96,7 @@ export interface CTDTreeWebviewContext { webviewSection: string; cdtTreeItemId: string; cdtTreeItemPath: string[]; + context?: string; } export namespace CTDTreeWebviewContext { @@ -98,10 +109,17 @@ export namespace CTDTreeWebviewContext { } } +export interface CDTTreeItemChangeValue { + item: CDTTreeItem; + field: string; + value: string; +} + export namespace CTDTreeMessengerType { export const updateState: NotificationType = { method: 'updateState' }; export const ready: NotificationType = { method: 'ready' }; export const executeCommand: NotificationType = { method: 'executeCommand' }; + export const changeValue: NotificationType = { method: 'changeValue' }; export const toggleNode: NotificationType = { method: 'toggleNode' }; export const clickNode: NotificationType = { method: 'clickNode' }; } diff --git a/src/plugin/peripheral/nodes/basenode.ts b/src/plugin/peripheral/nodes/basenode.ts index 82e8e20..887f26f 100644 --- a/src/plugin/peripheral/nodes/basenode.ts +++ b/src/plugin/peripheral/nodes/basenode.ts @@ -48,8 +48,8 @@ export abstract class PeripheralBaseNode extends BaseNode { this.pinned = false; } - public selected(): Thenable { - return Promise.resolve(false); + public async selected(): Promise { + return false; } public getId(): string { @@ -60,8 +60,8 @@ export abstract class PeripheralBaseNode extends BaseNode { return this.name ?? this.session?.id ?? 'unknown'; } - public abstract performUpdate(): Thenable; - public abstract updateData(): Thenable; + public abstract performUpdate(value?: string): Promise; + public abstract updateData(): Promise; public abstract getChildren(): PeripheralBaseNode[] | Promise; public abstract getPeripheral(): PeripheralBaseNode | undefined; diff --git a/src/plugin/peripheral/nodes/messagenode.ts b/src/plugin/peripheral/nodes/messagenode.ts index c788bae..b2ad81b 100644 --- a/src/plugin/peripheral/nodes/messagenode.ts +++ b/src/plugin/peripheral/nodes/messagenode.ts @@ -49,12 +49,12 @@ export class MessageNode extends PeripheralBaseNode { return undefined; } - public performUpdate(): Thenable { - return Promise.resolve(false); + public async performUpdate(): Promise { + return false; } - public updateData(): Thenable { - return Promise.resolve(false); + public async updateData(): Promise { + return false; } public getPeripheral(): PeripheralBaseNode | undefined { diff --git a/src/plugin/peripheral/nodes/peripheralclusternode.ts b/src/plugin/peripheral/nodes/peripheralclusternode.ts index 20da5a4..d119173 100644 --- a/src/plugin/peripheral/nodes/peripheralclusternode.ts +++ b/src/plugin/peripheral/nodes/peripheralclusternode.ts @@ -15,8 +15,6 @@ import { NumberFormat, NodeSetting } from '../../../common'; import { hexFormat } from '../../../utils'; import { CDTTreeItem } from '../../../components/tree/types'; - - export type PeripheralOrClusterNode = PeripheralNode | PeripheralClusterNode; export type PeripheralRegisterOrClusterNode = PeripheralRegisterNode | PeripheralClusterNode; @@ -92,13 +90,11 @@ export class PeripheralClusterNode extends ClusterOrRegisterBaseNode { }, columns: { 'title': { - type: 'expander', - label: this.getLabelTitle(), + value: this.getLabelTitle(), tooltip: this.description, }, 'value': { - type: 'string', - label: this.getLabelValue(), + value: this.getLabelValue(), tooltip: this.getLabelValue() } } @@ -139,15 +135,9 @@ export class PeripheralClusterNode extends ClusterOrRegisterBaseNode { } } - public updateData(): Thenable { - return new Promise((resolve, reject) => { - const promises = this.children.map((r) => r.updateData()); - Promise.all(promises).then(() => { - resolve(true); - }).catch(() => { - reject('Failed'); - }); - }); + public async updateData(): Promise { + await Promise.all(this.children.map((r) => r.updateData())); + return true; } public saveState(path: string): NodeSetting[] { @@ -189,7 +179,7 @@ export class PeripheralClusterNode extends ClusterOrRegisterBaseNode { throw new Error('Method not implemented.'); } - public performUpdate(): Thenable { + public performUpdate(): Promise { throw new Error('Method not implemented.'); } diff --git a/src/plugin/peripheral/nodes/peripheralfieldnode.ts b/src/plugin/peripheral/nodes/peripheralfieldnode.ts index 6815b4b..ec39e49 100644 --- a/src/plugin/peripheral/nodes/peripheralfieldnode.ts +++ b/src/plugin/peripheral/nodes/peripheralfieldnode.ts @@ -9,11 +9,11 @@ import * as vscode from 'vscode'; import { AddrRange } from '../../../addrranges'; import { AccessType, EnumerationMap, FieldOptions } from '../../../api-types'; import { CommandDefinition, NodeSetting, NumberFormat } from '../../../common'; +import { CDTTreeItem } from '../../../components/tree/types'; import { Commands } from '../../../manifest'; import { binaryFormat, hexFormat, parseInteger } from '../../../utils'; import { PERIPHERAL_ID_SEP, PeripheralBaseNode } from './basenode'; import { PeripheralRegisterNode } from './peripheralregisternode'; -import { CDTTreeItem } from '../../../components/tree/types'; export type PeripheralFieldNodeContextValue = 'field' | 'field-res' | 'fieldRO' | 'fieldWO' @@ -133,17 +133,16 @@ export class PeripheralFieldNode extends PeripheralBaseNode { }, columns: { 'title': { - type: 'expander', - label: this.getLabelTitle(), + value: this.getLabelTitle(), tooltip: this.generateTooltipMarkdown(this.isReserved())?.value ?? undefined, }, 'value': { - type: 'string', - label: labelValue, + value: labelValue, highlight: this.hasHighlights() ? [[0, labelValue.length]] : undefined, - tooltip: labelValue + tooltip: labelValue, + edit: { type: this.getContextValue() === 'field' || this.getContextValue() === 'fieldWO' ? 'text' : 'none' } } } }); @@ -305,9 +304,10 @@ export class PeripheralFieldNode extends PeripheralBaseNode { return []; } - public performUpdate(): Thenable { - return new Promise((resolve, reject) => { - if (this.enumeration) { + public async performUpdate(value?: string): Promise { + if (this.enumeration) { + let numval = value && this.enumerationValues.includes(value) ? this.enumerationMap[value] : undefined; + if (numval === undefined) { const items: vscode.QuickPickItem[] = []; for (const eStr of this.enumerationValues) { const numval = this.enumerationMap[eStr]; @@ -318,26 +318,24 @@ export class PeripheralFieldNode extends PeripheralBaseNode { }; items.push(item); } - vscode.window.showQuickPick(items).then((val) => { - if (val === undefined) { - return false; - } - - const numval = this.enumerationMap[val.label]; - this.parent.updateBits(this.offset, this.width, numval).then(resolve, reject); - }); - } else { - vscode.window.showInputBox({ prompt: 'Enter new value: (prefix hex with 0x, binary with 0b)', value: this.getCopyValue() }).then((val) => { - if (typeof val === 'string') { - const numval = parseInteger(val); - if (numval === undefined) { - return false; - } - this.parent.updateBits(this.offset, this.width, numval).then(resolve, reject); - } - }); + const val = await vscode.window.showQuickPick(items); + if (val === undefined) { + return false; + } + numval = this.enumerationMap[val.label]; } - }); + return this.parent.updateBits(this.offset, this.width, numval); + } else { + const val = value ?? await vscode.window.showInputBox({ prompt: 'Enter new value: (prefix hex with 0x, binary with 0b)', value: this.getCopyValue() }); + if (typeof val === 'string') { + const numval = parseInteger(val); + if (numval === undefined) { + return false; + } + return this.parent.updateBits(this.offset, this.width, numval); + } + } + return false; } public getCopyValue(): string { @@ -354,9 +352,8 @@ export class PeripheralFieldNode extends PeripheralBaseNode { } } - public updateData(): Thenable { - this.prevValue = this.getLabelValue(); - return Promise.resolve(true); + public async updateData(): Promise { + return true; } public getFormat(): NumberFormat { diff --git a/src/plugin/peripheral/nodes/peripheralnode.ts b/src/plugin/peripheral/nodes/peripheralnode.ts index c794a5a..74c1c96 100644 --- a/src/plugin/peripheral/nodes/peripheralnode.ts +++ b/src/plugin/peripheral/nodes/peripheralnode.ts @@ -112,13 +112,12 @@ export class PeripheralNode extends PeripheralBaseNode { }, columns: { 'title': { - type: 'expander', - label: this.getLabelTitle(), + value: this.getLabelTitle(), tooltip: this.description, + icon: this.pinned ? 'codicon codicon-pinned' : undefined }, 'value': { - type: 'string', - label: this.getLabelValue(), + value: this.getLabelValue(), tooltip: this.getLabelValue() } } @@ -251,7 +250,7 @@ export class PeripheralNode extends PeripheralBaseNode { return this; } - public selected(): Thenable { + public selected(): Promise { return this.performUpdate(); } @@ -287,7 +286,7 @@ export class PeripheralNode extends PeripheralBaseNode { } } - public performUpdate(): Thenable { + public performUpdate(): Promise { throw new Error('Method not implemented.'); } diff --git a/src/plugin/peripheral/nodes/peripheralregisternode.ts b/src/plugin/peripheral/nodes/peripheralregisternode.ts index ff839f1..ad2c302 100644 --- a/src/plugin/peripheral/nodes/peripheralregisternode.ts +++ b/src/plugin/peripheral/nodes/peripheralregisternode.ts @@ -17,6 +17,7 @@ import { MemUtils } from '../../../memreadutils'; import { extractBits, hexFormat, createMask, binaryFormat } from '../../../utils'; import { Commands } from '../../../manifest'; import { CDTTreeItem } from '../../../components/tree/types'; +import { resolve } from 'dns'; export type PeripheralRegisterNodeContextValue = 'registerRW' | 'registerRO' | 'registerWO' @@ -69,18 +70,15 @@ export class PeripheralRegisterNode extends ClusterOrRegisterBaseNode { return extractBits(this.currentValue, offset, width); } - public updateBits(offset: number, width: number, value: number): Thenable { - return new Promise((resolve, reject) => { - const limit = Math.pow(2, width); - if (value > limit) { - return reject(`Value entered is invalid. Maximum value for this field is ${limit - 1} (${hexFormat(limit - 1, 0)})`); - } else { - const mask = createMask(offset, width); - const sv = value << offset; - const newval = (this.currentValue & ~mask) | sv; - this.updateValueInternal(newval).then(resolve, reject); - } - }); + public async updateBits(offset: number, width: number, value: number): Promise { + const limit = Math.pow(2, width); + if (value > limit) { + throw new Error(`Value entered is invalid. Maximum value for this field is ${limit - 1} (${hexFormat(limit - 1, 0)})`); + } + const mask = createMask(offset, width); + const sv = value << offset; + const newval = (this.currentValue & ~mask) | sv; + return this.updateValueInternal(newval); } public getCommands(): CommandDefinition[] { @@ -155,6 +153,7 @@ export class PeripheralRegisterNode extends ClusterOrRegisterBaseNode { expanded: this.expanded, path: this.getId().split(PERIPHERAL_ID_SEP), options: { + // Show edit command even if inline editing is possible until we have more controls to cover proper enum support commands: this.getCommands(), contextValue: this.getContextValue(), tooltip: this.generateTooltipMarkdown()?.value ?? undefined, @@ -162,17 +161,16 @@ export class PeripheralRegisterNode extends ClusterOrRegisterBaseNode { }, columns: { 'title': { - type: 'expander', - label: this.getLabelTitle(), + value: this.getLabelTitle(), tooltip: this.generateTooltipMarkdown()?.value ?? undefined, }, 'value': { - type: 'string', - label: labelValue, + value: labelValue, highlight: this.hasHighlights() ? [[0, labelValue.length]] : undefined, - tooltip: labelValue + tooltip: labelValue, + edit: { type: this.getContextValue() === 'registerRW' ? 'text' : 'none' } } } }); @@ -290,17 +288,17 @@ export class PeripheralRegisterNode extends ClusterOrRegisterBaseNode { } } - public async performUpdate(): Promise { - const val = await vscode.window.showInputBox({ prompt: 'Enter new value: (prefix hex with 0x, binary with 0b)', value: this.getCopyValue() }); + public async performUpdate(value?: string): Promise { + const val = value ?? await vscode.window.showInputBox({ prompt: 'Enter new value: (prefix hex with 0x, binary with 0b)', value: this.getCopyValue() }); if (!val) { return false; } let numval: number; if (val.match(this.hexRegex)) { - numval = parseInt(val.substr(2), 16); + numval = parseInt(val.substring(2), 16); } else if (val.match(this.binaryRegex)) { - numval = parseInt(val.substr(2), 2); + numval = parseInt(val.substring(2), 2); } else if (val.match(/^[0-9]+/)) { numval = parseInt(val, 10); if (numval >= this.maxValue) { @@ -329,7 +327,7 @@ export class PeripheralRegisterNode extends ClusterOrRegisterBaseNode { return success; } - public updateData(): Thenable { + public async updateData(): Promise { const bc = this.size / 8; const bytes = this.parent.getBytes(this.offset, bc); const buffer = Buffer.from(bytes); @@ -350,7 +348,7 @@ export class PeripheralRegisterNode extends ClusterOrRegisterBaseNode { this.children.forEach((f) => f.updateData()); this.prevValue = this.getLabelValue(); - return Promise.resolve(true); + return true; } public saveState(path?: string): NodeSetting[] { diff --git a/src/plugin/peripheral/tree/peripheral-session-tree.ts b/src/plugin/peripheral/tree/peripheral-session-tree.ts index 1bca1d3..a7c7eea 100644 --- a/src/plugin/peripheral/tree/peripheral-session-tree.ts +++ b/src/plugin/peripheral/tree/peripheral-session-tree.ts @@ -188,16 +188,23 @@ export class PeripheralTreeForSession extends PeripheralBaseNode { this.errMessage = ''; } - public performUpdate(): Thenable { + public performUpdate(): Promise { throw new Error('Method not implemented.'); } - public updateData(): Thenable { + public async updateData(): Promise { if (this.loaded) { const promises = this.peripherials.map((p) => p.updateData()); - Promise.all(promises).then((_) => { this.fireCb(); }, (_) => { this.fireCb(); }); + try { + await Promise.all(promises); + return true; + } catch (error) { + return false; + } finally { + this.fireCb(); + } } - return Promise.resolve(true); + return true; } public getPeripheral(): PeripheralBaseNode { diff --git a/src/plugin/peripheral/tree/provider/peripheral-cdt-tree-data-provider.ts b/src/plugin/peripheral/tree/provider/peripheral-cdt-tree-data-provider.ts index 9297ed5..7706677 100644 --- a/src/plugin/peripheral/tree/provider/peripheral-cdt-tree-data-provider.ts +++ b/src/plugin/peripheral/tree/provider/peripheral-cdt-tree-data-provider.ts @@ -48,6 +48,17 @@ export class PeripheralCDTTreeDataProvider implements CDTTreeDataProvider { const node = this.getNodeByCDTTreeItem(e); this.dataTracker.toggleNode(node); + }), + webview.onDidChangeValue(async (e) => { + try { + const node = this.getNodeByCDTTreeItem(e.item); + const result = await node.performUpdate(e.value); + if (result) { + this.dataTracker.updateData(); + } + } catch (error) { + vscode.debug.activeDebugConsole.appendLine(`Unable to update value: ${(error as Error).message}`); + } }) ); } @@ -86,7 +97,7 @@ export class PeripheralCDTTreeDataProvider implements CDTTreeDataProvider