Skip to content

Store/restore view states to/from settings.json#997

Open
jthuangarm wants to merge 20 commits into
mainfrom
storeViewStates
Open

Store/restore view states to/from settings.json#997
jthuangarm wants to merge 20 commits into
mainfrom
storeViewStates

Conversation

@jthuangarm
Copy link
Copy Markdown
Collaborator

@jthuangarm jthuangarm commented May 20, 2026

Fixes

Changes

  • Added a new vscode-cmsis-debugger.viewState configuration property in package.json to persist dynamic view state per debug configuration, including fields for componentViewer, corePeripherals, and cpuStates.
  • Added a new command vscode-cmsis-debugger.resetDynamicViewState to reset all dynamic view states.
  • Added icons to periodic update enable/disable commands for Component Viewer and Core Peripherals.
  • Updated the CPU time status bar item to allow enabling/disabling the CPU timer from the UI, reflecting the current state.
  • Let attached session not take focus from currently active session.

Example output in settings.json:

    "vscode-cmsis-debugger.viewState": {
        "STM32H747I-EVAL::CM7 STLink@pyOCD (launch)": {
            "componentViewer": {
                "periodicUpdateEnabled": true,
                "filterPattern": "KERNEL"
            },
            "cpuStates": false
        },
        "STM32H747I-EVAL::CM4 STLink@pyOCD (attach)": {
            "corePeripherals": {
                "filterPattern": "SYST"
            }
        }
    },

Screenshots

To enable/disable CPU Timer:
image

To enable/disable periodic update:
image

Checklist

  • 🤖 This change is covered by unit tests (if applicable).
  • 🤹 Manual testing has been performed (if necessary).
  • 🛡️ Security impacts have been considered (if relevant).
  • 📖 Documentation updates are complete (if required).
  • 🧠 Third-party dependencies and TPIP updated (if required).

Comment thread src/views/component-viewer/component-viewer-base.ts Fixed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds persistence for “dynamic” UI state (periodic-update toggles, view filters, and CPU timer enablement) by storing/restoring per-debug-configuration state in settings.json, plus a new command to reset all persisted dynamic view state.

Changes:

  • Introduces a vscode-cmsis-debugger.viewState configuration object and helper module to read/merge/write per-config view state.
  • Restores/saves Component Viewer/Core Peripherals periodic update + filter state, and persists CPU timer enablement per debug configuration.
  • Adds a vscode-cmsis-debugger.resetDynamicViewState command and small UX updates (toolbar/command icons, status bar quick pick enable/disable).

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/views/dynamic-view-states.ts New helper for reading/merging/writing per-config dynamic view state to VS Code settings.
src/views/dynamic-view-states.test.ts Unit tests for dynamic view-state read/write/merge behavior.
src/views/component-viewer/component-viewer-base.ts Saves/restores periodic-update + filter state; debounced persistence while typing.
src/views/component-viewer/test/unit/component-viewer-base.test.ts Updates unit tests to cover view-state restore/reset behavior.
src/features/cpu-states/cpu-states.ts Persists CPU timer enable/disable per config and wires restored state into runtime.
src/features/cpu-states/cpu-states.test.ts Adds tests for persistence/restore and context switching behavior.
src/features/cpu-states/cpu-states-statusbar-item.ts Adds enable/disable CPU timer actions to the status bar quick pick.
src/desktop/extension.ts Registers “Reset All Dynamic View States” command and resets runtime state.
src/debug-session/gdbtarget-debug-session.ts Adds getConfigStateKey() to compute a stable persistence key including target-type.
src/debug-session/test/debug-session.factory.ts Extends test session factory types to support config state keys and target-type.
src/cbuild-run/cbuild-run-reader.ts Exposes getTargetType() used for deriving persistence keys.
package.json Adds new command, view-state configuration schema, and periodic-update command icons/menus.
mocks/vscode.js Extends VS Code mock to support configuration inspect/update and ConfigurationTarget.
Comments suppressed due to low confidence (4)

src/views/component-viewer/test/unit/component-viewer-base.test.ts:137

  • readComponentViewerState is configured with mockReturnValue(undefined) twice in a row. This is redundant and likely a copy/paste mistake; keep a single mock setup to avoid masking future changes to the intended behavior.
        controller = createController(context, provider);
        asMockedFunction(readComponentViewerState).mockReturnValue(undefined);
        asMockedFunction(readComponentViewerState).mockReturnValue(undefined);

src/views/component-viewer/test/unit/component-viewer-base.test.ts:1068

  • This test sets up readComponentViewerState.mockReturnValue(...) twice and asserts the call/side-effects twice, but restorePeriodicUpdateAndFilter only reads state once and applies the filter once. As written, the duplicated expectations are likely to fail; remove the duplicates and assert the expected single call / single setFilter invocation.
            asMockedFunction(readComponentViewerState).mockReturnValue({
                periodicUpdateEnabled: false,
                filterPattern: 'word',
            });
            asMockedFunction(readComponentViewerState).mockReturnValue({
                periodicUpdateEnabled: false,
                filterPattern: 'word',
            });
            const session = debugSessionFactory('s1');
            const restoreState = (controller as unknown as {
                restorePeriodicUpdateAndFilter: (s: Session) => Promise<void>;
            }).restorePeriodicUpdateAndFilter.bind(controller);
            await restoreState(session);

            expect(readComponentViewerState).toHaveBeenCalledWith('testClass', 's1');
            expect(readComponentViewerState).toHaveBeenCalledWith('testClass', 's1');
            expect((controller as unknown as { _refreshTimerEnabled: boolean })._refreshTimerEnabled).toBe(false);
            expect(vscode.commands.executeCommand).toHaveBeenCalledWith('setContext', 'testClass.periodicUpdateEnabled', false);
            expect(provider.setFilter).toHaveBeenCalledWith('word');
            expect(provider.setFilter).toHaveBeenCalledWith('word');
            expect(vscode.commands.executeCommand).toHaveBeenCalledWith('setContext', 'testClass.filterActive', true);

src/views/component-viewer/component-viewer-base.ts:241

  • Inside the debounce timer callback, saveCurrentState() (async) is invoked without awaiting or handling the returned Promise. If it rejects, this becomes an unhandled rejection. Consider void this.saveCurrentState().catch(...) (or make the callback async and handle errors) to avoid silent failures.
            this._filterDebounceTimer = setTimeout(() => {
                this._filterDebounceTimer = undefined;
                this.saveCurrentState();
            }, ComponentViewerBase.filterDebounceMs);

src/views/component-viewer/component-viewer-base.ts:278

  • handleClearFilter() calls saveCurrentState() (async) without awaiting/handling the returned Promise. If persisting settings fails, the rejection will be unobserved. Consider void this.saveCurrentState().catch(...) (or converting handleClearFilter to async and awaiting) for reliability.
        // Cancel any pending debounced save and persist the cleared state immediately.
        if (this._filterDebounceTimer) {
            clearTimeout(this._filterDebounceTimer);
            this._filterDebounceTimer = undefined;
        }
        this.saveCurrentState();
    }

Comment thread src/views/component-viewer/test/unit/component-viewer-base.test.ts Outdated
Comment thread src/views/component-viewer/component-viewer-base.ts Outdated
Comment thread src/views/dynamic-view-states.test.ts Outdated
Comment thread src/features/cpu-states/cpu-states.ts
Comment thread src/features/cpu-states/cpu-states.ts Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (6)

src/views/component-viewer/component-viewer-base.ts:140

  • vscode.commands.executeCommand('setContext', ...) returns a Thenable; here it’s invoked without await or void, which can violate no-floating-promises and may surface unhandled rejections. Please await it (if you need ordering) or prefix with void to explicitly ignore the promise.
        vscode.commands.executeCommand('setContext', `${this._viewId}.periodicUpdateEnabled`, true);
        return true;

src/views/component-viewer/component-viewer-base.ts:546

  • The setContext calls in this reset-to-defaults block are not awaited or explicitly ignored. To avoid floating promises (and keep consistent with other calls in this file, e.g. void vscode.commands.executeCommand(...)), use void or await for these commands.
        // Always reset to defaults before applying saved state to prevent state leaking while switching sessions
        this._refreshTimerEnabled = true;
        vscode.commands.executeCommand('setContext', `${this._viewId}.periodicUpdateEnabled`, true);
        this._componentViewerTreeDataProvider.setFilter(undefined);
        vscode.commands.executeCommand('setContext', `${this._viewId}.filterActive`, false);

src/views/component-viewer/component-viewer-base.ts:560

  • These additional setContext calls are also made without await/void. Please either await them (if subsequent logic depends on the context update) or explicitly ignore with void to satisfy promise-handling rules.
        if (state.periodicUpdateEnabled !== undefined) {
            this._refreshTimerEnabled = state.periodicUpdateEnabled;
            vscode.commands.executeCommand('setContext', `${this._viewId}.periodicUpdateEnabled`, state.periodicUpdateEnabled);
            componentViewerLogger.info(`${this._viewName}: Restored periodicUpdateEnabled=${state.periodicUpdateEnabled}`);
        }
        if (state.filterPattern !== undefined) {
            this._componentViewerTreeDataProvider.setFilter(state.filterPattern);
            vscode.commands.executeCommand('setContext', `${this._viewId}.filterActive`, true);
            componentViewerLogger.info(`${this._viewName}: Restored filterPattern='${state.filterPattern}'`);

src/views/component-viewer/component-viewer-base.ts:569

  • resetViewState() calls vscode.commands.executeCommand('setContext', ...) without await/void, which can trigger no-floating-promises and makes ordering unclear. Please handle the returned Thenable explicitly.
    public async resetViewState(): Promise<void> {
        // Reset in-memory state to defaults.
        this._refreshTimerEnabled = true;
        vscode.commands.executeCommand('setContext', `${this._viewId}.periodicUpdateEnabled`, true);
        this._componentViewerTreeDataProvider.setFilter(undefined);
        vscode.commands.executeCommand('setContext', `${this._viewId}.filterActive`, false);

src/features/cpu-states/cpu-states.ts:373

  • vscode.commands.executeCommand('setContext', ...) is invoked without await/void. Please handle/ignore the Thenable explicitly (consistent with the rest of the codebase) to avoid floating promises.
        for (const states of this.sessionCpuStates.values()) {
            states.enableCpuStatesFlag = true;
        }
        vscode.commands.executeCommand('setContext', 'vscode-cmsis-debugger.cpuTimerEnabled', true);
        logger.info('CPU States: CPU Timer reset');

src/views/component-viewer/test/unit/component-viewer-base.test.ts:1050

  • This test sets the same mockReturnValue twice and then repeats toHaveBeenCalledWith assertions. More importantly, it doesn’t assert the expected call sequence (setFilter(undefined) for reset, then setFilter('word')), so it won’t catch regressions where defaults aren’t applied first. Consider removing duplicate lines and asserting call order / call counts to verify the reset-then-apply behavior.
        it('restorePeriodicUpdateAndFilter restores periodicUpdateEnabled and filter from workspace settings', async () => {
            asMockedFunction(readComponentViewerState).mockReturnValue({
                periodicUpdateEnabled: false,
                filterPattern: 'word',
            });
            asMockedFunction(readComponentViewerState).mockReturnValue({
                periodicUpdateEnabled: false,
                filterPattern: 'word',
            });

Comment thread src/views/component-viewer/component-viewer-base.ts Outdated
Comment thread src/features/cpu-states/cpu-states.ts Outdated
Comment on lines 83 to 88
isRunning: true,
hasStates: undefined,
skipFrequencyUpdate: false
skipFrequencyUpdate: false,
enableCpuStatesFlag: true,
configStateKey: session.session.configuration.name
};
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional.
At session start, the cbuild-run file may not be parsed yet, so I use session.session.configuration.name as a temporary key. handleConnected() then refines it with session.getConfigStateKey() once the parsed cbuild-run data is available.
The risky window is only between session start and handleConnected() completion, which should be very short. Avoiding it completely would require pending writes or migration logic, which seems too complex for this edge case.

Comment thread src/views/component-viewer/test/unit/component-viewer-base.test.ts Outdated
@qltysh
Copy link
Copy Markdown

qltysh Bot commented May 20, 2026

Qlty


Coverage Impact

⬇️ Merging this pull request will decrease total coverage on main by 0.16%.

Modified Files with Diff Coverage (7)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
src/desktop/extension.ts50.0%92-93
Coverage rating: A Coverage rating: A
src/views/component-viewer/component-viewer-base.ts80.8%239-240, 535-537...
Coverage rating: A Coverage rating: A
src/features/cpu-states/cpu-states.ts92.6%350, 360
Coverage rating: A Coverage rating: A
src/cbuild-run/cbuild-run-reader.ts0.0%112
Coverage rating: A Coverage rating: A
src/debug-session/gdbtarget-debug-session.ts100.0%
Coverage rating: A Coverage rating: A
src/features/cpu-states/cpu-states-statusbar-item.ts33.3%85-90
New Coverage rating: A
src/views/dynamic-view-states.ts100.0%
Total87.1%
🤖 Increase coverage with AI coding...
In the `storeViewStates` branch, add test coverage for this new code:

- `src/cbuild-run/cbuild-run-reader.ts` -- Line 112
- `src/desktop/extension.ts` -- Line 92-93
- `src/features/cpu-states/cpu-states-statusbar-item.ts` -- Line 85-90
- `src/features/cpu-states/cpu-states.ts` -- Lines 350 and 360
- `src/views/component-viewer/component-viewer-base.ts` -- Lines 239-240, 535-537, and 571-575

🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

@jthuangarm jthuangarm marked this pull request as ready for review May 20, 2026 12:30
@jthuangarm jthuangarm requested a review from thorstendb-ARM May 20, 2026 12:34
@jthuangarm jthuangarm requested a review from jreineckearm May 20, 2026 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants