From 89663e2eaeb788d67574d0fefa61fb267fb36a40 Mon Sep 17 00:00:00 2001 From: QuocTrung76 <102133175+QuocTrung76@users.noreply.github.com> Date: Tue, 31 Dec 2024 18:37:01 +0700 Subject: [PATCH] implement export registers feature (#33) * implement export registers feature * implement export registers feature * implement export registers feature * implement export registers feature --- package.json | 48 +++++++- src/commands.ts | 23 ++++ src/common/peripheral-dto.ts | 2 + .../integration/peripheral-tree-converter.ts | 15 ++- src/fileUtils.ts | 16 +++ src/manifest.ts | 10 ++ src/plugin/peripheral/nodes/base-node.ts | 1 + .../nodes/peripheral-cluster-node.ts | 1 + .../peripheral/nodes/peripheral-field-node.ts | 1 + .../peripheral/nodes/peripheral-node.ts | 1 + .../nodes/peripheral-register-node.ts | 2 + .../tree/peripheral-data-tracker.ts | 109 +++++++++++++++++- 12 files changed, 223 insertions(+), 6 deletions(-) create mode 100644 src/fileUtils.ts diff --git a/package.json b/package.json index 6a636ab..88722e6 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,8 @@ "vscode-messenger": "^0.4.5", "vscode-messenger-common": "^0.4.5", "vscode-messenger-webview": "^0.4.5", - "xml2js": "^0.4.23" + "xml2js": "^0.4.23", + "xmlbuilder2": "^3.0.0" }, "devDependencies": { "@types/node": "^12.20.0", @@ -97,6 +98,11 @@ "title": "Update Value", "icon": "$(edit)" }, + { + "command": "peripheral-inspector.svd.exportNode", + "title": "Export Register", + "icon": "$(desktop-download)" + }, { "command": "peripheral-inspector.svd.copyValue", "title": "Copy Value", @@ -131,6 +137,11 @@ "command": "peripheral-inspector.svd.collapseAll", "title": "Collapse All", "icon": "$(collapse-all)" + }, + { + "command": "peripheral-inspector.svd.exportAll", + "title": "Export All", + "icon": "$(desktop-download)" } ], "menus": { @@ -143,6 +154,14 @@ "command": "peripheral-inspector.svd.copyValue", "when": "false" }, + { + "command": "peripheral-inspector.svd.exportNode", + "when": "false" + }, + { + "command": "peripheral-inspector.svd.exportAll", + "when": "false" + }, { "command": "peripheral-inspector.svd.forceRefresh", "when": "false" @@ -181,6 +200,26 @@ "command": "peripheral-inspector.svd.forceRefresh", "when": "view == peripheral-inspector.svd && viewItem =~ /peripheral.*/" }, + { + "command": "peripheral-inspector.svd.exportNode", + "when": "view == peripheral-inspector.svd && viewItem == cluster" + }, + { + "command": "peripheral-inspector.svd.exportNode", + "when": "view == peripheral-inspector.svd && viewItem == registerRW" + }, + { + "command": "peripheral-inspector.svd.exportNode", + "when": "view == peripheral-inspector.svd && viewItem == register" + }, + { + "command": "peripheral-inspector.svd.exportNode", + "when": "view == peripheral-inspector.svd && viewItem == registerRO" + }, + { + "command": "peripheral-inspector.svd.exportNode", + "when": "view == peripheral-inspector.svd" + }, { "command": "peripheral-inspector.svd.pin", "when": "view == peripheral-inspector.svd && viewItem == peripheral" @@ -200,6 +239,11 @@ "command": "peripheral-inspector.svd.collapseAll", "when": "view =~ /peripheral-inspector.peripheral-*/ && debugState == stopped", "group": "navigation" + }, + { + "command": "peripheral-inspector.svd.exportAll", + "when": "view =~ /peripheral-inspector.peripheral-*/ && debugState == stopped", + "group": "navigation" } ], "webview/context": [ @@ -264,4 +308,4 @@ "workspace", "ui" ] -} +} \ No newline at end of file diff --git a/src/commands.ts b/src/commands.ts index 09749f6..80e7afe 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -13,6 +13,7 @@ import { 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 { getFilePath } from './fileUtils'; export class PeripheralCommands { public constructor( @@ -22,6 +23,7 @@ export class PeripheralCommands { public async activate(context: vscode.ExtensionContext): Promise { context.subscriptions.push( vscode.commands.registerCommand(Commands.UPDATE_NODE_COMMAND.commandId, (node, value) => this.peripheralsUpdateNode(node, value)), + vscode.commands.registerCommand(Commands.EXPORT_NODE_COMMAND.commandId, node => this.peripheralsExportNode(node)), vscode.commands.registerCommand(Commands.COPY_VALUE_COMMAND.commandId, (node, value) => this.peripheralsCopyValue(node, value)), vscode.commands.registerCommand(Commands.SET_FORMAT_COMMAND.commandId, (node) => this.peripheralsSetFormat(node)), vscode.commands.registerCommand(Commands.FORCE_REFRESH_COMMAND.commandId, (node) => this.peripheralsForceRefresh(node)), @@ -29,6 +31,7 @@ export class PeripheralCommands { vscode.commands.registerCommand(Commands.UNPIN_COMMAND.commandId, (node, _, context) => this.peripheralsTogglePin(node, context)), 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()), ); } @@ -45,6 +48,26 @@ export class PeripheralCommands { } } + private async peripheralsExportNode( + node: PeripheralBaseNode, + ): Promise { + const filePath = await getFilePath(); + if (!filePath) { + this.dataTracker.fireOnDidChange(); + return; + } + this.dataTracker.exportNodeToXml(node, filePath); + } + + private async peripheralsExportAll(): Promise { + const filePath = await getFilePath(); + if (!filePath) { + this.dataTracker.fireOnDidChange(); + return; + } + this.dataTracker.exportAllNodesToXml(filePath); + } + private peripheralsCopyValue(_node: PeripheralBaseNode, value?: string): void { if (value) { vscode.env.clipboard.writeText(value); diff --git a/src/common/peripheral-dto.ts b/src/common/peripheral-dto.ts index 41d1ca6..dfbe3fa 100644 --- a/src/common/peripheral-dto.ts +++ b/src/common/peripheral-dto.ts @@ -42,6 +42,7 @@ export namespace PeripheralBaseTreeNodeDTO { export const PERIPHERAL_ID_SEP = '-'; export interface PeripheralBaseNodeDTO extends PeripheralBaseTreeNodeDTO { + name: string; format: NumberFormat; pinned?: boolean; session: string | undefined; @@ -159,6 +160,7 @@ export interface PeripheralRegisterNodeDTO extends ClusterOrRegisterBaseNodeDTO, resetValue: number; hexLength: number; offset: number; + size: number; address: number; } export namespace PeripheralRegisterNodeDTO { diff --git a/src/components/tree/integration/peripheral-tree-converter.ts b/src/components/tree/integration/peripheral-tree-converter.ts index ea422cb..4ce2a2a 100644 --- a/src/components/tree/integration/peripheral-tree-converter.ts +++ b/src/components/tree/integration/peripheral-tree-converter.ts @@ -90,7 +90,7 @@ export class PeripheralNodeConverter implements TreeResourceConverter): Record { const labelValue = hexFormat(peripheral.offset, 0); @@ -269,6 +274,10 @@ export class PeripheralClusterNodeConverter { type: 'string', label: labelValue, tooltip: labelValue + }, + 'actions': { + type: 'action', + commands: this.getCommands() } }; } diff --git a/src/fileUtils.ts b/src/fileUtils.ts new file mode 100644 index 0000000..e7aa630 --- /dev/null +++ b/src/fileUtils.ts @@ -0,0 +1,16 @@ +import * as vscode from 'vscode'; + +export async function getFilePath(): Promise { + const workspaceFolder = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri : undefined; + if (!workspaceFolder) { + return; + } + const defaultFilePath = vscode.Uri.joinPath(workspaceFolder, 'registers.xml'); + const fileUri = await vscode.window.showSaveDialog({ + defaultUri: defaultFilePath, + filters: { + 'xml': ['xml'], + }, + }); + return fileUri; +} diff --git a/src/manifest.ts b/src/manifest.ts index 8a11d58..d96c109 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -29,6 +29,11 @@ export namespace Commands { icon: 'edit', title: 'Update Value' } as const; + export const EXPORT_NODE_COMMAND: CommandDefinition = { + commandId: `${PACKAGE_NAME}.svd.exportNode`, + icon: 'desktop-download', + title: 'Export Register' + } as const; export const COPY_VALUE_COMMAND: CommandDefinition = { commandId: `${PACKAGE_NAME}.svd.copyValue`, icon: 'files', @@ -64,6 +69,11 @@ export namespace Commands { icon: 'collapse-all', title: 'Collapse All' } as const; + export const EXPORT_ALL_COMMAND: CommandDefinition = { + commandId: `${PACKAGE_NAME}.svd.exportAll`, + icon: 'desktop-download', + title: 'Export All' + } as const; } export namespace IgnorePeripherals { diff --git a/src/plugin/peripheral/nodes/base-node.ts b/src/plugin/peripheral/nodes/base-node.ts index acb3a41..f1ab824 100644 --- a/src/plugin/peripheral/nodes/base-node.ts +++ b/src/plugin/peripheral/nodes/base-node.ts @@ -90,6 +90,7 @@ export abstract class PeripheralBaseNode extends BaseTreeNode { return PeripheralBaseNodeDTO.create({ ...super.serialize(), id: this.getId(), + name: this.name || '', format: this.format, pinned: this.pinned, session: this.session?.id, diff --git a/src/plugin/peripheral/nodes/peripheral-cluster-node.ts b/src/plugin/peripheral/nodes/peripheral-cluster-node.ts index c5d454f..0b22e2b 100644 --- a/src/plugin/peripheral/nodes/peripheral-cluster-node.ts +++ b/src/plugin/peripheral/nodes/peripheral-cluster-node.ts @@ -144,6 +144,7 @@ export class PeripheralClusterNode extends ClusterOrRegisterBaseNode { return PeripheralClusterNodeDTO.create({ ...super.serialize(), ...this.options, + name: this.name, offset: this.offset, children: [] }); diff --git a/src/plugin/peripheral/nodes/peripheral-field-node.ts b/src/plugin/peripheral/nodes/peripheral-field-node.ts index 2370a20..3bafdd5 100644 --- a/src/plugin/peripheral/nodes/peripheral-field-node.ts +++ b/src/plugin/peripheral/nodes/peripheral-field-node.ts @@ -174,6 +174,7 @@ export class PeripheralFieldNode extends PeripheralBaseNode { return PeripheralFieldNodeDTO.create({ ...super.serialize(), ...this.options, + name: this.name, parentAddress: this.parent.getAddress(), previousValue: this.previousValue, currentValue: this.getCurrentValue(), diff --git a/src/plugin/peripheral/nodes/peripheral-node.ts b/src/plugin/peripheral/nodes/peripheral-node.ts index 9f01516..0fcdc34 100644 --- a/src/plugin/peripheral/nodes/peripheral-node.ts +++ b/src/plugin/peripheral/nodes/peripheral-node.ts @@ -229,6 +229,7 @@ export class PeripheralNode extends PeripheralBaseNode { ...super.serialize(), ...this.options, groupName: this.groupName, + name: this.name, children: [] }); } diff --git a/src/plugin/peripheral/nodes/peripheral-register-node.ts b/src/plugin/peripheral/nodes/peripheral-register-node.ts index eba1217..4509038 100644 --- a/src/plugin/peripheral/nodes/peripheral-register-node.ts +++ b/src/plugin/peripheral/nodes/peripheral-register-node.ts @@ -208,10 +208,12 @@ export class PeripheralRegisterNode extends ClusterOrRegisterBaseNode { return PeripheralRegisterNodeDTO.create({ ...super.serialize(), ...this.options, + name: this.name, previousValue: this.previousValue, currentValue: this.currentValue, resetValue: this.resetValue, offset: this.offset, + size: this.size, hexLength: this.hexLength, children: [], address: this.getAddress() diff --git a/src/plugin/peripheral/tree/peripheral-data-tracker.ts b/src/plugin/peripheral/tree/peripheral-data-tracker.ts index 2cb6a21..013d407 100644 --- a/src/plugin/peripheral/tree/peripheral-data-tracker.ts +++ b/src/plugin/peripheral/tree/peripheral-data-tracker.ts @@ -10,10 +10,20 @@ import { DebugTracker } from '../../../debug-tracker'; import * as manifest from '../../../manifest'; import { PeripheralInspectorAPI } from '../../../peripheral-inspector-api'; import { SvdResolver } from '../../../svd-resolver'; -import { MessageNode, PeripheralBaseNode, PeripheralRegisterNode } from '../nodes'; +import { + MessageNode, + PeripheralBaseNode, + PeripheralClusterNode, + PeripheralFieldNode, + PeripheralNode, + PeripheralRegisterNode, +} from '../nodes'; import { PeripheralTreeForSession } from './peripheral-session-tree'; import { TreeNotification, TreeNotificationContext } from '../../../common/notification'; import { PeripheralTreeDataProvider } from './peripheral-tree-data-provider'; +import * as xmlWriter from 'xmlbuilder2'; +import { XMLBuilder } from 'xmlbuilder2/lib/interfaces'; +import { hexFormat } from '../../../utils'; export class PeripheralDataTracker { protected onDidChangeEvent = new vscode.EventEmitter(); @@ -81,6 +91,103 @@ export class PeripheralDataTracker { } } + private async writeModuleToXml( + node: PeripheralBaseNode, + xmlBuilder: XMLBuilder, + ): Promise { + const item = await node.serialize(); + if (node instanceof PeripheralNode || node instanceof PeripheralClusterNode) { + const moduleElement = xmlBuilder.ele('module'); + moduleElement.att('name', item.name); + moduleElement.att('address', `${hexFormat(node.getAddress(0))}`); + + const childNodes = await this.getChildren(node); + await Promise.all( + childNodes.map((c: PeripheralBaseNode) => + this.writeModuleToXml(c, moduleElement), + ), + ); + } else if (node instanceof PeripheralRegisterNode) { + await this.writeRegisterToXml(node, xmlBuilder); + } + } + + private async writeRegisterToXml( + node: PeripheralRegisterNode, + parentElement: XMLBuilder, + ): Promise { + const item = await node.serialize(); + const registerElement = parentElement.ele('register'); + registerElement.att('name', item.name); + registerElement.att('address', `${hexFormat(item.address)}`); + registerElement.att('size', item.size.toString()); + registerElement.att('value', `${hexFormat(item.currentValue)}`); + + const childNodes = await this.getChildren(node); + await Promise.all( + childNodes.map((c: PeripheralBaseNode) => { + if (c instanceof PeripheralFieldNode) { + return this.writeFieldToXml(c, registerElement); + } + return this.writeRegisterToXml( + c as PeripheralRegisterNode, + registerElement, + ); + }), + ); + } + + private async writeFieldToXml( + node: PeripheralFieldNode, + parentElement: XMLBuilder, + ): Promise { + const item = await node.serialize(); + const rangestart = item.offset; + const rangeend = item.offset + item.width - 1; + + const fieldElement = parentElement.ele('bitfield'); + fieldElement.att('name', item.name); + fieldElement.att('bitrange', `[${rangeend}:${rangestart}]`); + fieldElement.att('value', `${hexFormat(item.currentValue)}`); + } + + public async exportNodeToXml( + node: PeripheralBaseNode, + filePath: vscode.Uri, + ): Promise { + const xmlBuilder = xmlWriter + .create({ version: '1.0', encoding: 'UTF-8' }) + .ele('moduletable'); + await this.writeModuleToXml(node, xmlBuilder); + + const xmlContent = this.finalizeXml(xmlBuilder); + await this.writeToFile(filePath, xmlContent); + } + + public async exportAllNodesToXml(filePath: vscode.Uri): Promise { + const xmlBuilder = xmlWriter + .create({ version: '1.0', encoding: 'UTF-8' }) + .ele('moduletable'); + const children = (await this.getChildren()) ?? []; + + await Promise.all( + children.map((c) => this.writeModuleToXml(c, xmlBuilder)), + ); + + const xmlContent = this.finalizeXml(xmlBuilder); + await this.writeToFile(filePath, xmlContent); + } + + private finalizeXml(xmlBuilder: XMLBuilder): Uint8Array { + const xmlString = xmlBuilder.end({ prettyPrint: true, allowEmptyTags: true }); + return new TextEncoder().encode(xmlString); + } + + private async writeToFile(filePath: vscode.Uri, content: Uint8Array): Promise { + await vscode.workspace.fs.writeFile(filePath, content); + this.fireOnDidChange(); + } + public expandNode( node: PeripheralBaseNode, emit = true,