From 81f0098eb64bbe7aa20b3db572a36b4c13583c45 Mon Sep 17 00:00:00 2001 From: "Desmond A. Kirkpatrick" Date: Thu, 27 Aug 2026 05:52:38 -0700 Subject: [PATCH 01/10] Fix shared popup menu type compatibility Use material_ui popup menu entries in the shared DevTools widget helpers so consumers using material_ui showMenu receive compatible types. Keep Flutter Material test harnesses for native dialog localization and assert the returned popup types. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../lib/src/bit_expansion_menu.dart | 2 +- .../lib/src/cross_probe_menu.dart | 2 +- .../packages/rohd_devtools_widgets/pubspec.yaml | 5 +++-- .../test/bit_expansion_menu_test.dart | 17 ++++++++++++----- .../test/cross_probe_menu_test.dart | 5 +++++ 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_expansion_menu.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_expansion_menu.dart index c01c55e31..fb7e0affd 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_expansion_menu.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_expansion_menu.dart @@ -10,7 +10,7 @@ // 2026 January // Author: Desmond Kirkpatrick -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'bit_field_utils.dart'; diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_menu.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_menu.dart index b8134e4ad..d52566a06 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_menu.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_menu.dart @@ -15,7 +15,7 @@ // 2026 June // Author: Desmond Kirkpatrick -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; import 'rohd_extension_status.dart'; diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/pubspec.yaml b/rohd_devtools_extension/packages/rohd_devtools_widgets/pubspec.yaml index dcb1b7a95..ae8a3fabe 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/pubspec.yaml +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/pubspec.yaml @@ -3,10 +3,11 @@ description: "Shared widgets for ROHD DevTools sub-packages: help button, PNG ex version: 0.1.0 publish_to: none environment: - sdk: '>=3.4.0 <4.0.0' - flutter: '>=3.0.0' + sdk: '>=3.12.0 <4.0.0' + flutter: '>=3.44.0' dependencies: flutter: {sdk: flutter} + material_ui: ^1.1.0 rohd: ^0.6.9 web: ^1.0.0 dev_dependencies: diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_expansion_menu_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_expansion_menu_test.dart index 921ebe684..df97790b9 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_expansion_menu_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_expansion_menu_test.dart @@ -9,6 +9,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:material_ui/material_ui.dart' as material_ui; import 'package:rohd_devtools_widgets/rohd_devtools_widgets.dart'; void main() { @@ -22,17 +23,23 @@ void main() { ); expect(items, hasLength(3)); - expect(items[0], isA()); - expect((items[1] as PopupMenuItem).value, 'expand_bits'); - expect((items[2] as PopupMenuItem).value, 'define_fields'); + expect(items[0], isA()); + expect( + (items[1] as material_ui.PopupMenuItem).value, + 'expand_bits', + ); + expect( + (items[2] as material_ui.PopupMenuItem).value, + 'define_fields', + ); await tester.pumpWidget( MaterialApp( home: Material( child: Column( children: [ - (items[1] as PopupMenuItem).child!, - (items[2] as PopupMenuItem).child!, + (items[1] as material_ui.PopupMenuItem).child!, + (items[2] as material_ui.PopupMenuItem).child!, ], ), ), diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_menu_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_menu_test.dart index 4da66a7af..cc34e7b0b 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_menu_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_menu_test.dart @@ -9,6 +9,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:material_ui/material_ui.dart' as material_ui; import 'package:rohd_devtools_widgets/rohd_devtools_widgets.dart'; void main() { @@ -105,6 +106,10 @@ void main() { ); expect(items, hasLength(2)); + expect( + items, + everyElement(isA>()), + ); expect(items[0].value, 'goto_source:rohd'); expect(items[1].value, 'goto_source:sv'); From 0777211d74b80de4bad0e5ec19ccdef335390c39 Mon Sep 17 00:00:00 2001 From: "Desmond A. Kirkpatrick" Date: Thu, 27 Aug 2026 05:56:41 -0700 Subject: [PATCH 02/10] Accept Flutter analyzer exclusions Track Flutter's generated exclusions for build and platform directories so dependency resolution does not leave the package worktree dirty. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../packages/rohd_devtools_widgets/analysis_options.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/analysis_options.yaml b/rohd_devtools_extension/packages/rohd_devtools_widgets/analysis_options.yaml index 572dd239d..c3810a1c8 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/analysis_options.yaml +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/analysis_options.yaml @@ -1 +1,10 @@ +analyzer: + exclude: + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** include: package:lints/recommended.yaml From 8938284f86f6d09c2886f8c019e812c6372628e0 Mon Sep 17 00:00:00 2001 From: "Desmond A. Kirkpatrick" Date: Thu, 27 Aug 2026 06:40:28 -0700 Subject: [PATCH 03/10] Share VS Code extension assets Store the module-info helper in rohd_devtools_widgets so the schematic and waveform extensions can package the same implementation from either a local or Git package source. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../assets/extension/module_info_helper.js | 159 ++++++++++++++++++ .../rohd_devtools_widgets/pubspec.yaml | 4 + 2 files changed, 163 insertions(+) create mode 100644 rohd_devtools_extension/packages/rohd_devtools_widgets/assets/extension/module_info_helper.js diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/assets/extension/module_info_helper.js b/rohd_devtools_extension/packages/rohd_devtools_widgets/assets/extension/module_info_helper.js new file mode 100644 index 000000000..fdcf44069 --- /dev/null +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/assets/extension/module_info_helper.js @@ -0,0 +1,159 @@ +// Copyright (C) 2026 Intel Corporation +// SPDX-License-Identifier: BSD-3-Clause +// +// module_info_helper.js +// Shared helper for querying FLC module info via the rohd_extension commands. +// +// Used by both rohd-schematic-viewer (plain JS) and rohd-wave-viewer +// (TypeScript) VS Code extensions to resolve format availability for a module. +// +// 2026 May +// Author: Desmond Kirkpatrick + +const vscode = require('vscode'); +const path = require('path'); +const fs = require('fs'); + +/** + * Resolve the `.flc.json` sidecar path for a given document URI. + * Delegates to the `rohd.resolveFlcPath` command in rohd_extension. + * Falls back to local convention if the command is unavailable. + * + * @param {import('vscode').Uri} documentUri + * @param {import('vscode').OutputChannel} [output] + * @returns {Promise} + */ +async function resolveFlcPath(documentUri, output) { + try { + const result = await vscode.commands.executeCommand('rohd.resolveFlcPath', { + documentFsPath: documentUri.fsPath, + }); + return result ?? null; + } catch (_) { + // rohd_extension not installed - fall back to local convention. + const fsPath = documentUri.fsPath; + const dir = path.dirname(fsPath); + const base = path.basename(fsPath); + // .vcd/.fst/.ghw/.rohd.json -> .flc.json + const flcName = base.replace(/\.(vcd|fst|ghw|rohd\.json)$/i, '.flc.json'); + if (flcName === base) return null; + const flcPath = path.join(dir, flcName); + return fs.existsSync(flcPath) ? flcPath : null; + } +} + +/** + * Build a RohdModuleInfo-compatible JSON object for [moduleName]. + * + * Delegates to the `rohd.queryModule` VS Code command provided by + * rohd_extension, which owns all FLC parsing logic. + * + * @param {import('vscode').Uri} documentUri + * @param {string|null} moduleName + * @param {string[]|undefined} instancePath + * @param {import('vscode').OutputChannel} [output] + * @returns {Promise} matching RohdModuleInfo.toJson() schema + */ +async function buildModuleInfo(documentUri, moduleName, instancePath, output) { + const flcPath = await resolveFlcPath(documentUri, output); + if (!flcPath) { + return { + extensionAvailable: true, + module: moduleName, + formats: {}, + error: 'No .flc.json sidecar found. Generate with TraceService.writeFlcFiles().', + fstLoading: false, + }; + } + + const queryModule = async module => { + const payload = { + flcPath, + module, + }; + if (instancePath && instancePath.length > 0) { + payload.instancePath = instancePath; + } + return vscode.commands.executeCommand('rohd.queryModule', payload); + }; + + try { + let info = await queryModule(moduleName); + + const hasFormats = + info && typeof info === 'object' && Object.keys(info.formats ?? {}).length > 0; + if (!hasFormats && instancePath && instancePath.length > 0) { + const instanceName = [...instancePath] + .reverse() + .find(segment => typeof segment === 'string' && segment.length > 0); + if (instanceName && instanceName !== moduleName) { + if (output) { + output.appendLine( + '[moduleInfo] no formats for ' + + String(moduleName ?? '') + + '; retrying with instance name ' + + instanceName, + ); + } + const fallbackInfo = await queryModule(instanceName); + const fallbackHasFormats = + fallbackInfo && + typeof fallbackInfo === 'object' && + Object.keys(fallbackInfo.formats ?? {}).length > 0; + if (fallbackHasFormats) { + info = fallbackInfo; + } + } + } + + return info ?? { + extensionAvailable: true, + module: moduleName, + formats: {}, + error: 'rohd.queryModule returned no result.', + fstLoading: false, + }; + } catch (e) { + if (output) output.appendLine('[moduleInfo] rohd.queryModule failed: ' + e.message); + return { + extensionAvailable: true, + module: moduleName, + formats: {}, + error: 'Install the ROHD extension for source format detection.', + fstLoading: false, + }; + } +} + +/** + * Look up signal source frames via rohd_extension's `rohd.lookupSignal` command. + * + * @param {string} flcPath + * @param {string|null} moduleName + * @param {string} signalName + * @param {string|undefined} format + * @param {import('vscode').OutputChannel} [output] + * @returns {Promise} + */ +async function lookupSignalFrames(flcPath, moduleName, signalName, format, output) { + try { + const frames = await vscode.commands.executeCommand('rohd.lookupSignal', { + flcPath, + module: moduleName, + signal: signalName, + format, + }); + const typedFrames = frames ?? []; + const filteredFrames = format + ? typedFrames.filter(frame => (frame?.type ?? 'rohd') === format) + : typedFrames; + // Reverse to outermost-first order (the ROHD extension returns + // innermost-first, matching raw stack-trace order). + return filteredFrames.reverse(); + } catch (e) { + if (output) output.appendLine('[crossProbe] rohd.lookupSignal failed: ' + e.message); + return []; + } +} + +module.exports = { resolveFlcPath, buildModuleInfo, lookupSignalFrames }; diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/pubspec.yaml b/rohd_devtools_extension/packages/rohd_devtools_widgets/pubspec.yaml index ae8a3fabe..4383b9631 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/pubspec.yaml +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/pubspec.yaml @@ -17,3 +17,7 @@ dev_dependencies: dependency_overrides: rohd: path: ../../.. + +flutter: + assets: + - assets/extension/ From 2fbb789a4795b5ec61dbc05e25b5b2222f9800e9 Mon Sep 17 00:00:00 2001 From: "Desmond A. Kirkpatrick" Date: Thu, 27 Aug 2026 07:23:15 -0700 Subject: [PATCH 04/10] updated analysis, fixes to support widgets on modern flutter --- rohd_devtools_extension/analysis_options.yaml | 8 + .../lib/src/app_bar_overlay.dart | 19 +- .../lib/src/bit_expansion_menu.dart | 53 ++--- .../lib/src/bit_field_utils.dart | 10 +- .../lib/src/capture_boundary.dart | 8 +- .../lib/src/cross_probe_menu.dart | 182 +++++++++--------- .../lib/src/cross_probe_service.dart | 5 +- .../lib/src/logic_type_utils.dart | 12 +- .../lib/src/markdown_help_button.dart | 118 +++++++----- .../lib/src/rohd_extension_client.dart | 6 +- .../lib/src/rohd_extension_status.dart | 82 ++++---- .../test/app_bar_overlay_test.dart | 21 +- .../test/bit_expansion_menu_test.dart | 15 +- .../test/bit_field_utils_test.dart | 97 +++++----- .../test/capture_boundary_test.dart | 41 ++-- .../test/cross_probe_button_test.dart | 6 +- .../test/cross_probe_menu_test.dart | 49 ++--- .../test/cross_probe_service_test.dart | 5 +- .../test/export_button_test.dart | 15 +- .../test/export_toast_test.dart | 5 +- .../test/logic_type_utils_test.dart | 158 +++++++-------- .../test/markdown_help_button_test.dart | 51 ++--- .../test/rohd_extension_status_test.dart | 4 +- 23 files changed, 492 insertions(+), 478 deletions(-) diff --git a/rohd_devtools_extension/analysis_options.yaml b/rohd_devtools_extension/analysis_options.yaml index f82d6cc51..1bfb3d100 100644 --- a/rohd_devtools_extension/analysis_options.yaml +++ b/rohd_devtools_extension/analysis_options.yaml @@ -2,6 +2,14 @@ # https://rydmike.com/blog_flutter_linting.html analyzer: + exclude: + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** language: strict-casts: true strict-inference: true diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/app_bar_overlay.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/app_bar_overlay.dart index ba210d5b2..4364bb93a 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/app_bar_overlay.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/app_bar_overlay.dart @@ -67,14 +67,17 @@ class _AppBarOverlayState extends State vsync: this, duration: widget.animationDuration, ); - _slideAnimation = Tween( - begin: const Offset(0, -1), // fully off-screen above - end: Offset.zero, - ).animate(CurvedAnimation( - parent: _controller, - curve: Curves.easeOutCubic, - reverseCurve: Curves.easeInCubic, - )); + _slideAnimation = + Tween( + begin: const Offset(0, -1), // fully off-screen above + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: _controller, + curve: Curves.easeOutCubic, + reverseCurve: Curves.easeInCubic, + ), + ); // If not auto-hiding, snap open. if (!widget.autoHide) { diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_expansion_menu.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_expansion_menu.dart index fb7e0affd..a676fed4f 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_expansion_menu.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_expansion_menu.dart @@ -11,20 +11,18 @@ // Author: Desmond Kirkpatrick import 'package:material_ui/material_ui.dart'; - -import 'bit_field_utils.dart'; +import 'package:rohd_devtools_widgets/src/bit_field_utils.dart'; /// Popup-menu values used by the bit-expansion items. /// /// Callers may match against these string constants when handling a /// `showMenu` result that includes bit-expansion items. abstract final class BitExpansionMenuValues { - /// Menu item: "Expand Bits [N]" — expand each bit (or a chosen range) - /// as a synthesized 1-bit waveform. + /// "Expand Bits" menu item for expanding each bit (or a chosen range) as a + /// synthesized 1-bit waveform. static const String expandBits = 'expand_bits'; - /// Menu item: "Define Bit Fields [N]..." — open a dialog that lets the - /// user name arbitrary bit ranges. + /// "Define Bit Fields" menu item for naming arbitrary bit ranges. static const String defineFields = 'define_fields'; } @@ -44,6 +42,8 @@ class BitExpandRangeAction extends BitExpansionAction { /// High bit (inclusive) of the range to expand. final int bitEnd; + /// Creates an action to expand the inclusive range from [bitStart] to + /// [bitEnd]. const BitExpandRangeAction(this.bitStart, this.bitEnd); } @@ -53,6 +53,7 @@ class BitDefineFieldsAction extends BitExpansionAction { /// The user-defined bit fields. final List fields; + /// Creates an action defining [fields] for a signal. const BitDefineFieldsAction(this.fields); } @@ -73,24 +74,22 @@ List> buildBitExpansionMenuItems({ double fontSize = 13, double itemHeight = 32, bool includeDivider = false, -}) { - return >[ - if (includeDivider) const PopupMenuDivider(height: 8), - PopupMenuItem( - height: itemHeight, - value: BitExpansionMenuValues.expandBits, - child: Text('Expand Bits [$width]', style: TextStyle(fontSize: fontSize)), - ), - PopupMenuItem( - height: itemHeight, - value: BitExpansionMenuValues.defineFields, - child: Text( - 'Define Bit Fields [$width]...', - style: TextStyle(fontSize: fontSize), - ), +}) => >[ + if (includeDivider) const PopupMenuDivider(height: 8), + PopupMenuItem( + height: itemHeight, + value: BitExpansionMenuValues.expandBits, + child: Text('Expand Bits [$width]', style: TextStyle(fontSize: fontSize)), + ), + PopupMenuItem( + height: itemHeight, + value: BitExpansionMenuValues.defineFields, + child: Text( + 'Define Bit Fields [$width]...', + style: TextStyle(fontSize: fontSize), ), - ]; -} + ), +]; /// Translate a popup-menu [value] returned by `showMenu` into a /// [BitExpansionAction], showing any follow-up dialog as needed. @@ -122,7 +121,9 @@ Future resolveBitExpansionMenuValue( signalName: signalName, width: width, ); - if (parsed == null) return null; + if (parsed == null) { + return null; + } final (high, low) = parsed; return BitExpandRangeAction(low, high); } @@ -132,7 +133,9 @@ Future resolveBitExpansionMenuValue( signalName: signalName, width: width, ); - if (fields == null || fields.isEmpty) return null; + if (fields == null || fields.isEmpty) { + return null; + } return BitDefineFieldsAction(fields); } return null; diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_field_utils.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_field_utils.dart index efb61eb93..e9156e686 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_field_utils.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_field_utils.dart @@ -94,7 +94,8 @@ abstract final class BitFieldUtils { } // Try: name bit (single bit) - final namedSingle = tokens.length == 2 && + final namedSingle = + tokens.length == 2 && _isWord(tokens[0]) && _isUnsignedDecimal(tokens[1]); if (namedSingle) { @@ -259,9 +260,7 @@ Future?> showDefineBitFieldsDialog( final hasExisting = existingDefs != null && existingDefs.isNotEmpty; final initialText = hasExisting ? '${existingDefs.map((f) { - return f.high == f.low - ? '${f.name} ${f.high}' - : '${f.name} ${f.high}:${f.low}'; + return f.high == f.low ? '${f.name} ${f.high}' : '${f.name} ${f.high}:${f.low}'; }).join('\n')}\n' : 'field0 $maxBit:0'; @@ -298,7 +297,8 @@ Future?> showDefineBitFieldsDialog( style: const TextStyle(fontFamily: 'monospace', fontSize: 13), decoration: InputDecoration( labelText: 'One field per line: name high:low', - hintText: 'exponent $maxBit:${maxBit - 10}\n' + hintText: + 'exponent $maxBit:${maxBit - 10}\n' 'mantissa ${maxBit - 11}:0', isDense: true, border: const OutlineInputBorder(), diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/capture_boundary.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/capture_boundary.dart index 9c3e05ab8..bc402cab8 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/capture_boundary.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/capture_boundary.dart @@ -42,7 +42,8 @@ Future captureBoundaryToPng( Future Function( RenderRepaintBoundary boundary, double pixelRatio, - )? encodeFn, + )? + encodeFn, }) async { final renderObject = boundaryKey.currentContext?.findRenderObject(); if (renderObject is! RenderRepaintBoundary) { @@ -64,8 +65,9 @@ Future captureBoundaryToPng( try { final save = saveFn ?? export_png.savePngBytes; final savedPath = await save(pngBytes, fileName); - final msg = - savedPath != null ? 'Saved: $savedPath' : 'Downloaded $fileName'; + final msg = savedPath != null + ? 'Saved: $savedPath' + : 'Downloaded $fileName'; debugPrint('[ExportPng] $msg'); if (context.mounted) { export_png.showExportToast(context, msg); diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_menu.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_menu.dart index d52566a06..638b5df4f 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_menu.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_menu.dart @@ -26,14 +26,12 @@ import 'rohd_extension_status.dart'; typedef AvailableSourceFormats = List Function(); /// Invoked when the user picks `Go to Source` for [signalPaths]. -typedef GoToSourceCallback = void Function( - RohdSourceFormat format, List signalPaths); +typedef GoToSourceCallback = + void Function(RohdSourceFormat format, List signalPaths); /// Builds an icon for a source/output [format]. -typedef SourceFormatIconBuilder = Widget Function( - RohdSourceFormat format, { - double size, -}); +typedef SourceFormatIconBuilder = + Widget Function(RohdSourceFormat format, {double size}); /// Prefix used to encode source-navigation entries in a `String`-valued popup /// menu (e.g. `'goto_source:rohd'`). Allows the shared items to coexist with @@ -70,11 +68,11 @@ RohdSourceFormat? gotoSourceFormatFromValue(String? value) { /// Short, menu-friendly name for [format] (e.g. `'ROHD'`, `'SV'`). String gotoSourceShortName(RohdSourceFormat format) => switch (format) { - RohdSourceFormat.rohd => 'ROHD', - RohdSourceFormat.sv => 'SV', - RohdSourceFormat.sc => 'SystemC', - RohdSourceFormat.fst => 'Waveform', - }; + RohdSourceFormat.rohd => 'ROHD', + RohdSourceFormat.sv => 'SV', + RohdSourceFormat.sc => 'SystemC', + RohdSourceFormat.fst => 'Waveform', +}; /// Menu label for `Go to Source`, pluralized with [count]. String gotoSourceMenuLabel(RohdSourceFormat format, {int count = 1}) { @@ -90,20 +88,20 @@ const _systemCIconAsset = 'assets/systemc_icon.png'; Widget sourceFormatMenuIcon(RohdSourceFormat format, {double size = 18}) => switch (format) { RohdSourceFormat.rohd => _sourceFormatAssetIcon( - _rohdIconAsset, - semanticLabel: 'ROHD Source', - size: size, - ), + _rohdIconAsset, + semanticLabel: 'ROHD Source', + size: size, + ), RohdSourceFormat.sv => _sourceFormatAssetIcon( - _systemVerilogIconAsset, - semanticLabel: 'SystemVerilog Source', - size: size, - ), + _systemVerilogIconAsset, + semanticLabel: 'SystemVerilog Source', + size: size, + ), RohdSourceFormat.sc => _sourceFormatAssetIcon( - _systemCIconAsset, - semanticLabel: 'SystemC Source', - size: size, - ), + _systemCIconAsset, + semanticLabel: 'SystemC Source', + size: size, + ), RohdSourceFormat.fst => Icon(Icons.timeline, size: size), }; @@ -115,44 +113,43 @@ Widget _sourceFormatAssetIcon( String asset, { required String semanticLabel, required double size, -}) => - Builder( - builder: (context) { - final isDark = Theme.of(context).brightness == Brightness.dark; - final image = Image.asset( - asset, - width: size, - height: size, - fit: BoxFit.contain, - filterQuality: FilterQuality.high, - semanticLabel: semanticLabel, - errorBuilder: (context, error, stackTrace) => Image.asset( - asset, - package: 'rohd_devtools_widgets', - width: size, - height: size, - fit: BoxFit.contain, - filterQuality: FilterQuality.high, - semanticLabel: semanticLabel, - errorBuilder: (context, error, stackTrace) => - Icon(Icons.code, size: size), - ), - ); - - if (!isDark) return image; - - return Container( - width: size + 4, - height: size + 4, - decoration: const BoxDecoration( - color: Color(0xFFE0E0E0), - shape: BoxShape.circle, - ), - padding: const EdgeInsets.all(2), - child: image, - ); - }, +}) => Builder( + builder: (context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + final image = Image.asset( + asset, + width: size, + height: size, + fit: BoxFit.contain, + filterQuality: FilterQuality.high, + semanticLabel: semanticLabel, + errorBuilder: (context, error, stackTrace) => Image.asset( + asset, + package: 'rohd_devtools_widgets', + width: size, + height: size, + fit: BoxFit.contain, + filterQuality: FilterQuality.high, + semanticLabel: semanticLabel, + errorBuilder: (context, error, stackTrace) => + Icon(Icons.code, size: size), + ), + ); + + if (!isDark) return image; + + return Container( + width: size + 4, + height: size + 4, + decoration: const BoxDecoration( + color: Color(0xFFE0E0E0), + shape: BoxShape.circle, + ), + padding: const EdgeInsets.all(2), + child: image, ); + }, +); /// Standard popup-menu row with a fixed-width prefix icon and ellipsized label. Widget sourcePopupMenuRow({ @@ -161,20 +158,19 @@ Widget sourcePopupMenuRow({ TextStyle? textStyle, double iconSlotWidth = 22, double gap = 8, -}) => - Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - width: iconSlotWidth, - child: Center(child: icon), - ), - SizedBox(width: gap), - Flexible( - child: Text(label, style: textStyle, overflow: TextOverflow.ellipsis), - ), - ], - ); +}) => Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: iconSlotWidth, + child: Center(child: icon), + ), + SizedBox(width: gap), + Flexible( + child: Text(label, style: textStyle, overflow: TextOverflow.ellipsis), + ), + ], +); /// Standard popup-menu item using the same fixed icon gutter as source rows. PopupMenuItem buildRohdPopupMenuItem({ @@ -184,17 +180,12 @@ PopupMenuItem buildRohdPopupMenuItem({ double height = 32, TextStyle? textStyle, bool enabled = true, -}) => - PopupMenuItem( - value: value, - height: height, - enabled: enabled, - child: sourcePopupMenuRow( - icon: icon, - label: label, - textStyle: textStyle, - ), - ); +}) => PopupMenuItem( + value: value, + height: height, + enabled: enabled, + child: sourcePopupMenuRow(icon: icon, label: label, textStyle: textStyle), +); /// Compact strip of source/output format icons for trace-picker menu rows. Widget sourceFormatIconStrip({ @@ -240,14 +231,13 @@ List> buildGotoSourceMenuItems({ TextStyle? textStyle, bool showIcons = true, SourceFormatIconBuilder iconBuilder = sourceFormatMenuIcon, -}) => - [ - for (final format in formats) - buildRohdPopupMenuItem( - value: gotoSourceMenuValue(format), - height: height, - icon: showIcons ? iconBuilder(format) : const SizedBox.shrink(), - label: gotoSourceMenuLabel(format, count: count), - textStyle: textStyle, - ), - ]; +}) => [ + for (final format in formats) + buildRohdPopupMenuItem( + value: gotoSourceMenuValue(format), + height: height, + icon: showIcons ? iconBuilder(format) : const SizedBox.shrink(), + label: gotoSourceMenuLabel(format, count: count), + textStyle: textStyle, + ), +]; diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_service.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_service.dart index 694950740..7ef90678e 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_service.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_service.dart @@ -102,9 +102,8 @@ class LocalCrossProbeService implements CrossProbeService { /// stable, descriptive tag such as `'waveform'` or `'schematic'`. LocalCrossProbeService( LocalCrossProbeChannel channel, { - required String source, - }) : _channel = channel, - _source = source { + required this._source, + }) : _channel = channel { _channel.addListener(_onChannelMessage); } diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/logic_type_utils.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/logic_type_utils.dart index adf89e928..d9602c5c3 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/logic_type_utils.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/logic_type_utils.dart @@ -271,9 +271,9 @@ String _extractContiguousBits(String binaryValue, int startBit, int width) { final totalWidth = binaryValue.length; final endBit = startBit + width; // exclusive if (startBit >= 0 && endBit <= totalWidth) { - return LogicValue.ofString(binaryValue) - .slice(endBit - 1, startBit) - .toString(includeWidth: false); + return LogicValue.ofString( + binaryValue, + ).slice(endBit - 1, startBit).toString(includeWidth: false); } final result = StringBuffer(); @@ -309,9 +309,9 @@ String? hexToBinary(String hexValue, int width) { final sourceWidth = cleaned.length * 4; final parseWidth = sourceWidth > width ? sourceWidth : width; try { - return LogicValue.ofRadixString("$parseWidth'h$cleaned") - .slice(width - 1, 0) - .toString(includeWidth: false); + return LogicValue.ofRadixString( + "$parseWidth'h$cleaned", + ).slice(width - 1, 0).toString(includeWidth: false); } on Exception { return null; } diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/markdown_help_button.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/markdown_help_button.dart index ddc373afd..1ae3ae266 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/markdown_help_button.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/markdown_help_button.dart @@ -142,8 +142,9 @@ class _MarkdownHelpButtonState extends State { // Use catch-all because AssetBundle.loadString throws FlutterError // (an Error, not Exception) when the asset is missing. try { - raw = await assetBundle - .loadString('packages/${widget.package}/${widget.assetPath}'); + raw = await assetBundle.loadString( + 'packages/${widget.package}/${widget.assetPath}', + ); // ignore: avoid_catches_without_on_clauses } catch (_) { raw = await assetBundle.loadString(widget.assetPath); @@ -188,9 +189,7 @@ class _MarkdownHelpButtonState extends State { decoration: BoxDecoration( color: isDark ? const Color(0xFF1E1E1E) : const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(8), - border: Border.all( - color: isDark ? Colors.white24 : Colors.black12, - ), + border: Border.all(color: isDark ? Colors.white24 : Colors.black12), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: isDark ? 0.4 : 0.15), @@ -210,15 +209,22 @@ class _MarkdownHelpButtonState extends State { child: GestureDetector( onTap: () { if (_content != null) { - _showHelpDialog(context, _content!, - isDark: isDark, titleIcon: widget.titleIcon); + _showHelpDialog( + context, + _content!, + isDark: isDark, + titleIcon: widget.titleIcon, + ); } }, child: Padding( padding: const EdgeInsets.all(8), - child: widget.labelIcon ?? - Text(widget.label, - style: const TextStyle(fontSize: 18, inherit: false)), + child: + widget.labelIcon ?? + Text( + widget.label, + style: const TextStyle(fontSize: 18, inherit: false), + ), ), ), ), @@ -241,43 +247,57 @@ class _MarkdownHelpButtonState extends State { final widgets = []; for (final block in content.detailBlocks) { if (block is _HeadingBlock) { - widgets.add(Padding( - padding: const EdgeInsets.only(top: 16, bottom: 4), - child: Text(block.text, + widgets.add( + Padding( + padding: const EdgeInsets.only(top: 16, bottom: 4), + child: Text( + block.text, style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: headingColor, - )), - )); + ), + ), + ), + ); } else if (block is _EntryBlock) { - widgets.add(Padding( - padding: const EdgeInsets.symmetric(vertical: 2), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 200, - child: Text(block.key, + widgets.add( + Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 200, + child: Text( + block.key, style: TextStyle( fontFamily: 'monospace', fontSize: 13, color: keyColor, - )), - ), - Expanded( - child: Text(block.description, - style: TextStyle(fontSize: 13, color: fgColor)), - ), - ], + ), + ), + ), + Expanded( + child: Text( + block.description, + style: TextStyle(fontSize: 13, color: fgColor), + ), + ), + ], + ), ), - )); + ); } else if (block is _ParagraphBlock) { - widgets.add(Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: - Text(block.text, style: TextStyle(fontSize: 13, color: fgColor)), - )); + widgets.add( + Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Text( + block.text, + style: TextStyle(fontSize: 13, color: fgColor), + ), + ), + ); } } @@ -302,12 +322,14 @@ class _MarkdownHelpButtonState extends State { const SizedBox(width: 10), ], Expanded( - child: Text(content.title, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: fgColor, - )), + child: Text( + content.title, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: fgColor, + ), + ), ), IconButton( icon: Icon(Icons.close, color: fgColor, size: 20), @@ -369,8 +391,9 @@ class _HelpContent { // Extract tooltip text. String tooltip = ''; if (tooltipIdx >= 0 && detailsIdx > tooltipIdx) { - tooltip = - raw.substring(tooltipIdx + tooltipMarker.length, detailsIdx).trim(); + tooltip = raw + .substring(tooltipIdx + tooltipMarker.length, detailsIdx) + .trim(); } // Parse detail blocks. @@ -428,10 +451,9 @@ class _HelpContent { .map((c) => c.trim()) .toList(); if (cells.length >= 2) { - blocks.add(_EntryBlock( - key: _stripInlineCode(cells[0]), - description: cells[1], - )); + blocks.add( + _EntryBlock(key: _stripInlineCode(cells[0]), description: cells[1]), + ); continue; } } diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_client.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_client.dart index 6c1c59d69..8ee033f2a 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_client.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_client.dart @@ -108,15 +108,13 @@ class NullExtensionClient implements RohdExtensionClient { Future queryModule( String module, { List? instancePath, - }) async => - RohdModuleInfo.unavailable; + }) async => RohdModuleInfo.unavailable; @override Future>> lookupSignalFrames({ required List> signals, String? format, - }) async => - const []; + }) async => const []; @override void openSourceLocation({ diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_status.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_status.dart index f87428254..f231260b5 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_status.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_status.dart @@ -124,11 +124,11 @@ class RohdModuleInfo { /// All format names for which data is available (as lower-case strings). List get availableFormatNames => [ - if (hasRohd) 'rohd', - if (hasSv) 'sv', - if (hasSc) 'sc', - if (hasFst) 'fst', - ]; + if (hasRohd) 'rohd', + if (hasSv) 'sv', + if (hasSc) 'sc', + if (hasFst) 'fst', + ]; /// True when any source navigation format is available. bool get hasAnySource => hasRohd || hasSv || hasSc; @@ -137,18 +137,18 @@ class RohdModuleInfo { /// this module, in display order. Excludes [RohdSourceFormat.fst] (a /// waveform, not a navigable source). List get navigableSourceFormats => [ - if (hasRohd) RohdSourceFormat.rohd, - if (hasSv) RohdSourceFormat.sv, - if (hasSc) RohdSourceFormat.sc, - ]; + if (hasRohd) RohdSourceFormat.rohd, + if (hasSv) RohdSourceFormat.sv, + if (hasSc) RohdSourceFormat.sc, + ]; /// Human-readable label for a format. static String formatLabel(RohdSourceFormat fmt) => switch (fmt) { - RohdSourceFormat.rohd => 'ROHD (Dart)', - RohdSourceFormat.sv => 'SystemVerilog', - RohdSourceFormat.sc => 'SystemC', - RohdSourceFormat.fst => 'Waveform (FST)', - }; + RohdSourceFormat.rohd => 'ROHD (Dart)', + RohdSourceFormat.sv => 'SystemVerilog', + RohdSourceFormat.sc => 'SystemC', + RohdSourceFormat.fst => 'Waveform (FST)', + }; /// Build from a JSON map (as returned by the extension host or DTD). factory RohdModuleInfo.fromJson(Map json) { @@ -188,36 +188,36 @@ class RohdModuleInfo { /// Serialize to JSON for transmission over DTD or postMessage. Map toJson() => { - 'extensionAvailable': extensionAvailable, - if (module != null) 'module': module, - 'formats': { - for (final e in formats.entries) - _formatKey(e.key): { - 'available': e.value.available, - 'fileFound': e.value.fileFound, - if (e.value.path != null) 'path': e.value.path, - }, + 'extensionAvailable': extensionAvailable, + if (module != null) 'module': module, + 'formats': { + for (final e in formats.entries) + _formatKey(e.key): { + 'available': e.value.available, + 'fileFound': e.value.fileFound, + if (e.value.path != null) 'path': e.value.path, }, - if (error != null) 'error': error, - if (dtdHealthy != null) 'dtdHealthy': dtdHealthy, - if (dtdRegistrationConflict) - 'dtdRegistrationConflict': dtdRegistrationConflict, - if (dtdStatusMessage != null) 'dtdStatusMessage': dtdStatusMessage, - 'fstLoading': fstLoading, - }; + }, + if (error != null) 'error': error, + if (dtdHealthy != null) 'dtdHealthy': dtdHealthy, + if (dtdRegistrationConflict) + 'dtdRegistrationConflict': dtdRegistrationConflict, + if (dtdStatusMessage != null) 'dtdStatusMessage': dtdStatusMessage, + 'fstLoading': fstLoading, + }; static RohdSourceFormat? _parseFormat(String key) => switch (key) { - 'rohd' => RohdSourceFormat.rohd, - 'sv' => RohdSourceFormat.sv, - 'sc' => RohdSourceFormat.sc, - 'fst' => RohdSourceFormat.fst, - _ => null, - }; + 'rohd' => RohdSourceFormat.rohd, + 'sv' => RohdSourceFormat.sv, + 'sc' => RohdSourceFormat.sc, + 'fst' => RohdSourceFormat.fst, + _ => null, + }; static String _formatKey(RohdSourceFormat fmt) => switch (fmt) { - RohdSourceFormat.rohd => 'rohd', - RohdSourceFormat.sv => 'sv', - RohdSourceFormat.sc => 'sc', - RohdSourceFormat.fst => 'fst', - }; + RohdSourceFormat.rohd => 'rohd', + RohdSourceFormat.sv => 'sv', + RohdSourceFormat.sc => 'sc', + RohdSourceFormat.fst => 'fst', + }; } diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/app_bar_overlay_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/app_bar_overlay_test.dart index 080f41c11..0870687e6 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/app_bar_overlay_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/app_bar_overlay_test.dart @@ -15,18 +15,16 @@ import 'package:rohd_devtools_widgets/rohd_devtools_widgets.dart'; void main() { PreferredSizeWidget testAppBar() => const PreferredSize( - preferredSize: Size.fromHeight(48), - child: Material(child: Text('Toolbar')), - ); + preferredSize: Size.fromHeight(48), + child: Material(child: Text('Toolbar')), + ); - testWidgets('lays out app bar above body when auto-hide is disabled', - (tester) async { + testWidgets('lays out app bar above body when auto-hide is disabled', ( + tester, + ) async { await tester.pumpWidget( MaterialApp( - home: AppBarOverlay( - appBar: testAppBar(), - body: const Text('Body'), - ), + home: AppBarOverlay(appBar: testAppBar(), body: const Text('Body')), ), ); @@ -36,8 +34,9 @@ void main() { expect(find.byType(Stack), findsNothing); }); - testWidgets('slides overlay app bar in when pointer enters trigger zone', - (tester) async { + testWidgets('slides overlay app bar in when pointer enters trigger zone', ( + tester, + ) async { await tester.pumpWidget( MaterialApp( home: AppBarOverlay( diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_expansion_menu_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_expansion_menu_test.dart index df97790b9..2d92775c2 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_expansion_menu_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_expansion_menu_test.dart @@ -13,8 +13,9 @@ import 'package:material_ui/material_ui.dart' as material_ui; import 'package:rohd_devtools_widgets/rohd_devtools_widgets.dart'; void main() { - testWidgets('buildBitExpansionMenuItems creates divider and actions', - (tester) async { + testWidgets('buildBitExpansionMenuItems creates divider and actions', ( + tester, + ) async { final items = buildBitExpansionMenuItems( width: 16, includeDivider: true, @@ -50,8 +51,9 @@ void main() { expect(find.text('Define Bit Fields [16]...'), findsOneWidget); }); - testWidgets('resolveBitExpansionMenuValue expands small widths immediately', - (tester) async { + testWidgets('resolveBitExpansionMenuValue expands small widths immediately', ( + tester, + ) async { late BuildContext context; await tester.pumpWidget( MaterialApp( @@ -90,8 +92,9 @@ void main() { ); }); - testWidgets('resolveBitExpansionMenuValue uses dialogs for large ranges', - (tester) async { + testWidgets('resolveBitExpansionMenuValue uses dialogs for large ranges', ( + tester, + ) async { late BuildContext context; await tester.pumpWidget( MaterialApp( diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_field_utils_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_field_utils_test.dart index 3999dfe58..318414b13 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_field_utils_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_field_utils_test.dart @@ -37,17 +37,14 @@ void main() { group('parseBitFieldDefs', () { test('parses named and unnamed bit fields', () { - final fields = BitFieldUtils.parseBitFieldDefs( - ''' + final fields = BitFieldUtils.parseBitFieldDefs(''' exponent 31:21 mantissa 20:0 sign 31 7:4 0 ignored-name 3 -''', - 31, - ); +''', 31); expect(fields, hasLength(5)); expect(fields[0].name, 'exponent'); @@ -68,13 +65,10 @@ ignored-name 3 }); test('normalizes reversed and out-of-range indexes', () { - final fields = BitFieldUtils.parseBitFieldDefs( - ''' + final fields = BitFieldUtils.parseBitFieldDefs(''' low_high 1:8 too_high 40:32 -''', - 31, - ); +''', 31); expect(fields, hasLength(2)); expect(fields[0].high, 8); @@ -84,50 +78,53 @@ too_high 40:32 }); }); - testWidgets('showBitRangeDialog returns parsed input and cancel returns null', - (tester) async { - late BuildContext dialogContext; - await tester.pumpWidget( - MaterialApp( - theme: ThemeData( - useMaterial3: false, - splashFactory: NoSplash.splashFactory, + testWidgets( + 'showBitRangeDialog returns parsed input and cancel returns null', + (tester) async { + late BuildContext dialogContext; + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + useMaterial3: false, + splashFactory: NoSplash.splashFactory, + ), + home: Builder( + builder: (context) { + dialogContext = context; + return const SizedBox.shrink(); + }, + ), ), - home: Builder( - builder: (context) { - dialogContext = context; - return const SizedBox.shrink(); - }, - ), - ), - ); + ); - final future = showBitRangeDialog( - dialogContext, - signalName: 'data', - width: 16, - ); - await tester.pumpAndSettle(); - await tester.enterText(find.byType(TextField), '2:5'); - await tester.tap(find.text('OK')); - await tester.pumpAndSettle(); + final future = showBitRangeDialog( + dialogContext, + signalName: 'data', + width: 16, + ); + await tester.pumpAndSettle(); + await tester.enterText(find.byType(TextField), '2:5'); + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); - expect(await future, (5, 2)); + expect(await future, (5, 2)); - final cancelled = showBitRangeDialog( - dialogContext, - signalName: 'data', - width: 16, - ); - await tester.pumpAndSettle(); - await tester.tap(find.text('Cancel')); - await tester.pumpAndSettle(); + final cancelled = showBitRangeDialog( + dialogContext, + signalName: 'data', + width: 16, + ); + await tester.pumpAndSettle(); + await tester.tap(find.text('Cancel')); + await tester.pumpAndSettle(); - expect(await cancelled, isNull); - }); + expect(await cancelled, isNull); + }, + ); - testWidgets('showDefineBitFieldsDialog parses edited definitions', - (tester) async { + testWidgets('showDefineBitFieldsDialog parses edited definitions', ( + tester, + ) async { late BuildContext dialogContext; await tester.pumpWidget( MaterialApp( @@ -148,9 +145,7 @@ too_high 40:32 dialogContext, signalName: 'floatBits', width: 32, - existingDefs: const [ - BitFieldDef(name: 'sign', high: 31, low: 31), - ], + existingDefs: const [BitFieldDef(name: 'sign', high: 31, low: 31)], ); await tester.pumpAndSettle(); diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/capture_boundary_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/capture_boundary_test.dart index d1574c192..8f8f4ba6f 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/capture_boundary_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/capture_boundary_test.dart @@ -15,8 +15,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:rohd_devtools_widgets/rohd_devtools_widgets.dart'; void main() { - testWidgets('returns false when no repaint boundary is found', - (tester) async { + testWidgets('returns false when no repaint boundary is found', ( + tester, + ) async { final key = GlobalKey(); late BuildContext captureContext; @@ -37,8 +38,9 @@ void main() { ); }); - testWidgets('returns false when the key has no mounted context', - (tester) async { + testWidgets('returns false when the key has no mounted context', ( + tester, + ) async { final key = GlobalKey(); late BuildContext captureContext; @@ -59,8 +61,9 @@ void main() { ); }); - testWidgets('saves injected PNG bytes and shows saved path toast', - (tester) async { + testWidgets('saves injected PNG bytes and shows saved path toast', ( + tester, + ) async { final key = GlobalKey(); late BuildContext captureContext; Uint8List? savedBytes; @@ -107,8 +110,9 @@ void main() { await _letExportToastExpire(tester); }); - testWidgets('shows downloaded toast when save function returns no path', - (tester) async { + testWidgets('shows downloaded toast when save function returns no path', ( + tester, + ) async { final key = GlobalKey(); late BuildContext captureContext; @@ -139,8 +143,9 @@ void main() { await _letExportToastExpire(tester); }); - testWidgets('skips saved toast when context unmounts during save', - (tester) async { + testWidgets('skips saved toast when context unmounts during save', ( + tester, + ) async { final key = GlobalKey(); late BuildContext captureContext; @@ -166,8 +171,9 @@ void main() { expect(find.textContaining('Saved:'), findsNothing); }); - testWidgets('returns false and shows failure toast when save throws', - (tester) async { + testWidgets('returns false and shows failure toast when save throws', ( + tester, + ) async { final key = GlobalKey(); late BuildContext captureContext; @@ -186,13 +192,16 @@ void main() { await tester.pump(); expect(succeeded, isFalse); - expect(find.textContaining('Export failed: Bad state: disk full'), - findsOneWidget); + expect( + find.textContaining('Export failed: Bad state: disk full'), + findsOneWidget, + ); await _letExportToastExpire(tester); }); - testWidgets('returns false when injected encoder returns null', - (tester) async { + testWidgets('returns false when injected encoder returns null', ( + tester, + ) async { final key = GlobalKey(); late BuildContext captureContext; diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_button_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_button_test.dart index 22df3efc5..4bb7060bb 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_button_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_button_test.dart @@ -21,10 +21,10 @@ void main() { await tester.pumpWidget( MaterialApp( theme: ThemeData( - useMaterial3: false, splashFactory: NoSplash.splashFactory), - home: Scaffold( - body: CrossProbeButton(service: service), + useMaterial3: false, + splashFactory: NoSplash.splashFactory, ), + home: Scaffold(body: CrossProbeButton(service: service)), ), ); diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_menu_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_menu_test.dart index cc34e7b0b..45e83afed 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_menu_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_menu_test.dart @@ -15,10 +15,7 @@ import 'package:rohd_devtools_widgets/rohd_devtools_widgets.dart'; void main() { test('encodes, decodes, and labels source menu values', () { expect(gotoSourceMenuValue(RohdSourceFormat.rohd), 'goto_source:rohd'); - expect( - gotoSourceFormatFromValue('goto_source:sv'), - RohdSourceFormat.sv, - ); + expect(gotoSourceFormatFromValue('goto_source:sv'), RohdSourceFormat.sv); expect(gotoSourceFormatFromValue(null), isNull); expect(gotoSourceFormatFromValue('other'), isNull); expect(gotoSourceFormatFromValue('goto_source:missing'), isNull); @@ -27,10 +24,7 @@ void main() { expect(gotoSourceShortName(RohdSourceFormat.sv), 'SV'); expect(gotoSourceShortName(RohdSourceFormat.sc), 'SystemC'); expect(gotoSourceShortName(RohdSourceFormat.fst), 'Waveform'); - expect( - gotoSourceMenuLabel(RohdSourceFormat.rohd), - 'Go to ROHD Source', - ); + expect(gotoSourceMenuLabel(RohdSourceFormat.rohd), 'Go to ROHD Source'); expect( gotoSourceMenuLabel(RohdSourceFormat.sv, count: 3), 'Go to SV Source (3)', @@ -86,39 +80,32 @@ void main() { }, ); - expect( - resolveNavigableFormats(info), - [RohdSourceFormat.rohd, RohdSourceFormat.sc], - ); + expect(resolveNavigableFormats(info), [ + RohdSourceFormat.rohd, + RohdSourceFormat.sc, + ]); }); - testWidgets('builds popup menu items with encoded values and custom icons', - (tester) async { + testWidgets('builds popup menu items with encoded values and custom icons', ( + tester, + ) async { final items = buildGotoSourceMenuItems( formats: [RohdSourceFormat.rohd, RohdSourceFormat.sv], count: 2, showIcons: true, - iconBuilder: (format, {double size = 18}) => Icon( - Icons.code, - key: ValueKey(format), - size: size, - ), + iconBuilder: (format, {double size = 18}) => + Icon(Icons.code, key: ValueKey(format), size: size), ); expect(items, hasLength(2)); - expect( - items, - everyElement(isA>()), - ); + expect(items, everyElement(isA>())); expect(items[0].value, 'goto_source:rohd'); expect(items[1].value, 'goto_source:sv'); await tester.pumpWidget( MaterialApp( home: Material( - child: Column( - children: [for (final item in items) item.child!], - ), + child: Column(children: [for (final item in items) item.child!]), ), ), ); @@ -129,8 +116,9 @@ void main() { expect(find.byKey(const ValueKey(RohdSourceFormat.sv)), findsOneWidget); }); - testWidgets('builds rows, disabled menu items, and no-icon source items', - (tester) async { + testWidgets('builds rows, disabled menu items, and no-icon source items', ( + tester, + ) async { final disabled = buildRohdPopupMenuItem( value: 'disabled', icon: const Icon(Icons.block), @@ -173,8 +161,9 @@ void main() { expect(find.byIcon(Icons.timeline), findsOneWidget); }); - testWidgets('source format icons cover strips, aliases, and waveform icon', - (tester) async { + testWidgets('source format icons cover strips, aliases, and waveform icon', ( + tester, + ) async { await tester.pumpWidget( MaterialApp( home: Material( diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_service_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_service_test.dart index 3f91273e8..262e273de 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_service_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_service_test.dart @@ -27,10 +27,7 @@ void main() { expect(schematic.incomingSignals.value, ['top.clk', 'top.reset']); expect(channel.lastSource, 'waveform'); expect(channel.lastPaths, ['top.clk', 'top.reset']); - expect( - () => channel.lastPaths!.add('top.extra'), - throwsUnsupportedError, - ); + expect(() => channel.lastPaths!.add('top.extra'), throwsUnsupportedError); }); test('does not broadcast while inactive or for empty selections', () { diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/export_button_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/export_button_test.dart index bdabebc52..4d330db6e 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/export_button_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/export_button_test.dart @@ -12,15 +12,14 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:rohd_devtools_widgets/rohd_devtools_widgets.dart'; void main() { - testWidgets('renders camera icon, tooltip, and invokes callback', - (tester) async { + testWidgets('renders camera icon, tooltip, and invokes callback', ( + tester, + ) async { var taps = 0; await tester.pumpWidget( MaterialApp( - theme: ThemeData( - splashFactory: NoSplash.splashFactory, - ), + theme: ThemeData(splashFactory: NoSplash.splashFactory), home: Scaffold( body: Center( child: ExportPngButton( @@ -33,8 +32,10 @@ void main() { ); expect(find.byIcon(Icons.camera_alt_outlined), findsOneWidget); - expect(tester.widget(find.byType(Tooltip)).message, - 'Save waveform PNG'); + expect( + tester.widget(find.byType(Tooltip)).message, + 'Save waveform PNG', + ); await tester.tap(find.byType(ExportPngButton)); await tester.pump(); diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/export_toast_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/export_toast_test.dart index b2fa49cf9..4a6592ed6 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/export_toast_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/export_toast_test.dart @@ -12,8 +12,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:rohd_devtools_widgets/rohd_devtools_widgets.dart'; void main() { - testWidgets('showExportToast inserts and removes an overlay entry', - (tester) async { + testWidgets('showExportToast inserts and removes an overlay entry', ( + tester, + ) async { late BuildContext toastContext; await tester.pumpWidget( MaterialApp( diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/logic_type_utils_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/logic_type_utils_test.dart index 86c9b6e68..1922c16b5 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/logic_type_utils_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/logic_type_utils_test.dart @@ -36,13 +36,10 @@ void main() { }); test('expandLogicType slices contiguous array elements with LogicValue', () { - final nodes = expandLogicType( - { - 'arrayDims': [2], - 'elementWidth': 4, - }, - parentBinaryValue: '10101100', - ); + final nodes = expandLogicType({ + 'arrayDims': [2], + 'elementWidth': 4, + }, parentBinaryValue: '10101100'); expect(nodes, hasLength(2)); expect(nodes[0].value, '1100'); @@ -50,38 +47,35 @@ void main() { }); test('expandLogicType normalizes absolute struct bits and nested fields', () { - final nodes = expandLogicType( - { - 'typeName': 'Packet', - 'fields': [ - { - 'name': 'payload', - 'width': 4, - 'bits': [100, 101, 102, 103], - 'type': { - 'fields': [ - { - 'name': 'low', - 'width': 2, - 'bits': [0, 1], - }, - { - 'name': 'high', - 'width': 2, - 'bits': [2, 3], - }, - ], - }, - }, - { - 'name': 'valid', - 'width': 1, - 'bits': [104], + final nodes = expandLogicType({ + 'typeName': 'Packet', + 'fields': [ + { + 'name': 'payload', + 'width': 4, + 'bits': [100, 101, 102, 103], + 'type': { + 'fields': [ + { + 'name': 'low', + 'width': 2, + 'bits': [0, 1], + }, + { + 'name': 'high', + 'width': 2, + 'bits': [2, 3], + }, + ], }, - ], - }, - parentBinaryValue: '11010', - ); + }, + { + 'name': 'valid', + 'width': 1, + 'bits': [104], + }, + ], + }, parentBinaryValue: '11010'); expect(nodes, hasLength(2)); expect(nodes[0].name, 'payload'); @@ -96,13 +90,10 @@ void main() { }); test('expandLogicType expands multidimensional arrays', () { - final nodes = expandLogicType( - { - 'arrayDims': [2, 2], - 'elementWidth': 2, - }, - parentBinaryValue: '11100100', - ); + final nodes = expandLogicType({ + 'arrayDims': [2, 2], + 'elementWidth': 2, + }, parentBinaryValue: '11100100'); expect(nodes, hasLength(2)); expect(nodes[0].name, '[0]'); @@ -113,48 +104,47 @@ void main() { expect(nodes[1].children.map((child) => child.value), ['10', '11']); }); - test('formatTypeTooltip includes signal name, type, values, and depth limit', - () { - final tooltip = formatTypeTooltip( - { - 'typeName': 'Packet', - 'fields': [ - { - 'name': 'payload', - 'width': 4, - 'bits': [0, 1, 2, 3], - 'type': { - 'fields': [ - { - 'name': 'nibble', - 'width': 4, - 'bits': [0, 1, 2, 3], - 'type': { - 'fields': [ - { - 'name': 'bit0', - 'width': 1, - 'bits': [0], - }, - ], + test( + 'formatTypeTooltip includes signal name, type, values, and depth limit', + () { + final tooltip = formatTypeTooltip( + { + 'typeName': 'Packet', + 'fields': [ + { + 'name': 'payload', + 'width': 4, + 'bits': [0, 1, 2, 3], + 'type': { + 'fields': [ + { + 'name': 'nibble', + 'width': 4, + 'bits': [0, 1, 2, 3], + 'type': { + 'fields': [ + { + 'name': 'bit0', + 'width': 1, + 'bits': [0], + }, + ], + }, }, - }, - ], + ], + }, }, - }, - ], - }, - parentBinaryValue: '1010', - signalName: 'packet', - maxDepth: 2, - ); + ], + }, + parentBinaryValue: '1010', + signalName: 'packet', + maxDepth: 2, + ); - expect( - tooltip, - '''packet (Packet) + expect(tooltip, '''packet (Packet) payload: 4'b1010 nibble: 4'b1010 - ...''', - ); - }); + ...'''); + }, + ); } diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/markdown_help_button_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/markdown_help_button_test.dart index c1731db48..8f3e8b8b8 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/markdown_help_button_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/markdown_help_button_test.dart @@ -50,8 +50,9 @@ second line expect(tester.widget(find.byType(Tooltip)).message, expected); } - testWidgets('loads markdown, applies substitutions, and renders dialog', - (tester) async { + testWidgets('loads markdown, applies substitutions, and renders dialog', ( + tester, + ) async { await tester.pumpWidget( MaterialApp( home: DefaultAssetBundle( @@ -83,30 +84,32 @@ second line expect(find.text('First line second line'), findsOneWidget); }); - testWidgets('falls back to bare asset path when package asset is unavailable', - (tester) async { - await tester.pumpWidget( - MaterialApp( - home: DefaultAssetBundle( - bundle: _MapAssetBundle({assetPath: markdown}), - child: const MarkdownHelpButton( - assetPath: assetPath, - package: 'rohd_devtools_widgets', - isDark: true, - labelIcon: Icon(Icons.help_outline), - substitutions: {'VERSION': '2.0.0', 'THING': 'signals'}, + testWidgets( + 'falls back to bare asset path when package asset is unavailable', + (tester) async { + await tester.pumpWidget( + MaterialApp( + home: DefaultAssetBundle( + bundle: _MapAssetBundle({assetPath: markdown}), + child: const MarkdownHelpButton( + assetPath: assetPath, + package: 'rohd_devtools_widgets', + isDark: true, + labelIcon: Icon(Icons.help_outline), + substitutions: {'VERSION': '2.0.0', 'THING': 'signals'}, + ), ), ), - ), - ); - await pumpUntilTooltipMessage(tester, 'Quick help for signals'); - - expect(find.byIcon(Icons.help_outline), findsOneWidget); - expect( - tester.widget(find.byType(Tooltip)).message, - 'Quick help for signals', - ); - }); + ); + await pumpUntilTooltipMessage(tester, 'Quick help for signals'); + + expect(find.byIcon(Icons.help_outline), findsOneWidget); + expect( + tester.widget(find.byType(Tooltip)).message, + 'Quick help for signals', + ); + }, + ); } class _MapAssetBundle extends CachingAssetBundle { diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/rohd_extension_status_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/rohd_extension_status_test.dart index d340c0c45..ada6db403 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/rohd_extension_status_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/rohd_extension_status_test.dart @@ -86,7 +86,9 @@ void main() { expect(RohdModuleInfo.formatLabel(RohdSourceFormat.sv), 'SystemVerilog'); expect(RohdModuleInfo.formatLabel(RohdSourceFormat.sc), 'SystemC'); expect( - RohdModuleInfo.formatLabel(RohdSourceFormat.fst), 'Waveform (FST)'); + RohdModuleInfo.formatLabel(RohdSourceFormat.fst), + 'Waveform (FST)', + ); const unavailable = RohdModuleInfo.unavailable; expect(unavailable.extensionAvailable, isFalse); From 701522a6b671aef4d9c05dac3d650e5dee78925e Mon Sep 17 00:00:00 2001 From: "Desmond A. Kirkpatrick" Date: Thu, 27 Aug 2026 10:18:28 -0700 Subject: [PATCH 05/10] new analysis level fixes --- analysis_options.yaml | 7 + .../analysis_options.yaml | 2 +- .../lib/rohd_devtools_widgets.dart | 35 ++-- .../lib/src/app_bar_overlay.dart | 20 ++- .../lib/src/bit_field_utils.dart | 162 ++++++++++-------- .../lib/src/cross_probe_button.dart | 43 +++-- .../lib/src/cross_probe_menu.dart | 6 +- .../lib/src/cross_probe_service.dart | 20 ++- .../lib/src/export_button.dart | 12 +- .../lib/src/markdown_help_button.dart | 117 ++++++++----- .../lib/src/rohd_extension_client.dart | 3 +- .../lib/src/rohd_extension_status.dart | 6 +- .../lib/src/save_png_web.dart | 5 +- .../test/bit_expansion_menu_test.dart | 23 +-- .../test/bit_field_utils_test.dart | 2 +- .../test/capture_boundary_test.dart | 4 +- .../test/cross_probe_menu_test.dart | 34 ++-- .../test/logic_type_utils_test.dart | 3 +- 18 files changed, 285 insertions(+), 219 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index b4e2c5480..2399314f3 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -12,6 +12,13 @@ analyzer: - packages/rohd_waveform - rohd_devtools_extension - rohd_extension + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** # keep up to date, matching https://dart.dev/tools/linter-rules/all # some lints are not yet available, so disabled and marked with [not currently recognized] diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/analysis_options.yaml b/rohd_devtools_extension/packages/rohd_devtools_widgets/analysis_options.yaml index c3810a1c8..a96029588 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/analysis_options.yaml +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/analysis_options.yaml @@ -7,4 +7,4 @@ analyzer: - windows/** - macos/** - linux/** -include: package:lints/recommended.yaml +include: ../../analysis_options.yaml diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/rohd_devtools_widgets.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/rohd_devtools_widgets.dart index 452567fae..799f4e945 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/rohd_devtools_widgets.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/rohd_devtools_widgets.dart @@ -8,34 +8,27 @@ // 2026 April // Author: Desmond Kirkpatrick -// Help -export 'src/markdown_help_button.dart'; - // Overlay export 'src/app_bar_overlay.dart'; - +// Shared "Expand Bits" / "Define Bit Fields" popup-menu helpers +export 'src/bit_expansion_menu.dart'; +// Bit-field parsing, formatting, and dialog utilities +export 'src/bit_field_utils.dart'; // PNG export export 'src/capture_boundary.dart'; -export 'src/export_button.dart'; -export 'src/export_toast.dart'; -export 'src/save_png_stub.dart' - if (dart.library.io) 'src/save_png_native.dart' - if (dart.library.js_interop) 'src/save_png_web.dart'; - -// Cross-probing -export 'src/cross_probe_service.dart'; export 'src/cross_probe_button.dart'; export 'src/cross_probe_menu.dart'; - +// Cross-probing +export 'src/cross_probe_service.dart'; +export 'src/export_button.dart'; +export 'src/export_toast.dart'; // Logic type utilities export 'src/logic_type_utils.dart'; - -// Bit-field parsing, formatting, and dialog utilities -export 'src/bit_field_utils.dart'; - -// Shared "Expand Bits" / "Define Bit Fields" popup-menu helpers -export 'src/bit_expansion_menu.dart'; - +// Help +export 'src/markdown_help_button.dart'; +export 'src/rohd_extension_client.dart'; // ROHD extension client export 'src/rohd_extension_status.dart'; -export 'src/rohd_extension_client.dart'; +export 'src/save_png_stub.dart' + if (dart.library.io) 'src/save_png_native.dart' + if (dart.library.js_interop) 'src/save_png_web.dart'; diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/app_bar_overlay.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/app_bar_overlay.dart index 4364bb93a..38848173a 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/app_bar_overlay.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/app_bar_overlay.dart @@ -14,6 +14,7 @@ // 2026 April // Author: Desmond Kirkpatrick +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// Wraps a [body] widget and an [appBar] widget, where the AppBar @@ -41,10 +42,11 @@ class AppBarOverlay extends StatefulWidget { /// Duration of the slide animation. final Duration animationDuration; + /// Creates an app bar overlay around [body]. const AppBarOverlay({ - super.key, required this.appBar, required this.body, + super.key, this.autoHide = false, this.triggerHeight = 12, this.panelOpacity = 0.92, @@ -53,6 +55,18 @@ class AppBarOverlay extends StatefulWidget { @override State createState() => _AppBarOverlayState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('autoHide', autoHide)) + ..add(DoubleProperty('triggerHeight', triggerHeight)) + ..add(DoubleProperty('panelOpacity', panelOpacity)) + ..add( + DiagnosticsProperty('animationDuration', animationDuration), + ); + } } class _AppBarOverlayState extends State @@ -108,7 +122,9 @@ class _AppBarOverlayState extends State } void _hide() { - if (!widget.autoHide) return; + if (!widget.autoHide) { + return; + } _controller.reverse(); } diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_field_utils.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_field_utils.dart index e9156e686..f13d5e523 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_field_utils.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_field_utils.dart @@ -7,7 +7,7 @@ // 2025 May // Author: Desmond Kirkpatrick -import 'package:flutter/material.dart'; +import 'package:material_ui/material_ui.dart'; /// A named bit-field definition within a bitvector signal. class BitFieldDef { @@ -20,6 +20,7 @@ class BitFieldDef { /// Low bit (inclusive, LSB of the field). final int low; + /// Creates a bit-field definition. const BitFieldDef({ required this.name, required this.high, @@ -50,16 +51,22 @@ abstract final class BitFieldUtils { static (int, int)? parseBitRange(String input, int maxBit) { if (input.contains(':')) { final parts = input.split(':'); - if (parts.length != 2) return null; + if (parts.length != 2) { + return null; + } final high = int.tryParse(parts[0].trim()); final low = int.tryParse(parts[1].trim()); - if (high == null || low == null) return null; + if (high == null || low == null) { + return null; + } final h = high.clamp(0, maxBit); final l = low.clamp(0, maxBit); return h >= l ? (h, l) : (l, h); } final bit = int.tryParse(input); - if (bit == null) return null; + if (bit == null) { + return null; + } final clamped = bit.clamp(0, maxBit); return (clamped, clamped); } @@ -76,7 +83,9 @@ abstract final class BitFieldUtils { final fields = []; for (final rawLine in lines) { final line = rawLine.trim(); - if (line.isEmpty) continue; + if (line.isEmpty) { + continue; + } final tokens = _splitAsciiWhitespace(line); // Try: name high:low @@ -159,7 +168,9 @@ abstract final class BitFieldUtils { } static bool _isWord(String value) { - if (value.isEmpty) return false; + if (value.isEmpty) { + return false; + } for (var i = 0; i < value.length; i++) { final char = value.codeUnitAt(i); final isUppercase = char >= 65 && char <= 90; @@ -174,10 +185,14 @@ abstract final class BitFieldUtils { } static bool _isUnsignedDecimal(String value) { - if (value.isEmpty) return false; + if (value.isEmpty) { + return false; + } for (var i = 0; i < value.length; i++) { final char = value.codeUnitAt(i); - if (char < 48 || char > 57) return false; + if (char < 48 || char > 57) { + return false; + } } return true; } @@ -209,37 +224,37 @@ Future<(int, int)?> showBitRangeDialog( final result = await showDialog( context: context, barrierColor: Colors.black26, - builder: (ctx) { - return AlertDialog( - title: Text( - '$signalName [$width bits]', - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + builder: (ctx) => AlertDialog( + title: Text( + '$signalName [$width bits]', + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + ), + content: TextField( + controller: controller, + autofocus: true, + decoration: InputDecoration( + labelText: 'Bit range (high:low) or single bit', + hintText: '$maxBit:0', + isDense: true, ), - content: TextField( - controller: controller, - autofocus: true, - decoration: InputDecoration( - labelText: 'Bit range (high:low) or single bit', - hintText: '$maxBit:0', - isDense: true, - ), - onSubmitted: (value) => Navigator.of(ctx).pop(value), + onSubmitted: (value) => Navigator.of(ctx).pop(value), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(), + child: const Text('Cancel'), ), - actions: [ - TextButton( - onPressed: () => Navigator.of(ctx).pop(), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => Navigator.of(ctx).pop(controller.text), - child: const Text('OK'), - ), - ], - ); - }, + TextButton( + onPressed: () => Navigator.of(ctx).pop(controller.text), + child: const Text('OK'), + ), + ], + ), ); - if (result == null || result.trim().isEmpty) return null; + if (result == null || result.trim().isEmpty) { + return null; + } return BitFieldUtils.parseBitRange(result.trim(), maxBit); } @@ -258,10 +273,11 @@ Future?> showDefineBitFieldsDialog( // newline and place the cursor at the end so the user can immediately // type additional fields without accidentally replacing existing ones. final hasExisting = existingDefs != null && existingDefs.isNotEmpty; + String formatField(BitFieldDef field) => field.high == field.low + ? '${field.name} ${field.high}' + : '${field.name} ${field.high}:${field.low}'; final initialText = hasExisting - ? '${existingDefs.map((f) { - return f.high == f.low ? '${f.name} ${f.high}' : '${f.name} ${f.high}:${f.low}'; - }).join('\n')}\n' + ? '${existingDefs.map(formatField).join('\n')}\n' : 'field0 $maxBit:0'; final controller = TextEditingController(text: initialText); @@ -281,45 +297,45 @@ Future?> showDefineBitFieldsDialog( final result = await showDialog( context: context, barrierColor: Colors.black26, - builder: (ctx) { - return AlertDialog( - title: Text( - '$signalName [$width bits] — Define Fields', - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), - ), - content: SizedBox( - width: 320, - child: TextField( - controller: controller, - autofocus: true, - maxLines: 8, - minLines: 3, - style: const TextStyle(fontFamily: 'monospace', fontSize: 13), - decoration: InputDecoration( - labelText: 'One field per line: name high:low', - hintText: - 'exponent $maxBit:${maxBit - 10}\n' - 'mantissa ${maxBit - 11}:0', - isDense: true, - border: const OutlineInputBorder(), - ), + builder: (ctx) => AlertDialog( + title: Text( + '$signalName [$width bits] — Define Fields', + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + ), + content: SizedBox( + width: 320, + child: TextField( + controller: controller, + autofocus: true, + maxLines: 8, + minLines: 3, + style: const TextStyle(fontFamily: 'monospace', fontSize: 13), + decoration: InputDecoration( + labelText: 'One field per line: name high:low', + hintText: + 'exponent $maxBit:${maxBit - 10}\n' + 'mantissa ${maxBit - 11}:0', + isDense: true, + border: const OutlineInputBorder(), ), ), - actions: [ - TextButton( - onPressed: () => Navigator.of(ctx).pop(), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => Navigator.of(ctx).pop(controller.text), - child: const Text('OK'), - ), - ], - ); - }, + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => Navigator.of(ctx).pop(controller.text), + child: const Text('OK'), + ), + ], + ), ); - if (result == null || result.trim().isEmpty) return null; + if (result == null || result.trim().isEmpty) { + return null; + } final fields = BitFieldUtils.parseBitFieldDefs(result, maxBit); return fields.isEmpty ? null : fields; } diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_button.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_button.dart index ea6148492..a3281c169 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_button.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_button.dart @@ -7,8 +7,9 @@ // 2026 June // Author: Desmond Kirkpatrick +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'cross_probe_service.dart'; +import 'package:rohd_devtools_widgets/src/cross_probe_service.dart'; /// A toolbar icon button for cross-probing signal selections between viewers. /// @@ -26,23 +27,27 @@ class CrossProbeButton extends StatelessWidget { const CrossProbeButton({required this.service, super.key}); @override - Widget build(BuildContext context) { - return ValueListenableBuilder( - valueListenable: service.isActive, - builder: (context, active, _) { - final color = active - ? Theme.of(context).colorScheme.primary - : Theme.of(context).disabledColor; - return Tooltip( - message: active - ? 'Cross-probing active — tap to disable' - : 'Cross-probing disabled — tap to enable', - child: IconButton( - icon: Icon(Icons.compare_arrows, color: color), - onPressed: () => service.isActive.value = !service.isActive.value, - ), - ); - }, - ); + Widget build(BuildContext context) => ValueListenableBuilder( + valueListenable: service.isActive, + builder: (context, active, _) { + final color = active + ? Theme.of(context).colorScheme.primary + : Theme.of(context).disabledColor; + return Tooltip( + message: active + ? 'Cross-probing active — tap to disable' + : 'Cross-probing disabled — tap to enable', + child: IconButton( + icon: Icon(Icons.compare_arrows, color: color), + onPressed: () => service.isActive.value = !service.isActive.value, + ), + ); + }, + ); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('service', service)); } } diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_menu.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_menu.dart index 638b5df4f..001de5b73 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_menu.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_menu.dart @@ -17,7 +17,7 @@ import 'package:material_ui/material_ui.dart'; -import 'rohd_extension_status.dart'; +import 'package:rohd_devtools_widgets/src/rohd_extension_status.dart'; /// Returns the source formats currently navigable for the active module. /// @@ -136,7 +136,9 @@ Widget _sourceFormatAssetIcon( ), ); - if (!isDark) return image; + if (!isDark) { + return image; + } return Container( width: size + 4, diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_service.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_service.dart index 7ef90678e..8b90a5e8f 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_service.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_service.dart @@ -64,7 +64,9 @@ class LocalCrossProbeChannel extends ChangeNotifier { /// /// Does nothing when [signalPaths] is empty. void broadcast(List signalPaths, String source) { - if (signalPaths.isEmpty) return; + if (signalPaths.isEmpty) { + return; + } _lastSource = source; _lastPaths = List.unmodifiable(signalPaths); notifyListeners(); @@ -75,7 +77,7 @@ class LocalCrossProbeChannel extends ChangeNotifier { /// /// Create one [LocalCrossProbeService] per viewer, all sharing the same /// [LocalCrossProbeChannel]. Each service filters out its own broadcasts -/// (matched by [source]) so viewers do not receive their own selections. +/// (matched by the source tag) so viewers do not receive their own selections. /// /// ```dart /// final channel = LocalCrossProbeChannel(); @@ -98,7 +100,7 @@ class LocalCrossProbeService implements CrossProbeService { /// Creates a [LocalCrossProbeService] backed by [channel]. /// - /// [source] is the identifier used to filter self-broadcasts. Use a + /// The `source` argument identifies self-broadcasts to filter. Use a /// stable, descriptive tag such as `'waveform'` or `'schematic'`. LocalCrossProbeService( LocalCrossProbeChannel channel, { @@ -108,15 +110,21 @@ class LocalCrossProbeService implements CrossProbeService { } void _onChannelMessage() { - if (!isActive.value) return; + if (!isActive.value) { + return; + } final src = _channel.lastSource; - if (src == null || src == _source) return; // ignore own broadcasts + if (src == null || src == _source) { + return; + } incomingSignals.value = _channel.lastPaths; } @override void send(List signalPaths, {required String source}) { - if (!isActive.value || signalPaths.isEmpty) return; + if (!isActive.value || signalPaths.isEmpty) { + return; + } _channel.broadcast(signalPaths, source); } diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/export_button.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/export_button.dart index 4c0dd1327..d48f2e6cf 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/export_button.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/export_button.dart @@ -7,6 +7,7 @@ // 2026 April // Author: Desmond Kirkpatrick +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// Small camera-icon button for triggering PNG export. @@ -20,9 +21,10 @@ class ExportPngButton extends StatelessWidget { /// Tooltip text shown on hover. final String tooltip; + /// Creates a PNG export button. const ExportPngButton({ - super.key, required this.onPressed, + super.key, this.tooltip = 'Export as PNG', }); @@ -50,4 +52,12 @@ class ExportPngButton extends StatelessWidget { ), ); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(ObjectFlagProperty.has('onPressed', onPressed)) + ..add(StringProperty('tooltip', tooltip)); + } } diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/markdown_help_button.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/markdown_help_button.dart index 1ae3ae266..312b8d78d 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/markdown_help_button.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/markdown_help_button.dart @@ -20,6 +20,9 @@ // 2026 March // Author: Desmond Kirkpatrick +import 'dart:async'; + +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// A help button that loads its content from a markdown asset file. @@ -109,6 +112,22 @@ class MarkdownHelpButton extends StatefulWidget { @override State createState() => _MarkdownHelpButtonState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(StringProperty('assetPath', assetPath)) + ..add(DiagnosticsProperty('isDark', isDark)) + ..add(StringProperty('label', label)) + ..add(StringProperty('package', package)) + ..add( + DiagnosticsProperty?>( + 'substitutions', + substitutions, + ), + ); + } } class _MarkdownHelpButtonState extends State { @@ -118,7 +137,7 @@ class _MarkdownHelpButtonState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - _loadContent(); + unawaited(_loadContent()); } @override @@ -126,7 +145,7 @@ class _MarkdownHelpButtonState extends State { super.didUpdateWidget(oldWidget); if (oldWidget.assetPath != widget.assetPath || oldWidget.package != widget.package) { - _loadContent(); + unawaited(_loadContent()); } } @@ -301,53 +320,57 @@ class _MarkdownHelpButtonState extends State { } } - showDialog( - context: context, - builder: (ctx) => Dialog( - backgroundColor: bgColor, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 600, maxHeight: 600), - child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Title row - Row( - children: [ - if (titleIcon != null) ...[ - titleIcon, - const SizedBox(width: 10), - ], - Expanded( - child: Text( - content.title, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: fgColor, + unawaited( + showDialog( + context: context, + builder: (ctx) => Dialog( + backgroundColor: bgColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 600, maxHeight: 600), + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Title row + Row( + children: [ + if (titleIcon != null) ...[ + titleIcon, + const SizedBox(width: 10), + ], + Expanded( + child: Text( + content.title, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: fgColor, + ), ), ), - ), - IconButton( - icon: Icon(Icons.close, color: fgColor, size: 20), - onPressed: () => Navigator.of(ctx).pop(), - ), - ], - ), - Divider(color: dividerColor), - // Scrollable content - Flexible( - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: widgets, + IconButton( + icon: Icon(Icons.close, color: fgColor, size: 20), + onPressed: () => Navigator.of(ctx).pop(), + ), + ], + ), + Divider(color: dividerColor), + // Scrollable content + Flexible( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: widgets, + ), ), ), - ), - ], + ], + ), ), ), ), @@ -389,7 +412,7 @@ class _HelpContent { final title = _extractTitle(raw) ?? 'Help'; // Extract tooltip text. - String tooltip = ''; + var tooltip = ''; if (tooltipIdx >= 0 && detailsIdx > tooltipIdx) { tooltip = raw .substring(tooltipIdx + tooltipMarker.length, detailsIdx) @@ -415,7 +438,7 @@ class _HelpContent { final blocks = <_DetailBlock>[]; final lines = raw.split('\n'); - for (int i = 0; i < lines.length; i++) { + for (var i = 0; i < lines.length; i++) { final line = lines[i]; final trimmed = line.trim(); diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_client.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_client.dart index 8ee033f2a..8e270848a 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_client.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_client.dart @@ -13,8 +13,9 @@ // Author: Desmond Kirkpatrick import 'package:flutter/foundation.dart'; +import 'package:rohd_devtools_widgets/src/rohd_extension_status.dart'; + export 'rohd_extension_status.dart'; -import 'rohd_extension_status.dart'; /// Abstract client for the ROHD VS Code extension handshake. /// diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_status.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_status.dart index f231260b5..4ec1e0bfb 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_status.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_status.dart @@ -39,6 +39,7 @@ class RohdFormatInfo { /// Resolved absolute path to the file, if known. final String? path; + /// Creates availability information for a source format. const RohdFormatInfo({ required this.available, this.fileFound = false, @@ -92,6 +93,7 @@ class RohdModuleInfo { /// `true` while the extension is still loading an FST file asynchronously. final bool fstLoading; + /// Creates information returned by the ROHD extension for a module. const RohdModuleInfo({ required this.extensionAvailable, this.module, @@ -165,7 +167,9 @@ class RohdModuleInfo { final formats = {}; for (final entry in rawFormats.entries) { final fmt = _parseFormat(entry.key); - if (fmt == null) continue; + if (fmt == null) { + continue; + } final fmtMap = entry.value as Map? ?? const {}; formats[fmt] = RohdFormatInfo( available: fmtMap['available'] as bool? ?? false, diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/save_png_web.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/save_png_web.dart index 5b0b9221e..555d3cc0e 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/save_png_web.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/save_png_web.dart @@ -25,8 +25,9 @@ Future savePngBytes(Uint8List pngBytes, String fileName) async { ..download = fileName ..style.display = 'none'; web.document.body!.append(anchor); - anchor.click(); - anchor.remove(); + anchor + ..click() + ..remove(); web.URL.revokeObjectURL(url); return null; } diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_expansion_menu_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_expansion_menu_test.dart index 2d92775c2..5c21d5bf2 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_expansion_menu_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_expansion_menu_test.dart @@ -7,9 +7,8 @@ // 2026 July // Author: Desmond Kirkpatrick -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:material_ui/material_ui.dart' as material_ui; +import 'package:material_ui/material_ui.dart'; import 'package:rohd_devtools_widgets/rohd_devtools_widgets.dart'; void main() { @@ -24,23 +23,17 @@ void main() { ); expect(items, hasLength(3)); - expect(items[0], isA()); - expect( - (items[1] as material_ui.PopupMenuItem).value, - 'expand_bits', - ); - expect( - (items[2] as material_ui.PopupMenuItem).value, - 'define_fields', - ); + expect(items[0], isA()); + expect((items[1] as PopupMenuItem).value, 'expand_bits'); + expect((items[2] as PopupMenuItem).value, 'define_fields'); await tester.pumpWidget( MaterialApp( home: Material( child: Column( children: [ - (items[1] as material_ui.PopupMenuItem).child!, - (items[2] as material_ui.PopupMenuItem).child!, + (items[1] as PopupMenuItem).child!, + (items[2] as PopupMenuItem).child!, ], ), ), @@ -122,7 +115,7 @@ void main() { await tester.tap(find.text('OK')); await tester.pumpAndSettle(); - final range = await rangeFuture as BitExpandRangeAction; + final range = (await rangeFuture)! as BitExpandRangeAction; expect(range.bitStart, 3); expect(range.bitEnd, 6); @@ -137,7 +130,7 @@ void main() { await tester.tap(find.text('OK')); await tester.pumpAndSettle(); - final fields = await fieldsFuture as BitDefineFieldsAction; + final fields = (await fieldsFuture)! as BitDefineFieldsAction; expect(fields.fields.map((field) => field.name), ['upper', 'lower']); }); } diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_field_utils_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_field_utils_test.dart index 318414b13..a72f62197 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_field_utils_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/bit_field_utils_test.dart @@ -7,8 +7,8 @@ // 2026 July // Author: Desmond Kirkpatrick -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:material_ui/material_ui.dart'; import 'package:rohd_devtools_widgets/rohd_devtools_widgets.dart'; void main() { diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/capture_boundary_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/capture_boundary_test.dart index 8f8f4ba6f..5a1d170e0 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/capture_boundary_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/capture_boundary_test.dart @@ -103,7 +103,7 @@ void main() { find.byWidgetPredicate( (widget) => widget is Text && - widget.data?.startsWith('Saved: /tmp/wave_') == true, + (widget.data?.startsWith('Saved: /tmp/wave_') ?? false), ), findsOneWidget, ); @@ -136,7 +136,7 @@ void main() { find.byWidgetPredicate( (widget) => widget is Text && - widget.data?.startsWith('Downloaded capture_') == true, + (widget.data?.startsWith('Downloaded capture_') ?? false), ), findsOneWidget, ); diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_menu_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_menu_test.dart index 45e83afed..2182cf360 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_menu_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/cross_probe_menu_test.dart @@ -7,9 +7,8 @@ // 2026 July // Author: Desmond Kirkpatrick -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:material_ui/material_ui.dart' as material_ui; +import 'package:material_ui/material_ui.dart'; import 'package:rohd_devtools_widgets/rohd_devtools_widgets.dart'; void main() { @@ -34,7 +33,7 @@ void main() { test('resolves default and exact navigable formats', () { expect(resolveNavigableFormats(null), kDefaultNavigableFormats); expect( - resolveNavigableFormats(const RohdModuleInfo(extensionAvailable: false)), + resolveNavigableFormats(RohdModuleInfo.unavailable), kDefaultNavigableFormats, ); expect( @@ -48,10 +47,7 @@ void main() { const RohdModuleInfo( extensionAvailable: true, formats: { - RohdSourceFormat.rohd: RohdFormatInfo( - available: true, - fileFound: false, - ), + RohdSourceFormat.rohd: RohdFormatInfo(available: true), RohdSourceFormat.sv: RohdFormatInfo( available: false, fileFound: true, @@ -62,21 +58,12 @@ void main() { isEmpty, ); - final info = RohdModuleInfo( + const info = RohdModuleInfo( extensionAvailable: true, formats: { - RohdSourceFormat.rohd: const RohdFormatInfo( - available: true, - fileFound: true, - ), - RohdSourceFormat.sc: const RohdFormatInfo( - available: true, - fileFound: true, - ), - RohdSourceFormat.fst: const RohdFormatInfo( - available: true, - fileFound: true, - ), + RohdSourceFormat.rohd: RohdFormatInfo(available: true, fileFound: true), + RohdSourceFormat.sc: RohdFormatInfo(available: true, fileFound: true), + RohdSourceFormat.fst: RohdFormatInfo(available: true, fileFound: true), }, ); @@ -92,13 +79,12 @@ void main() { final items = buildGotoSourceMenuItems( formats: [RohdSourceFormat.rohd, RohdSourceFormat.sv], count: 2, - showIcons: true, - iconBuilder: (format, {double size = 18}) => + iconBuilder: (format, {size = 18}) => Icon(Icons.code, key: ValueKey(format), size: size), ); expect(items, hasLength(2)); - expect(items, everyElement(isA>())); + expect(items, everyElement(isA>())); expect(items[0].value, 'goto_source:rohd'); expect(items[1].value, 'goto_source:sv'); @@ -179,7 +165,7 @@ void main() { ], size: 13, gap: 5, - iconBuilder: (format, {double size = 16}) => Icon( + iconBuilder: (format, {size = 16}) => Icon( Icons.code, key: ValueKey('strip-${format.name}'), size: size, diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/logic_type_utils_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/logic_type_utils_test.dart index 1922c16b5..bae71c8c2 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/logic_type_utils_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/logic_type_utils_test.dart @@ -141,7 +141,8 @@ void main() { maxDepth: 2, ); - expect(tooltip, '''packet (Packet) + expect(tooltip, ''' +packet (Packet) payload: 4'b1010 nibble: 4'b1010 ...'''); From 7d219e3372cdf045195c430ad434550483a77bb2 Mon Sep 17 00:00:00 2001 From: "Desmond A. Kirkpatrick" Date: Fri, 28 Aug 2026 07:51:12 -0700 Subject: [PATCH 06/10] bump flutter version --- .github/workflows/build_devtool.yml | 2 +- .github/workflows/general.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_devtool.yml b/.github/workflows/build_devtool.yml index 6263bb692..f6c7ada43 100644 --- a/.github/workflows/build_devtool.yml +++ b/.github/workflows/build_devtool.yml @@ -22,7 +22,7 @@ jobs: uses: flutter-actions/setup-flutter@v2 with: channel: stable - version: 3.29.0 + version: 3.44.0 - name: Run Flutter Test run: tool/gh_actions/devtool/run_devtool_test.sh diff --git a/.github/workflows/general.yml b/.github/workflows/general.yml index f2ed2cf65..d385bb1c7 100644 --- a/.github/workflows/general.yml +++ b/.github/workflows/general.yml @@ -111,7 +111,7 @@ jobs: uses: flutter-actions/setup-flutter@v2 with: channel: stable - version: 3.29.0 + version: 3.44.0 - name: Run Flutter Test run: tool/gh_actions/devtool/run_devtool_test.sh From d9b28c00d48ffc44cf2110de4129502ed2c50e4c Mon Sep 17 00:00:00 2001 From: "Desmond A. Kirkpatrick" Date: Fri, 28 Aug 2026 09:49:24 -0700 Subject: [PATCH 07/10] manifest extension change with new flutter --- rohd_devtools_extension/tool/test_devtools_install.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rohd_devtools_extension/tool/test_devtools_install.dart b/rohd_devtools_extension/tool/test_devtools_install.dart index 01e6a1c04..f04097f14 100644 --- a/rohd_devtools_extension/tool/test_devtools_install.dart +++ b/rohd_devtools_extension/tool/test_devtools_install.dart @@ -61,7 +61,7 @@ Future main(List args) async { _requireFile(extensionAssetsPath, 'flutter.js'); _requireFile(extensionAssetsPath, 'main.dart.js'); _requireFile(extensionAssetsPath, 'version.json'); - _requireFile(extensionAssetsPath, p.join('assets', 'AssetManifest.json')); + _requireFile(extensionAssetsPath, p.join('assets', 'AssetManifest.bin')); _requireFile(extensionAssetsPath, p.join('assets', 'FontManifest.json')); _requireFile(extensionAssetsPath, p.join('canvaskit', 'canvaskit.js')); _requireFile(extensionAssetsPath, p.join('canvaskit', 'canvaskit.wasm')); From 6bea167696b909ba03a739eaa683acaabe687422 Mon Sep 17 00:00:00 2001 From: "Desmond A. Kirkpatrick" Date: Fri, 28 Aug 2026 11:37:54 -0700 Subject: [PATCH 08/10] update floor versions for deprecated flutter classes --- .github/workflows/build_devtool.yml | 2 +- .github/workflows/general.yml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_devtool.yml b/.github/workflows/build_devtool.yml index f6c7ada43..7c718b471 100644 --- a/.github/workflows/build_devtool.yml +++ b/.github/workflows/build_devtool.yml @@ -22,7 +22,7 @@ jobs: uses: flutter-actions/setup-flutter@v2 with: channel: stable - version: 3.44.0 + version: 3.47.0 - name: Run Flutter Test run: tool/gh_actions/devtool/run_devtool_test.sh diff --git a/.github/workflows/general.yml b/.github/workflows/general.yml index d385bb1c7..17aca201e 100644 --- a/.github/workflows/general.yml +++ b/.github/workflows/general.yml @@ -34,6 +34,8 @@ jobs: - name: Setup Dart uses: dart-lang/setup-dart@v1 + with: + sdk: 3.13.0 - name: Setup Node uses: actions/setup-node@v4 @@ -111,7 +113,7 @@ jobs: uses: flutter-actions/setup-flutter@v2 with: channel: stable - version: 3.44.0 + version: 3.47.0 - name: Run Flutter Test run: tool/gh_actions/devtool/run_devtool_test.sh @@ -121,4 +123,3 @@ jobs: - name: Test DevTools Installation run: tool/gh_actions/devtool/test_devtools_install.sh extension/devtools - From 1d3ad78a772afa7cab5eff53baf93e25955b4cf0 Mon Sep 17 00:00:00 2001 From: "Desmond A. Kirkpatrick" Date: Fri, 28 Aug 2026 13:48:30 -0700 Subject: [PATCH 09/10] Flutter deprecations require us to move to 3.44. This pulls in Dart 3.12 or above, which formats differently. To avoid churn due to formatting as we have downstream PRs, we lower-bound to Dart 3.6 for now, eventually 3.13. Hopefully this threads the needly on formatting and deprecation until we can do an en-masse reformat once PRs are resolved. --- .github/workflows/build_devtool.yml | 2 +- .github/workflows/general.yml | 2 +- .../lib/src/app_bar_overlay.dart | 21 +- .../lib/src/bit_expansion_menu.dart | 34 ++-- .../lib/src/bit_field_utils.dart | 6 +- .../lib/src/capture_boundary.dart | 8 +- .../lib/src/cross_probe_button.dart | 30 +-- .../lib/src/cross_probe_menu.dart | 180 +++++++++--------- .../lib/src/cross_probe_service.dart | 7 +- .../lib/src/markdown_help_button.dart | 8 +- .../lib/src/rohd_extension_client.dart | 6 +- .../lib/src/rohd_extension_status.dart | 82 ++++---- .../rohd_devtools_widgets/pubspec.yaml | 2 +- .../test/app_bar_overlay_test.dart | 6 +- 14 files changed, 198 insertions(+), 196 deletions(-) diff --git a/.github/workflows/build_devtool.yml b/.github/workflows/build_devtool.yml index 7c718b471..f6c7ada43 100644 --- a/.github/workflows/build_devtool.yml +++ b/.github/workflows/build_devtool.yml @@ -22,7 +22,7 @@ jobs: uses: flutter-actions/setup-flutter@v2 with: channel: stable - version: 3.47.0 + version: 3.44.0 - name: Run Flutter Test run: tool/gh_actions/devtool/run_devtool_test.sh diff --git a/.github/workflows/general.yml b/.github/workflows/general.yml index 17aca201e..ad9dfe11a 100644 --- a/.github/workflows/general.yml +++ b/.github/workflows/general.yml @@ -113,7 +113,7 @@ jobs: uses: flutter-actions/setup-flutter@v2 with: channel: stable - version: 3.47.0 + version: 3.44.0 - name: Run Flutter Test run: tool/gh_actions/devtool/run_devtool_test.sh diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/app_bar_overlay.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/app_bar_overlay.dart index 38848173a..7213f454c 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/app_bar_overlay.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/app_bar_overlay.dart @@ -81,17 +81,16 @@ class _AppBarOverlayState extends State vsync: this, duration: widget.animationDuration, ); - _slideAnimation = - Tween( - begin: const Offset(0, -1), // fully off-screen above - end: Offset.zero, - ).animate( - CurvedAnimation( - parent: _controller, - curve: Curves.easeOutCubic, - reverseCurve: Curves.easeInCubic, - ), - ); + _slideAnimation = Tween( + begin: const Offset(0, -1), // fully off-screen above + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: _controller, + curve: Curves.easeOutCubic, + reverseCurve: Curves.easeInCubic, + ), + ); // If not auto-hiding, snap open. if (!widget.autoHide) { diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_expansion_menu.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_expansion_menu.dart index a676fed4f..ec72c54ed 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_expansion_menu.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_expansion_menu.dart @@ -74,22 +74,24 @@ List> buildBitExpansionMenuItems({ double fontSize = 13, double itemHeight = 32, bool includeDivider = false, -}) => >[ - if (includeDivider) const PopupMenuDivider(height: 8), - PopupMenuItem( - height: itemHeight, - value: BitExpansionMenuValues.expandBits, - child: Text('Expand Bits [$width]', style: TextStyle(fontSize: fontSize)), - ), - PopupMenuItem( - height: itemHeight, - value: BitExpansionMenuValues.defineFields, - child: Text( - 'Define Bit Fields [$width]...', - style: TextStyle(fontSize: fontSize), - ), - ), -]; +}) => + >[ + if (includeDivider) const PopupMenuDivider(height: 8), + PopupMenuItem( + height: itemHeight, + value: BitExpansionMenuValues.expandBits, + child: + Text('Expand Bits [$width]', style: TextStyle(fontSize: fontSize)), + ), + PopupMenuItem( + height: itemHeight, + value: BitExpansionMenuValues.defineFields, + child: Text( + 'Define Bit Fields [$width]...', + style: TextStyle(fontSize: fontSize), + ), + ), + ]; /// Translate a popup-menu [value] returned by `showMenu` into a /// [BitExpansionAction], showing any follow-up dialog as needed. diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_field_utils.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_field_utils.dart index f13d5e523..9b705cdfd 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_field_utils.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/bit_field_utils.dart @@ -103,8 +103,7 @@ abstract final class BitFieldUtils { } // Try: name bit (single bit) - final namedSingle = - tokens.length == 2 && + final namedSingle = tokens.length == 2 && _isWord(tokens[0]) && _isUnsignedDecimal(tokens[1]); if (namedSingle) { @@ -312,8 +311,7 @@ Future?> showDefineBitFieldsDialog( style: const TextStyle(fontFamily: 'monospace', fontSize: 13), decoration: InputDecoration( labelText: 'One field per line: name high:low', - hintText: - 'exponent $maxBit:${maxBit - 10}\n' + hintText: 'exponent $maxBit:${maxBit - 10}\n' 'mantissa ${maxBit - 11}:0', isDense: true, border: const OutlineInputBorder(), diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/capture_boundary.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/capture_boundary.dart index bc402cab8..9c3e05ab8 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/capture_boundary.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/capture_boundary.dart @@ -42,8 +42,7 @@ Future captureBoundaryToPng( Future Function( RenderRepaintBoundary boundary, double pixelRatio, - )? - encodeFn, + )? encodeFn, }) async { final renderObject = boundaryKey.currentContext?.findRenderObject(); if (renderObject is! RenderRepaintBoundary) { @@ -65,9 +64,8 @@ Future captureBoundaryToPng( try { final save = saveFn ?? export_png.savePngBytes; final savedPath = await save(pngBytes, fileName); - final msg = savedPath != null - ? 'Saved: $savedPath' - : 'Downloaded $fileName'; + final msg = + savedPath != null ? 'Saved: $savedPath' : 'Downloaded $fileName'; debugPrint('[ExportPng] $msg'); if (context.mounted) { export_png.showExportToast(context, msg); diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_button.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_button.dart index a3281c169..6dc511088 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_button.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_button.dart @@ -28,22 +28,22 @@ class CrossProbeButton extends StatelessWidget { @override Widget build(BuildContext context) => ValueListenableBuilder( - valueListenable: service.isActive, - builder: (context, active, _) { - final color = active - ? Theme.of(context).colorScheme.primary - : Theme.of(context).disabledColor; - return Tooltip( - message: active - ? 'Cross-probing active — tap to disable' - : 'Cross-probing disabled — tap to enable', - child: IconButton( - icon: Icon(Icons.compare_arrows, color: color), - onPressed: () => service.isActive.value = !service.isActive.value, - ), + valueListenable: service.isActive, + builder: (context, active, _) { + final color = active + ? Theme.of(context).colorScheme.primary + : Theme.of(context).disabledColor; + return Tooltip( + message: active + ? 'Cross-probing active — tap to disable' + : 'Cross-probing disabled — tap to enable', + child: IconButton( + icon: Icon(Icons.compare_arrows, color: color), + onPressed: () => service.isActive.value = !service.isActive.value, + ), + ); + }, ); - }, - ); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_menu.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_menu.dart index 001de5b73..0c186214d 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_menu.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_menu.dart @@ -26,12 +26,12 @@ import 'package:rohd_devtools_widgets/src/rohd_extension_status.dart'; typedef AvailableSourceFormats = List Function(); /// Invoked when the user picks `Go to Source` for [signalPaths]. -typedef GoToSourceCallback = - void Function(RohdSourceFormat format, List signalPaths); +typedef GoToSourceCallback = void Function( + RohdSourceFormat format, List signalPaths); /// Builds an icon for a source/output [format]. -typedef SourceFormatIconBuilder = - Widget Function(RohdSourceFormat format, {double size}); +typedef SourceFormatIconBuilder = Widget Function(RohdSourceFormat format, + {double size}); /// Prefix used to encode source-navigation entries in a `String`-valued popup /// menu (e.g. `'goto_source:rohd'`). Allows the shared items to coexist with @@ -68,11 +68,11 @@ RohdSourceFormat? gotoSourceFormatFromValue(String? value) { /// Short, menu-friendly name for [format] (e.g. `'ROHD'`, `'SV'`). String gotoSourceShortName(RohdSourceFormat format) => switch (format) { - RohdSourceFormat.rohd => 'ROHD', - RohdSourceFormat.sv => 'SV', - RohdSourceFormat.sc => 'SystemC', - RohdSourceFormat.fst => 'Waveform', -}; + RohdSourceFormat.rohd => 'ROHD', + RohdSourceFormat.sv => 'SV', + RohdSourceFormat.sc => 'SystemC', + RohdSourceFormat.fst => 'Waveform', + }; /// Menu label for `Go to Source`, pluralized with [count]. String gotoSourceMenuLabel(RohdSourceFormat format, {int count = 1}) { @@ -88,20 +88,20 @@ const _systemCIconAsset = 'assets/systemc_icon.png'; Widget sourceFormatMenuIcon(RohdSourceFormat format, {double size = 18}) => switch (format) { RohdSourceFormat.rohd => _sourceFormatAssetIcon( - _rohdIconAsset, - semanticLabel: 'ROHD Source', - size: size, - ), + _rohdIconAsset, + semanticLabel: 'ROHD Source', + size: size, + ), RohdSourceFormat.sv => _sourceFormatAssetIcon( - _systemVerilogIconAsset, - semanticLabel: 'SystemVerilog Source', - size: size, - ), + _systemVerilogIconAsset, + semanticLabel: 'SystemVerilog Source', + size: size, + ), RohdSourceFormat.sc => _sourceFormatAssetIcon( - _systemCIconAsset, - semanticLabel: 'SystemC Source', - size: size, - ), + _systemCIconAsset, + semanticLabel: 'SystemC Source', + size: size, + ), RohdSourceFormat.fst => Icon(Icons.timeline, size: size), }; @@ -113,45 +113,46 @@ Widget _sourceFormatAssetIcon( String asset, { required String semanticLabel, required double size, -}) => Builder( - builder: (context) { - final isDark = Theme.of(context).brightness == Brightness.dark; - final image = Image.asset( - asset, - width: size, - height: size, - fit: BoxFit.contain, - filterQuality: FilterQuality.high, - semanticLabel: semanticLabel, - errorBuilder: (context, error, stackTrace) => Image.asset( - asset, - package: 'rohd_devtools_widgets', - width: size, - height: size, - fit: BoxFit.contain, - filterQuality: FilterQuality.high, - semanticLabel: semanticLabel, - errorBuilder: (context, error, stackTrace) => - Icon(Icons.code, size: size), - ), - ); - - if (!isDark) { - return image; - } - - return Container( - width: size + 4, - height: size + 4, - decoration: const BoxDecoration( - color: Color(0xFFE0E0E0), - shape: BoxShape.circle, - ), - padding: const EdgeInsets.all(2), - child: image, +}) => + Builder( + builder: (context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + final image = Image.asset( + asset, + width: size, + height: size, + fit: BoxFit.contain, + filterQuality: FilterQuality.high, + semanticLabel: semanticLabel, + errorBuilder: (context, error, stackTrace) => Image.asset( + asset, + package: 'rohd_devtools_widgets', + width: size, + height: size, + fit: BoxFit.contain, + filterQuality: FilterQuality.high, + semanticLabel: semanticLabel, + errorBuilder: (context, error, stackTrace) => + Icon(Icons.code, size: size), + ), + ); + + if (!isDark) { + return image; + } + + return Container( + width: size + 4, + height: size + 4, + decoration: const BoxDecoration( + color: Color(0xFFE0E0E0), + shape: BoxShape.circle, + ), + padding: const EdgeInsets.all(2), + child: image, + ); + }, ); - }, -); /// Standard popup-menu row with a fixed-width prefix icon and ellipsized label. Widget sourcePopupMenuRow({ @@ -160,19 +161,20 @@ Widget sourcePopupMenuRow({ TextStyle? textStyle, double iconSlotWidth = 22, double gap = 8, -}) => Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - width: iconSlotWidth, - child: Center(child: icon), - ), - SizedBox(width: gap), - Flexible( - child: Text(label, style: textStyle, overflow: TextOverflow.ellipsis), - ), - ], -); +}) => + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: iconSlotWidth, + child: Center(child: icon), + ), + SizedBox(width: gap), + Flexible( + child: Text(label, style: textStyle, overflow: TextOverflow.ellipsis), + ), + ], + ); /// Standard popup-menu item using the same fixed icon gutter as source rows. PopupMenuItem buildRohdPopupMenuItem({ @@ -182,12 +184,13 @@ PopupMenuItem buildRohdPopupMenuItem({ double height = 32, TextStyle? textStyle, bool enabled = true, -}) => PopupMenuItem( - value: value, - height: height, - enabled: enabled, - child: sourcePopupMenuRow(icon: icon, label: label, textStyle: textStyle), -); +}) => + PopupMenuItem( + value: value, + height: height, + enabled: enabled, + child: sourcePopupMenuRow(icon: icon, label: label, textStyle: textStyle), + ); /// Compact strip of source/output format icons for trace-picker menu rows. Widget sourceFormatIconStrip({ @@ -233,13 +236,14 @@ List> buildGotoSourceMenuItems({ TextStyle? textStyle, bool showIcons = true, SourceFormatIconBuilder iconBuilder = sourceFormatMenuIcon, -}) => [ - for (final format in formats) - buildRohdPopupMenuItem( - value: gotoSourceMenuValue(format), - height: height, - icon: showIcons ? iconBuilder(format) : const SizedBox.shrink(), - label: gotoSourceMenuLabel(format, count: count), - textStyle: textStyle, - ), -]; +}) => + [ + for (final format in formats) + buildRohdPopupMenuItem( + value: gotoSourceMenuValue(format), + height: height, + icon: showIcons ? iconBuilder(format) : const SizedBox.shrink(), + label: gotoSourceMenuLabel(format, count: count), + textStyle: textStyle, + ), + ]; diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_service.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_service.dart index 8b90a5e8f..4bfe77cb2 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_service.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/cross_probe_service.dart @@ -100,12 +100,13 @@ class LocalCrossProbeService implements CrossProbeService { /// Creates a [LocalCrossProbeService] backed by [channel]. /// - /// The `source` argument identifies self-broadcasts to filter. Use a + /// [source] identifies self-broadcasts to filter. Use a /// stable, descriptive tag such as `'waveform'` or `'schematic'`. LocalCrossProbeService( LocalCrossProbeChannel channel, { - required this._source, - }) : _channel = channel { + required String source, + }) : _channel = channel, + _source = source { _channel.addListener(_onChannelMessage); } diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/markdown_help_button.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/markdown_help_button.dart index 312b8d78d..2fce2e0ba 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/markdown_help_button.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/markdown_help_button.dart @@ -238,8 +238,7 @@ class _MarkdownHelpButtonState extends State { }, child: Padding( padding: const EdgeInsets.all(8), - child: - widget.labelIcon ?? + child: widget.labelIcon ?? Text( widget.label, style: const TextStyle(fontSize: 18, inherit: false), @@ -414,9 +413,8 @@ class _HelpContent { // Extract tooltip text. var tooltip = ''; if (tooltipIdx >= 0 && detailsIdx > tooltipIdx) { - tooltip = raw - .substring(tooltipIdx + tooltipMarker.length, detailsIdx) - .trim(); + tooltip = + raw.substring(tooltipIdx + tooltipMarker.length, detailsIdx).trim(); } // Parse detail blocks. diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_client.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_client.dart index 8e270848a..155197562 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_client.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_client.dart @@ -109,13 +109,15 @@ class NullExtensionClient implements RohdExtensionClient { Future queryModule( String module, { List? instancePath, - }) async => RohdModuleInfo.unavailable; + }) async => + RohdModuleInfo.unavailable; @override Future>> lookupSignalFrames({ required List> signals, String? format, - }) async => const []; + }) async => + const []; @override void openSourceLocation({ diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_status.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_status.dart index 4ec1e0bfb..551994fca 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_status.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/lib/src/rohd_extension_status.dart @@ -126,11 +126,11 @@ class RohdModuleInfo { /// All format names for which data is available (as lower-case strings). List get availableFormatNames => [ - if (hasRohd) 'rohd', - if (hasSv) 'sv', - if (hasSc) 'sc', - if (hasFst) 'fst', - ]; + if (hasRohd) 'rohd', + if (hasSv) 'sv', + if (hasSc) 'sc', + if (hasFst) 'fst', + ]; /// True when any source navigation format is available. bool get hasAnySource => hasRohd || hasSv || hasSc; @@ -139,18 +139,18 @@ class RohdModuleInfo { /// this module, in display order. Excludes [RohdSourceFormat.fst] (a /// waveform, not a navigable source). List get navigableSourceFormats => [ - if (hasRohd) RohdSourceFormat.rohd, - if (hasSv) RohdSourceFormat.sv, - if (hasSc) RohdSourceFormat.sc, - ]; + if (hasRohd) RohdSourceFormat.rohd, + if (hasSv) RohdSourceFormat.sv, + if (hasSc) RohdSourceFormat.sc, + ]; /// Human-readable label for a format. static String formatLabel(RohdSourceFormat fmt) => switch (fmt) { - RohdSourceFormat.rohd => 'ROHD (Dart)', - RohdSourceFormat.sv => 'SystemVerilog', - RohdSourceFormat.sc => 'SystemC', - RohdSourceFormat.fst => 'Waveform (FST)', - }; + RohdSourceFormat.rohd => 'ROHD (Dart)', + RohdSourceFormat.sv => 'SystemVerilog', + RohdSourceFormat.sc => 'SystemC', + RohdSourceFormat.fst => 'Waveform (FST)', + }; /// Build from a JSON map (as returned by the extension host or DTD). factory RohdModuleInfo.fromJson(Map json) { @@ -192,36 +192,36 @@ class RohdModuleInfo { /// Serialize to JSON for transmission over DTD or postMessage. Map toJson() => { - 'extensionAvailable': extensionAvailable, - if (module != null) 'module': module, - 'formats': { - for (final e in formats.entries) - _formatKey(e.key): { - 'available': e.value.available, - 'fileFound': e.value.fileFound, - if (e.value.path != null) 'path': e.value.path, + 'extensionAvailable': extensionAvailable, + if (module != null) 'module': module, + 'formats': { + for (final e in formats.entries) + _formatKey(e.key): { + 'available': e.value.available, + 'fileFound': e.value.fileFound, + if (e.value.path != null) 'path': e.value.path, + }, }, - }, - if (error != null) 'error': error, - if (dtdHealthy != null) 'dtdHealthy': dtdHealthy, - if (dtdRegistrationConflict) - 'dtdRegistrationConflict': dtdRegistrationConflict, - if (dtdStatusMessage != null) 'dtdStatusMessage': dtdStatusMessage, - 'fstLoading': fstLoading, - }; + if (error != null) 'error': error, + if (dtdHealthy != null) 'dtdHealthy': dtdHealthy, + if (dtdRegistrationConflict) + 'dtdRegistrationConflict': dtdRegistrationConflict, + if (dtdStatusMessage != null) 'dtdStatusMessage': dtdStatusMessage, + 'fstLoading': fstLoading, + }; static RohdSourceFormat? _parseFormat(String key) => switch (key) { - 'rohd' => RohdSourceFormat.rohd, - 'sv' => RohdSourceFormat.sv, - 'sc' => RohdSourceFormat.sc, - 'fst' => RohdSourceFormat.fst, - _ => null, - }; + 'rohd' => RohdSourceFormat.rohd, + 'sv' => RohdSourceFormat.sv, + 'sc' => RohdSourceFormat.sc, + 'fst' => RohdSourceFormat.fst, + _ => null, + }; static String _formatKey(RohdSourceFormat fmt) => switch (fmt) { - RohdSourceFormat.rohd => 'rohd', - RohdSourceFormat.sv => 'sv', - RohdSourceFormat.sc => 'sc', - RohdSourceFormat.fst => 'fst', - }; + RohdSourceFormat.rohd => 'rohd', + RohdSourceFormat.sv => 'sv', + RohdSourceFormat.sc => 'sc', + RohdSourceFormat.fst => 'fst', + }; } diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/pubspec.yaml b/rohd_devtools_extension/packages/rohd_devtools_widgets/pubspec.yaml index 4383b9631..e62bd669c 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/pubspec.yaml +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/pubspec.yaml @@ -3,7 +3,7 @@ description: "Shared widgets for ROHD DevTools sub-packages: help button, PNG ex version: 0.1.0 publish_to: none environment: - sdk: '>=3.12.0 <4.0.0' + sdk: '>=3.6.0 <4.0.0' flutter: '>=3.44.0' dependencies: flutter: {sdk: flutter} diff --git a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/app_bar_overlay_test.dart b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/app_bar_overlay_test.dart index 0870687e6..d67fd81c9 100644 --- a/rohd_devtools_extension/packages/rohd_devtools_widgets/test/app_bar_overlay_test.dart +++ b/rohd_devtools_extension/packages/rohd_devtools_widgets/test/app_bar_overlay_test.dart @@ -15,9 +15,9 @@ import 'package:rohd_devtools_widgets/rohd_devtools_widgets.dart'; void main() { PreferredSizeWidget testAppBar() => const PreferredSize( - preferredSize: Size.fromHeight(48), - child: Material(child: Text('Toolbar')), - ); + preferredSize: Size.fromHeight(48), + child: Material(child: Text('Toolbar')), + ); testWidgets('lays out app bar above body when auto-hide is disabled', ( tester, From 276a41ce8544338ba090a7fdd69e0ac538dab636 Mon Sep 17 00:00:00 2001 From: "Desmond A. Kirkpatrick" Date: Sun, 30 Aug 2026 05:51:45 -0700 Subject: [PATCH 10/10] Actually deploy the devtools web app as part of the github page --- .github/workflows/general.yml | 21 +++++- rohd_devtools_extension/Makefile | 19 +++++- rohd_devtools_extension/README.md | 13 ++++ .../tool/gh_actions/build_app.sh | 68 +++++++++++++++++++ 4 files changed, 118 insertions(+), 3 deletions(-) create mode 100755 rohd_devtools_extension/tool/gh_actions/build_app.sh diff --git a/.github/workflows/general.yml b/.github/workflows/general.yml index ad9dfe11a..938afc975 100644 --- a/.github/workflows/general.yml +++ b/.github/workflows/general.yml @@ -77,7 +77,9 @@ jobs: deploy-documentation: name: Deploy Documentation - needs: run-checks + needs: + - run-checks + - build-devtool if: github.event_name == 'push' permissions: contents: write # required for "JamesIves/github-pages-deploy-action" @@ -96,6 +98,12 @@ jobs: - name: Generate project documentation run: tool/gh_actions/generate_documentation.sh + - name: Add standalone DevTools application + uses: actions/download-artifact@v4 + with: + name: rohd-devtools-standalone + path: doc/api/rohd_devtools_extension + - name: Deploy generated documentation uses: JamesIves/github-pages-deploy-action@v4 with: @@ -123,3 +131,14 @@ jobs: - name: Test DevTools Installation run: tool/gh_actions/devtool/test_devtools_install.sh extension/devtools + + - name: Build standalone DevTools application + run: rohd_devtools_extension/tool/gh_actions/build_app.sh /rohd_devtools_extension/ + + - name: Upload standalone DevTools application + uses: actions/upload-artifact@v4 + with: + name: rohd-devtools-standalone + path: rohd_devtools_extension/build/web + if-no-files-found: error + include-hidden-files: true diff --git a/rohd_devtools_extension/Makefile b/rohd_devtools_extension/Makefile index 19c7ac8ca..c8915711c 100644 --- a/rohd_devtools_extension/Makefile +++ b/rohd_devtools_extension/Makefile @@ -1,8 +1,9 @@ # Minimal build entrypoints for the upstream standalone extension package. ROOT := $(shell pwd) +FLUTTER_WEB_BUILD_ARGS ?= -.PHONY: all linux clean-linux help +.PHONY: all linux web web-release clean-linux clean-web help all: linux @@ -10,14 +11,28 @@ linux: @flutter pub get @flutter build linux --target=lib/main_standalone.dart +web: + @flutter pub get + @flutter build web --release --target=lib/main_standalone.dart $(FLUTTER_WEB_BUILD_ARGS) + +web-release: + @rm -f build/web/index.html + @$(MAKE) web + clean-linux: -rm -rf $(ROOT)/build/linux -rm -rf $(ROOT)/linux/flutter/ephemeral +clean-web: + -rm -rf $(ROOT)/build/web + help: @echo "ROHD DevTools Extension" @echo "" @echo "Targets:" @echo " all - Build the Linux standalone app (default)" @echo " linux - Run flutter pub get and build Linux standalone" - @echo " clean-linux - Remove Linux build outputs" \ No newline at end of file + @echo " web - Build the web standalone app" + @echo " web-release - Force a release web build" + @echo " clean-linux - Remove Linux build outputs" + @echo " clean-web - Remove web build outputs" \ No newline at end of file diff --git a/rohd_devtools_extension/README.md b/rohd_devtools_extension/README.md index acb09c486..b78feb2ab 100644 --- a/rohd_devtools_extension/README.md +++ b/rohd_devtools_extension/README.md @@ -37,6 +37,19 @@ cd rohd_devtools_extension flutter run --release -d web-server --web-port=9099 --web-hostname=0.0.0.0 lib/main_standalone.dart ``` +Build a static bundle for a repository-scoped web path: + +```sh +cd rohd_devtools_extension +tool/gh_actions/build_app.sh /rohd_devtools_extension/ +``` + +The bundle is written to `build/web/`. The optional argument must start and end +with `/`; it becomes the Flutter base href used when the app is hosted below a +site root. Local builds default to `/rohd_devtools_extension/`. On pushes to +`main`, the `General` workflow includes this bundle in the existing ROHD +documentation deployment at `/rohd_devtools_extension/`. + Run the release Linux standalone form: ```sh diff --git a/rohd_devtools_extension/tool/gh_actions/build_app.sh b/rohd_devtools_extension/tool/gh_actions/build_app.sh new file mode 100755 index 000000000..072b23e2b --- /dev/null +++ b/rohd_devtools_extension/tool/gh_actions/build_app.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +# Copyright (C) 2026 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# +# Builds the standalone ROHD DevTools application for static hosting. +# +# Usage: +# tool/gh_actions/build_app.sh [base-href] + +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +base_href="${1:-}" + +if [[ -z "$base_href" ]]; then + if [[ -n "${GITHUB_REPOSITORY:-}" ]]; then + owner="${GITHUB_REPOSITORY%%/*}" + repository="${GITHUB_REPOSITORY#*/}" + if [[ "$repository" == "$owner.github.io" ]]; then + base_href="/" + else + base_href="/$repository/" + fi + else + base_href="/rohd_devtools_extension/" + fi +fi + +if [[ ! "$base_href" =~ ^/[A-Za-z0-9._/-]*/$ ]]; then + echo "error: base href must start and end with '/': $base_href" >&2 + exit 64 +fi + +cd "$repo_root" + +make web-release \ + FLUTTER_WEB_BUILD_ARGS="--pwa-strategy=none --base-href=$base_href" + +rm -f build/web/flutter_service_worker.js + +required_files=( + build/web/index.html + build/web/flutter_bootstrap.js + build/web/main.dart.js + build/web/assets/AssetManifest.bin +) + +for required_file in "${required_files[@]}"; do + if [[ ! -f "$required_file" ]]; then + echo "error: expected static app artifact not found: $required_file" >&2 + exit 1 + fi +done + +if grep -Fq 'serviceWorkerSettings: {' build/web/flutter_bootstrap.js; then + echo "error: Flutter bootstrap unexpectedly registers a service worker." >&2 + exit 1 +fi + +if ! grep -Fq "" build/web/index.html; then + echo "error: generated index.html does not use base href $base_href" >&2 + exit 1 +fi + +touch build/web/.nojekyll + +echo "Standalone app ready in build/web/ (base href: $base_href)."