Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"compile"
],
"showOutput": "always",
"isBuildCommand": true
"isBuildCommand": true,
"problemMatcher":"$tsc"
},
{
"taskName": "test",
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@

* 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))

#### Debugger
* 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))
* 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))


## 1.17.1 (November 11, 2018)

* 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.
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 26 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
"stream": "0.0.2",
"strip-bom": "3.0.0",
"tmp": "0.0.33",
"vscode-debugprotocol": "1.6.1",
"vscode-debugprotocol": "1.32.0",
"vscode-extension-telemetry": "0.1.0",
"yauzl": "2.10.0"
},
Expand Down Expand Up @@ -784,6 +784,11 @@
"title": "List processes on remote connection for attach",
"category": "CSharp"
},
{
"command": "csharp.setNextStatement",
"title": "Set Next Statement (.NET)",
"category": "Debug"
},
{
"command": "csharp.reportIssue",
"title": "Start authoring a new issue on GitHub",
Expand All @@ -805,6 +810,16 @@
"command": "o.showOutput",
"key": "Ctrl+L L",
"mac": "Cmd+L L"
},
{
"command": "csharp.setNextStatement",
"key": "Ctrl+Shift+F10",
"when": "inDebugMode && debugType == 'coreclr'"
},
{
"command": "csharp.setNextStatement",
"key": "Ctrl+Shift+F10",
"when": "inDebugMode && debugType == 'clr'"
}
],
"snippets": [
Expand Down Expand Up @@ -2882,6 +2897,16 @@
"command": "extension.showRazorHtmlWindow",
"when": "resourceLangId == aspnetcorerazor"
}
],
"editor/context": [
{
"command": "csharp.setNextStatement",
"when": "inDebugMode && debugType == 'coreclr'"
},
{
"command": "csharp.setNextStatement",
"when": "inDebugMode && debugType == 'clr'"
}
]
}
}
Expand Down
85 changes: 85 additions & 0 deletions src/coreclr-debug/setNextStatement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import { DebugProtocol } from 'vscode-debugprotocol';
import { SetNextStatementHelpers } from './setNextStatementHelpers';

export default async function setNextStatement() : Promise<void> {
try {
const debugSession = vscode.debug.activeDebugSession;
if (!debugSession) {
throw new Error("There isn't an active .NET debug session.");
}

const debugType: string = debugSession.type;
if (debugType !== "coreclr" && debugType !== "clr") {
throw new Error("There isn't an active .NET debug session.");
}

const currentEditor = vscode.window.activeTextEditor;
if (!currentEditor) {
throw new Error("There isn't an active source file.");
}

const position = currentEditor.selection.active;
if (!position) {
throw new Error("There isn't a current source position.");
}

const currentDocument = currentEditor.document;
if (currentDocument.isDirty) {
throw new Error("The current document has unsaved edits.");
}

const gotoTargetsArg : DebugProtocol.GotoTargetsArguments = {
source: {
// NOTE: in the case of embedded documents, this is the rather odd value of something like the following,
// but vsdbg-ui is okay with it, so I guess we will leave it.
// "Source file extracted from PDB file. Original path: C:\Debuggee-Libraries-Build-Dir\EmbeddedSourceTest\Class1\Main.cs"
path: currentDocument.uri.fsPath
},
line: position.line + 1,
column: position.character + 1
};

const gotoTargetsResponseBody = await debugSession.customRequest('gotoTargets', gotoTargetsArg);
const targets: DebugProtocol.GotoTarget[] = gotoTargetsResponseBody.targets;
if (targets.length === 0) {
throw new Error(`No executable code is associated with line ${gotoTargetsArg.line}.`);
}

let selectedTarget = targets[0];

if (targets.length > 1) {

// If we have multiple possible targets, then let the user pick.
const labelDict: { [key: string]: DebugProtocol.GotoTarget } = SetNextStatementHelpers.makeLabelsUnique(targets);
const labels : string[] = targets.map((target) => target.label);

const options: vscode.QuickPickOptions = {
matchOnDescription: true,
placeHolder: "Choose the specific location"
};

const selectedLabelName : string = await vscode.window.showQuickPick(labels, options);
if (!selectedLabelName) {
return; // operation was cancelled
}
selectedTarget = labelDict[selectedLabelName];
}

// 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.
// The following bug tracks fixing this the right way: https://github.com/Microsoft/vscode/issues/63943
const gotoArg /*: DebugProtocol.GotoArguments*/ = {
targetId: selectedTarget.id
};

await debugSession.customRequest('goto', gotoArg);
}
catch (err) {
vscode.window.showErrorMessage(`Unable to set the next statement. ${err}`);
}
}
61 changes: 61 additions & 0 deletions src/coreclr-debug/setNextStatementHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { DebugProtocol } from 'vscode-debugprotocol';

// Contains any unit-testable parts of SetNextStatement
export class SetNextStatementHelpers
{
public static makeLabelsUnique(targets: DebugProtocol.GotoTarget[]) : { [key: string]: DebugProtocol.GotoTarget } {

// first try: use the original label names
let labelDict : { [key: string]: DebugProtocol.GotoTarget } | undefined = SetNextStatementHelpers.makeLabelDictorary(targets);
if (!labelDict) {
// next try to add on the source position
labelDict = SetNextStatementHelpers.tryMakeLabelsUnique(targets, (target: DebugProtocol.GotoTarget) => `${target.label} : source position (${target.line},${target.column})-(${target.endLine},${target.endColumn})`);
if (!labelDict) {
// nothing worked, so just add on the array index as a prefix
labelDict = SetNextStatementHelpers.tryMakeLabelsUnique(targets, (target: DebugProtocol.GotoTarget, index: number) => `${index+1}: ${target.label}`);
}
}

return labelDict;
}

static tryMakeLabelsUnique(targets: DebugProtocol.GotoTarget[], getLabel: (target: DebugProtocol.GotoTarget, index?:number) => string) : { [key: string]: DebugProtocol.GotoTarget } | undefined {
const labelDict = SetNextStatementHelpers.makeLabelDictorary(targets, getLabel);
if (!labelDict) {
// The specified 'getLabel' function wasn't able to make the label names unique
return undefined;
}

// The specified 'getLabel' fenction worked. Update the 'label' names in the 'targets' array.
targets.forEach((target, index) => {
target.label = getLabel(target, index);
});
return labelDict;
}

static makeLabelDictorary(targets: DebugProtocol.GotoTarget[], getLabel?: (target: DebugProtocol.GotoTarget, index?:number) => string) : { [key: string]: DebugProtocol.GotoTarget } | undefined {
if (!getLabel) {
getLabel = (target) => target.label;
}

const labelNameDict : { [key: string]: DebugProtocol.GotoTarget } = {};
let index:number = 0;
for (const target of targets) {
const key:string = getLabel(target, index);
let existingItem = labelNameDict[key];
if (existingItem !== undefined) {
// multiple values with the same label found
return undefined;
}
labelNameDict[key] = target;
index++;
}

return labelNameDict;
}
}
3 changes: 3 additions & 0 deletions src/features/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { PlatformInformation } from '../platform';
import CompositeDisposable from '../CompositeDisposable';
import OptionProvider from '../observers/OptionProvider';
import reportIssue from './reportIssue';
import setNextStatement from '../coreclr-debug/setNextStatement';
import { IMonoResolver } from '../constants/IMonoResolver';
import { getDotnetInfo } from '../utils/getDotnetInfo';

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

disposable.add(vscode.commands.registerCommand('csharp.setNextStatement', async () => setNextStatement()));

// Register command for adapter executable command.
disposable.add(vscode.commands.registerCommand('csharp.coreclrAdapterExecutableCommand', async (args) => getAdapterExecutionCommand(platformInfo, eventStream, packageJSON, extensionPath)));
disposable.add(vscode.commands.registerCommand('csharp.clrAdapterExecutableCommand', async (args) => getAdapterExecutionCommand(platformInfo, eventStream, packageJSON, extensionPath)));
Expand Down
102 changes: 102 additions & 0 deletions test/unitTests/setNextStatementHelpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { SetNextStatementHelpers } from '../../src/coreclr-debug/setNextStatementHelpers';
import { DebugProtocol } from 'vscode-debugprotocol';
import { should } from 'chai';

suite("SetNextStatementHelpers", () => {
suiteSetup(() => should());

test("makeLabelsUnique handles distinct label names", () => {

const targets: DebugProtocol.GotoTarget[] = [
{
id: 1000,
label: "Example.dll!MyClass.A()",
line: 100,
column: 12,
endLine: 100,
endColumn: 16
},
{
id: 1001,
label: "Example.dll!MyClass.B()",
line: 110,
column: 14,
endLine: 110,
endColumn: 25
}
];

const labelDict: { [key: string]: DebugProtocol.GotoTarget } = SetNextStatementHelpers.makeLabelsUnique(targets);

targets.length.should.equal(2);
targets[0].label.should.equal("Example.dll!MyClass.A()");
targets[1].label.should.equal("Example.dll!MyClass.B()");
labelDict["Example.dll!MyClass.A()"].id.should.equal(1000);
labelDict["Example.dll!MyClass.B()"].id.should.equal(1001);
});

test("makeLabelsUnique handles different source ranges", () => {

const targets: DebugProtocol.GotoTarget[] = [
{
id: 1000,
label: "Example.dll!MyClass.MyFunction()",
line: 100,
column: 12,
endLine: 100,
endColumn: 16
},
{
id: 1001,
label: "Example.dll!MyClass.MyFunction()",
line: 100,
column: 14,
endLine: 100,
endColumn: 25
}
];

const labelDict: { [key: string]: DebugProtocol.GotoTarget } = SetNextStatementHelpers.makeLabelsUnique(targets);

targets.length.should.equal(2);
targets[0].label.should.equal("Example.dll!MyClass.MyFunction() : source position (100,12)-(100,16)");
targets[1].label.should.equal("Example.dll!MyClass.MyFunction() : source position (100,14)-(100,25)");
labelDict["Example.dll!MyClass.MyFunction() : source position (100,12)-(100,16)"].id.should.equal(1000);
labelDict["Example.dll!MyClass.MyFunction() : source position (100,14)-(100,25)"].id.should.equal(1001);
});

test("makeLabelsUnique handles idential source positions", () => {

const targets: DebugProtocol.GotoTarget[] = [
{
id: 1000,
label: "Example.dll!MyClass.MyFunction()",
line: 100,
column: 12,
endLine: 100,
endColumn: 16
},
{
id: 1001,
label: "Example.dll!MyClass.MyFunction()",
line: 100,
column: 12,
endLine: 100,
endColumn: 16
}
];

const labelDict: { [key: string]: DebugProtocol.GotoTarget } = SetNextStatementHelpers.makeLabelsUnique(targets);

targets.length.should.equal(2);
targets[0].label.should.equal("1: Example.dll!MyClass.MyFunction()");
targets[1].label.should.equal("2: Example.dll!MyClass.MyFunction()");
labelDict["1: Example.dll!MyClass.MyFunction()"].id.should.equal(1000);
labelDict["2: Example.dll!MyClass.MyFunction()"].id.should.equal(1001);
});
});