diff --git a/__mocks__/vscode.js b/__mocks__/vscode.js
index 2b4a26c2..b8a46710 100644
--- a/__mocks__/vscode.js
+++ b/__mocks__/vscode.js
@@ -71,6 +71,7 @@ module.exports = {
warn: jest.fn(),
error: jest.fn(),
})),
+ registerTreeDataProvider: jest.fn(() => ({ dispose: jest.fn() })),
showWarningMessage: jest.fn(),
createStatusBarItem: jest.fn(),
showQuickPick: jest.fn(),
diff --git a/media/trace-and-live-dark.svg b/media/trace-and-live-dark.svg
new file mode 100644
index 00000000..28c6e80a
--- /dev/null
+++ b/media/trace-and-live-dark.svg
@@ -0,0 +1,61 @@
+
+
diff --git a/media/trace-and-live-light.svg b/media/trace-and-live-light.svg
new file mode 100644
index 00000000..61fdca39
--- /dev/null
+++ b/media/trace-and-live-light.svg
@@ -0,0 +1,58 @@
+
+
diff --git a/package.json b/package.json
index 095e6e4e..5c65fb11 100644
--- a/package.json
+++ b/package.json
@@ -51,8 +51,8 @@
"activitybar": [
{
"id": "cmsis-debugger",
- "title": "CMSIS Debug",
- "icon": "$(rocket)"
+ "title": "Trace and Live View",
+ "icon": "media/trace-and-live-dark.svg"
}
]
},
@@ -60,7 +60,8 @@
"cmsis-debugger": [
{
"id": "cmsis-debugger.liveWatch",
- "name": "Live Watch"
+ "name": "Live Watch",
+ "icon": "media/trace-and-live-light.svg"
}
]
},
@@ -80,27 +81,27 @@
"when": "inDebugMode"
},
{
- "command": "cmsis-debugger.liveWatch.add",
+ "command": "vscode-cmsis-debugger.liveWatch.add",
"title": "Add Expression",
"icon": "$(add)"
},
{
- "command": "cmsis-debugger.liveWatch.deleteAll",
+ "command": "vscode-cmsis-debugger.liveWatch.deleteAll",
"title": "Delete All Expressions",
"icon": "$(close-all)"
},
{
- "command": "cmsis-debugger.liveWatch.delete",
+ "command": "vscode-cmsis-debugger.liveWatch.delete",
"title": "Delete Expression",
"icon": "$(close)"
},
{
- "command": "cmsis-debugger.liveWatch.refresh",
+ "command": "vscode-cmsis-debugger.liveWatch.refresh",
"title": "Refresh",
"icon": "$(refresh)"
},
{
- "command": "cmsis-debugger.liveWatch.modify",
+ "command": "vscode-cmsis-debugger.liveWatch.modify",
"title": "Modify Expression",
"icon": "$(pencil)"
}
@@ -118,29 +119,29 @@
],
"view/title": [
{
- "command": "cmsis-debugger.liveWatch.add",
+ "command": "vscode-cmsis-debugger.liveWatch.add",
"when": "view == cmsis-debugger.liveWatch",
"group": "navigation@1"
},
{
- "command": "cmsis-debugger.liveWatch.deleteAll",
+ "command": "vscode-cmsis-debugger.liveWatch.deleteAll",
"when": "view == cmsis-debugger.liveWatch",
"group": "navigation@3"
},
{
- "command": "cmsis-debugger.liveWatch.refresh",
+ "command": "vscode-cmsis-debugger.liveWatch.refresh",
"when": "view == cmsis-debugger.liveWatch",
"group": "navigation@2"
}
],
"view/item/context": [
{
- "command": "cmsis-debugger.liveWatch.modify",
+ "command": "vscode-cmsis-debugger.liveWatch.modify",
"when": "view == cmsis-debugger.liveWatch && viewItem == expression",
"group": "inline@1"
},
{
- "command": "cmsis-debugger.liveWatch.delete",
+ "command": "vscode-cmsis-debugger.liveWatch.delete",
"when": "view == cmsis-debugger.liveWatch && viewItem == expression",
"group": "inline@2"
}
diff --git a/src/debug-session/gdbtarget-debug-session.test.ts b/src/debug-session/gdbtarget-debug-session.test.ts
index 12176f4b..0da77663 100644
--- a/src/debug-session/gdbtarget-debug-session.test.ts
+++ b/src/debug-session/gdbtarget-debug-session.test.ts
@@ -59,18 +59,18 @@ describe('GDBTargetDebugSession', () => {
it('evaluates a global expression without active stack frame and returns a value', async () => {
// Only mock relevant properties, return value is body of EvaluateResponse
- (debugSession.customRequest as jest.Mock).mockReturnValueOnce({ result: '1234567' });
+ (debugSession.customRequest as jest.Mock).mockReturnValueOnce({ result: '1234567', variableReference: 0 });
const result = await gdbTargetSession.evaluateGlobalExpression('myGlobalVariable');
- expect(result).toEqual('1234567');
+ expect(result).toEqual({ result: '1234567', variableReference: 0 });
expect(debugSession.customRequest as jest.Mock).toHaveBeenCalledWith('evaluate', { expression: 'myGlobalVariable', frameId: 0, context: 'hover' });
});
it('evaluates a global expression with active stack frame and returns a value', async () => {
// Only mock relevant properties, return value is body of EvaluateResponse
- (debugSession.customRequest as jest.Mock).mockReturnValueOnce({ result: '1234567' });
+ (debugSession.customRequest as jest.Mock).mockReturnValueOnce({ result: '1234567', variableReference: 0 });
(vscode.debug.activeStackItem as unknown) = { session: debugSession, threadId: 1, frameId: 2 };
const result = await gdbTargetSession.evaluateGlobalExpression('myGlobalVariable');
- expect(result).toEqual('1234567');
+ expect(result).toEqual({ result: '1234567', variableReference: 0 });
expect(debugSession.customRequest as jest.Mock).toHaveBeenCalledWith('evaluate', { expression: 'myGlobalVariable', frameId: 2, context: 'hover' });
// restore default
(vscode.debug.activeStackItem as unknown) = undefined;
diff --git a/src/debug-session/gdbtarget-debug-session.ts b/src/debug-session/gdbtarget-debug-session.ts
index 82197fed..77d4a690 100644
--- a/src/debug-session/gdbtarget-debug-session.ts
+++ b/src/debug-session/gdbtarget-debug-session.ts
@@ -54,7 +54,8 @@ export class GDBTargetDebugSession {
this._cbuildRunParsePromise = undefined;
}
- public async evaluateGlobalExpression(expression: string, context = 'hover'): Promise {
+ /** Function returns string only in case of failure */
+ public async evaluateGlobalExpression(expression: string, context = 'hover'): Promise {
try {
const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0;
const args: DebugProtocol.EvaluateArguments = {
@@ -63,7 +64,7 @@ export class GDBTargetDebugSession {
context: context
};
const response = await this.session.customRequest('evaluate', args) as DebugProtocol.EvaluateResponse['body'];
- return response.result;
+ return response;
} catch (error: unknown) {
const errorMessage = (error as Error)?.message;
logger.debug(`Session '${this.session.name}': Failed to evaluate global expression '${expression}' - '${errorMessage}'`);
diff --git a/src/features/cpu-states/cpu-states.ts b/src/features/cpu-states/cpu-states.ts
index 7c1da098..e6cececf 100644
--- a/src/features/cpu-states/cpu-states.ts
+++ b/src/features/cpu-states/cpu-states.ts
@@ -215,7 +215,10 @@ export class CpuStates {
protected async getFrequency(): Promise {
const result = await this.activeSession?.evaluateGlobalExpression('SystemCoreClock');
- const frequencyString = result?.match(/\d+/) ? result : undefined;
+ if (typeof result == 'string') {
+ return undefined;
+ }
+ const frequencyString = result?.result.match(/\d+/) ? result.result : undefined;
if (!frequencyString) {
return undefined;
}
diff --git a/src/views/live-watch/live-watch.test.ts b/src/views/live-watch/live-watch.test.ts
index 5b700124..4a36a4bd 100644
--- a/src/views/live-watch/live-watch.test.ts
+++ b/src/views/live-watch/live-watch.test.ts
@@ -17,13 +17,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as vscode from 'vscode';
import { debugSessionFactory, extensionContextFactory } from '../../__test__/vscode.factory';
-import { LiveWatchTreeDataProvider } from './live-watch';
+import { LiveWatchValue, LiveWatchTreeDataProvider } from './live-watch';
import { GDBTargetDebugSession, GDBTargetDebugTracker } from '../../debug-session';
import { gdbTargetConfiguration } from '../../debug-configuration/debug-configuration.factory';
import { GDBTargetConfiguration } from '../../debug-configuration';
-// Inline mock for registerTreeDataProvider specific to these tests
-const registerTreeDataProviderMock = jest.fn(() => ({ dispose: jest.fn() }));
describe('LiveWatchTreeDataProvider', () => {
let liveWatchTreeDataProvider: LiveWatchTreeDataProvider;
@@ -33,8 +31,8 @@ describe('LiveWatchTreeDataProvider', () => {
let debugConfig: GDBTargetConfiguration;
// Helper: create a dummy node
- function makeNode(expression = 'x', value = '1', id = 1) {
- return { id, expression, value, parent: undefined };
+ function makeNode(expression = 'x', value: LiveWatchValue = { result: '1', variablesReference: 0 }, id = 1) {
+ return { id, expression, value, parent: undefined, children: [] };
}
beforeEach(() => {
@@ -50,19 +48,15 @@ describe('LiveWatchTreeDataProvider', () => {
describe('session management and connection tests', () => {
it('should activate the live watch tree data provider', () => {
- (vscode.window).registerTreeDataProvider = registerTreeDataProviderMock;
liveWatchTreeDataProvider.activate(tracker);
});
it('registers the live watch tree data provider', async () => {
- (vscode.window).registerTreeDataProvider = registerTreeDataProviderMock;
- registerTreeDataProviderMock.mockClear();
liveWatchTreeDataProvider.activate(tracker);
- expect(registerTreeDataProviderMock).toHaveBeenCalledWith('cmsis-debugger.liveWatch', liveWatchTreeDataProvider);
+ expect(vscode.window.registerTreeDataProvider).toHaveBeenCalledWith('cmsis-debugger.liveWatch', liveWatchTreeDataProvider);
});
it('manages session lifecycles correctly', async () => {
- (vscode.window).registerTreeDataProvider = registerTreeDataProviderMock;
liveWatchTreeDataProvider.activate(tracker);
// No active session yet
expect((liveWatchTreeDataProvider as any).activeSession).toBeUndefined();
@@ -129,7 +123,7 @@ describe('LiveWatchTreeDataProvider', () => {
describe('tree data methods', () => {
it('getChildren returns roots when no element is passed', async () => {
- (liveWatchTreeDataProvider as any).roots = [makeNode('node-1', '1', 1), makeNode('node-2', '2', 2)];
+ (liveWatchTreeDataProvider as any).roots = [makeNode('node-1', { result: '1', variablesReference: 0 }, 1), makeNode('node-2', { result: '2', variablesReference: 0 }, 2)];
const children = await liveWatchTreeDataProvider.getChildren();
expect(children.length).toBe(2);
expect(children[0].expression).toBe('node-1');
@@ -137,16 +131,31 @@ describe('LiveWatchTreeDataProvider', () => {
});
it('getChildren returns children of element', async () => {
- const childNode = makeNode('childNode', '2', 2);
- const parent = { ...makeNode('parentNode', '1', 1), children: [childNode] };
+ const parent = makeNode('parentNode', { result: '1', variablesReference: 123 }, 1);
(liveWatchTreeDataProvider as any).roots = [parent];
+ // Mock active session with customRequest returning one variable
+ (liveWatchTreeDataProvider as any)._activeSession = {
+ session: {
+ customRequest: jest.fn().mockResolvedValue({
+ variables: [
+ { name: 'childNode', value: '2', variablesReference: 0 }
+ ]
+ })
+ },
+ evaluateGlobalExpression: jest.fn()
+ };
const children = await liveWatchTreeDataProvider.getChildren(parent);
+ expect((liveWatchTreeDataProvider as any)._activeSession.session.customRequest).toHaveBeenCalledWith('variables', { variablesReference: parent.value.variablesReference });
expect(children.length).toBe(1);
expect(children[0].expression).toBe('childNode');
+ expect(children[0].value.result).toBe('2');
+ expect(children[0].value.variablesReference).toBe(0);
+ // Ensure dynamic children not persisted on parent
+ expect(parent.children.length).toBe(0);
});
it('getTreeItem returns correct TreeItem', () => {
- const node = makeNode('expression', 'value', 1);
+ const node = makeNode('expression', { result: 'value', variablesReference: 1 }, 1);
const item = liveWatchTreeDataProvider.getTreeItem(node);
expect(item.label).toBe('expression = ');
expect(item.description).toBe('value');
@@ -156,28 +165,29 @@ describe('LiveWatchTreeDataProvider', () => {
describe('node management', () => {
it('add creates a new root node', async () => {
- jest.spyOn(liveWatchTreeDataProvider as any, 'evaluate').mockResolvedValue('1234');
- await (liveWatchTreeDataProvider as any).add('expression');
+ jest.spyOn(liveWatchTreeDataProvider as any, 'evaluate').mockResolvedValue({ result: '1234', variablesReference: 0 });
+ // adapt method name addToRoots (changed implementation)
+ await (liveWatchTreeDataProvider as any).addToRoots('expression');
expect((liveWatchTreeDataProvider as any).roots.length).toBe(1);
expect((liveWatchTreeDataProvider as any).roots[0].expression).toBe('expression');
- expect((liveWatchTreeDataProvider as any).roots[0].value).toBe('1234');
+ expect((liveWatchTreeDataProvider as any).roots[0].value.result).toBe('1234');
});
it('clear removes all nodes', async () => {
- (liveWatchTreeDataProvider as any).roots = [makeNode('expression', '1', 1)];
+ (liveWatchTreeDataProvider as any).roots = [makeNode('expression', { result: '1', variablesReference: 0 })];
await (liveWatchTreeDataProvider as any).clear();
expect((liveWatchTreeDataProvider as any).roots.length).toBe(0);
});
it('delete removes a node by id', async () => {
- (liveWatchTreeDataProvider as any).roots = [makeNode('node-1', '1', 1), makeNode('node-2', '2', 2)];
+ (liveWatchTreeDataProvider as any).roots = [makeNode('node-1', { result: '1', variablesReference: 0 }, 1), makeNode('node-2', { result: '2', variablesReference: 0 }, 2)];
await (liveWatchTreeDataProvider as any).delete({ id: 1 });
expect((liveWatchTreeDataProvider as any).roots.length).toBe(1);
expect((liveWatchTreeDataProvider as any).roots[0].id).toBe(2);
});
it('rename updates node expression', async () => {
- const node = makeNode('node-1', '1', 1);
+ const node = makeNode('node-1', { result: '1', variablesReference: 0 }, 1);
(liveWatchTreeDataProvider as any).roots = [node];
await (liveWatchTreeDataProvider as any).rename(node, 'node-1-renamed');
expect(node.expression).toBe('node-1-renamed');
@@ -186,26 +196,41 @@ describe('LiveWatchTreeDataProvider', () => {
describe('refresh', () => {
it('refresh updates all root node values', async () => {
- const node = makeNode('expression', 'old-value', 1);
+ const node = makeNode('expression', { result: 'old-value', variablesReference: 1 }, 1);
(liveWatchTreeDataProvider as any).roots = [node];
- jest.spyOn(liveWatchTreeDataProvider as any, 'evaluate').mockResolvedValue('new-value');
+ jest.spyOn(liveWatchTreeDataProvider as any, 'evaluate').mockResolvedValue({ result: 'new-value', variablesReference: 0 });
await (liveWatchTreeDataProvider as any).refresh();
- expect(node.value).toBe('new-value');
+ expect(node.value.result).toBe('new-value');
});
it('refresh(node) updates only that node', async () => {
- const node = makeNode('expression', 'old-value', 1);
+ const node = makeNode('expression', { result: 'old-value', variablesReference: 1 }, 1);
(liveWatchTreeDataProvider as any).roots = [node];
- jest.spyOn(liveWatchTreeDataProvider as any, 'evaluate').mockResolvedValue('new-value');
+ jest.spyOn(liveWatchTreeDataProvider as any, 'evaluate').mockResolvedValue({ result: 'new-value', variablesReference: 0 });
await (liveWatchTreeDataProvider as any).refresh(node);
- expect(node.value).toBe('new-value');
+ expect(node.value.result).toBe('new-value');
+ });
+
+ it('refresh without argument evaluates each root and fires tree change once', async () => {
+ const nodeA = makeNode('node-A', { result: 'value-A', variablesReference: 0 }, 1);
+ const nodeB = makeNode('node-B', { result: 'value-B', variablesReference: 0 }, 2);
+ (liveWatchTreeDataProvider as any).roots = [nodeA, nodeB];
+ const evalMock = jest.spyOn(liveWatchTreeDataProvider as any, 'evaluate')
+ .mockImplementation(async (expr: unknown) => ({ result: String(expr) + '-updated', variablesReference: 0 }));
+ const fireSpy = jest.spyOn((liveWatchTreeDataProvider as any)._onDidChangeTreeData, 'fire');
+ await (liveWatchTreeDataProvider as any).refresh();
+ expect(evalMock).toHaveBeenCalledTimes(2);
+ expect(nodeA.value.result).toBe('node-A-updated');
+ expect(nodeB.value.result).toBe('node-B-updated');
+ expect(fireSpy).toHaveBeenCalledTimes(1);
+ // fire called with undefined (no specific node) per implementation
+ expect(fireSpy.mock.calls[0][0]).toBeUndefined();
});
});
describe('command registration', () => {
beforeEach(() => {
(vscode.commands as any).registerCommand?.mockClear?.();
- (vscode.window as any).registerTreeDataProvider = registerTreeDataProviderMock;
});
function getRegisteredHandler(commandId: string) {
@@ -218,11 +243,11 @@ describe('LiveWatchTreeDataProvider', () => {
liveWatchTreeDataProvider.activate(tracker);
const calls = (vscode.commands.registerCommand as jest.Mock).mock.calls.map(call => call[0]);
expect(calls).toEqual(expect.arrayContaining([
- 'cmsis-debugger.liveWatch.add',
- 'cmsis-debugger.liveWatch.deleteAll',
- 'cmsis-debugger.liveWatch.delete',
- 'cmsis-debugger.liveWatch.refresh',
- 'cmsis-debugger.liveWatch.modify'
+ 'vscode-cmsis-debugger.liveWatch.add',
+ 'vscode-cmsis-debugger.liveWatch.deleteAll',
+ 'vscode-cmsis-debugger.liveWatch.delete',
+ 'vscode-cmsis-debugger.liveWatch.refresh',
+ 'vscode-cmsis-debugger.liveWatch.modify'
]));
});
@@ -230,7 +255,7 @@ describe('LiveWatchTreeDataProvider', () => {
(vscode.window as any).showInputBox = jest.fn().mockResolvedValue('expression');
const evaluateSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'evaluate').mockResolvedValue('someValue');
liveWatchTreeDataProvider.activate(tracker);
- const handler = getRegisteredHandler('cmsis-debugger.liveWatch.add');
+ const handler = getRegisteredHandler('vscode-cmsis-debugger.liveWatch.add');
expect(handler).toBeDefined();
await handler();
const roots = (liveWatchTreeDataProvider as any).roots;
@@ -242,26 +267,26 @@ describe('LiveWatchTreeDataProvider', () => {
it('add command does nothing when expression undefined', async () => {
(vscode.window as any).showInputBox = jest.fn().mockResolvedValue(undefined);
liveWatchTreeDataProvider.activate(tracker);
- const handler = getRegisteredHandler('cmsis-debugger.liveWatch.add');
+ const handler = getRegisteredHandler('vscode-cmsis-debugger.liveWatch.add');
await handler();
expect((liveWatchTreeDataProvider as any).roots.length).toBe(0);
});
it('deleteAll command clears roots', async () => {
- (liveWatchTreeDataProvider as any).roots = [makeNode('nodeA','1',1), makeNode('nodeB','2',2)];
+ (liveWatchTreeDataProvider as any).roots = [makeNode('nodeA', { result: '1', variablesReference: 0 }, 1), makeNode('nodeB', { result: '2', variablesReference: 0 }, 2)];
const clearSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'clear');
liveWatchTreeDataProvider.activate(tracker);
- const handler = getRegisteredHandler('cmsis-debugger.liveWatch.deleteAll');
+ const handler = getRegisteredHandler('vscode-cmsis-debugger.liveWatch.deleteAll');
await handler();
expect(clearSpy).toHaveBeenCalled();
expect((liveWatchTreeDataProvider as any).roots.length).toBe(0);
});
it('delete command removes provided node', async () => {
- (liveWatchTreeDataProvider as any).roots = [makeNode('nodeA','1',1), makeNode('nodeB','2',2)];
+ (liveWatchTreeDataProvider as any).roots = [makeNode('nodeA', { result: '1', variablesReference: 0 }, 1), makeNode('nodeB', { result: '2', variablesReference: 0 }, 2)];
const deleteSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'delete');
liveWatchTreeDataProvider.activate(tracker);
- const handler = getRegisteredHandler('cmsis-debugger.liveWatch.delete');
+ const handler = getRegisteredHandler('vscode-cmsis-debugger.liveWatch.delete');
const target = (liveWatchTreeDataProvider as any).roots[0];
await handler(target);
expect(deleteSpy).toHaveBeenCalledWith(target);
@@ -269,24 +294,24 @@ describe('LiveWatchTreeDataProvider', () => {
});
it('modify command renames a node when expression provided', async () => {
- const node = makeNode('oldExpression','1',1);
+ const node = makeNode('oldExpression',{ result: '1', variablesReference: 0 },1);
(liveWatchTreeDataProvider as any).roots = [node];
(vscode.window as any).showInputBox = jest.fn().mockResolvedValue('newExpression');
const renameSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'rename');
liveWatchTreeDataProvider.activate(tracker);
- const handler = getRegisteredHandler('cmsis-debugger.liveWatch.modify');
+ const handler = getRegisteredHandler('vscode-cmsis-debugger.liveWatch.modify');
await handler(node);
expect(renameSpy).toHaveBeenCalledWith(node,'newExpression');
expect(node.expression).toBe('newExpression');
});
it('modify command does nothing when expression undefined', async () => {
- const node = makeNode('oldExpression','1',1);
+ const node = makeNode('oldExpression', { result: '1', variablesReference: 0 }, 1);
(liveWatchTreeDataProvider as any).roots = [node];
(vscode.window as any).showInputBox = jest.fn().mockResolvedValue(undefined);
const renameSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'rename');
liveWatchTreeDataProvider.activate(tracker);
- const handler = getRegisteredHandler('cmsis-debugger.liveWatch.modify');
+ const handler = getRegisteredHandler('vscode-cmsis-debugger.liveWatch.modify');
await handler(node);
expect(renameSpy).not.toHaveBeenCalled();
expect(node.expression).toBe('oldExpression');
@@ -295,10 +320,40 @@ describe('LiveWatchTreeDataProvider', () => {
it('refresh command triggers provider.refresh()', async () => {
const refreshSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'refresh').mockResolvedValue(undefined);
liveWatchTreeDataProvider.activate(tracker);
- const handler = getRegisteredHandler('cmsis-debugger.liveWatch.refresh');
+ const handler = getRegisteredHandler('vscode-cmsis-debugger.liveWatch.refresh');
await handler();
expect(refreshSpy).toHaveBeenCalled();
});
});
+
+ describe('evaluate', () => {
+ it('returns No active session when none set', async () => {
+ const result = await (liveWatchTreeDataProvider as any).evaluate('myExpression');
+ expect(result.result).toBe('No active session');
+ expect(result.variablesReference).toBe(0);
+ });
+
+ it('maps string result into LiveWatchValue', async () => {
+ // mock active session with evaluateGlobalExpression returning a string
+ (liveWatchTreeDataProvider as any)._activeSession = {
+ evaluateGlobalExpression: jest.fn().mockResolvedValue('string-value'),
+ session: {}
+ };
+ const evalResult = await (liveWatchTreeDataProvider as any).evaluate('myExpression');
+ expect(evalResult.result).toBe('string-value');
+ expect(evalResult.variablesReference).toBe(0);
+ });
+
+ it('maps object result into LiveWatchValue', async () => {
+ const responseObj = { result: 'value', variablesReference: 1234 };
+ (liveWatchTreeDataProvider as any)._activeSession = {
+ evaluateGlobalExpression: jest.fn().mockResolvedValue(responseObj),
+ session: {}
+ };
+ const evalResult = await (liveWatchTreeDataProvider as any).evaluate('myExpression');
+ expect(evalResult.result).toBe('value');
+ expect(evalResult.variablesReference).toBe(1234);
+ });
+ });
});
/* eslint-enable @typescript-eslint/no-explicit-any */
diff --git a/src/views/live-watch/live-watch.ts b/src/views/live-watch/live-watch.ts
index beab4e16..1bb35b72 100644
--- a/src/views/live-watch/live-watch.ts
+++ b/src/views/live-watch/live-watch.ts
@@ -15,14 +15,20 @@
*/
import * as vscode from 'vscode';
+import { DebugProtocol } from '@vscode/debugprotocol';
import { GDBTargetDebugSession, GDBTargetDebugTracker } from '../../debug-session';
interface LiveWatchNode {
id: number;
expression: string;
- value: string;
parent: LiveWatchNode | undefined; // if undefined, it's a root node
- children?: LiveWatchNode[]; // keep for future grouping; flat list for now
+ children: LiveWatchNode[];
+ value: LiveWatchValue
+}
+
+export interface LiveWatchValue {
+ result: string;
+ variablesReference: number;
}
export class LiveWatchTreeDataProvider implements vscode.TreeDataProvider {
@@ -45,18 +51,37 @@ export class LiveWatchTreeDataProvider implements vscode.TreeDataProvider {
+ public async getChildren(element?: LiveWatchNode): Promise {
if (!element) {
return Promise.resolve(this.roots);
}
- return Promise.resolve(element.children ?? []);
+ try {
+ const children = await this._activeSession?.session.customRequest('variables', { variablesReference: element.value.variablesReference });
+ const childNodes = children?.variables.map((child: DebugProtocol.Variable) => ({
+ id: this.nodeID++,
+ expression: child.name,
+ children: [],
+ parent: element,
+ value: {
+ result: child.value,
+ variablesReference: child.variablesReference
+ }
+ })) ?? [];
+
+ // We do not store children of nodes in the tree, as they are dynamic
+ return childNodes;
+ } catch (error) {
+ console.error('Error fetching children:', error);
+ return [];
+ }
}
public getTreeItem(element: LiveWatchNode): vscode.TreeItem {
- const item = new vscode.TreeItem(element.expression + ' = ', vscode.TreeItemCollapsibleState.None);
- item.description = element.value || '';
+ const item = new vscode.TreeItem(element.expression + ' = ');
+ item.description = element.value.result;
item.contextValue = 'expression';
item.tooltip = element.expression;
+ item.collapsibleState = element.value.variablesReference !== 0 ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None;
return item;
}
@@ -108,11 +133,11 @@ export class LiveWatchTreeDataProvider implements vscode.TreeDataProvider await this.registerAddCommand());
- const deleteAllCommand = vscode.commands.registerCommand('cmsis-debugger.liveWatch.deleteAll', async () => await this.registerDeleteAllCommand());
- const deleteCommand = vscode.commands.registerCommand('cmsis-debugger.liveWatch.delete', async (node) => await this.registerDeleteCommand(node));
- const refreshCommand = vscode.commands.registerCommand('cmsis-debugger.liveWatch.refresh', async () => await this.refresh());
- const modifyCommand = vscode.commands.registerCommand('cmsis-debugger.liveWatch.modify', async (node) => await this.registerRenameCommand(node));
+ const addCommand = vscode.commands.registerCommand('vscode-cmsis-debugger.liveWatch.add', async () => await this.registerAddCommand());
+ const deleteAllCommand = vscode.commands.registerCommand('vscode-cmsis-debugger.liveWatch.deleteAll', async () => await this.registerDeleteAllCommand());
+ const deleteCommand = vscode.commands.registerCommand('vscode-cmsis-debugger.liveWatch.delete', async (node) => await this.registerDeleteCommand(node));
+ const refreshCommand = vscode.commands.registerCommand('vscode-cmsis-debugger.liveWatch.refresh', async () => await this.refresh());
+ const modifyCommand = vscode.commands.registerCommand('vscode-cmsis-debugger.liveWatch.modify', async (node) => await this.registerRenameCommand(node));
this._context.subscriptions.push(registerLiveWatchView,
addCommand,
deleteAllCommand, deleteCommand, refreshCommand, modifyCommand);
@@ -123,7 +148,7 @@ export class LiveWatchTreeDataProvider implements vscode.TreeDataProvider {
+ private async evaluate(expression: string): Promise {
+ const response: LiveWatchValue = { result: '', variablesReference: 0 };
if (!this._activeSession) {
- return 'No active session';
+ response.result = 'No active session';
+ return response;
}
const result = await this._activeSession.evaluateGlobalExpression(expression, 'watch');
- return result;
+ if (typeof result == 'string') {
+ response.result = result;
+ return response;
+ }
+ response.result = result.result;
+ response.variablesReference = result.variablesReference;
+ return response;
}
- private async add(expression: string, parent?: LiveWatchNode) {
+ private async addToRoots(expression: string, parent?: LiveWatchNode) {
// Create a new node with a unique ID and evaluate its value
const newNode: LiveWatchNode = {
- id: ++this.nodeID,
+ id: this.nodeID++,
+ children: [],
expression,
- value : await this.evaluate(expression),
- parent: parent ?? undefined
+ parent: parent ?? undefined,
+ value: await this.evaluate(expression)
};
+
if (!parent) {
this.roots.push(newNode);
} else {
@@ -176,6 +211,7 @@ export class LiveWatchTreeDataProvider implements vscode.TreeDataProvider