Skip to content

Commit 9323811

Browse files
Improvements to launch.json/tasks.json
This commit contains many improvements to our launch.json and task.json generation. Highlights: - Schema related changes: - Make most of the 'launchBrowser' attributes optional, remove the extra properties from the templates, and improve the schema - In the configuration resolver, add a default value for 'cwd' if we are local debugging - Generator related fixes: - Use a quick pick to allow the user to select which project to launch. Before we would always launch the first one OmniSharp returned. - Remove 'internalConsoleOptions' from the generated templates, and instead default it in the configuration resolver. - Fix several problems with regenerating launch/tasks.json in the case that the file already existed. We had code to delete the files and recreate it, but the logic was wrong leading us to sometimes duplicate content or not create the file at all. - Switch to using the '$tsc' problem matcher. We were using the '$msCompile' matcher, but that assumes that file names will be absolute paths, which isn't what 'dotnet build' provides. - Stop supporting project.json based projects for purposes of generating launch/tasks.json. Continuing to support these now that we let the user pick the startup project was going to be more work than it made sense to support. - In the case of generating launch.json through the configuration provider, we will no longer generate a set of generic configurations in error cases. We will now either use a fall back configuration which is designed to feel like an error, or we will put up an error prompt and return nothing. - Allow generating a tasks.json even if there is no launchable project. Note that we will not automatically generate a tasks.json in this case as I wasn't sure people would really like this, but the 'Generate Assets' command will force it.
1 parent 265499a commit 9323811

File tree

9 files changed

+373
-506
lines changed

9 files changed

+373
-506
lines changed

gulpfile.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ require('./tasks/offlinePackagingTasks');
1616
require('./tasks/backcompatTasks');
1717
require('./tasks/coverageTasks');
1818

19-
gulp.task('generateOptionsSchema', () : void => {
19+
// Disable warning about wanting an async function
20+
// tslint:disable-next-line
21+
gulp.task('generateOptionsSchema', () : Promise<void> => {
2022
optionsSchemaGenerator.GenerateOptionsSchema();
23+
return Promise.resolve();
2124
});
2225

2326
// Disable warning about wanting an async function

package.json

Lines changed: 70 additions & 101 deletions
Large diffs are not rendered by default.

src/assets.ts

Lines changed: 201 additions & 179 deletions
Large diffs are not rendered by default.

src/configurationProvider.ts

Lines changed: 73 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import * as serverUtils from './omnisharp/utils';
99
import * as vscode from 'vscode';
1010
import { ParsedEnvironmentFile } from './coreclr-debug/ParsedEnvironmentFile';
1111

12-
import { AssetGenerator, addTasksJsonIfNecessary, createAttachConfiguration, createLaunchConfiguration, createWebLaunchConfiguration } from './assets';
12+
import { AssetGenerator, AssetOperations, addTasksJsonIfNecessary, createAttachConfiguration, createFallbackLaunchConfiguration, getBuildOperations } from './assets';
1313

1414
import { OmniSharpServer } from './omnisharp/server';
15-
import { containsDotNetCoreProjects } from './omnisharp/protocol';
15+
import { WorkspaceInformationResponse } from './omnisharp/protocol';
1616
import { isSubfolderOf } from './common';
1717
import { parse } from 'jsonc-parser';
1818
import { MessageItem } from './vscodeAdapter';
@@ -30,11 +30,12 @@ export class CSharpConfigurationProvider implements vscode.DebugConfigurationPro
3030
* Note: serverUtils.requestWorkspaceInformation only retrieves one folder for multi-root workspaces. Therefore, generator will be incorrect for all folders
3131
* 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.
3232
*/
33-
private async checkWorkspaceInformationMatchesWorkspaceFolder(folder: vscode.WorkspaceFolder | undefined): Promise<boolean> {
33+
private async checkWorkspaceInformationMatchesWorkspaceFolder(folder: vscode.WorkspaceFolder): Promise<boolean> {
34+
3435
const solutionPathOrFolder: string = this.server.getSolutionPathOrFolder();
3536

3637
// Make sure folder, folder.uri, and solutionPathOrFolder are defined.
37-
if (!folder || !folder.uri || !solutionPathOrFolder)
38+
if (!solutionPathOrFolder)
3839
{
3940
return Promise.resolve(false);
4041
}
@@ -60,46 +61,63 @@ export class CSharpConfigurationProvider implements vscode.DebugConfigurationPro
6061
/**
6162
* Returns a list of initial debug configurations based on contextual information, e.g. package.json or folder.
6263
*/
63-
provideDebugConfigurations(folder: vscode.WorkspaceFolder | undefined, token?: vscode.CancellationToken): vscode.ProviderResult<vscode.DebugConfiguration[]> {
64-
return serverUtils.requestWorkspaceInformation(this.server).then(async info => {
65-
return this.checkWorkspaceInformationMatchesWorkspaceFolder(folder).then(async workspaceMatches => {
66-
const generator = new AssetGenerator(info);
67-
if (workspaceMatches && containsDotNetCoreProjects(info)) {
68-
const dotVscodeFolder: string = path.join(folder.uri.fsPath, '.vscode');
69-
const tasksJsonPath: string = path.join(dotVscodeFolder, 'tasks.json');
70-
71-
// Make sure .vscode folder exists, addTasksJsonIfNecessary will fail to create tasks.json if the folder does not exist.
72-
return fs.ensureDir(dotVscodeFolder).then(async () => {
73-
// Check to see if tasks.json exists.
74-
return fs.pathExists(tasksJsonPath);
75-
}).then(async tasksJsonExists => {
76-
// Enable addTasksJson if it does not exist.
77-
return addTasksJsonIfNecessary(generator, {addTasksJson: !tasksJsonExists});
78-
}).then(() => {
79-
const isWebProject = generator.hasWebServerDependency();
80-
const launchJson: string = generator.createLaunchJson(isWebProject);
81-
82-
// jsonc-parser's parse function parses a JSON string with comments into a JSON object. However, this removes the comments.
83-
return parse(launchJson);
84-
});
64+
async provideDebugConfigurations(folder: vscode.WorkspaceFolder | undefined, token?: vscode.CancellationToken): Promise<vscode.DebugConfiguration[]> {
65+
66+
if (!folder || !folder.uri) {
67+
vscode.window.showErrorMessage("Cannot create .NET debug configurations. No workspace folder was selected.");
68+
return [];
69+
}
70+
71+
if (!this.server.isRunning()) {
72+
vscode.window.showErrorMessage("Cannot create .NET debug configurations. The OmniSharp server is still initializing or has exited unexpectedly.");
73+
return [];
74+
}
75+
76+
try
77+
{
78+
let hasWorkspaceMatches : boolean = await this.checkWorkspaceInformationMatchesWorkspaceFolder(folder);
79+
if (!hasWorkspaceMatches) {
80+
vscode.window.showErrorMessage(`Cannot create .NET debug configurations. The active C# project is not within folder '${folder.uri.fsPath}'.`);
81+
return [];
82+
}
83+
84+
let info: WorkspaceInformationResponse = await serverUtils.requestWorkspaceInformation(this.server);
85+
86+
const generator = new AssetGenerator(info, folder);
87+
if (generator.hasExecutableProjects()) {
88+
89+
if (!await generator.selectStartupProject())
90+
{
91+
return [];
8592
}
93+
94+
// Make sure .vscode folder exists, addTasksJsonIfNecessary will fail to create tasks.json if the folder does not exist.
95+
await fs.ensureDir(generator.vscodeFolder);
96+
97+
// Add a tasks.json
98+
const buildOperations : AssetOperations = await getBuildOperations(generator);
99+
await addTasksJsonIfNecessary(generator, buildOperations);
86100

101+
const isWebProject = generator.hasWebServerDependency();
102+
const launchJson: string = generator.createLaunchJson(isWebProject);
103+
104+
// jsonc-parser's parse function parses a JSON string with comments into a JSON object. However, this removes the comments.
105+
return parse(launchJson);
106+
107+
} else {
87108
// Error to be caught in the .catch() below to write default C# configurations
88109
throw new Error("Does not contain .NET Core projects.");
89-
});
90-
}).catch((err) => {
110+
}
111+
}
112+
catch
113+
{
91114
// Provider will always create an launch.json file. Providing default C# configurations.
92115
// jsonc-parser's parse to convert to JSON object without comments.
93116
return [
94-
parse(createLaunchConfiguration(
95-
"${workspaceFolder}/bin/Debug/<insert-target-framework-here>/<insert-project-name-here>.dll",
96-
'${workspaceFolder}')),
97-
parse(createWebLaunchConfiguration(
98-
"${workspaceFolder}/bin/Debug/<insert-target-framework-here>/<insert-project-name-here>.dll",
99-
'${workspaceFolder}')),
117+
createFallbackLaunchConfiguration(),
100118
parse(createAttachConfiguration())
101119
];
102-
});
120+
}
103121
}
104122

105123
/**
@@ -135,13 +153,28 @@ export class CSharpConfigurationProvider implements vscode.DebugConfigurationPro
135153
*/
136154
resolveDebugConfiguration(folder: vscode.WorkspaceFolder | undefined, config: vscode.DebugConfiguration, token?: vscode.CancellationToken): vscode.ProviderResult<vscode.DebugConfiguration> {
137155

138-
// read from envFile and set config.env
139-
if (config.envFile) {
140-
config = this.parseEnvFile(config.envFile.replace(/\${workspaceFolder}/g, folder.uri.fsPath), config);
156+
if (!config.type)
157+
{
158+
// If the config doesn't look functional force VSCode to open a configuration file https://github.com/Microsoft/vscode/issues/54213
159+
return null;
160+
}
161+
162+
if (config.request === "launch")
163+
{
164+
if (!config.cwd && !config.pipeTransport) {
165+
config.cwd = "${workspaceFolder}";
166+
}
167+
if (!config.internalConsoleOptions) {
168+
config.internalConsoleOptions = "openOnSessionStart";
169+
}
170+
171+
// read from envFile and set config.env
172+
if (config.envFile) {
173+
config = this.parseEnvFile(config.envFile.replace(/\${workspaceFolder}/g, folder.uri.fsPath), config);
174+
}
141175
}
142176

143-
// If the config looks functional return it, otherwise force VSCode to open a configuration file https://github.com/Microsoft/vscode/issues/54213
144-
return config && config.type ? config : null;
177+
return config;
145178
}
146179

147180
private static async showFileWarningAsync(message: string, fileName: string) {

src/omnisharp/protocol.ts

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -778,31 +778,3 @@ export function findExecutableMSBuildProjects(projects: MSBuildProject[]) {
778778

779779
return result;
780780
}
781-
782-
export function findExecutableProjectJsonProjects(projects: DotNetProject[], configurationName: string) {
783-
let result: DotNetProject[] = [];
784-
785-
projects.forEach(project => {
786-
project.Configurations.forEach(configuration => {
787-
if (configuration.Name === configurationName && configuration.EmitEntryPoint === true) {
788-
if (project.Frameworks.length > 0) {
789-
result.push(project);
790-
}
791-
}
792-
});
793-
});
794-
795-
return result;
796-
}
797-
798-
export function containsDotNetCoreProjects(workspaceInfo: WorkspaceInformationResponse) {
799-
if (workspaceInfo.DotNet && findExecutableProjectJsonProjects(workspaceInfo.DotNet.Projects, 'Debug').length > 0) {
800-
return true;
801-
}
802-
803-
if (workspaceInfo.MsBuild && findExecutableMSBuildProjects(workspaceInfo.MsBuild.Projects).length > 0) {
804-
return true;
805-
}
806-
807-
return false;
808-
}

src/omnisharp/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ export class OmniSharpServer {
501501
public async makeRequest<TResponse>(command: string, data?: any, token?: CancellationToken): Promise<TResponse> {
502502

503503
if (!this.isRunning()) {
504-
return Promise.reject<TResponse>('server has been stopped or not started');
504+
return Promise.reject<TResponse>('OmniSharp server is not running.');
505505
}
506506

507507
let startTime: number;

src/tools/OptionsSchema.json

Lines changed: 17 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -176,35 +176,26 @@
176176
},
177177
"LaunchBrowserPlatformOptions": {
178178
"type": "object",
179+
"required": [ "command" ],
179180
"properties": {
180181
"command": {
181182
"type": "string",
182-
"description": "The command to execute for launching the web browser",
183+
"description": "The executable which will start the web browser",
183184
"default": "open"
184185
},
185186
"args": {
186187
"type": "string",
187-
"description": "The arguments to pass to the command to open the browser. Use ${auto-detect-url} to automatically use the address the server is listening to",
188+
"description": "The arguments to pass to the command to open the browser. Use ${auto-detect-url} to automatically use the address the server is listening to.",
188189
"default": "${auto-detect-url}"
189190
}
190191
}
191192
},
192193
"LaunchBrowser": {
193194
"type": "object",
194-
"description": "Describes options to launch a web browser as part of launch",
195+
"required": [ "enabled" ],
196+
"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.",
195197
"default": {
196-
"enabled": true,
197-
"args": "${auto-detect-url}",
198-
"windows": {
199-
"command": "cmd.exe",
200-
"args": "/C start ${auto-detect-url}"
201-
},
202-
"osx": {
203-
"command": "open"
204-
},
205-
"linux": {
206-
"command": "xdg-open"
207-
}
198+
"enabled": true
208199
},
209200
"properties": {
210201
"enabled": {
@@ -213,39 +204,29 @@
213204
"default": true
214205
},
215206
"args": {
216-
"anyOf": [
217-
{
218-
"type": "array",
219-
"description": "Command line arguments passed to the program.",
220-
"items": {
221-
"type": "string"
222-
},
223-
"default": []
224-
},
225-
{
226-
"type": "string",
227-
"description": "Stringified version of command line arguments passed to the program.",
228-
"default": ""
229-
}
230-
]
207+
"type": "string",
208+
"description": "The arguments to pass to the command to open the browser. This is used only if the platform-specific element (`osx`, `linux` or `windows`) doesn't specify a value for `args`. Use ${auto-detect-url} to automatically use the address the server is listening to.",
209+
"default": "${auto-detect-url}"
231210
},
232211
"osx": {
233212
"$ref": "#/definitions/LaunchBrowserPlatformOptions",
234-
"description": "OSX-specific web launch configuration options",
213+
"description": "OSX-specific web launch configuration options. By default, this will start the browser using `open`.",
235214
"default": {
236-
"command": "open"
215+
"command": "open",
216+
"args": "${auto-detect-url}"
237217
}
238218
},
239219
"linux": {
240220
"$ref": "#/definitions/LaunchBrowserPlatformOptions",
241-
"description": "Linux-specific web launch configuration options",
221+
"description": "Linux-specific web launch configuration options. By default, this will start the browser using `xdg-open`.",
242222
"default": {
243-
"command": "xdg-open"
223+
"command": "xdg-open",
224+
"args": "${auto-detect-url}"
244225
}
245226
},
246227
"windows": {
247228
"$ref": "#/definitions/LaunchBrowserPlatformOptions",
248-
"description": "Windows-specific web launch configuration options",
229+
"description": "Windows-specific web launch configuration options. By default, this will start the browser using `cmd /c start`.",
249230
"default": {
250231
"command": "cmd.exe",
251232
"args": "/C start ${auto-detect-url}"
@@ -295,18 +276,7 @@
295276
"$ref": "#/definitions/LaunchBrowser",
296277
"description": "Describes options to launch a web browser as part of launch",
297278
"default": {
298-
"enabled": true,
299-
"args": "${auto-detect-url}",
300-
"windows": {
301-
"command": "cmd.exe",
302-
"args": "/C start ${auto-detect-url}"
303-
},
304-
"osx": {
305-
"command": "open"
306-
},
307-
"linux": {
308-
"command": "xdg-open"
309-
}
279+
"enabled": true
310280
}
311281
},
312282
"env": {

src/tools/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
OptionsSchema.json defines the type for Launch/Attach options.
33

44
# GenerateOptionsSchema
5-
If there are any modifications to the OptionsSchema.json file. Please run `gulp generateOptionsSchema` at the repo root.
5+
If there are any modifications to the OptionsSchema.json file. Please run `npm run gulp generateOptionsSchema` at the repo root.
66
This will call GenerateOptionsSchema and update the package.json file.
77

88
### Important notes:

0 commit comments

Comments
 (0)