diff --git a/package.json b/package.json index be01f308c9..410ace4e70 100644 --- a/package.json +++ b/package.json @@ -104,11 +104,6 @@ "command": "dotnet.restore", "title": "Restore Packages", "category": "dotnet" - }, - { - "command": "csharp.addTasksJson", - "title": "Add tasks.json (if missing)", - "category": "Debugger" } ], "keybindings": [ diff --git a/src/features/commands.ts b/src/features/commands.ts index 6e760188f7..d734afd16b 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -23,9 +23,8 @@ export default function registerCommands(server: OmnisharpServer, extensionPath: let d5 = vscode.commands.registerCommand('o.execute-last-command', () => dnxExecuteLastCommand(server)); let d6 = vscode.commands.registerCommand('o.showOutput', () => server.getChannel().show(vscode.ViewColumn.Three)); let d7 = vscode.commands.registerCommand('dotnet.restore', () => dotnetRestore(server)); - let d8 = vscode.commands.registerCommand('csharp.addTasksJson', () => addTasksJson(server, extensionPath)); - return vscode.Disposable.from(d1, d2, d3, d4, d5, d6, d7, d8); + return vscode.Disposable.from(d1, d2, d3, d4, d5, d6, d7); } function pickProjectAndStart(server: OmnisharpServer) { @@ -198,57 +197,4 @@ function getFolderPath(fileOrFolderPath: string): Promise { ? path.dirname(fileOrFolderPath) : fileOrFolderPath; }); -} - -function getExpectedVsCodeFolderPath(solutionPathOrFolder: string): Promise { - return getFolderPath(solutionPathOrFolder).then(folder => - path.join(folder, '.vscode')); -} - -export function addTasksJson(server: OmnisharpServer, extensionPath: string) { - return new Promise((resolve, reject) => { - if (!server.isRunning()) { - return reject('OmniSharp is not running.'); - } - - let solutionPathOrFolder = server.getSolutionPathOrFolder(); - if (!solutionPathOrFolder) - { - return reject('No solution or folder open.'); - } - - return getExpectedVsCodeFolderPath(solutionPathOrFolder).then(vscodeFolder => { - let tasksJsonPath = path.join(vscodeFolder, 'tasks.json'); - - return fs.existsAsync(tasksJsonPath).then(e => { - if (e) { - return vscode.window.showInformationMessage(`${tasksJsonPath} already exists.`).then(_ => { - return resolve(tasksJsonPath); - }); - } - else { - let templatePath = path.join(extensionPath, 'template-tasks.json'); - - return fs.existsAsync(templatePath).then(e => { - if (!e) { - return reject('Could not find template-tasks.json file in extension.'); - } - - return fs.ensureDirAsync(vscodeFolder).then(ok => { - if (ok) { - let oldFile = fs.createReadStream(templatePath); - let newFile = fs.createWriteStream(tasksJsonPath); - oldFile.pipe(newFile); - - return resolve(tasksJsonPath); - } - else { - return reject(`Could not create ${vscodeFolder} directory.`); - } - }); - }); - } - }); - }); - }); } \ No newline at end of file diff --git a/src/omnisharpMain.ts b/src/omnisharpMain.ts index 9765056276..6c47afc75f 100644 --- a/src/omnisharpMain.ts +++ b/src/omnisharpMain.ts @@ -22,45 +22,46 @@ import registerCommands from './features/commands'; import {StdioOmnisharpServer} from './omnisharpServer'; import forwardChanges from './features/changeForwarding'; import reportStatus from './features/omnisharpStatus'; -import {Disposable, ExtensionContext, DocumentSelector, languages} from 'vscode'; import {installCoreClrDebug} from './coreclr-debug'; +import {promptToAddBuildTaskIfNecessary} from './tasks'; +import * as vscode from 'vscode'; -export function activate(context: ExtensionContext): any { +export function activate(context: vscode.ExtensionContext): any { - const _selector: DocumentSelector = { + const _selector: vscode.DocumentSelector = { language: 'csharp', scheme: 'file' // only files from disk }; const server = new StdioOmnisharpServer(); const advisor = new Advisor(server); // create before server is started - const disposables: Disposable[] = []; - const localDisposables: Disposable[] = []; + const disposables: vscode.Disposable[] = []; + const localDisposables: vscode.Disposable[] = []; disposables.push(server.onServerStart(() => { // register language feature provider on start - localDisposables.push(languages.registerDefinitionProvider(_selector, new DefinitionProvider(server))); - localDisposables.push(languages.registerCodeLensProvider(_selector, new CodeLensProvider(server))); - localDisposables.push(languages.registerDocumentHighlightProvider(_selector, new DocumentHighlightProvider(server))); - localDisposables.push(languages.registerDocumentSymbolProvider(_selector, new DocumentSymbolProvider(server))); - localDisposables.push(languages.registerReferenceProvider(_selector, new ReferenceProvider(server))); - localDisposables.push(languages.registerHoverProvider(_selector, new HoverProvider(server))); - localDisposables.push(languages.registerRenameProvider(_selector, new RenameProvider(server))); - localDisposables.push(languages.registerDocumentRangeFormattingEditProvider(_selector, new FormatProvider(server))); - localDisposables.push(languages.registerOnTypeFormattingEditProvider(_selector, new FormatProvider(server), '}', ';')); - localDisposables.push(languages.registerCompletionItemProvider(_selector, new CompletionItemProvider(server), '.', '<')); - localDisposables.push(languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(server))); - localDisposables.push(languages.registerSignatureHelpProvider(_selector, new SignatureHelpProvider(server), '(', ',')); + localDisposables.push(vscode.languages.registerDefinitionProvider(_selector, new DefinitionProvider(server))); + localDisposables.push(vscode.languages.registerCodeLensProvider(_selector, new CodeLensProvider(server))); + localDisposables.push(vscode.languages.registerDocumentHighlightProvider(_selector, new DocumentHighlightProvider(server))); + localDisposables.push(vscode.languages.registerDocumentSymbolProvider(_selector, new DocumentSymbolProvider(server))); + localDisposables.push(vscode.languages.registerReferenceProvider(_selector, new ReferenceProvider(server))); + localDisposables.push(vscode.languages.registerHoverProvider(_selector, new HoverProvider(server))); + localDisposables.push(vscode.languages.registerRenameProvider(_selector, new RenameProvider(server))); + localDisposables.push(vscode.languages.registerDocumentRangeFormattingEditProvider(_selector, new FormatProvider(server))); + localDisposables.push(vscode.languages.registerOnTypeFormattingEditProvider(_selector, new FormatProvider(server), '}', ';')); + localDisposables.push(vscode.languages.registerCompletionItemProvider(_selector, new CompletionItemProvider(server), '.', '<')); + localDisposables.push(vscode.languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(server))); + localDisposables.push(vscode.languages.registerSignatureHelpProvider(_selector, new SignatureHelpProvider(server), '(', ',')); const codeActionProvider = new CodeActionProvider(server); localDisposables.push(codeActionProvider); - localDisposables.push(languages.registerCodeActionsProvider(_selector, codeActionProvider)); + localDisposables.push(vscode.languages.registerCodeActionsProvider(_selector, codeActionProvider)); localDisposables.push(reportDiagnostics(server, advisor)); localDisposables.push(forwardChanges(server)); })); disposables.push(server.onServerStop(() => { // remove language feature providers on stop - Disposable.from(...localDisposables).dispose(); + vscode.Disposable.from(...localDisposables).dispose(); })); disposables.push(registerCommands(server, context.extensionPath)); @@ -71,14 +72,17 @@ export function activate(context: ExtensionContext): any { server.autoStart(context.workspaceState.get('lastSolutionPathOrFolder')); // stop server on deactivate - disposables.push(new Disposable(() => { + disposables.push(new vscode.Disposable(() => { advisor.dispose(); server.stop(); })); + // Check to see if there is a tasks.json with a "build" task and prompt the user to add it if missing. + promptToAddBuildTaskIfNecessary(); + // install coreclr-debug installCoreClrDebug(context); - + context.subscriptions.push(...disposables); } diff --git a/src/tasks.ts b/src/tasks.ts new file mode 100644 index 0000000000..8bdcf70c86 --- /dev/null +++ b/src/tasks.ts @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as fs from 'fs-extra-promise'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import * as tasks from 'vscode-tasks'; + +function promptToAddBuildTask(): Promise { + return new Promise((resolve, reject) => { + const item = { title: 'Yes' } + + vscode.window.showInformationMessage('Would you like to add a build task for your project?', item).then(selection => { + return selection + ? resolve(true) + : resolve(false); + }); + }); +} + +function createBuildTaskDescription(): tasks.TaskDescription { + return { + taskName: "build", + args: [], + isBuildCommand: true, + problemMatcher: "$msCompile" + }; +} + +function createTasksConfiguration(): tasks.TaskConfiguration { + return { + version: "0.1.0", + command: "dotnet", + isShellCommand: true, + args: [], + tasks: [ createBuildTaskDescription() ] + }; +} + +function writeTasksJson(tasksJsonPath: string, tasksConfig: tasks.TaskConfiguration) { + const tasksJsonText = JSON.stringify(tasksConfig, null, ' '); + fs.writeFileSync(tasksJsonPath, tasksJsonText); +} + +export function promptToAddBuildTaskIfNecessary() { + if (!vscode.workspace.rootPath) { + return; + } + + // If there is no project.json, we won't bother to prompt the user for tasks.json. + const projectJsonPath = path.join(vscode.workspace.rootPath, 'project.json'); + if (!fs.existsSync(projectJsonPath)) { + return; + } + + const vscodeFolder = path.join(vscode.workspace.rootPath, '.vscode'); + const tasksJsonPath = path.join(vscodeFolder, 'tasks.json'); + + fs.ensureDirAsync(vscodeFolder).then(() => { + fs.existsAsync(tasksJsonPath).then(exists => { + if (exists) { + fs.readFileAsync(tasksJsonPath).then(text => { + const fileText = text.toString(); + let tasksConfig: tasks.TaskConfiguration = JSON.parse(fileText); + let buildTask = tasksConfig.tasks.find(td => td.taskName === 'build'); + if (!buildTask) { + promptToAddBuildTask().then(shouldAdd => { + if (shouldAdd) { + buildTask = createBuildTaskDescription(); + tasksConfig.tasks.push(buildTask); + writeTasksJson(tasksJsonPath, tasksConfig); + } + }); + } + }); + } + else { + promptToAddBuildTask().then(shouldAdd => { + if (shouldAdd) { + const tasksConfig = createTasksConfiguration(); + writeTasksJson(tasksJsonPath, tasksConfig); + } + }); + } + }); + }); +} \ No newline at end of file diff --git a/src/typings/vscode-extension-telemetry.d.ts b/src/typings/vscode-extension-telemetry.d.ts index 4b2981b9b2..56fcd5b8a9 100644 --- a/src/typings/vscode-extension-telemetry.d.ts +++ b/src/typings/vscode-extension-telemetry.d.ts @@ -1,3 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + declare module 'vscode-extension-telemetry' { export default class TelemetryReporter { constructor(extensionId: string,extensionVersion: string, key: string); diff --git a/src/typings/vscode-tasks.d.ts b/src/typings/vscode-tasks.d.ts new file mode 100644 index 0000000000..25c76eaf61 --- /dev/null +++ b/src/typings/vscode-tasks.d.ts @@ -0,0 +1,291 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Copied from http://code.visualstudio.com/docs/editor/tasks_appendix + +declare module "vscode-tasks" { + export interface TaskConfiguration extends BaseTaskConfiguration { + + /** + * The configuration's version number + */ + version: string; + + /** + * Windows specific task configuration + */ + windows?: BaseTaskConfiguration; + + /** + * Mac specific task configuration + */ + osx?: BaseTaskConfiguration; + + /** + * Linux specific task configuration + */ + linux?: BaseTaskConfiguration; + } + + export interface BaseTaskConfiguration { + + /** + * The command to be executed. Can be an external program or a shell + * command. + */ + command: string; + + /** + * Specifies whether the command is a shell command and therefore must + * be executed in a shell interpreter (e.g. cmd.exe, bash, ...). + * + * Defaults to false if omitted. + */ + isShellCommand?: boolean; + + /** + * The command options used when the command is executed. Can be omitted. + */ + options?: CommandOptions; + + /** + * The arguments passed to the command. Can be omitted. + */ + args?: string[]; + + /** + * Controls whether the output view of the running tasks is brought to front or not. + * + * Valid values are: + * "always": bring the output window always to front when a task is executed. + * "silent": only bring it to front if no problem matcher is defined for the task executed. + * "never": never bring the output window to front. + * + * If omitted "always" is used. + */ + showOutput?: string; + + /** + * If set to false the task name is added as an additional argument to the + * command when executed. If set to true the task name is suppressed. If + * omitted false is used. + */ + suppressTaskName?: boolean; + + /** + * Some commands require that the task argument is highlighted with a special + * prefix (e.g. /t: for msbuild). This property can be used to control such + * a prefix. + */ + taskSelector?:string; + + /** + * The problem matcher to be used if a global command is executed (e.g. no tasks + * are defined). A tasks.json file can either contain a global problemMatcher + * property or a tasks property but not both. + */ + problemMatcher?: string | ProblemMatcher | (string | ProblemMatcher)[]; + + /** + * The configuration of the available tasks. A tasks.json file can either + * contain a global problemMatcher property or a tasks property but not both. + */ + tasks?: TaskDescription[]; + } + + /** + * Options to be passed to the external program or shell + */ + export interface CommandOptions { + + /** + * The current working directory of the executed program or shell. + * If omitted Ticino's current workspace root is used. + */ + cwd?: string; + + /** + * The environment of the executed program or shell. If omitted + * the parent process' environment is used. + */ + env?: { [key:string]:string; }; + } + + /** + * The description of a task. + */ + export interface TaskDescription { + + /** + * The task's name + */ + taskName: string; + + /** + * Additional arguments passed to the command when this task is + * executed. + */ + args?: string[]; + + /** + * Whether this task maps to the default build command. + */ + isBuildCommand?:boolean; + + /** + * Whether this task maps to the default test command. + */ + isTestCommand?: boolean; + + /** + * Controls whether the output view of the running tasks is brought to front or not. + * See BaseTaskConfiguration#showOutput for details. + */ + showOutput?: string; + + /** + * See BaseTaskConfiguration#suppressTaskName for details. + */ + suppressTaskName?: boolean; + + /** + * The problem matcher(s) to use to capture problems in the tasks + * output. + */ + problemMatcher?: string | ProblemMatcher | (string | ProblemMatcher)[]; + } + + /** + * A description of a problem matcher that detects problems + * in build output. + */ + export interface ProblemMatcher { + + /** + * The name of a base problem matcher to use. If specified the + * base problem matcher will be used as a template and properties + * specified here will replace properties of the base problem + * matcher + */ + base?: string; + + /** + * The owner of the produced VS Code problem. This is typically + * the identifier of a VS Code language service if the problems are + * to be merged with the one produced by the language service + * or 'external'. Defaults to 'external' if omitted. + */ + owner?: string; + + /** + * The severity of the VS Code problem produced by this problem matcher. + * + * Valid values are: + * "error": to produce errors. + * "warning": to produce warnings. + * "info": to produce infos. + * + * The value is used if a pattern doesn't specify a severity match group. + * Defaults to "error" if omitted. + */ + severity?: string; + + /** + * Defines how filename reported in a problem pattern + * should be read. Valid values are: + * - "absolute": the filename is always treated absolute. + * - "relative": the filename is always treated relative to + * the current working directory. This is the default. + * - ["relative", "path value"]: the filename is always + * treated relative to the given path value. + */ + fileLocation?: string | string[]; + + /** + * The name of a predefined problem pattern, the inline definintion + * of a problem pattern or an array of problem patterns to match + * problems spread over multiple lines. + */ + pattern?: string | ProblemPattern | ProblemPattern[]; + } + + export interface ProblemPattern { + + /** + * The regular expression to find a problem in the console output of an + * executed task. + */ + regexp: string; + + /** + * The match group index of the filename. + * If omitted 1 is used. + */ + file?: number; + + /** + * The match group index of the problems's location. Valid location + * patterns are: (line), (line,column) and (startLine,startColumn,endLine,endColumn). + * If omitted the line and column properties are used. + */ + location?: number; + + /** + * The match group index of the problem's line in the source file. + * + * Defaults to 2. + */ + line?: number; + + /** + * The match group index of the problem's column in the source file. + * + * Defaults to 3. + */ + column?: number; + + /** + * The match group index of the problem's end line in the source file. + * + * Defaults to undefined. No end line is captured. + */ + endLine?: number; + + /** + * The match group index of the problem's end column in the source file. + * + * Defaults to undefined. No end column is captured. + */ + endColumn?: number; + + /** + * The match group index of the problem's severity. + * + * Defaults to undefined. In this case the problem matcher's severity + * is used. + */ + severity?: number; + + /** + * The match group index of the problems's code. + * + * Defaults to undefined. No code is captured. + */ + code?: number; + + /** + * The match group index of the message. If omitted it defaults + * to 4 if location is specified. Otherwise it defaults to 5. + */ + message?: number; + + /** + * Specifies if the last pattern in a multi line problem matcher should + * loop as long as it does match a line consequently. Only valid on the + * last problem pattern in a multi line problem matcher. + */ + loop?: boolean; + } +} \ No newline at end of file diff --git a/template-tasks.json b/template-tasks.json deleted file mode 100644 index 24e89ccd3e..0000000000 --- a/template-tasks.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "version": "0.1.0", - "command": "dotnet", - "isShellCommand": true, - "args": [], - "tasks": [ - { - "taskName": "build", - "args": [ ], - "isBuildCommand": true, - "problemMatcher": "$msCompile" - } - ] -}