Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions __mocks__/vscode.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ module.exports = {
showErrorMessage: jest.fn(),
showInformationMessage: jest.fn(() => Promise.resolve(undefined)),
showWarningMessage: jest.fn(),
showInputBox: jest.fn(),
showQuickPick: jest.fn(),
createInputBox: jest.fn(() => {
const handlers = { onDidChangeValue: [], onDidAccept: [], onDidHide: [] };
Expand Down
14 changes: 14 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@
"title": "Show in Memory Inspector",
"category": "Live Watch"
},
{
"command": "vscode-cmsis-debugger.liveWatch.setValue",
"title": "Set Value",
"category": "Live Watch"
},
{
"command": "vscode-cmsis-debugger.componentViewer.lockComponent",
"title": "Lock Component",
Expand Down Expand Up @@ -326,6 +331,10 @@
"command": "vscode-cmsis-debugger.liveWatch.showInMemoryInspector",
"when": "false"
},
{
"command": "vscode-cmsis-debugger.liveWatch.setValue",
"when": "false"
},
{
"command": "vscode-cmsis-debugger.componentViewer.lockComponent",
"when": "false"
Expand Down Expand Up @@ -473,6 +482,11 @@
"when": "view == cmsis-debugger.liveWatch && viewItem == parentExpression",
"group": "contextMenuG1@4"
},
{
"command": "vscode-cmsis-debugger.liveWatch.setValue",
"when": "view == cmsis-debugger.liveWatch && inDebugMode",
"group": "contextMenuG1@5"
},
{
"command": "vscode-cmsis-debugger.liveWatch.deleteAll",
"when": "view == cmsis-debugger.liveWatch",
Expand Down
75 changes: 73 additions & 2 deletions src/views/live-watch/live-watch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,45 @@ describe('LiveWatchTreeDataProvider', () => {
expect(vscode.env.clipboard.writeText).toHaveBeenCalledWith('myExpression');
});

it('set root value and send a setExpression request to session and calls refresh right after', async () => {
const node = makeNode('myVar', { result: '1', variablesReference: 0 }, 1);
const refreshSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'refresh').mockResolvedValue('');
(liveWatchTreeDataProvider as any)._activeSession = {
session: {
customRequest: jest.fn().mockResolvedValue({ result: '2', variablesReference: 0 })
},
evaluateGlobalExpression: jest.fn()
};
(vscode.window as any).showInputBox = jest.fn().mockResolvedValue('2');
await (liveWatchTreeDataProvider as any).handleSetValueCommand(node);
expect((liveWatchTreeDataProvider as any)._activeSession.session.customRequest).toHaveBeenCalledWith('setExpression', { expression: 'myVar', frameId: 0, value: '2' });
expect(refreshSpy).toHaveBeenCalled();
});

it('set child node value and send a setVariable request to session', async () => {
const parent = makeNode('parentVar', { result: '1', variablesReference: 123 }, 1);
const child = makeNode('childVar', { result: '1', variablesReference: 0 }, 2, parent);
const refreshSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'refresh').mockResolvedValue('');
(liveWatchTreeDataProvider as any)._activeSession = {
session: {
customRequest: jest.fn().mockResolvedValue({ result: '2', variablesReference: 0 })
},
evaluateGlobalExpression: jest.fn()
};
(vscode.window as any).showInputBox = jest.fn().mockResolvedValue('2');
await (liveWatchTreeDataProvider as any).handleSetValueCommand(child);
expect((liveWatchTreeDataProvider as any)._activeSession.session.customRequest).toHaveBeenCalledWith('setVariable', { name: 'childVar', value: '2', variablesReference: 123 });
expect(refreshSpy).toHaveBeenCalled();
});

it('should show error message when trying to set value with no active session', async () => {
const node = makeNode('node', { result: '1', variablesReference: 0 }, 1);
(liveWatchTreeDataProvider as any)._activeSession = undefined;
const showErrorMessageSpy = jest.spyOn(vscode.window, 'showErrorMessage').mockImplementation(undefined);
await (liveWatchTreeDataProvider as any).handleSetValueCommand(node);
expect(showErrorMessageSpy).toHaveBeenCalledWith('No active debug session');
});

it('AddFromSelection adds selected text as new live watch expression to roots', async () => {
jest.spyOn(liveWatchTreeDataProvider as any, 'evaluate').mockResolvedValue({ result: '5678', variablesReference: 0 });
// Mock the active text editor with fake range
Expand Down Expand Up @@ -333,18 +372,19 @@ describe('LiveWatchTreeDataProvider', () => {
it('registers all live watch commands on activate', async () => {
await liveWatchTreeDataProvider.activate(tracker);
const calls = (vscode.commands.registerCommand as jest.Mock).mock.calls.map(call => call[0]);
expect(calls).toEqual(expect.arrayContaining([
expect(calls).toEqual([
'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',
'vscode-cmsis-debugger.liveWatch.copy',
'vscode-cmsis-debugger.liveWatch.setValue',
'vscode-cmsis-debugger.liveWatch.addToLiveWatchFromTextEditor',
'vscode-cmsis-debugger.liveWatch.addToLiveWatchFromWatchWindow',
'vscode-cmsis-debugger.liveWatch.addToLiveWatchFromVariablesView',
'vscode-cmsis-debugger.liveWatch.showInMemoryInspector'
]));
]);
});

it('add command adds a node when expression provided', async () => {
Expand Down Expand Up @@ -458,6 +498,37 @@ describe('LiveWatchTreeDataProvider', () => {
expect((liveWatchTreeDataProvider as any).roots.length).toBe(0);
});

it('set value command calls handleSetValueCommand with node', async () => {
const node = makeNode('node', { result: '1', variablesReference: 0 }, 1);
const handleSetValueSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'handleSetValueCommand').mockResolvedValue('');
await liveWatchTreeDataProvider.activate(tracker);
// Simulate command invocation with node argument
const handler = getRegisteredHandler('vscode-cmsis-debugger.liveWatch.setValue');
expect(handler).toBeDefined();
await handler(node);
expect(handleSetValueSpy).toHaveBeenCalledWith(node);
});

it('set value command does nothing when node is undefined', async () => {
const handleSetValueSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'handleSetValueCommand').mockResolvedValue('');
await liveWatchTreeDataProvider.activate(tracker);
const handler = getRegisteredHandler('vscode-cmsis-debugger.liveWatch.setValue');
expect(handler).toBeDefined();
await handler(undefined);
expect(handleSetValueSpy).toHaveBeenCalled();
});

it('set value command does nothing when no new value provided', async () => {
const node = makeNode('node', { result: '1', variablesReference: 0 }, 1);
const handleSetValueSpy = jest.spyOn(liveWatchTreeDataProvider as any, 'handleSetValueCommand').mockResolvedValue('');
(vscode.window as any).showInputBox = jest.fn().mockResolvedValue(undefined);
await liveWatchTreeDataProvider.activate(tracker);
const handler = getRegisteredHandler('vscode-cmsis-debugger.liveWatch.setValue');
expect(handler).toBeDefined();
await handler(node);
expect(handleSetValueSpy).toHaveBeenCalled();
});

it('showInMemoryInspector command does nothing when node is undefined', async () => {
await liveWatchTreeDataProvider.activate(tracker);
const handler = getRegisteredHandler('vscode-cmsis-debugger.liveWatch.showInMemoryInspector');
Expand Down
34 changes: 34 additions & 0 deletions src/views/live-watch/live-watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ export class LiveWatchTreeDataProvider implements vscode.TreeDataProvider<LiveWa
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.handleRenameCommand(node));
const copyCommand = vscode.commands.registerCommand('vscode-cmsis-debugger.liveWatch.copy', async (node) => await this.handleCopyCommand(node));
const setValueCommand = vscode.commands.registerCommand('vscode-cmsis-debugger.liveWatch.setValue', async (node) => {
await this.handleSetValueCommand(node);
});
const addToLiveWatchCommand = vscode.commands.registerCommand('vscode-cmsis-debugger.liveWatch.addToLiveWatchFromTextEditor',
async () => await this.handleAddFromSelectionCommand());
/* omarArm: I am using the same callback function for both watch window and variables view, as they have the same payload structure for now.
Expand All @@ -174,6 +177,7 @@ export class LiveWatchTreeDataProvider implements vscode.TreeDataProvider<LiveWa
refreshCommand,
modifyCommand,
copyCommand,
setValueCommand,
addToLiveWatchCommand,
addToLiveWatchFromWatchWindowCommand,
addToLiveWatchFromVariablesViewCommand,
Expand Down Expand Up @@ -284,6 +288,36 @@ export class LiveWatchTreeDataProvider implements vscode.TreeDataProvider<LiveWa
return response;
}

private async handleSetValueCommand(node: LiveWatchNode) {
if (!node) {
return;
}
if (!this._activeSession) {
vscode.window.showErrorMessage('No active debug session');
return;
}
const newValue = await vscode.window.showInputBox({ prompt: 'New Value', value: node.value.result });
if (newValue === undefined) {
return;
}
// VSCode sends setExpression requests for parent nodes, and setVariable requests for child nodes. We can use the presence of parent to determine which request to send.
if (node.parent) {
await this._activeSession?.session.customRequest('setVariable', {
name: node.expression,
value: newValue,
variablesReference: node.parent.value.variablesReference
});
} else {
const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0;
await this._activeSession?.session.customRequest('setExpression', {
Comment thread
omarArm marked this conversation as resolved.
expression: node.expression,
value: newValue,
frameId: frameId
});
Comment thread
omarArm marked this conversation as resolved.
}
await this.refresh();
}

private async addToRoots(expression: string, parent?: LiveWatchNode) {
// Create a new node with a unique ID and evaluate its value
const newNode: LiveWatchNode = {
Expand Down
Loading