From 20ffba558014bcf5a48935febbc01cc2d20c5b1c Mon Sep 17 00:00:00 2001 From: Marcel Ball Date: Wed, 27 Jan 2021 21:00:50 -0400 Subject: [PATCH] Added additional details to the hover tooltips for `Register` and `Field` nodes in the peripheral register viewer. Currently displays the following: 1) Field Node: * Name, computed memory address (with bit range), current value in selected format (also displayed directly in node) * Description from SVD file * Current value in the following formats: Enumeration value (if the SVD file defines enumeration values), hex, decimal and binary 2) Register Node: * Name, computed memory address, current value in selected format (also displayed directly in node) * Description from SVD file * Current value in hex, decimal and binary formats * Summary of fields if defined --- CHANGELOG.md | 2 +- .../views/nodes/peripheralfieldnode.ts | 142 +++++++++++++----- .../views/nodes/peripheralregisternode.ts | 80 ++++++++-- 3 files changed, 173 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57c1b86d..e5520580 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ New Features: * Enable ChibiOS RTOS support for the J-Link server - + * Added additional details to the the register and field level hover tooltips in the peripheral register view. # V0.3.10 This feature upgrades our VSCode dependency for the extension - as a result V0.3.10 will only support the Visual Studio Code version 1.52 and above. diff --git a/src/frontend/views/nodes/peripheralfieldnode.ts b/src/frontend/views/nodes/peripheralfieldnode.ts index dfdeca2c..e68d24eb 100644 --- a/src/frontend/views/nodes/peripheralfieldnode.ts +++ b/src/frontend/views/nodes/peripheralfieldnode.ts @@ -1,4 +1,4 @@ -import { TreeItem, TreeItemCollapsibleState, window, debug } from 'vscode'; +import { TreeItem, TreeItemCollapsibleState, window, debug, MarkdownString, TreeItemLabel } from 'vscode'; import { PeripheralBaseNode } from './basenode'; import { AccessType } from '../../svd'; import { PeripheralRegisterNode } from './peripheralregisternode'; @@ -74,49 +74,123 @@ export class PeripheralFieldNode extends PeripheralBaseNode { } public getTreeItem(): TreeItem | Promise { - const value = this.parent.extractBits(this.offset, this.width); + const isReserved = this.name.toLowerCase() === 'reserved'; + + const context = isReserved ? 'field-res' : (this.parent.accessType === AccessType.ReadOnly ? 'field-ro' : 'field'); + const rangestart = this.offset; const rangeend = this.offset + this.width - 1; + + const item = new TreeItem(`${this.name} [${rangeend}:${rangestart}]`, TreeItemCollapsibleState.None); + + item.contextValue = context; + item.tooltip = this.generateTooltipMarkdown(isReserved); + item.description = this.getFormattedValue(this.getFormat()); - const label = `${this.name} [${rangeend}:${rangestart}]`; + return item; + } + + private generateTooltipMarkdown(isReserved: boolean): MarkdownString | null { + const mds = new MarkdownString('', true); + mds.isTrusted = true; - const context = this.name.toLowerCase() === 'reserved' ? 'field-res' : (this.parent.accessType === AccessType.ReadOnly ? 'field-ro' : 'field'); + const address = `${ hexFormat(this.parent.getAddress()) }${ this.getFormattedRange() }`; - const item = new TreeItem(label, TreeItemCollapsibleState.None); - item.contextValue = context; - item.tooltip = this.description; + if (isReserved) { + mds.appendMarkdown(`| ${this.name }@${address} |        | *Reserved* |\n`); + mds.appendMarkdown('|:---|:---:|---:|'); + return mds; + } + + const formattedValue = this.getFormattedValue(this.getFormat(), true); - if (this.name.toLowerCase() !== 'reserved') { - if (this.accessType === AccessType.WriteOnly) { - item.description = ''; + mds.appendMarkdown(`| ${this.name }@${address} |        | *${formattedValue}* |\n`); + mds.appendMarkdown('|:---|:---:|---:|'); + + mds.appendMarkdown('\n____\n\n'); + mds.appendMarkdown(this.description); + + mds.appendMarkdown('\n_____\n\n'); + + // Don't try to display current value table for write only fields + if (this.accessType === AccessType.WriteOnly) { + return mds; + } + + const value = this.parent.extractBits(this.offset, this.width); + const hex = hexFormat(value, Math.ceil(this.width / 4), true); + const decimal = value.toString(); + const binary = binaryFormat(value, this.width); + + if (this.enumeration) { + mds.appendMarkdown('| Enumeration Value    | Hex    | Decimal    | Binary    |\n'); + mds.appendMarkdown('|:---|:---|:---|:---|\n'); + let ev = 'Unknown'; + if (this.enumeration[value]) { + ev = this.enumeration[value].name; } - else { - let formatted = ''; - switch (this.getFormat()) { - case NumberFormat.Decimal: - formatted = value.toString(); - break; - case NumberFormat.Binary: - formatted = binaryFormat(value, this.width); - break; - case NumberFormat.Hexidecimal: - formatted = hexFormat(value, Math.ceil(this.width / 4), true); - break; - default: - formatted = this.width >= 4 ? hexFormat(value, Math.ceil(this.width / 4), true) : binaryFormat(value, this.width); - break; - } - - if (this.enumeration && this.enumeration[value]) { - item.description = `${this.enumeration[value].name} (${formatted})`; - } - else { - item.description = formatted; - } + + mds.appendMarkdown(`| ${ ev }    | ${ hex }    | ${ decimal }    | ${ binary }    |\n\n`); + if (this.enumeration[value].description) { + mds.appendMarkdown(this.enumeration[value].description); } + } else { + mds.appendMarkdown('| Hex    | Decimal    | Binary    |\n'); + mds.appendMarkdown('|:---|:---|:---|\n'); + mds.appendMarkdown(`| ${ hex }    | ${ decimal }    | ${ binary }    |\n`); } - return item; + return mds; + } + + public getFormattedRange(): string { + const rangestart = this.offset; + const rangeend = this.offset + this.width - 1; + return `[${rangeend}:${rangestart}]`; + } + + public getFormattedValue(format: NumberFormat, includeEnumeration: boolean = true): string { + if (this.accessType === AccessType.WriteOnly) { + return ''; + } + + let formatted = ''; + const value = this.parent.extractBits(this.offset, this.width); + + switch (format) { + case NumberFormat.Decimal: + formatted = value.toString(); + break; + case NumberFormat.Binary: + formatted = binaryFormat(value, this.width); + break; + case NumberFormat.Hexidecimal: + formatted = hexFormat(value, Math.ceil(this.width / 4), true); + break; + default: + formatted = this.width >= 4 ? hexFormat(value, Math.ceil(this.width / 4), true) : binaryFormat(value, this.width); + break; + } + + if (includeEnumeration && this.enumeration) { + if (this.enumeration[value]) { + formatted = `${this.enumeration[value].name} (${formatted})`; + } else { + formatted = `Unkown Enumeration Value (${formatted})`; + } + } + + return formatted; + } + + public getEnumerationValue(value: number): string | null { + if (!this.enumeration) { + return null; + } + + if (this.enumeration[value]) { + return this.enumeration[value].name; + } } public getChildren(): PeripheralBaseNode[] | Promise { diff --git a/src/frontend/views/nodes/peripheralregisternode.ts b/src/frontend/views/nodes/peripheralregisternode.ts index 9f56230e..d2f97080 100644 --- a/src/frontend/views/nodes/peripheralregisternode.ts +++ b/src/frontend/views/nodes/peripheralregisternode.ts @@ -1,4 +1,4 @@ -import { TreeItem, TreeItemCollapsibleState, window, debug } from 'vscode'; +import { TreeItem, TreeItemCollapsibleState, window, debug, MarkdownString } from 'vscode'; import { PeripheralNode } from './peripheralnode'; import { PeripheralClusterNode } from './peripheralclusternode'; import { PeripheralBaseNode } from './basenode'; @@ -7,6 +7,7 @@ import { AccessType } from '../../svd'; import { extractBits, createMask, hexFormat, binaryFormat } from '../../utils'; import { NumberFormat, NodeSetting } from '../../../common'; import { AddressRangesInUse } from '../../addrranges'; +import { MessageChannel } from 'worker_threads'; export interface PeripheralRegisterOptions { name: string; @@ -83,26 +84,69 @@ export class PeripheralRegisterNode extends PeripheralBaseNode { const item = new TreeItem(label, collapseState); item.contextValue = this.accessType === AccessType.ReadWrite ? 'registerRW' : (this.accessType === AccessType.ReadOnly ? 'registerRO' : 'registerWO'); - item.tooltip = this.description; + item.tooltip = this.generateTooltipMarkdown(); + item.description = this.getFormattedValue(this.getFormat()); + return item; + } + + private generateTooltipMarkdown(): MarkdownString | null { + const mds = new MarkdownString('', true); + mds.isTrusted = true; + + const address = `${ hexFormat(this.getAddress()) }`; + + const formattedValue = this.getFormattedValue(this.getFormat()); + + mds.appendMarkdown(`| ${this.name }@${address} |        | *${formattedValue}* |\n`); + mds.appendMarkdown('|:---|:---:|---:|\n'); + + mds.appendMarkdown('\n____\n\n'); + mds.appendMarkdown(this.description); + + mds.appendMarkdown('\n_____\n\n'); + + // Don't try to display current value table for write only fields if (this.accessType === AccessType.WriteOnly) { - item.description = ''; + return mds; } - else { - switch (this.getFormat()) { - case NumberFormat.Decimal: - item.description = this.currentValue.toString(); - break; - case NumberFormat.Binary: - item.description = binaryFormat(this.currentValue, this.hexLength * 4, false, true); - break; - default: - item.description = hexFormat(this.currentValue, this.hexLength); - break; - } + + const hex = this.getFormattedValue(NumberFormat.Hexidecimal); + const decimal = this.getFormattedValue(NumberFormat.Decimal); + const binary = this.getFormattedValue(NumberFormat.Binary); + + mds.appendMarkdown('| Hex    | Decimal    | Binary    |\n'); + mds.appendMarkdown('|:---|:---|:---|\n'); + mds.appendMarkdown(`| ${ hex }    | ${ decimal }    | ${ binary }    |\n\n`); + + const children = this.getChildren(); + if (children.length === 0) { return mds; } + + mds.appendMarkdown('**Fields**\n\n'); + mds.appendMarkdown('| Field |        | Bit-Range | Value |\n'); + mds.appendMarkdown('|:---|:---:|:---|:---|\n'); + + children.forEach((field) => { + mds.appendMarkdown(`| ${ field.name } |        | ${ field.getFormattedRange() } | ${ field.getFormattedValue(field.getFormat(), true) } |\n`); + }); + + return mds; + } + + public getFormattedValue(format: NumberFormat): string { + if (this.accessType === AccessType.WriteOnly) { + return ''; } + const value = this.currentValue; - return item; + switch (format) { + case NumberFormat.Decimal: + return value.toString(); + case NumberFormat.Binary: + return binaryFormat(value, this.hexLength * 4); + default: + return hexFormat(value, this.hexLength, true); + } } public getChildren(): PeripheralFieldNode[] { @@ -157,6 +201,10 @@ export class PeripheralRegisterNode extends PeripheralBaseNode { }); } + public getAddress(): number { + return this.parent.getAddress(this.offset); + } + private updateValueInternal(value: number): Thenable { const address = this.parent.getAddress(this.offset); const bytes = [];