From 17b04ac8a2d8a17477a9611dac85ba6110391201 Mon Sep 17 00:00:00 2001 From: Andrew Ross Date: Fri, 5 Apr 2019 11:41:40 +0200 Subject: [PATCH 1/2] Added processCommand to attach config Added processCommand to optionsSchema.json Added processCommand to resolve debugCOnfig --- package.json | 10 ++++++ src/configurationProvider.ts | 66 +++++++++++++++++++++--------------- src/omnisharp/extension.ts | 2 +- src/tools/OptionsSchema.json | 31 +++++++++++++---- 4 files changed, 74 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index 459df55fb..00e478742 100644 --- a/package.json +++ b/package.json @@ -1409,6 +1409,11 @@ "type": "object", "required": [], "properties": { + "processCommand": { + "type": "string", + "description": "Attaches to a process with this command", + "default": "If this is use 'processId' and 'processName' should not be used." + }, "processName": { "type": "string", "description": "", @@ -2456,6 +2461,11 @@ "type": "object", "required": [], "properties": { + "processCommand": { + "type": "string", + "description": "Attaches to a process with this command", + "default": "If this is use 'processId' and 'processName' should not be used." + }, "processName": { "type": "string", "description": "", diff --git a/src/configurationProvider.ts b/src/configurationProvider.ts index ed22e26b5..eed887cce 100644 --- a/src/configurationProvider.ts +++ b/src/configurationProvider.ts @@ -16,17 +16,19 @@ import { WorkspaceInformationResponse } from './omnisharp/protocol'; import { isSubfolderOf } from './common'; import { parse } from 'jsonc-parser'; import { MessageItem } from './vscodeAdapter'; +import { AttachItemsProvider, DotNetAttachItemsProviderFactory } from './features/processPicker'; export class CSharpConfigurationProvider implements vscode.DebugConfigurationProvider { - private server: OmniSharpServer; - public constructor(server: OmniSharpServer) { - this.server = server; + private attachItemsProvider: AttachItemsProvider; + + public constructor(private server: OmniSharpServer) { + this.attachItemsProvider = DotNetAttachItemsProviderFactory.Get(); } /** * TODO: Remove function when https://github.com/OmniSharp/omnisharp-roslyn/issues/909 is resolved. - * + * * Note: serverUtils.requestWorkspaceInformation only retrieves one folder for multi-root workspaces. Therefore, generator will be incorrect for all folders * except the first in a workspace. Currently, this only works if the requested folder is the same as the server's solution path or folder. */ @@ -35,8 +37,7 @@ export class CSharpConfigurationProvider implements vscode.DebugConfigurationPro const solutionPathOrFolder: string = this.server.getSolutionPathOrFolder(); // Make sure folder, folder.uri, and solutionPathOrFolder are defined. - if (!solutionPathOrFolder) - { + if (!solutionPathOrFolder) { return Promise.resolve(false); } @@ -45,8 +46,7 @@ export class CSharpConfigurationProvider implements vscode.DebugConfigurationPro return fs.lstat(solutionPathOrFolder).then(stat => { return stat.isFile(); }).then(isFile => { - if (isFile) - { + if (isFile) { serverFolder = path.dirname(solutionPathOrFolder); } @@ -73,38 +73,36 @@ export class CSharpConfigurationProvider implements vscode.DebugConfigurationPro return []; } - try - { - let hasWorkspaceMatches : boolean = await this.checkWorkspaceInformationMatchesWorkspaceFolder(folder); + try { + let hasWorkspaceMatches: boolean = await this.checkWorkspaceInformationMatchesWorkspaceFolder(folder); if (!hasWorkspaceMatches) { vscode.window.showErrorMessage(`Cannot create .NET debug configurations. The active C# project is not within folder '${folder.uri.fsPath}'.`); return []; } - let info: WorkspaceInformationResponse = await serverUtils.requestWorkspaceInformation(this.server); + let info: WorkspaceInformationResponse = await serverUtils.requestWorkspaceInformation(this.server); const generator = new AssetGenerator(info, folder); if (generator.hasExecutableProjects()) { - - if (!await generator.selectStartupProject()) - { + + if (!await generator.selectStartupProject()) { return []; } - - // Make sure .vscode folder exists, addTasksJsonIfNecessary will fail to create tasks.json if the folder does not exist. + + // Make sure .vscode folder exists, addTasksJsonIfNecessary will fail to create tasks.json if the folder does not exist. await fs.ensureDir(generator.vscodeFolder); // Add a tasks.json - const buildOperations : AssetOperations = await getBuildOperations(generator); + const buildOperations: AssetOperations = await getBuildOperations(generator); await addTasksJsonIfNecessary(generator, buildOperations); - + const isWebProject = generator.hasWebServerDependency(); const launchJson: string = generator.createLaunchJson(isWebProject); - // jsonc-parser's parse function parses a JSON string with comments into a JSON object. However, this removes the comments. + // jsonc-parser's parse function parses a JSON string with comments into a JSON object. However, this removes the comments. return parse(launchJson); - } else { + } else { // Error to be caught in the .catch() below to write default C# configurations throw new Error("Does not contain .NET Core projects."); } @@ -112,9 +110,9 @@ export class CSharpConfigurationProvider implements vscode.DebugConfigurationPro catch { // Provider will always create an launch.json file. Providing default C# configurations. - // jsonc-parser's parse to convert to JSON object without comments. + // jsonc-parser's parse to convert to JSON object without comments. return [ - createFallbackLaunchConfiguration(), + createFallbackLaunchConfiguration(), parse(createAttachConfiguration()) ]; } @@ -127,7 +125,7 @@ export class CSharpConfigurationProvider implements vscode.DebugConfigurationPro if (envFile) { try { const parsedFile: ParsedEnvironmentFile = ParsedEnvironmentFile.CreateFromFile(envFile, config["env"]); - + // show error message if single lines cannot get parsed if (parsedFile.Warning) { CSharpConfigurationProvider.showFileWarningAsync(parsedFile.Warning, envFile); @@ -153,14 +151,26 @@ export class CSharpConfigurationProvider implements vscode.DebugConfigurationPro */ resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken): vscode.ProviderResult { - if (!config.type) - { + if (!config.type) { // If the config doesn't look functional force VSCode to open a configuration file https://github.com/Microsoft/vscode/issues/54213 return null; } - if (config.request === "launch") - { + if (config.request === "attach" && config.processCommand) { + const processCommand = config.processCommand.replace(/\${workspaceFolder}/g, folder.uri.fsPath); + delete config.processCommand; + return this.attachItemsProvider.getAttachItems() + .then(attachItems => { + const attachItem = attachItems.find(ai => ai.detail === processCommand); + if (attachItem == null) { + throw new Error("Can't find process"); + } + config.processId = attachItem.id; + return config; + }); + } + + if (config.request === "launch") { if (!config.cwd && !config.pipeTransport) { config.cwd = "${workspaceFolder}"; } diff --git a/src/omnisharp/extension.ts b/src/omnisharp/extension.ts index f2691a744..0822ea8b3 100644 --- a/src/omnisharp/extension.ts +++ b/src/omnisharp/extension.ts @@ -181,4 +181,4 @@ export async function activate(context: vscode.ExtensionContext, packageJSON: an return new Promise(resolve => server.onServerStart(e => resolve({ server, advisor }))); -} \ No newline at end of file +} diff --git a/src/tools/OptionsSchema.json b/src/tools/OptionsSchema.json index 4d673e917..532b87ed0 100644 --- a/src/tools/OptionsSchema.json +++ b/src/tools/OptionsSchema.json @@ -58,7 +58,9 @@ }, "PipeTransport": { "type": "object", - "required": [ "debuggerPath" ], + "required": [ + "debuggerPath" + ], "description": "When present, this tells the debugger to connect to a remote computer using another executable as a pipe that will relay standard input/output between VS Code and the .NET Core debugger backend executable (vsdbg).", "default": { "pipeCwd": "${workspaceFolder}", @@ -176,7 +178,9 @@ }, "LaunchBrowserPlatformOptions": { "type": "object", - "required": [ "command" ], + "required": [ + "command" + ], "properties": { "command": { "type": "string", @@ -192,7 +196,9 @@ }, "LaunchBrowser": { "type": "object", - "required": [ "enabled" ], + "required": [ + "enabled" + ], "description": "Configures starting a web browser as part of the launch -- should a web browser be started, and if so, what command should be run to start it. This option can be modified to launch a specific browser.", "default": { "enabled": true @@ -294,7 +300,11 @@ }, "console": { "type": "string", - "enum": [ "internalConsole", "integratedTerminal", "externalTerminal" ], + "enum": [ + "internalConsole", + "integratedTerminal", + "externalTerminal" + ], "enumDescriptions": [ "Output to the VS Code Debug Console. This doesn't support reading console input (ex:Console.ReadLine)", "VS Code's integrated terminal", @@ -358,7 +368,9 @@ "$ref": "#/definitions/SourceLinkOptions", "description": "Options to control how Source Link connects to web servers. For more information: https://aka.ms/VSCode-CS-LaunchJson#source-link-options", "default": { - "*": { "enabled": true } + "*": { + "enabled": true + } } } } @@ -367,6 +379,11 @@ "type": "object", "required": [], "properties": { + "processCommand": { + "type": "string", + "description": "Attaches to a process with this command", + "default": "If this is use 'processId' and 'processName' should not be used." + }, "processName": { "type": "string", "description": "", @@ -436,7 +453,9 @@ "$ref": "#/definitions/SourceLinkOptions", "description": "Options to control how Source Link connects to web servers. For more information: https://aka.ms/VSCode-CS-LaunchJson#source-link-options", "default": { - "*": { "enabled": true } + "*": { + "enabled": true + } } } } From d7b825418f640790fbdb19d6496452dfbf054135 Mon Sep 17 00:00:00 2001 From: Andrew Ross Date: Wed, 10 Apr 2019 19:12:49 +0200 Subject: [PATCH 2/2] Changed resolveDebugConfiguration to async Better error message if the process can't be found Error message if process was found more than once Deleted processCommand in unitDebuggingOptions --- src/configurationProvider.ts | 23 +++++++++++++---------- src/tools/GenerateOptionsSchema.ts | 11 ++++++----- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/configurationProvider.ts b/src/configurationProvider.ts index eed887cce..f270b01d5 100644 --- a/src/configurationProvider.ts +++ b/src/configurationProvider.ts @@ -149,7 +149,7 @@ export class CSharpConfigurationProvider implements vscode.DebugConfigurationPro /** * Try to add all missing attributes to the debug configuration being launched. */ - resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken): vscode.ProviderResult { + async resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken): Promise { if (!config.type) { // If the config doesn't look functional force VSCode to open a configuration file https://github.com/Microsoft/vscode/issues/54213 @@ -159,15 +159,18 @@ export class CSharpConfigurationProvider implements vscode.DebugConfigurationPro if (config.request === "attach" && config.processCommand) { const processCommand = config.processCommand.replace(/\${workspaceFolder}/g, folder.uri.fsPath); delete config.processCommand; - return this.attachItemsProvider.getAttachItems() - .then(attachItems => { - const attachItem = attachItems.find(ai => ai.detail === processCommand); - if (attachItem == null) { - throw new Error("Can't find process"); - } - config.processId = attachItem.id; - return config; - }); + const attachItems = await this.attachItemsProvider.getAttachItems(); + const foundAttatchItems = attachItems.filter(ai => ai.detail === processCommand); + + if (foundAttatchItems.length === 0) { + throw new Error(`Couldn't find a process with the command "${processCommand}"`); + } + + if (foundAttatchItems.length > 1) { + throw new Error(`Find ${foundAttatchItems.length} processes with the command "${processCommand}"`); + } + config.processId = foundAttatchItems[0].id; + return config; } if (config.request === "launch") { diff --git a/src/tools/GenerateOptionsSchema.ts b/src/tools/GenerateOptionsSchema.ts index c87eee505..e44410fa2 100644 --- a/src/tools/GenerateOptionsSchema.ts +++ b/src/tools/GenerateOptionsSchema.ts @@ -11,7 +11,7 @@ function AppendFieldsToObject(reference: any, obj: any) { // Make sure it is an object type if (typeof obj == 'object') { for (let referenceKey in reference) { - // If key exists in original object and is an object. + // If key exists in original object and is an object. if (obj.hasOwnProperty(referenceKey)) { obj[referenceKey] = AppendFieldsToObject(reference[referenceKey], obj[referenceKey]); } else { @@ -24,7 +24,7 @@ function AppendFieldsToObject(reference: any, obj: any) { return obj; } -// Combines two object's fields, giving the parentDefault a higher precedence. +// Combines two object's fields, giving the parentDefault a higher precedence. function MergeDefaults(parentDefault: any, childDefault: any) { let newDefault: any = {}; @@ -75,7 +75,7 @@ function ReplaceReferences(definitions: any, objects: any) { delete objects[key]['$ref']; } - // Recursively replace references if this object has properties. + // Recursively replace references if this object has properties. if (objects[key].hasOwnProperty('type') && objects[key].type === 'object' && objects[key].properties !== null) { objects[key].properties = ReplaceReferences(definitions, objects[key].properties); objects[key].properties = UpdateDefaults(objects[key].properties, objects[key].default); @@ -85,7 +85,7 @@ function ReplaceReferences(definitions: any, objects: any) { return objects; } -function mergeReferences(baseDefinitions: any, additionalDefinitions: any) : void { +function mergeReferences(baseDefinitions: any, additionalDefinitions: any): void { for (let key in additionalDefinitions) { if (baseDefinitions[key]) { throw `Error: '${key}' defined in multiple schema files.`; @@ -117,6 +117,7 @@ export function GenerateOptionsSchema() { delete unitTestDebuggingOptions.processName; delete unitTestDebuggingOptions.processId; delete unitTestDebuggingOptions.pipeTransport; + delete unitTestDebuggingOptions.processCommand; // Add the additional options we do want unitTestDebuggingOptions["type"] = { "type": "string", @@ -138,7 +139,7 @@ export function GenerateOptionsSchema() { if (os.platform() === 'win32') { content = content.replace(/\n/gm, "\r\n"); } - + // We use '\u200b' (unicode zero-length space character) to break VS Code's URL detection regex for URLs that are examples. This process will // convert that from the readable espace sequence, to just an invisible character. Convert it back to the visible espace sequence. content = content.replace(/\u200b/gm, "\\u200b");