diff --git a/src/assets.ts b/src/assets.ts new file mode 100644 index 0000000000..d1749bb19d --- /dev/null +++ b/src/assets.ts @@ -0,0 +1,282 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; +import {OmnisharpServer} from './omnisharpServer'; +import * as serverUtils from './omnisharpUtils'; +import * as protocol from './protocol.ts' + +interface DebugConfiguration { + name: string, + type: string, + request: string, +} + +interface ConsoleLaunchConfiguration extends DebugConfiguration { + preLaunchTask: string, + program: string, + args: string[], + cwd: string, + stopAtEntry: boolean +} + +interface CommandLine { + command: string, + args?: string +} + +interface LaunchBrowserConfiguration { + enabled: boolean, + args: string, + windows?: CommandLine, + osx: CommandLine, + linux: CommandLine +} + +interface WebLaunchConfiguration extends ConsoleLaunchConfiguration { + launchBrowser: LaunchBrowserConfiguration +} + +interface AttachConfiguration extends DebugConfiguration { + processName: string +} + +interface Paths { + vscodeFolder: string; + tasksJsonPath: string; + launchJsonPath: string; +} + +function getPaths(): Paths { + const vscodeFolder = path.join(vscode.workspace.rootPath, '.vscode'); + + return { + vscodeFolder: vscodeFolder, + tasksJsonPath: path.join(vscodeFolder, 'tasks.json'), + launchJsonPath: path.join(vscodeFolder, 'launch.json') + } +} + +interface Operations { + addTasksJson?: boolean, + updateTasksJson?: boolean, + addLaunchJson?: boolean +} + +function hasOperations(operations: Operations) { + return operations.addLaunchJson || + operations.updateTasksJson || + operations.addLaunchJson; +} + +function getOperations() { + const paths = getPaths(); + + return getBuildOperations(paths.tasksJsonPath).then(operations => + getLaunchOperations(paths.launchJsonPath, operations)); +} + +function getBuildOperations(tasksJsonPath: string) { + return new Promise((resolve, reject) => { + return fs.existsAsync(tasksJsonPath).then(exists => { + if (exists) { + fs.readFileAsync(tasksJsonPath).then(buffer => { + const text = buffer.toString(); + const tasksJson: tasks.TaskConfiguration = JSON.parse(text); + const buildTask = tasksJson.tasks.find(td => td.taskName === 'build'); + + resolve({ updateTasksJson: (buildTask === undefined) }); + }); + } + else { + resolve({ addTasksJson: true }); + } + }); + }); +} + +function getLaunchOperations(launchJsonPath: string, operations: Operations) { + return new Promise((resolve, reject) => { + return fs.existsAsync(launchJsonPath).then(exists => { + if (exists) { + resolve(operations); + } + else { + operations.addLaunchJson = true; + resolve(operations); + } + }); + }); +} + +function promptToAddAssets() { + return new Promise((resolve, reject) => { + const item = { title: 'Yes' } + + vscode.window.showInformationMessage('Required assets to build and debug are missing from your project. Add them?', item).then(selection => { + return selection + ? resolve(true) + : resolve(false); + }); + }); +} + +function createLaunchConfiguration(targetFramework: string, executableName: string): ConsoleLaunchConfiguration { + return { + name: '.NET Core Launch (console)', + type: 'coreclr', + request: 'launch', + preLaunchTask: 'build', + program: '${workspaceRoot}/bin/Debug/' + targetFramework + '/'+ executableName, + args: [], + cwd: '${workspaceRoot}', + stopAtEntry: false + } +} + +function createWebLaunchConfiguration(targetFramework: string, executableName: string): WebLaunchConfiguration { + return { + name: '.NET Core Launch (web)', + type: 'coreclr', + request: 'launch', + preLaunchTask: 'build', + program: '${workspaceRoot}/bin/Debug/' + targetFramework + '/'+ executableName, + args: [], + cwd: '${workspaceRoot}', + stopAtEntry: false, + launchBrowser: { + enabled: true, + args: '${auto-detect-url}', + windows: { + command: 'cmd.exe', + args: '/C start ${auto-detect-url}' + }, + osx: { + command: 'open' + }, + linux: { + command: 'xdg-open' + } + } + } +} + +function createAttachConfiguration(): AttachConfiguration { + return { + name: '.NET Core Attach', + type: 'coreclr', + request: 'attach', + processName: '' + } +} + +function createLaunchJson(targetFramework: string, executableName: string): any { + return { + version: '0.2.0', + configurations: [ + createLaunchConfiguration(targetFramework, executableName), + createWebLaunchConfiguration(targetFramework, executableName), + createAttachConfiguration() + ] + } +} + +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 addTasksJsonIfNecessary(info: protocol.DotNetWorkspaceInformation, paths: Paths, operations: Operations) { + return new Promise((resolve, reject) => { + if (!operations.addTasksJson) { + return resolve(); + } + + const tasksJson = createTasksConfiguration(); + const tasksJsonText = JSON.stringify(tasksJson, null, ' '); + + return fs.writeFileAsync(paths.tasksJsonPath, tasksJsonText); + }); +} + +function addLaunchJsonIfNecessary(info: protocol.DotNetWorkspaceInformation, paths: Paths, operations: Operations) { + return new Promise((resolve, reject) => { + if (!operations.addLaunchJson) { + return resolve(); + } + + let targetFramework = ''; + let executableName = ''; + + let projectWithEntryPoint = info.Projects.find(project => project.EmitEntryPoint === true); + + if (projectWithEntryPoint) { + targetFramework = projectWithEntryPoint.TargetFramework.ShortName; + executableName = path.basename(projectWithEntryPoint.CompilationOutputAssemblyFile); + } + + const launchJson = createLaunchJson(targetFramework, executableName); + const launchJsonText = JSON.stringify(launchJson, null, ' '); + + return fs.writeFileAsync(paths.launchJsonPath, launchJsonText); + }); +} + +export function addAssetsIfNecessary(server: OmnisharpServer) { + 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; + } + + return serverUtils.requestWorkspaceInformation(server).then(info => { + // If there are no .NET Core projects, we won't bother offering to add assets. + if ('DotNet' in info && info.DotNet.Projects.length > 0) { + return getOperations().then(operations => { + if (!hasOperations(operations)) { + return; + } + + promptToAddAssets().then(addAssets => { + if (!addAssets) { + return; + } + + const paths = getPaths(); + + return fs.ensureDirAsync(paths.vscodeFolder).then(() => { + return Promise.all([ + addTasksJsonIfNecessary(info.DotNet, paths, operations), + addLaunchJsonIfNecessary(info.DotNet, paths, operations) + ]); + }); + }); + }); + } + }); +} \ No newline at end of file diff --git a/src/omnisharpMain.ts b/src/omnisharpMain.ts index c41261a359..d31e1d08ca 100644 --- a/src/omnisharpMain.ts +++ b/src/omnisharpMain.ts @@ -23,7 +23,7 @@ import {StdioOmnisharpServer} from './omnisharpServer'; import forwardChanges from './features/changeForwarding'; import reportStatus from './features/omnisharpStatus'; import {installCoreClrDebug} from './coreclr-debug'; -import {promptToAddBuildTaskIfNecessary} from './tasks'; +import {addAssetsIfNecessary} from './assets'; import * as vscode from 'vscode'; import TelemetryReporter from 'vscode-extension-telemetry'; @@ -74,6 +74,11 @@ export function activate(context: vscode.ExtensionContext): any { disposables.push(registerCommands(server, context.extensionPath)); disposables.push(reportStatus(server)); + + disposables.push(server.onServerStart(() => { + // Update or add tasks.json and launch.json + addAssetsIfNecessary(server); + })); // read and store last solution or folder path disposables.push(server.onBeforeServerStart(path => context.workspaceState.update('lastSolutionPathOrFolder', path))); @@ -86,9 +91,6 @@ export function activate(context: vscode.ExtensionContext): any { 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, reporter); diff --git a/src/omnisharpServer.ts b/src/omnisharpServer.ts index ba225504f4..a87c5e5e5a 100644 --- a/src/omnisharpServer.ts +++ b/src/omnisharpServer.ts @@ -268,8 +268,8 @@ export abstract class OmnisharpServer { this._serverProcess = value.process; this._requestDelays = {}; this._fireEvent(Events.StdOut, `[INFO] Started OmniSharp from '${value.command}' with process id ${value.process.pid}...\n`); - this._fireEvent(Events.ServerStart, solutionPath); this._setState(ServerState.Started); + this._fireEvent(Events.ServerStart, solutionPath); return this._doConnect(); }).then(_ => { this._processQueue(); diff --git a/src/protocol.ts b/src/protocol.ts index 384bd1751d..fa0835fef7 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -268,12 +268,20 @@ export interface DotNetWorkspaceInformation { export interface DotNetProject { Path: string; Name: string; + TargetFramework: DotNetFramework; CompilationOutputPath: string; CompilationOutputAssemblyFile: string; CompilationOutputPdbFile: string; + EmitEntryPoint?: boolean; SourceFiles: string[]; } +export interface DotNetFramework { + Name: string; + FriendlyName: string; + ShortName: string; +} + export interface RenameRequest extends Request { RenameTo: string; WantsTextChanges?: boolean; diff --git a/src/tasks.ts b/src/tasks.ts deleted file mode 100644 index 8bdcf70c86..0000000000 --- a/src/tasks.ts +++ /dev/null @@ -1,91 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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