Skip to content

Commit 8c0a7ee

Browse files
authored
Add a Live Watch Window for the debug extension (#577)
Adds CMSIS Debug view container. Added a live watch window that gets updated every 500ms.
1 parent 514f2bf commit 8c0a7ee

File tree

8 files changed

+626
-7
lines changed

8 files changed

+626
-7
lines changed

__mocks__/vscode.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,23 @@ const StatusBarAlignment = {
3030
Right: 2
3131
};
3232

33+
const MockTreeItemCollapsibleState = {
34+
None: 0,
35+
Collapsed: 1,
36+
Expanded: 2
37+
};
38+
39+
class MockTreeItem {
40+
label;
41+
description;
42+
contextValue;
43+
collapsibleState;
44+
constructor(label, collapsibleState) {
45+
this.label = label;
46+
this.collapsibleState = collapsibleState;
47+
}
48+
}
49+
3350
module.exports = {
3451
EventEmitter: jest.fn(() => {
3552
const callbacks = [];
@@ -43,6 +60,8 @@ module.exports = {
4360
};
4461
}),
4562
Uri: URI,
63+
TreeItem: MockTreeItem,
64+
TreeItemCollapsibleState: MockTreeItemCollapsibleState,
4665
window: {
4766
createOutputChannel: jest.fn(() => ({
4867
appendLine: jest.fn(),

package.json

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,23 @@
4747
"onStartupFinished"
4848
],
4949
"contributes": {
50+
"viewsContainers": {
51+
"activitybar": [
52+
{
53+
"id": "cmsis-debugger",
54+
"title": "CMSIS Debug",
55+
"icon": "$(rocket)"
56+
}
57+
]
58+
},
59+
"views": {
60+
"cmsis-debugger": [
61+
{
62+
"id": "cmsis-debugger.liveWatch",
63+
"name": "Live Watch"
64+
}
65+
]
66+
},
5067
"commands": [
5168
{
5269
"title": "CPU Time",
@@ -61,6 +78,31 @@
6178
"command": "vscode-cmsis-debugger.resetCpuTimeHistory",
6279
"category": "Run and Debug",
6380
"when": "inDebugMode"
81+
},
82+
{
83+
"command": "cmsis-debugger.liveWatch.add",
84+
"title": "Add Expression",
85+
"icon": "$(add)"
86+
},
87+
{
88+
"command": "cmsis-debugger.liveWatch.deleteAll",
89+
"title": "Delete All Expressions",
90+
"icon": "$(close-all)"
91+
},
92+
{
93+
"command": "cmsis-debugger.liveWatch.delete",
94+
"title": "Delete Expression",
95+
"icon": "$(close)"
96+
},
97+
{
98+
"command": "cmsis-debugger.liveWatch.refresh",
99+
"title": "Refresh",
100+
"icon": "$(refresh)"
101+
},
102+
{
103+
"command": "cmsis-debugger.liveWatch.modify",
104+
"title": "Modify Expression",
105+
"icon": "$(pencil)"
64106
}
65107
],
66108
"menus": {
@@ -73,6 +115,35 @@
73115
"command": "vscode-cmsis-debugger.resetCpuTimeHistory",
74116
"when": "inDebugMode"
75117
}
118+
],
119+
"view/title": [
120+
{
121+
"command": "cmsis-debugger.liveWatch.add",
122+
"when": "view == cmsis-debugger.liveWatch",
123+
"group": "navigation@1"
124+
},
125+
{
126+
"command": "cmsis-debugger.liveWatch.deleteAll",
127+
"when": "view == cmsis-debugger.liveWatch",
128+
"group": "navigation@3"
129+
},
130+
{
131+
"command": "cmsis-debugger.liveWatch.refresh",
132+
"when": "view == cmsis-debugger.liveWatch",
133+
"group": "navigation@2"
134+
}
135+
],
136+
"view/item/context": [
137+
{
138+
"command": "cmsis-debugger.liveWatch.modify",
139+
"when": "view == cmsis-debugger.liveWatch && viewItem == expression",
140+
"group": "inline@1"
141+
},
142+
{
143+
"command": "cmsis-debugger.liveWatch.delete",
144+
"when": "view == cmsis-debugger.liveWatch && viewItem == expression",
145+
"group": "inline@2"
146+
}
76147
]
77148
},
78149
"debuggers": [

src/debug-session/gdbtarget-debug-session.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,12 @@ describe('GDBTargetDebugSession', () => {
7676
(vscode.debug.activeStackItem as unknown) = undefined;
7777
});
7878

79-
it('returns undefined if evaluating a global expression fails', async () => {
79+
it('returns a string if evaluating a global expression fails', async () => {
8080
// Only mock relevant properties, return value is body of EvaluateResponse
8181
const logDebugSpy = jest.spyOn(logger, 'debug');
8282
(debugSession.customRequest as jest.Mock).mockRejectedValueOnce(new Error('myError'));
8383
const result = await gdbTargetSession.evaluateGlobalExpression('myGlobalVariable');
84-
expect(result).toBeUndefined();
84+
expect(result).toBe('myError');
8585
expect(debugSession.customRequest as jest.Mock).toHaveBeenCalledWith('evaluate', { expression: 'myGlobalVariable', frameId: 0, context: 'hover' });
8686
expect(logDebugSpy).toHaveBeenCalledWith('Session \'session-name\': Failed to evaluate global expression \'myGlobalVariable\' - \'myError\'');
8787
});

src/debug-session/gdbtarget-debug-session.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,21 @@ export class GDBTargetDebugSession {
5454
this._cbuildRunParsePromise = undefined;
5555
}
5656

57-
public async evaluateGlobalExpression(expression: string): Promise<string|undefined> {
57+
public async evaluateGlobalExpression(expression: string, context = 'hover'): Promise<string> {
5858
try {
5959
const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0;
6060
const args: DebugProtocol.EvaluateArguments = {
6161
expression,
6262
frameId, // Currently required by CDT GDB Adapter
63-
context: 'hover'
63+
context: context
6464
};
6565
const response = await this.session.customRequest('evaluate', args) as DebugProtocol.EvaluateResponse['body'];
66-
return response.result.match(/\d+/) ? response.result : undefined;
66+
return response.result;
6767
} catch (error: unknown) {
6868
const errorMessage = (error as Error)?.message;
6969
logger.debug(`Session '${this.session.name}': Failed to evaluate global expression '${expression}' - '${errorMessage}'`);
70+
return errorMessage === 'custom request failed' ? 'No active session' : errorMessage;
7071
}
71-
return undefined;
7272
}
7373

7474
public async readMemory(address: number, length = 4): Promise<ArrayBuffer|undefined> {

src/desktop/extension.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,23 @@ import { addToolsToPath } from './add-to-path';
2222
import { CpuStatesStatusBarItem } from '../features/cpu-states/cpu-states-statusbar-item';
2323
import { CpuStates } from '../features/cpu-states/cpu-states';
2424
import { CpuStatesCommands } from '../features/cpu-states/cpu-states-commands';
25+
import { LiveWatchTreeDataProvider } from '../views/live-watch/live-watch';
2526

2627
const BUILTIN_TOOLS_PATHS = [
2728
'tools/pyocd/pyocd',
2829
'tools/gdb/bin/arm-none-eabi-gdb'
2930
];
3031

32+
let liveWatchTreeDataProvider: LiveWatchTreeDataProvider;
33+
3134
export const activate = async (context: vscode.ExtensionContext): Promise<void> => {
3235
const gdbtargetDebugTracker = new GDBTargetDebugTracker();
3336
const gdbtargetConfigurationProvider = new GDBTargetConfigurationProvider();
3437
const cpuStates = new CpuStates();
3538
const cpuStatesCommands = new CpuStatesCommands();
3639
const cpuStatesStatusBarItem = new CpuStatesStatusBarItem();
40+
// Register the Tree View under the id from package.json
41+
liveWatchTreeDataProvider = new LiveWatchTreeDataProvider(context);
3742

3843
addToolsToPath(context, BUILTIN_TOOLS_PATHS);
3944
// Activate components
@@ -43,6 +48,16 @@ export const activate = async (context: vscode.ExtensionContext): Promise<void>
4348
cpuStates.activate(gdbtargetDebugTracker);
4449
cpuStatesCommands.activate(context, cpuStates);
4550
cpuStatesStatusBarItem.activate(context, cpuStates);
51+
// Live Watch view
52+
liveWatchTreeDataProvider.activate(gdbtargetDebugTracker);
4653

4754
logger.debug('Extension Pack activated');
4855
};
56+
57+
export const deactivate = async (): Promise<void> => {
58+
// Call deactivate of Live Watch to save its state
59+
if (liveWatchTreeDataProvider) {
60+
await liveWatchTreeDataProvider.deactivate();
61+
}
62+
logger.debug('Extension Pack deactivated');
63+
};

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ interface SessionCpuStates {
4444
}
4545

4646
export class CpuStates {
47+
// onRefresh event to notify GUI components of the cpu states updates (This is different than that of the periodic refresh timer)
4748
private readonly _onRefresh: vscode.EventEmitter<number> = new vscode.EventEmitter<number>();
4849
public readonly onRefresh: vscode.Event<number> = this._onRefresh.event;
4950

@@ -213,7 +214,8 @@ export class CpuStates {
213214
}
214215

215216
protected async getFrequency(): Promise<number|undefined> {
216-
const frequencyString = await this.activeSession?.evaluateGlobalExpression('SystemCoreClock');
217+
const result = await this.activeSession?.evaluateGlobalExpression('SystemCoreClock');
218+
const frequencyString = result?.match(/\d+/) ? result : undefined;
217219
if (!frequencyString) {
218220
return undefined;
219221
}

0 commit comments

Comments
 (0)