Skip to content

Commit e58f005

Browse files
authored
Add command for update while running (#829)
* adding command for enabling refresh timer * changing default behaviour to true * updating tests
1 parent f1ab609 commit e58f005

3 files changed

Lines changed: 163 additions & 13 deletions

File tree

package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,16 @@
155155
"title": "Unlock Component",
156156
"icon": "$(lock)",
157157
"category": "Component Viewer"
158+
},
159+
{
160+
"command": "vscode-cmsis-debugger.componentViewer.enablePeriodicUpdate",
161+
"title": "Enable Periodic Update",
162+
"category": "Component Viewer"
163+
},
164+
{
165+
"command": "vscode-cmsis-debugger.componentViewer.disablePeriodicUpdate",
166+
"title": "Disable Periodic Update",
167+
"category": "Component Viewer"
158168
}
159169
],
160170
"menus": {
@@ -246,6 +256,14 @@
246256
{
247257
"command": "vscode-cmsis-debugger.componentViewer.unlockComponent",
248258
"when": "false"
259+
},
260+
{
261+
"command": "vscode-cmsis-debugger.componentViewer.enablePeriodicUpdate",
262+
"when": "true"
263+
},
264+
{
265+
"command": "vscode-cmsis-debugger.componentViewer.disablePeriodicUpdate",
266+
"when": "true"
249267
}
250268
],
251269
"view/title": [

src/views/component-viewer/component-viewer-main.ts

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export class ComponentViewer {
4141
private _pendingUpdateTimer: NodeJS.Timeout | undefined;
4242
private _pendingUpdate: boolean = false;
4343
private _runningUpdate: boolean = false;
44+
private _refreshTimerEnabled: boolean = true;
4445
private static readonly pendingUpdateDelayMs = 150;
4546

4647
public constructor(context: vscode.ExtensionContext) {
@@ -63,10 +64,20 @@ export class ComponentViewer {
6364
const unlockInstanceCommandDisposable = vscode.commands.registerCommand('vscode-cmsis-debugger.componentViewer.unlockComponent', async (node) => {
6465
this.handleLockInstance(node);
6566
});
67+
const enablePeriodicUpdateCommandDisposable = vscode.commands.registerCommand('vscode-cmsis-debugger.componentViewer.enablePeriodicUpdate', async () => {
68+
this._refreshTimerEnabled = true;
69+
componentViewerLogger.info('Component Viewer: Auto refresh enabled');
70+
});
71+
const disablePeriodicUpdateCommandDisposable = vscode.commands.registerCommand('vscode-cmsis-debugger.componentViewer.disablePeriodicUpdate', async () => {
72+
this._refreshTimerEnabled = false;
73+
componentViewerLogger.info('Component Viewer: Auto refresh disabled');
74+
});
6675
this._context.subscriptions.push(
6776
treeProviderDisposable,
6877
lockInstanceCommandDisposable,
69-
unlockInstanceCommandDisposable
78+
unlockInstanceCommandDisposable,
79+
enablePeriodicUpdateCommandDisposable,
80+
disablePeriodicUpdateCommandDisposable
7081
);
7182
}
7283

@@ -214,9 +225,12 @@ export class ComponentViewer {
214225
}
215226

216227
private async handleRefreshTimerEvent(session: GDBTargetDebugSession): Promise<void> {
217-
if (this._activeSession?.session.id === session.session.id) {
228+
if(this._activeSession?.session.id !== session.session.id) {
229+
throw new Error(`Component Viewer: Received refresh timer event for session ${session.session.id} while active session is ${this._activeSession?.session.id}`);
230+
}
231+
if (this._refreshTimerEnabled) {
218232
// Update component viewer instance(s)
219-
//this.schedulePendingUpdate('refreshTimer');
233+
this.schedulePendingUpdate('refreshTimer');
220234
}
221235
}
222236

@@ -252,19 +266,38 @@ export class ComponentViewer {
252266
this._runningUpdate = false;
253267
}
254268

269+
private shouldUpdateInstances(session: GDBTargetDebugSession): boolean {
270+
this._instanceUpdateCounter = 0;
271+
if (this._instances.length === 0) {
272+
return false;
273+
}
274+
if (session.targetState === 'unknown') {
275+
return false;
276+
}
277+
if (session.targetState === 'running') {
278+
if (this._refreshTimerEnabled === false ) {
279+
return false;
280+
}
281+
if (session.canAccessWhileRunning === false) {
282+
return false;
283+
}
284+
}
285+
return true;
286+
}
287+
255288
private async updateInstances(updateReason: fifoUpdateReason): Promise<void> {
256289
if (!this._activeSession) {
257290
this._componentViewerTreeDataProvider?.clear();
258291
return;
259292
}
260293
componentViewerLogger.debug(`Component Viewer: Queuing update due to '${updateReason}'`);
261294
this._instanceUpdateCounter = 0;
262-
if (this._instances.length === 0) {
263-
return;
264-
}
265-
if (this._activeSession.targetState !== 'stopped') {
295+
296+
if (!this.shouldUpdateInstances(this._activeSession)) {
297+
componentViewerLogger.debug(`Component Viewer: Skipping update due to '${updateReason}' - conditions not met`);
266298
return;
267299
}
300+
268301
perf?.resetBackendStats();
269302
perf?.resetUiStats();
270303
const activeSessionID = this._activeSession.session.id;

src/views/component-viewer/test/unit/component-viewer-main.test.ts

Lines changed: 105 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ type Session = {
8080
getPname: () => Promise<string | undefined>;
8181
refreshTimer: { onRefresh: (cb: (session: Session) => void) => void };
8282
targetState?: TargetState;
83+
canAccessWhileRunning?: boolean;
8384
};
8485

8586
type TrackerCallbacks = {
@@ -176,7 +177,7 @@ describe('ComponentViewer', () => {
176177
expect(vscode.commands.registerCommand).toHaveBeenCalledWith('vscode-cmsis-debugger.componentViewer.lockComponent', expect.any(Function));
177178
expect(vscode.commands.registerCommand).toHaveBeenCalledWith('vscode-cmsis-debugger.componentViewer.unlockComponent', expect.any(Function));
178179
// tree provider + 2 commands + 5 tracker disposables
179-
expect(context.subscriptions.length).toBe(9);
180+
expect(context.subscriptions.length).toBe(11);
180181
});
181182

182183
it('skips reading scvd files when session or cbuild-run is missing', async () => {
@@ -270,10 +271,9 @@ describe('ComponentViewer', () => {
270271
await tracker.callbacks.connected?.(session);
271272

272273
const refreshCallback = (session.refreshTimer.onRefresh as jest.Mock).mock.calls[0]?.[0];
273-
//expect(refreshCallback).toBeDefined();
274+
expect(refreshCallback).toBeDefined();
274275
if (refreshCallback) {
275276
await refreshCallback(session);
276-
await refreshCallback(otherSession);
277277
}
278278

279279
await tracker.callbacks.connected?.(otherSession);
@@ -284,9 +284,6 @@ describe('ComponentViewer', () => {
284284

285285
(controller as unknown as { _activeSession?: Session })._activeSession = session;
286286
await tracker.callbacks.stackTrace?.({ session });
287-
await expect(tracker.callbacks.stackTrace?.({ session: otherSession }) as Promise<void>).rejects.toThrow(
288-
'Component Viewer: Received stack trace event for session s2 while active session is s1'
289-
);
290287
expect((controller as unknown as { _activeSession?: Session })._activeSession).toBe(session);
291288

292289

@@ -525,6 +522,32 @@ describe('ComponentViewer', () => {
525522
expect(root.isLocked).toBe(false);
526523
});
527524

525+
it('toggles periodic updates via commands', async () => {
526+
const context = extensionContextFactory();
527+
const tracker = makeTracker();
528+
const controller = new ComponentViewer(context);
529+
controller.activate(tracker as unknown as GDBTargetDebugTracker);
530+
531+
const registerCommandMock = asMockedFunction(vscode.commands.registerCommand);
532+
const enableHandler = registerCommandMock.mock.calls.find(([command]) => command === 'vscode-cmsis-debugger.componentViewer.enablePeriodicUpdate')?.[1] as
533+
| (() => Promise<void> | void)
534+
| undefined;
535+
const disableHandler = registerCommandMock.mock.calls.find(([command]) => command === 'vscode-cmsis-debugger.componentViewer.disablePeriodicUpdate')?.[1] as
536+
| (() => Promise<void> | void)
537+
| undefined;
538+
539+
expect(enableHandler).toBeDefined();
540+
expect(disableHandler).toBeDefined();
541+
542+
await enableHandler?.();
543+
expect((controller as unknown as { _refreshTimerEnabled: boolean })._refreshTimerEnabled).toBe(true);
544+
expect(componentViewerLogger.info).toHaveBeenCalledWith('Component Viewer: Auto refresh enabled');
545+
546+
await disableHandler?.();
547+
expect((controller as unknown as { _refreshTimerEnabled: boolean })._refreshTimerEnabled).toBe(false);
548+
expect(componentViewerLogger.info).toHaveBeenCalledWith('Component Viewer: Auto refresh disabled');
549+
});
550+
528551
it('invokes unlock handler and skips lock when no matching instance exists', async () => {
529552
const context = extensionContextFactory();
530553
const tracker = makeTracker();
@@ -628,4 +651,80 @@ describe('ComponentViewer', () => {
628651
expect((controller as unknown as { _runningUpdate: boolean })._runningUpdate).toBe(false);
629652
});
630653

654+
it('shouldUpdateInstances returns false when no instances', () => {
655+
const controller = new ComponentViewer(extensionContextFactory());
656+
const session = makeSession('s1', [], 'stopped');
657+
658+
(controller as unknown as { _instances: ComponentViewerInstancesWrapper[] })._instances = [];
659+
660+
const shouldUpdateInstances = (controller as unknown as { shouldUpdateInstances: (s: Session) => boolean }).shouldUpdateInstances.bind(controller);
661+
expect(shouldUpdateInstances(session)).toBe(false);
662+
});
663+
664+
it('shouldUpdateInstances returns false when target state is unknown', () => {
665+
const controller = new ComponentViewer(extensionContextFactory());
666+
const session = makeSession('s1', [], 'unknown');
667+
668+
(controller as unknown as { _instances: ComponentViewerInstancesWrapper[] })._instances = [
669+
{ componentViewerInstance: instanceFactory() as unknown as ComponentViewerInstancesWrapper['componentViewerInstance'], lockState: false, sessionId: 's1' },
670+
];
671+
672+
const shouldUpdateInstances = (controller as unknown as { shouldUpdateInstances: (s: Session) => boolean }).shouldUpdateInstances.bind(controller);
673+
expect(shouldUpdateInstances(session)).toBe(false);
674+
});
675+
676+
it('shouldUpdateInstances returns false when running and refresh disabled', () => {
677+
const controller = new ComponentViewer(extensionContextFactory());
678+
const session = makeSession('s1', [], 'running');
679+
(session as unknown as { canAccessWhileRunning: boolean }).canAccessWhileRunning = true;
680+
681+
(controller as unknown as { _instances: ComponentViewerInstancesWrapper[] })._instances = [
682+
{ componentViewerInstance: instanceFactory() as unknown as ComponentViewerInstancesWrapper['componentViewerInstance'], lockState: false, sessionId: 's1' },
683+
];
684+
(controller as unknown as { _refreshTimerEnabled: boolean })._refreshTimerEnabled = false;
685+
686+
const shouldUpdateInstances = (controller as unknown as { shouldUpdateInstances: (s: Session) => boolean }).shouldUpdateInstances.bind(controller);
687+
expect(shouldUpdateInstances(session)).toBe(false);
688+
});
689+
690+
it('shouldUpdateInstances returns false when running without access', () => {
691+
const controller = new ComponentViewer(extensionContextFactory());
692+
const session = makeSession('s1', [], 'running');
693+
(session as unknown as { canAccessWhileRunning: boolean }).canAccessWhileRunning = false;
694+
695+
(controller as unknown as { _instances: ComponentViewerInstancesWrapper[] })._instances = [
696+
{ componentViewerInstance: instanceFactory() as unknown as ComponentViewerInstancesWrapper['componentViewerInstance'], lockState: false, sessionId: 's1' },
697+
];
698+
(controller as unknown as { _refreshTimerEnabled: boolean })._refreshTimerEnabled = true;
699+
700+
const shouldUpdateInstances = (controller as unknown as { shouldUpdateInstances: (s: Session) => boolean }).shouldUpdateInstances.bind(controller);
701+
expect(shouldUpdateInstances(session)).toBe(false);
702+
});
703+
704+
it('shouldUpdateInstances returns true when running with refresh and access', () => {
705+
const controller = new ComponentViewer(extensionContextFactory());
706+
const session = makeSession('s1', [], 'running');
707+
(session as unknown as { canAccessWhileRunning: boolean }).canAccessWhileRunning = true;
708+
709+
(controller as unknown as { _instances: ComponentViewerInstancesWrapper[] })._instances = [
710+
{ componentViewerInstance: instanceFactory() as unknown as ComponentViewerInstancesWrapper['componentViewerInstance'], lockState: false, sessionId: 's1' },
711+
];
712+
(controller as unknown as { _refreshTimerEnabled: boolean })._refreshTimerEnabled = true;
713+
714+
const shouldUpdateInstances = (controller as unknown as { shouldUpdateInstances: (s: Session) => boolean }).shouldUpdateInstances.bind(controller);
715+
expect(shouldUpdateInstances(session)).toBe(true);
716+
});
717+
718+
it('shouldUpdateInstances returns true when stopped', () => {
719+
const controller = new ComponentViewer(extensionContextFactory());
720+
const session = makeSession('s1', [], 'stopped');
721+
722+
(controller as unknown as { _instances: ComponentViewerInstancesWrapper[] })._instances = [
723+
{ componentViewerInstance: instanceFactory() as unknown as ComponentViewerInstancesWrapper['componentViewerInstance'], lockState: false, sessionId: 's1' },
724+
];
725+
726+
const shouldUpdateInstances = (controller as unknown as { shouldUpdateInstances: (s: Session) => boolean }).shouldUpdateInstances.bind(controller);
727+
expect(shouldUpdateInstances(session)).toBe(true);
728+
});
729+
631730
});

0 commit comments

Comments
 (0)