Skip to content

Commit 0213350

Browse files
Merge pull request #2775 from gregg-miskelly/SetNextStatement
Set next statement support
2 parents 1fa27e8 + 24c6973 commit 0213350

File tree

8 files changed

+287
-5
lines changed

8 files changed

+287
-5
lines changed

.vscode/tasks.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"compile"
1111
],
1212
"showOutput": "always",
13-
"isBuildCommand": true
13+
"isBuildCommand": true,
14+
"problemMatcher":"$tsc"
1415
},
1516
{
1617
"taskName": "test",

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515

1616
* Added a `csharp.maxProjectFileCountForDiagnosticAnalysis` setting to configure the file limit when the extension stops reporting errors for whole workspace. When this threshold is reached, the diagnostics are reported for currently opened files only. This mechanism was available in previous versions, but now can be configured. The default is 1000. (PR: [#1877](https://github.com/OmniSharp/omnisharp-vscode/pull/1877))
1717

18+
#### Debugger
19+
* Added support for set next statement. Set next statement is a feature that have been available for a long time in full Visual Studio, and this brings the feature to Visual Studio Code. This feature allows developers to change what code is executed in your program. For example, you can move the instruction pointer back to re-execute a function that you just ran so you can debug into it, or you can skip over some code that you don't want to execute. To use this feature, move your cursor to the next statement you would like to execute next, and either open the editor context menu and invoke 'Set Next Statement (.NET)', or use the keyboard shortcut of <kbd>Ctrl+Shift+F10</kbd> ([#1753](https://github.com/OmniSharp/omnisharp-vscode/issues/1753))
20+
* Fixed evaluating string functions with interpretation in .NET Core 2.1+. Evaluation uses interpretation for conditional breakpoints, evaluating methods that take a lambda, etc ([#2683](https://github.com/OmniSharp/omnisharp-vscode/issues/2683))
21+
22+
1823
## 1.17.1 (November 11, 2018)
1924

2025
* Updated Razor language service to fix various Razor editing reliability issues. For details see https://github.com/aspnet/Razor.VSCode/releases/tag/1.0.0-alpha2.

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
"stream": "0.0.2",
8585
"strip-bom": "3.0.0",
8686
"tmp": "0.0.33",
87-
"vscode-debugprotocol": "1.6.1",
87+
"vscode-debugprotocol": "1.32.0",
8888
"vscode-extension-telemetry": "0.1.0",
8989
"yauzl": "2.10.0"
9090
},
@@ -784,6 +784,11 @@
784784
"title": "List processes on remote connection for attach",
785785
"category": "CSharp"
786786
},
787+
{
788+
"command": "csharp.setNextStatement",
789+
"title": "Set Next Statement (.NET)",
790+
"category": "Debug"
791+
},
787792
{
788793
"command": "csharp.reportIssue",
789794
"title": "Start authoring a new issue on GitHub",
@@ -805,6 +810,16 @@
805810
"command": "o.showOutput",
806811
"key": "Ctrl+L L",
807812
"mac": "Cmd+L L"
813+
},
814+
{
815+
"command": "csharp.setNextStatement",
816+
"key": "Ctrl+Shift+F10",
817+
"when": "inDebugMode && debugType == 'coreclr'"
818+
},
819+
{
820+
"command": "csharp.setNextStatement",
821+
"key": "Ctrl+Shift+F10",
822+
"when": "inDebugMode && debugType == 'clr'"
808823
}
809824
],
810825
"snippets": [
@@ -2882,6 +2897,16 @@
28822897
"command": "extension.showRazorHtmlWindow",
28832898
"when": "resourceLangId == aspnetcorerazor"
28842899
}
2900+
],
2901+
"editor/context": [
2902+
{
2903+
"command": "csharp.setNextStatement",
2904+
"when": "inDebugMode && debugType == 'coreclr'"
2905+
},
2906+
{
2907+
"command": "csharp.setNextStatement",
2908+
"when": "inDebugMode && debugType == 'clr'"
2909+
}
28852910
]
28862911
}
28872912
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
import { DebugProtocol } from 'vscode-debugprotocol';
8+
import { SetNextStatementHelpers } from './setNextStatementHelpers';
9+
10+
export default async function setNextStatement() : Promise<void> {
11+
try {
12+
const debugSession = vscode.debug.activeDebugSession;
13+
if (!debugSession) {
14+
throw new Error("There isn't an active .NET debug session.");
15+
}
16+
17+
const debugType: string = debugSession.type;
18+
if (debugType !== "coreclr" && debugType !== "clr") {
19+
throw new Error("There isn't an active .NET debug session.");
20+
}
21+
22+
const currentEditor = vscode.window.activeTextEditor;
23+
if (!currentEditor) {
24+
throw new Error("There isn't an active source file.");
25+
}
26+
27+
const position = currentEditor.selection.active;
28+
if (!position) {
29+
throw new Error("There isn't a current source position.");
30+
}
31+
32+
const currentDocument = currentEditor.document;
33+
if (currentDocument.isDirty) {
34+
throw new Error("The current document has unsaved edits.");
35+
}
36+
37+
const gotoTargetsArg : DebugProtocol.GotoTargetsArguments = {
38+
source: {
39+
// NOTE: in the case of embedded documents, this is the rather odd value of something like the following,
40+
// but vsdbg-ui is okay with it, so I guess we will leave it.
41+
// "Source file extracted from PDB file. Original path: C:\Debuggee-Libraries-Build-Dir\EmbeddedSourceTest\Class1\Main.cs"
42+
path: currentDocument.uri.fsPath
43+
},
44+
line: position.line + 1,
45+
column: position.character + 1
46+
};
47+
48+
const gotoTargetsResponseBody = await debugSession.customRequest('gotoTargets', gotoTargetsArg);
49+
const targets: DebugProtocol.GotoTarget[] = gotoTargetsResponseBody.targets;
50+
if (targets.length === 0) {
51+
throw new Error(`No executable code is associated with line ${gotoTargetsArg.line}.`);
52+
}
53+
54+
let selectedTarget = targets[0];
55+
56+
if (targets.length > 1) {
57+
58+
// If we have multiple possible targets, then let the user pick.
59+
const labelDict: { [key: string]: DebugProtocol.GotoTarget } = SetNextStatementHelpers.makeLabelsUnique(targets);
60+
const labels : string[] = targets.map((target) => target.label);
61+
62+
const options: vscode.QuickPickOptions = {
63+
matchOnDescription: true,
64+
placeHolder: "Choose the specific location"
65+
};
66+
67+
const selectedLabelName : string = await vscode.window.showQuickPick(labels, options);
68+
if (!selectedLabelName) {
69+
return; // operation was cancelled
70+
}
71+
selectedTarget = labelDict[selectedLabelName];
72+
}
73+
74+
// NOTE: we don't have a thread id, so this doesn't furfill the contract of 'GotoArguments'. So we will make vsdbg-ui guess the thread id.
75+
// The following bug tracks fixing this the right way: https://github.com/Microsoft/vscode/issues/63943
76+
const gotoArg /*: DebugProtocol.GotoArguments*/ = {
77+
targetId: selectedTarget.id
78+
};
79+
80+
await debugSession.customRequest('goto', gotoArg);
81+
}
82+
catch (err) {
83+
vscode.window.showErrorMessage(`Unable to set the next statement. ${err}`);
84+
}
85+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { DebugProtocol } from 'vscode-debugprotocol';
7+
8+
// Contains any unit-testable parts of SetNextStatement
9+
export class SetNextStatementHelpers
10+
{
11+
public static makeLabelsUnique(targets: DebugProtocol.GotoTarget[]) : { [key: string]: DebugProtocol.GotoTarget } {
12+
13+
// first try: use the original label names
14+
let labelDict : { [key: string]: DebugProtocol.GotoTarget } | undefined = SetNextStatementHelpers.makeLabelDictorary(targets);
15+
if (!labelDict) {
16+
// next try to add on the source position
17+
labelDict = SetNextStatementHelpers.tryMakeLabelsUnique(targets, (target: DebugProtocol.GotoTarget) => `${target.label} : source position (${target.line},${target.column})-(${target.endLine},${target.endColumn})`);
18+
if (!labelDict) {
19+
// nothing worked, so just add on the array index as a prefix
20+
labelDict = SetNextStatementHelpers.tryMakeLabelsUnique(targets, (target: DebugProtocol.GotoTarget, index: number) => `${index+1}: ${target.label}`);
21+
}
22+
}
23+
24+
return labelDict;
25+
}
26+
27+
static tryMakeLabelsUnique(targets: DebugProtocol.GotoTarget[], getLabel: (target: DebugProtocol.GotoTarget, index?:number) => string) : { [key: string]: DebugProtocol.GotoTarget } | undefined {
28+
const labelDict = SetNextStatementHelpers.makeLabelDictorary(targets, getLabel);
29+
if (!labelDict) {
30+
// The specified 'getLabel' function wasn't able to make the label names unique
31+
return undefined;
32+
}
33+
34+
// The specified 'getLabel' fenction worked. Update the 'label' names in the 'targets' array.
35+
targets.forEach((target, index) => {
36+
target.label = getLabel(target, index);
37+
});
38+
return labelDict;
39+
}
40+
41+
static makeLabelDictorary(targets: DebugProtocol.GotoTarget[], getLabel?: (target: DebugProtocol.GotoTarget, index?:number) => string) : { [key: string]: DebugProtocol.GotoTarget } | undefined {
42+
if (!getLabel) {
43+
getLabel = (target) => target.label;
44+
}
45+
46+
const labelNameDict : { [key: string]: DebugProtocol.GotoTarget } = {};
47+
let index:number = 0;
48+
for (const target of targets) {
49+
const key:string = getLabel(target, index);
50+
let existingItem = labelNameDict[key];
51+
if (existingItem !== undefined) {
52+
// multiple values with the same label found
53+
return undefined;
54+
}
55+
labelNameDict[key] = target;
56+
index++;
57+
}
58+
59+
return labelNameDict;
60+
}
61+
}

src/features/commands.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { PlatformInformation } from '../platform';
2020
import CompositeDisposable from '../CompositeDisposable';
2121
import OptionProvider from '../observers/OptionProvider';
2222
import reportIssue from './reportIssue';
23+
import setNextStatement from '../coreclr-debug/setNextStatement';
2324
import { IMonoResolver } from '../constants/IMonoResolver';
2425
import { getDotnetInfo } from '../utils/getDotnetInfo';
2526

@@ -46,6 +47,8 @@ export default function registerCommands(server: OmniSharpServer, platformInfo:
4647
// Register command for remote process picker for attach
4748
disposable.add(vscode.commands.registerCommand('csharp.listRemoteProcess', async (args) => RemoteAttachPicker.ShowAttachEntries(args, platformInfo)));
4849

50+
disposable.add(vscode.commands.registerCommand('csharp.setNextStatement', async () => setNextStatement()));
51+
4952
// Register command for adapter executable command.
5053
disposable.add(vscode.commands.registerCommand('csharp.coreclrAdapterExecutableCommand', async (args) => getAdapterExecutionCommand(platformInfo, eventStream, packageJSON, extensionPath)));
5154
disposable.add(vscode.commands.registerCommand('csharp.clrAdapterExecutableCommand', async (args) => getAdapterExecutionCommand(platformInfo, eventStream, packageJSON, extensionPath)));
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { SetNextStatementHelpers } from '../../src/coreclr-debug/setNextStatementHelpers';
7+
import { DebugProtocol } from 'vscode-debugprotocol';
8+
import { should } from 'chai';
9+
10+
suite("SetNextStatementHelpers", () => {
11+
suiteSetup(() => should());
12+
13+
test("makeLabelsUnique handles distinct label names", () => {
14+
15+
const targets: DebugProtocol.GotoTarget[] = [
16+
{
17+
id: 1000,
18+
label: "Example.dll!MyClass.A()",
19+
line: 100,
20+
column: 12,
21+
endLine: 100,
22+
endColumn: 16
23+
},
24+
{
25+
id: 1001,
26+
label: "Example.dll!MyClass.B()",
27+
line: 110,
28+
column: 14,
29+
endLine: 110,
30+
endColumn: 25
31+
}
32+
];
33+
34+
const labelDict: { [key: string]: DebugProtocol.GotoTarget } = SetNextStatementHelpers.makeLabelsUnique(targets);
35+
36+
targets.length.should.equal(2);
37+
targets[0].label.should.equal("Example.dll!MyClass.A()");
38+
targets[1].label.should.equal("Example.dll!MyClass.B()");
39+
labelDict["Example.dll!MyClass.A()"].id.should.equal(1000);
40+
labelDict["Example.dll!MyClass.B()"].id.should.equal(1001);
41+
});
42+
43+
test("makeLabelsUnique handles different source ranges", () => {
44+
45+
const targets: DebugProtocol.GotoTarget[] = [
46+
{
47+
id: 1000,
48+
label: "Example.dll!MyClass.MyFunction()",
49+
line: 100,
50+
column: 12,
51+
endLine: 100,
52+
endColumn: 16
53+
},
54+
{
55+
id: 1001,
56+
label: "Example.dll!MyClass.MyFunction()",
57+
line: 100,
58+
column: 14,
59+
endLine: 100,
60+
endColumn: 25
61+
}
62+
];
63+
64+
const labelDict: { [key: string]: DebugProtocol.GotoTarget } = SetNextStatementHelpers.makeLabelsUnique(targets);
65+
66+
targets.length.should.equal(2);
67+
targets[0].label.should.equal("Example.dll!MyClass.MyFunction() : source position (100,12)-(100,16)");
68+
targets[1].label.should.equal("Example.dll!MyClass.MyFunction() : source position (100,14)-(100,25)");
69+
labelDict["Example.dll!MyClass.MyFunction() : source position (100,12)-(100,16)"].id.should.equal(1000);
70+
labelDict["Example.dll!MyClass.MyFunction() : source position (100,14)-(100,25)"].id.should.equal(1001);
71+
});
72+
73+
test("makeLabelsUnique handles idential source positions", () => {
74+
75+
const targets: DebugProtocol.GotoTarget[] = [
76+
{
77+
id: 1000,
78+
label: "Example.dll!MyClass.MyFunction()",
79+
line: 100,
80+
column: 12,
81+
endLine: 100,
82+
endColumn: 16
83+
},
84+
{
85+
id: 1001,
86+
label: "Example.dll!MyClass.MyFunction()",
87+
line: 100,
88+
column: 12,
89+
endLine: 100,
90+
endColumn: 16
91+
}
92+
];
93+
94+
const labelDict: { [key: string]: DebugProtocol.GotoTarget } = SetNextStatementHelpers.makeLabelsUnique(targets);
95+
96+
targets.length.should.equal(2);
97+
targets[0].label.should.equal("1: Example.dll!MyClass.MyFunction()");
98+
targets[1].label.should.equal("2: Example.dll!MyClass.MyFunction()");
99+
labelDict["1: Example.dll!MyClass.MyFunction()"].id.should.equal(1000);
100+
labelDict["2: Example.dll!MyClass.MyFunction()"].id.should.equal(1001);
101+
});
102+
});

0 commit comments

Comments
 (0)