diff --git a/.gitignore b/.gitignore index 10415c3b8e..566b7d5501 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ bin node_modules out +.omnisharp *.vsix diff --git a/.travis.yml b/.travis.yml index b26935e498..4b7b2137f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,24 @@ sudo: false language: node_js node_js: - - "5.1" +- "5.1" os: -osx -linux install: - - npm install \ No newline at end of file +- npm install +- npm install -g vsce +- vsce package + +deploy: + provider: releases + api_key: + secure: T6OzDRQEXpIsIKM2V+X+YgSa1vPDXDdoRBOV39BCDkKv7fDH5rTHs/hVtrwIfLvLVAtJq4kDm6hSVAFLpni1nUHzx2hFCi31CCWPlqhDo31v0CMD6pMccUlCFi2iul3T3EAiT4HmoSEKoCLET8uLIKaYoQ6wc+D11z55kM9DFtv/fGwfxL8FWvbNHYaGc3KNvNOwoMcOc8VpXXGQ1Rtc1IAx62G6qS8V6Yn89sUev/AxT/wcU/wmzNW5UgdAUgnJUFbSumwzfFYFJUdkUSP5iLxjlAJJSNHjHzN5wNX1bIMfSv5DJn1P15ev5OyfjJ+EmjFFmOTJiPlgver/scrZzhrv3kvBYULI8vAAgRWde5CgESGJqv5gEIj3oSICv+2MPmB+Kjr/79AfXrCvORc3an9UASyfgQvamfIoboFLNLZTIe/66oH7UmSJ2rtcaYEKMWHo7qq9cI4yFX6SexwqQehrGPr+QFbjpxFFAhs6aJEFGGgw4i/yheEP6Z7mrKx5wqScVfSLz1iBDmPAkB8pSFhXPk4XpYPjZsCI/wzLERYZ1gBweAKosnMt2anf0Eu1USUo1nXvFmsKTH6lBdBIthVW2va2DCgW+aWQj+j/G670UXT7BoHkcx0GaOzhPc6HeS1ovA9/kX5ZBaslPNAOmGycmGJAuNs2sSvFJf9JZY4= + file_glob: true + file: "*.vsix" + skip_cleanup: true + on: + repo: OmniSharp/omnisharp-vscode + tags: true diff --git a/.vscodeignore b/.vscodeignore index 7f7db592ee..485aee7a51 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -5,6 +5,7 @@ src/** **/*.map .vscode/** +.omnisharp/** coreclr-debug/debugAdapters/** coreclr-debug/bin/** diff --git a/coreclr-debug/.gitignore b/coreclr-debug/.gitignore index 0541f8ebe1..60944094f9 100644 --- a/coreclr-debug/.gitignore +++ b/coreclr-debug/.gitignore @@ -2,4 +2,6 @@ bin obj project.lock.json debugAdapters -install.log \ No newline at end of file +install.log +extension.log +project.json diff --git a/coreclr-debug/project.json b/coreclr-debug/project.json deleted file mode 100644 index 099df9d0d6..0000000000 --- a/coreclr-debug/project.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "dummy", - "compilationOptions": { - "emitEntryPoint": true - }, - "dependencies": { - "Microsoft.VisualStudio.clrdbg": "14.0.25109-preview-2865786", - "Microsoft.VisualStudio.clrdbg.MIEngine": "14.0.30309-preview-2", - "Microsoft.VisualStudio.OpenDebugAD7": "1.0.20308-preview-1", - "NETStandard.Library": "1.0.0-rc3-23829", - "Newtonsoft.Json": "7.0.1", - "Microsoft.VisualStudio.Debugger.Interop.Portable": "1.0.1", - "System.Collections.Specialized": "4.0.1-rc3-23829", - "System.Collections.Immutable": "1.2.0-rc3-23829", - "System.Diagnostics.Process" : "4.1.0-rc3-23829", - "System.Diagnostics.StackTrace": "4.0.1-rc3-23829", - "System.Dynamic.Runtime": "4.0.11-rc3-23829", - "Microsoft.CSharp": "4.0.1-rc3-23829", - "System.Threading.Tasks.Dataflow": "4.6.0-rc3-23829", - "System.Threading.Thread": "4.0.0-rc3-23829", - "System.Xml.XDocument": "4.0.11-rc3-23829", - "System.Xml.XmlDocument": "4.0.1-rc3-23829", - "System.Xml.XmlSerializer": "4.0.11-rc3-23829", - "System.ComponentModel": "4.0.1-rc3-23829", - "System.ComponentModel.Annotations": "4.1.0-rc3-23829", - "System.ComponentModel.EventBasedAsync": "4.0.11-rc3-23829", - "System.Runtime.Serialization.Primitives": "4.1.0-rc3-23829", - "System.Net.Http": "4.0.1-rc3-23829" - }, - "frameworks": { - "dnxcore50": { } - } -} diff --git a/gulpfile.js b/gulpfile.js index 5f7e30266b..7ebcf0e682 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,70 +1,31 @@ -var gulp = require('gulp'); -var decompress = require('gulp-decompress'); -var tslint = require("gulp-tslint"); -var es = require('event-stream'); -var GitHub = require('github-releases'); -var tmp = require('tmp'); -var vfs = require('vinyl-fs'); -var del = require('del'); -var fs = require('fs'); -var path = require('path'); +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ -tmp.setGracefulCleanup(); +'use strict'; -function downloadOmnisharp(version) { - var result = es.through(); +const del = require('del'); +const gulp = require('gulp'); +const tslint = require('gulp-tslint'); +const vsce = require('vsce'); +const omnisharpDownload = require('./out/omnisharpDownload'); - function onError(err) { - result.emit('error', err); - } - - var repo = new GitHub({ - repo: 'OmniSharp/omnisharp-roslyn', - token: process.env['GITHUB_TOKEN'] - }); - - repo.getReleases({ tag_name: version }, function (err, releases) { - if (err) { return onError(err); } - if (!releases.length) { return onError(new Error('Release not found')); } - if (!releases[0].assets.length) { return onError(new Error('Assets not found')); } - - repo.downloadAsset(releases[0].assets[0], function (err, istream) { - if (err) { return onError(err); } - - tmp.file(function (err, tmpPath, fd, cleanupCallback) { - if (err) { return onError(err); } - - var ostream = fs.createWriteStream(null, { fd: fd }); - ostream.once('error', onError); - istream.once('error', onError); - ostream.once('finish', function () { - vfs.src(tmpPath).pipe(result); - }); - istream.pipe(ostream); - }); - }); - }); - - return result; -} - -gulp.task('omnisharp:clean', function () { - return del('bin'); +gulp.task('omnisharp:clean', () => { + return del('.omnisharp'); }); -gulp.task('omnisharp:fetch', ['omnisharp:clean'], function () { - return downloadOmnisharp('v1.6.7.9') - .pipe(decompress({strip: 1})) - .pipe(gulp.dest('bin')); +gulp.task('omnisharp:fetch', ['omnisharp:clean'], () => { + return omnisharpDownload.downloadOmnisharp(); }); -var allTypeScript = [ +const allTypeScript = [ 'src/**/*.ts', '!**/*.d.ts', '!**/typings**' ]; -var lintReporter = function (output, file, options) { +const lintReporter = (output, file, options) => { //emits: src/helloWorld.c:5:3: warning: implicit declaration of function ‘prinft’ var relativeBase = file.base.substring(file.cwd.length + 1).replace('\\', '/'); output.forEach(function(e) { @@ -73,48 +34,19 @@ var lintReporter = function (output, file, options) { }); }; -gulp.task('tslint', function () { +gulp.task('tslint', () => { gulp.src(allTypeScript) - .pipe(tslint({ - rulesDirectory: "node_modules/tslint-microsoft-contrib" - })) - .pipe(tslint.report(lintReporter, { - summarizeFailureOutput: false, - emitError: false - })) -}); - -gulp.task('omnisharp:fixscripts', ['omnisharp:fetch'], function () { - - var _fixes = Object.create(null); - _fixes['./bin/omnisharp.cmd'] = '@"%~dp0packages\\dnx-clr-win-x86.1.0.0-beta4\\bin\\dnx.exe" "%~dp0packages\\OmniSharp\\1.0.0\\root" run %*'; - _fixes['./bin/omnisharp'] = '#!/bin/bash\n\ -SOURCE="${BASH_SOURCE[0]}"\n\ -while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink\n\ - DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"\n\ - SOURCE="$(readlink "$SOURCE")"\n\ - [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located\n\ -done\n\ -DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"\n\ -export SET DNX_APPBASE="$DIR/packages/OmniSharp/1.0.0/root"\n\ -export PATH=/usr/local/bin:/Library/Frameworks/Mono.framework/Commands:$PATH # this is required for the users of the Homebrew Mono package\n\ -exec "$DIR/packages/dnx-mono.1.0.0-beta4/bin/dnx" "$DNX_APPBASE" run "$@"\n\ -\n'; - - var promises = Object.keys(_fixes).map(function (key) { - return new Promise(function(resolve, reject) { - fs.writeFile(path.join(__dirname, key), _fixes[key], function (err) { - if (err) { - reject(err); - } else { - resolve(); - } - }) - }); - }); - - return Promise.all(promises) + .pipe(tslint({ + rulesDirectory: "node_modules/tslint-microsoft-contrib" + })) + .pipe(tslint.report(lintReporter, { + summarizeFailureOutput: false, + emitError: false + })) }); +gulp.task('omnisharp', ['omnisharp:fetch']); -gulp.task('omnisharp', ['omnisharp:fixscripts']); \ No newline at end of file +gulp.task('package', () => { + vsce(['', '', 'package']); +}); \ No newline at end of file diff --git a/package.json b/package.json index 88b4656235..34c10ce38d 100644 --- a/package.json +++ b/package.json @@ -1,373 +1,365 @@ { - "name": "csharp", - "publisher": "ms-vscode", - "version": "0.3.8", - "description": "C# for Visual Studio Code (powered by OmniSharp).", - "displayName": "C#", - "author": "Microsoft Corporation", - "license": "SEE LICENSE IN RuntimeLicenses/license.txt", - "icon": "images/csharpIcon.png", - "categories": [ - "Debuggers", - "Languages", - "Linters", - "Snippets" - ], - "main": "./out/omnisharpMain", - "scripts": { - "postinstall": "gulp omnisharp && tsc" - }, - "dependencies": { - "run-in-terminal": "*", - "semver": "*", - "vscode-extension-telemetry": "0.0.4" - }, - "devDependencies": { - "vscode": "^0.10.1", - "del": "^2.0.2", - "event-stream": "^3.3.2", - "github-releases": "^0.3.0", - "gulp": "^3.8.9", - "gulp-decompress": "^1.2.0", - "tmp": "0.0.28", - "vinyl-fs": "^2.2.1", - "typescript": "^1.7.3", - "tslint": "^3.3.0", - "gulp-tslint": "^4.3.0", - "tslint-microsoft-contrib": "^2.0.0" - }, - "engines": { - "vscode": "^0.10.10" - }, - "activationEvents": [ - "onLanguage:csharp", - "onCommand:o.restart", - "onCommand:o.pickProjectAndStart", - "onCommand:o.restore", - "onCommand:o.execute", - "onCommand:o.showOutput", - "onCommand:o.execute-last-command", - "onCommand:dotnet.restore", - "onCommand:csharp.addTasksJson", - "workspaceContains:project.json" - ], - "contributes": { - "languages": [ - { - "id": "csharp", - "extensions": [ - ".cs", - ".csx" - ], - "aliases": [ - "C#", - "csharp" - ], - "configuration": "./csharp.configuration.json" - } - ], - "grammars": [ - { - "language": "csharp", - "scopeName": "source.cs", - "path": "./syntaxes/csharp.json" - } - ], - "jsonValidation": [ - { - "fileMatch": "project.json", - "url": "http://json.schemastore.org/project" - } - ], - "commands": [ - { - "command": "o.restart", - "title": "Restart OmniSharp", - "category": "OmniSharp" - }, - { - "command": "o.pickProjectAndStart", - "title": "Select Project", - "category": "OmniSharp" - }, - { - "command": "o.restore", - "title": "Restore Packages", - "category": "dnx" - }, - { - "command": "o.execute", - "title": "Run Command", - "category": "dnx" - }, - { - "command": "dotnet.restore", - "title": "Restore Packages", - "category": "dotnet" - }, - { - "command": "csharp.addTasksJson", - "title": "Add tasks.json (if missing)", - "category": "Debugger" - } + "name": "csharp", + "publisher": "ms-vscode", + "version": "1.0.0-rc2", + "description": "C# for Visual Studio Code (powered by OmniSharp).", + "displayName": "C#", + "author": "Microsoft Corporation", + "license": "SEE LICENSE IN RuntimeLicenses/license.txt", + "icon": "images/csharpIcon.png", + "categories": [ + "Debuggers", + "Languages", + "Linters", + "Snippets" + ], + "main": "./out/omnisharpMain", + "scripts": { + "postinstall": "tsc" + }, + "dependencies": { + "decompress": "^3.0.0", + "del": "^2.0.2", + "fs-extra-promise": "^0.3.1", + "github-releases": "^0.3.0", + "run-in-terminal": "*", + "semver": "*", + "vscode-debugprotocol": "^1.6.1", + "vscode-extension-telemetry": "0.0.4", + "tmp": "0.0.28", + "open": "*" + }, + "devDependencies": { + "gulp": "^3.9.1", + "gulp-tslint": "^4.3.0", + "tslint": "^3.3.0", + "tslint-microsoft-contrib": "^2.0.0", + "typescript": "^1.7.3", + "vscode": "^0.10.1", + "vsce": "^1.3.0" + }, + "engines": { + "vscode": "^0.10.10" + }, + "activationEvents": [ + "onLanguage:csharp", + "onCommand:o.restart", + "onCommand:o.pickProjectAndStart", + "onCommand:o.showOutput", + "onCommand:dotnet.restore", + "onCommand:csharp.downloadDebugger", + "workspaceContains:project.json" + ], + "contributes": { + "languages": [ + { + "id": "csharp", + "extensions": [ + ".cs", + ".csx" ], - "keybindings": [ - { - "command": "o.showOutput", - "key": "Ctrl+L L", - "mac": "Cmd+L L" - }, - { - "command": "o.execute", - "key": "Ctrl+L Shift+R", - "mac": "Cmd+L Shift+R" - }, - { - "command": "o.execute-last-command", - "key": "Ctrl+L R", - "mac": "Cmd+L R" - }, - { - "key": "shift+0", - "command": "^acceptSelectedSuggestion", - "when": "editorTextFocus && suggestWidgetVisible && editorLangId == 'csharp' && suggestionSupportsAcceptOnKey" - }, - { - "key": "shift+9", - "command": "^acceptSelectedSuggestion", - "when": "editorTextFocus && suggestWidgetVisible && editorLangId == 'csharp' && suggestionSupportsAcceptOnKey" - }, - { - "key": ".", - "command": "^acceptSelectedSuggestion", - "when": "editorTextFocus && suggestWidgetVisible && editorLangId == 'csharp' && suggestionSupportsAcceptOnKey" - } - ], - "snippets": [ - { - "language": "csharp", - "path": "./snippets/csharp.json" - } + "aliases": [ + "C#", + "csharp" ], - "debuggers": [ - { - "type": "coreclr", - "label": ".NET Core", - "enableBreakpointsFor": { "languageIds": [ "csharp" ] }, - - "program": "./coreclr-debug/debugAdapters/OpenDebugAD7", - "windows": { - "program": "./coreclr-debug/debugAdapters/OpenDebugAD7.exe" + "configuration": "./csharp.configuration.json" + } + ], + "grammars": [ + { + "language": "csharp", + "scopeName": "source.cs", + "path": "./syntaxes/csharp.json" + } + ], + "jsonValidation": [ + { + "fileMatch": "project.json", + "url": "http://json.schemastore.org/project" + } + ], + "commands": [ + { + "command": "o.restart", + "title": "Restart OmniSharp", + "category": "OmniSharp" + }, + { + "command": "o.pickProjectAndStart", + "title": "Select Project", + "category": "OmniSharp" + }, + { + "command": "dotnet.restore", + "title": "Restore Packages", + "category": "dotnet" + }, + { + "command": "csharp.downloadDebugger", + "title": "Download .NET Core Debugger", + "category": "Debug" + } + ], + "keybindings": [ + { + "command": "o.showOutput", + "key": "Ctrl+L L", + "mac": "Cmd+L L" + }, + { + "key": "shift+0", + "command": "^acceptSelectedSuggestion", + "when": "editorTextFocus && suggestWidgetVisible && editorLangId == 'csharp' && suggestionSupportsAcceptOnKey" + }, + { + "key": "shift+9", + "command": "^acceptSelectedSuggestion", + "when": "editorTextFocus && suggestWidgetVisible && editorLangId == 'csharp' && suggestionSupportsAcceptOnKey" + }, + { + "key": ".", + "command": "^acceptSelectedSuggestion", + "when": "editorTextFocus && suggestWidgetVisible && editorLangId == 'csharp' && suggestionSupportsAcceptOnKey" + } + ], + "snippets": [ + { + "language": "csharp", + "path": "./snippets/csharp.json" + } + ], + "debuggers": [ + { + "type": "coreclr", + "label": ".NET Core", + "enableBreakpointsFor": { + "languageIds": [ + "csharp" + ] + }, + "runtime": "node", + "runtimeArgs": [], + "program": "./out/coreclr-debug/proxy.js", + "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "configurationAttributes": { + "launch": { + "required": [ + "program", + "cwd" + ], + "properties": { + "program": { + "type": "string", + "description": "Path to the application dll or .NET Core host executable to launch. Example: '${workspaceRoot}/bin/Debug//' where:\n: (example: 'netstandard1.5') This is the name of the framework that the app is being built for. It is set in the project.json file.\n: (example: 'MyApp') The name of the project being debugged.", + "default": "${workspaceRoot}/bin/Debug//" + }, + "cwd": { + "type": "string", + "description": "Path to the working directory of the program being debugged. Default is the current workspace.", + "default": "${workspaceRoot}" + }, + "args": { + "type": "array", + "description": "Command line arguments passed to the program.", + "items": { + "type": "string" }, - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", - - "configurationAttributes": { - "launch": { - "required": [ "program", "cwd" ], + "default": [] + }, + "stopAtEntry": { + "type": "boolean", + "description": "If true, the debugger should stop at the entry point of the target.", + "default": false + }, + "launchBrowser": { + "type": "object", + "description": "Describes options to launch a web browser as part of launch", + "default": { + "enabled": true, + "args": "${auto-detect-url}", + "windows": { + "command": "cmd.exe", + "args": "/C start ${auto-detect-url}" + }, + "osx": { + "command": "open" + }, + "linux": { + "command": "xdg-open" + } + }, + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether web browser launch is enabled", + "default": true + }, + "args": { + "type": "string", + "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", + "default": "${auto-detect-url}" + }, + "osx": { + "type": "object", + "description": "OSX-specific web launch configuration options", + "default": { + "command": "open" + }, "properties": { - "program": { + "command": { "type": "string", - "description": "Path to the program (executable file) to launch. On Windows, a '.exe' suffix is appended if not specified already.", - "default": "${workspaceRoot}/bin/Debug/" - }, - "cwd": { + "description": "The command to execute for launching the web browser", + "default": "open" + }, + "args": { "type": "string", - "description": "Path to the working directory of the program being debugged. Default is the current workspace.", - "default": "${workspaceRoot}" - }, - "args": { - "type": "array", - "description": "Command line arguments passed to the program.", - "items": { "type": "string" }, - "default": [ ] - }, - "stopAtEntry": { - "type": "boolean", - "description": "If true, the debugger should stop at the entry point of the target.", - "default": false - }, - "launchBrowser": { - "type": "object", - "description": "Describes options to launch a web browser as part of launch", - "default": { - "enabled": true, - "args": "${auto-detect-url}", - "windows": { - "command": "cmd.exe", - "args": "/C start ${auto-detect-url}" - }, - "osx": { - "command": "open" - }, - "linux": { - "command": "xdg-open" - } - }, - "properties": { - "enabled": { - "type": "boolean", - "description": "Whether web browser launch is enabled", - "default": true - }, - "args": { - "type": "string", - "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", - "default": "${auto-detect-url}" - }, - "osx": { - "type": "object", - "description": "OSX-specific web launch configuration options", - "default": { - "command": "open" - }, - "properties": { - "command": { - "type": "string", - "description": "The command to execute for launching the web browser", - "default": "open" - }, - "args": { - "type": "string", - "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", - "default": "${auto-detect-url}" - } - } - }, - "linux": { - "type": "object", - "description": "Linux-specific web launch configuration options", - "default": { - "command": "xdg-open" - }, - "properties": { - "command": { - "type": "string", - "description": "The command to execute for launching the web browser", - "default": "xdg-open" - }, - "args": { - "type": "string", - "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", - "default": "${auto-detect-url}" - } - } - }, - "windows": { - "type": "object", - "description": "Windows-specific web launch configuration options", - "default": { - "command": "cmd.exe", - "args": "/C start ${auto-detect-url}" - }, - "properties": { - "command": { - "type": "string", - "description": "The command to execute for launching the web browser", - "default": "cmd.exe" - }, - "args": { - "type": "string", - "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", - "default": "/C start ${auto-detect-url}" - } - } - } - } - }, - "sourceFileMap": { - "type": "object", - "description": "Optional source file mappings passed to the debug engine. Example: '{ \"C:\\foo\":\"/home/user/foo\" }'", - "default": { "" : "" } - }, - "justMyCode": { - "type": "boolean", - "description": "Optional flag to only show user code.", - "default": true - }, - "symbolPath": { - "type": "array", - "description": "Array of directories to use to search for .pdb files. These directories will be searched in addition to the default locations -- next to the module and the path where the pdb was originally dropped to. Example: '[ \"/Volumes/symbols\" ]", - "items": { "type": "string" }, - "default": [ ] + "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", + "default": "${auto-detect-url}" + } } - } - }, - "attach": { - "required": [ ], + }, + "linux": { + "type": "object", + "description": "Linux-specific web launch configuration options", + "default": { + "command": "xdg-open" + }, "properties": { - "processName": { + "command": { "type": "string", - "description": "", - "default": "The process name to attach to. If this is used, 'processId' should not be used." - }, - "processId": { - "type": "integer", - "description": "The process id to attach to. If this is used, 'processName' should not be used.", - "default": "" - }, - "sourceFileMap": { - "type": "object", - "description": "Optional source file mappings passed to the debug engine. Example: '{ \"C:\\foo\":\"/home/user/foo\" }'", - "default": { "" : "" } - }, - "justMyCode": { - "type": "boolean", - "description": "Optional flag to only show user code.", - "default": true - }, - "symbolPath": { - "type": "array", - "description": "Array of directories to use to search for .pdb files. These directories will be searched in addition to the default locations -- next to the module and the path where the pdb was originally dropped to. Example: '[ \"~/symbols\" ]", - "items": { "type": "string" }, - "default": [ ] + "description": "The command to execute for launching the web browser", + "default": "xdg-open" + }, + "args": { + "type": "string", + "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", + "default": "${auto-detect-url}" + } } + }, + "windows": { + "type": "object", + "description": "Windows-specific web launch configuration options", + "default": { + "command": "cmd.exe", + "args": "/C start ${auto-detect-url}" + }, + "properties": { + "command": { + "type": "string", + "description": "The command to execute for launching the web browser", + "default": "cmd.exe" + }, + "args": { + "type": "string", + "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", + "default": "/C start ${auto-detect-url}" + } } + } } + }, + "sourceFileMap": { + "type": "object", + "description": "Optional source file mappings passed to the debug engine. Example: '{ \"C:\\foo\":\"/home/user/foo\" }'", + "default": { + "": "" + } + }, + "justMyCode": { + "type": "boolean", + "description": "Optional flag to only show user code.", + "default": true + }, + "symbolPath": { + "type": "array", + "description": "Array of directories to use to search for .pdb files. These directories will be searched in addition to the default locations -- next to the module and the path where the pdb was originally dropped to. Example: '[ \"/Volumes/symbols\" ]", + "items": { + "type": "string" }, - - "initialConfigurations": [ - { - "name": ".NET Core Launch (console)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceRoot}/bin/Debug/", - "args": [ ], - "cwd": "${workspaceRoot}", - "stopAtEntry": false - }, - { - "name": ".NET Core Launch (web)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceRoot}/bin/Debug/", - "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" - } - } - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach", - "processName": "" - } - ] + "default": [] + } + } + }, + "attach": { + "required": [], + "properties": { + "processName": { + "type": "string", + "description": "", + "default": "The process name to attach to. If this is used, 'processId' should not be used." + }, + "processId": { + "type": "integer", + "description": "The process id to attach to. If this is used, 'processName' should not be used.", + "default": "" + }, + "sourceFileMap": { + "type": "object", + "description": "Optional source file mappings passed to the debug engine. Example: '{ \"C:\\foo\":\"/home/user/foo\" }'", + "default": { + "": "" + } + }, + "justMyCode": { + "type": "boolean", + "description": "Optional flag to only show user code.", + "default": true + }, + "symbolPath": { + "type": "array", + "description": "Array of directories to use to search for .pdb files. These directories will be searched in addition to the default locations -- next to the module and the path where the pdb was originally dropped to. Example: '[ \"~/symbols\" ]", + "items": { + "type": "string" + }, + "default": [] + } + } + } + }, + "initialConfigurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceRoot}/bin/Debug//", + "args": [], + "cwd": "${workspaceRoot}", + "stopAtEntry": false + }, + { + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceRoot}/bin/Debug//", + "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" + } } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processName": "" + } ] - } + } + ] + } } \ No newline at end of file diff --git a/src/assets.ts b/src/assets.ts new file mode 100644 index 0000000000..7260a79f34 --- /dev/null +++ b/src/assets.ts @@ -0,0 +1,294 @@ +/*--------------------------------------------------------------------------------------------- + * 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 done = false; + for (var project of info.Projects) { + for (var configuration of project.Configurations) { + if (configuration.Name === "Debug" && configuration.EmitEntryPoint === true) { + if (project.Frameworks.length > 0) { + targetFramework = project.Frameworks[0].ShortName; + executableName = path.basename(configuration.CompilationOutputAssemblyFile) + } + + done = true; + break; + } + } + + if (done) { + break; + } + } + + 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/coreclr-debug.ts b/src/coreclr-debug.ts deleted file mode 100644 index 27a0b0e7c9..0000000000 --- a/src/coreclr-debug.ts +++ /dev/null @@ -1,262 +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 vscode from 'vscode'; -import * as child_process from 'child_process'; -import * as fs from 'fs'; -import * as path from 'path'; -import TelemetryReporter from 'vscode-extension-telemetry'; - -let _coreClrDebugDir: string; -let _debugAdapterDir: string; -let _channel: vscode.OutputChannel; -let _installLog: NodeJS.WritableStream; -let _reporter: TelemetryReporter; // Telemetry reporter -const _completionFileName: string = 'install.complete'; - -export function installCoreClrDebug(context: vscode.ExtensionContext) { - _coreClrDebugDir = path.join(context.extensionPath, 'coreclr-debug'); - _debugAdapterDir = path.join(_coreClrDebugDir, 'debugAdapters'); - - if (existsSync(path.join(_debugAdapterDir, _completionFileName))) { - console.log('.NET Core Debugger tools already installed'); - return; - } - - if (!isOnPath('dotnet')) { - // TODO: In a future release, this should be an error. For this release, we will let it go - console.log("The .NET CLI tools are not installed. .NET Core debugging will not be enabled.") - return; - } - - initializeTelemetry(context); - _channel = vscode.window.createOutputChannel('coreclr-debug'); - - // Create our log file and override _channel.append to also outpu to the log - _installLog = fs.createWriteStream(path.join(_coreClrDebugDir, 'install.log')); - (function() { - var proxied = _channel.append; - _channel.append = function(val: string) { - _installLog.write(val); - proxied.apply(this, arguments); - }; - })(); - - _channel.appendLine("Downloading and configuring the .NET Core Debugger..."); - _channel.show(vscode.ViewColumn.Three); - - var installStage = 'dotnet restore'; - var installError = ''; - - spawnChildProcess('dotnet', ['--verbose', 'restore', '--configfile', 'NuGet.config'], _channel, _coreClrDebugDir) - .then(function() { - installStage = "dotnet publish"; - return spawnChildProcess('dotnet', ['--verbose', 'publish', '-o', _debugAdapterDir], _channel, _coreClrDebugDir); - }).then(function() { - installStage = "ensureAd7"; - return ensureAd7EngineExists(_channel, _debugAdapterDir); - }).then(function() { - installStage = "additionalTasks" - var promises: Promise[] = []; - - promises.push(renameDummyEntrypoint()); - promises.push(removeLibCoreClrTraceProvider()); - - return Promise.all(promises); - }).then(function() { - installStage = "writeCompletionFile"; - return writeCompletionFile(); - }).then(function() { - installStage = "completeSuccess"; - _channel.appendLine('Succesfully installed .NET Core Debugger.'); - }) - .catch(function(error) { - _channel.appendLine('Error while installing .NET Core Debugger.'); - - installError = error.toString(); - console.log(error); - }).then(function() { - // log telemetry - logTelemetry('Acquisition', {installStage: installStage, installError: installError}); - }); -} - -function initializeTelemetry(context: vscode.ExtensionContext) { - // parse our own package.json into json - var packageJson = JSON.parse(fs.readFileSync(path.join(context.extensionPath, 'package.json')).toString()); - - let extensionId = packageJson["publisher"] + "." + packageJson["name"]; - let extensionVersion = packageJson["version"]; - let aiKey = packageJson.contributes.debuggers[0]["aiKey"]; - - _reporter = new TelemetryReporter(extensionId, extensionVersion, aiKey); -} - -function logTelemetry(eventName: string, properties?: {[prop: string]: string}) { - if (_reporter) - { - _reporter.sendTelemetryEvent('coreclr-debug/' + eventName, properties); - } -} - -function writeCompletionFile() : Promise { - return new Promise(function(resolve, reject) { - fs.writeFile(path.join(_debugAdapterDir, _completionFileName), '', function(err) { - if (err) { - reject(err.code); - } - else { - resolve(); - } - }); - }); -} - -function renameDummyEntrypoint() : Promise { - var src = path.join(_debugAdapterDir, 'dummy'); - var dest = path.join(_debugAdapterDir, 'OpenDebugAD7'); - - src += getPlatformExeExtension(); - dest += getPlatformExeExtension(); - - var promise = new Promise(function(resolve, reject) { - fs.rename(src, dest, function(err) { - if (err) { - reject(err.code); - } else { - resolve(); - } - }); - }); - - return promise; -} - -function removeLibCoreClrTraceProvider() : Promise -{ - var filePath = path.join(_debugAdapterDir, 'libcoreclrtraceptprovider' + getPlatformLibExtension()); - - if (!existsSync(filePath)) { - return Promise.resolve(); - } else { - return new Promise(function(resolve, reject) { - fs.unlink(filePath, function(err) { - if (err) { - reject(err.code); - } else { - _channel.appendLine('Succesfully deleted ' + filePath); - resolve(); - } - }); - }); - } -} - -function existsSync(path: string) : boolean { - try { - fs.accessSync(path, fs.F_OK); - return true; - } catch (err) { - if (err.code === 'ENOENT') { - return false; - } else { - throw Error(err.code); - } - } -} - -function getPlatformExeExtension() : string { - if (process.platform === 'win32') { - return '.exe'; - } - - return ''; -} - -function getPlatformLibExtension() : string { - switch (process.platform) { - case 'win32': - return '.dll'; - case 'darwin': - return '.dylib'; - case 'linux': - return '.so'; - default: - throw Error('Unsupported platform ' + process.platform); - } -} - -// Determines if the specified command is in one of the directories in the PATH environment variable. -function isOnPath(command : string) : boolean { - let pathValue = process.env['PATH']; - if (!pathValue) { - return false; - } - let fileName = command; - let seperatorChar = ':'; - if (process.platform == 'win32') { - // on Windows, add a '.exe', and the path is semi-colon seperatode - fileName = fileName + ".exe"; - seperatorChar = ';'; - } - - let pathSegments: string[] = pathValue.split(seperatorChar); - for (var segment of pathSegments) { - if (segment.length === 0 || !path.isAbsolute(segment)) { - continue; - } - var segmentPath = path.join(segment, fileName); - if (existsSync(segmentPath)) { - return true; - } - } - - return false; -} - -function ensureAd7EngineExists(channel: vscode.OutputChannel, outputDirectory: string) : Promise { - let filePath = path.join(outputDirectory, "coreclr.ad7Engine.json"); - return new Promise((resolve, reject) => { - fs.exists(filePath, (exists) => { - if (exists) { - return resolve(); - } else { - channel.appendLine(`${filePath} does not exist.`); - channel.appendLine(''); - // NOTE: The minimum build number is actually less than 1584, but this is the minimum - // build that I have tested. - channel.appendLine("Error: The .NET CLI did not correctly restore debugger files. Ensure that you have .NET CLI version 1.0.0 build #001584 or newer. You can check your .NET CLI version using 'dotnet --version'."); - return reject("The .NET CLI did not correctly restore debugger files."); - } - }); - }); -} - -function spawnChildProcess(process: string, args: string[], channel: vscode.OutputChannel, workingDirectory: string) : Promise { - var promise = new Promise( function (resolve, reject) { - const child = child_process.spawn(process, args, {cwd: workingDirectory}); - - child.stdout.on('data', (data) => { - channel.append(`${data}`); - }); - - child.stderr.on('data', (data) => { - channel.appendLine(`Error: ${data}`); - }); - - child.on('close', (code: number) => { - if (code != 0) { - channel.appendLine(`${process} exited with error code ${code}`); - reject(new Error(code.toString())); - } - else { - resolve(); - } - }); - }); - - return promise; -} \ No newline at end of file diff --git a/src/coreclr-debug/main.ts b/src/coreclr-debug/main.ts new file mode 100644 index 0000000000..e125742973 --- /dev/null +++ b/src/coreclr-debug/main.ts @@ -0,0 +1,327 @@ +/*--------------------------------------------------------------------------------------------- + * 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 vscode from 'vscode'; +import * as child_process from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import TelemetryReporter from 'vscode-extension-telemetry'; +import CoreClrDebugUtil from './util' + +let _channel: vscode.OutputChannel; +let _installLog: NodeJS.WritableStream; +let _reporter: TelemetryReporter; // Telemetry reporter +let _util: CoreClrDebugUtil; + +export function activate(context: vscode.ExtensionContext, reporter: TelemetryReporter) { + _util = new CoreClrDebugUtil(context.extensionPath); + + if (CoreClrDebugUtil.existsSync(_util.installCompleteFilePath())) { + console.log('.NET Core Debugger tools already installed'); + return; + } + + if (!isOnPath('dotnet')) { + const getDotNetMessage = "Get .NET CLI tools"; + vscode.window.showErrorMessage("The .NET CLI tools cannot be located. .NET Core debugging will not be enabled. Make sure .NET CLI tools are installed and are on the path.", + getDotNetMessage).then(function (value) { + if (value === getDotNetMessage) { + let open = require('open'); + open("http://dotnet.github.io/getting-started/"); + } + }); + + return; + } + + _reporter = reporter; + _channel = vscode.window.createOutputChannel('coreclr-debug'); + + // Create our log file and override _channel.append to also output to the log + _installLog = fs.createWriteStream(_util.installLogPath()); + (function() { + let proxied = _channel.append; + _channel.append = function(val: string) { + _installLog.write(val); + proxied.apply(this, arguments); + }; + })(); + + let statusBarMessage = vscode.window.setStatusBarMessage("Downloading and configuring the .NET Core Debugger..."); + + let installStage = 'installBegin'; + let installError = ''; + + writeInstallBeginFile().then(function() { + installStage = 'writeProjectJson'; + return writeProjectJson(); + }).then(function() { + installStage = 'dotnetRestore' + return spawnChildProcess('dotnet', ['--verbose', 'restore', '--configfile', 'NuGet.config'], _channel, _util.coreClrDebugDir()) + }).then(function() { + installStage = "dotnetPublish"; + return spawnChildProcess('dotnet', ['--verbose', 'publish', '-o', _util.debugAdapterDir()], _channel, _util.coreClrDebugDir()); + }).then(function() { + installStage = "ensureAd7"; + return ensureAd7EngineExists(_channel, _util.debugAdapterDir()); + }).then(function() { + installStage = "additionalTasks"; + let promises: Promise[] = []; + + promises.push(renameDummyEntrypoint()); + promises.push(removeLibCoreClrTraceProvider()); + + return Promise.all(promises); + }).then(function() { + installStage = "rewriteManifest"; + rewriteManifest(); + installStage = "writeCompletionFile"; + return writeCompletionFile(); + }).then(function() { + installStage = "completeSuccess"; + statusBarMessage.dispose(); + vscode.window.setStatusBarMessage('Successfully installed .NET Core Debugger.'); + }) + .catch(function(error) { + const viewLogMessage = "View Log"; + vscode.window.showErrorMessage('Error while installing .NET Core Debugger.', viewLogMessage).then(function (value) { + if (value === viewLogMessage) { + _channel.show(vscode.ViewColumn.Three); + } + }); + statusBarMessage.dispose(); + + installError = error.toString(); + console.log(error); + + + }).then(function() { + // log telemetry and delete install begin file + logTelemetry('Acquisition', {installStage: installStage, installError: installError}); + + try { + deleteInstallBeginFile(); + } catch (err) { + // if this throws there's really nothing we can do + } + }); +} + +function logTelemetry(eventName: string, properties?: {[prop: string]: string}) { + if (_reporter) + { + _reporter.sendTelemetryEvent('coreclr-debug/' + eventName, properties); + } +} + +function rewriteManifest() : void { + const manifestPath = path.join(_util.extensionDir(), 'package.json'); + let manifestString = fs.readFileSync(manifestPath, 'utf8'); + let manifestObject = JSON.parse(manifestString); + manifestObject.contributes.debuggers[0].runtime = ''; + manifestObject.contributes.debuggers[0].program = './coreclr-debug/debugAdapters/OpenDebugAD7' + CoreClrDebugUtil.getPlatformExeExtension(); + manifestString = JSON.stringify(manifestObject, null, 2); + fs.writeFileSync(manifestPath, manifestString); +} + +function writeInstallBeginFile() : Promise { + return writeEmptyFile(_util.installBeginFilePath()); +} + +function deleteInstallBeginFile() { + if (CoreClrDebugUtil.existsSync(_util.installBeginFilePath())) { + fs.unlinkSync(_util.installBeginFilePath()); + } +} + +function writeCompletionFile() : Promise { + return writeEmptyFile(_util.installCompleteFilePath()); +} + +function writeEmptyFile(path: string) : Promise { + return new Promise(function(resolve, reject) { + fs.writeFile(path, '', function(err) { + if (err) { + reject(err.code); + } else { + resolve(); + } + }); + }); +} + +function renameDummyEntrypoint() : Promise { + let src = path.join(_util.debugAdapterDir(), 'dummy'); + let dest = path.join(_util.debugAdapterDir(), 'OpenDebugAD7'); + + src += CoreClrDebugUtil.getPlatformExeExtension(); + dest += CoreClrDebugUtil.getPlatformExeExtension(); + + const promise = new Promise(function(resolve, reject) { + fs.rename(src, dest, function(err) { + if (err) { + reject(err.code); + } else { + resolve(); + } + }); + }); + + return promise; +} + +function removeLibCoreClrTraceProvider() : Promise +{ + const filePath = path.join(_util.debugAdapterDir(), 'libcoreclrtraceptprovider' + CoreClrDebugUtil.getPlatformLibExtension()); + + if (!CoreClrDebugUtil.existsSync(filePath)) { + return Promise.resolve(); + } else { + return new Promise(function(resolve, reject) { + fs.unlink(filePath, function(err) { + if (err) { + reject(err.code); + } else { + _channel.appendLine('Succesfully deleted ' + filePath); + resolve(); + } + }); + }); + } +} + +// Determines if the specified command is in one of the directories in the PATH environment variable. +function isOnPath(command : string) : boolean { + let pathValue = process.env['PATH']; + if (!pathValue) { + return false; + } + let fileName = command; + let seperatorChar = ':'; + if (process.platform == 'win32') { + // on Windows, add a '.exe', and the path is semi-colon seperatode + fileName = fileName + ".exe"; + seperatorChar = ';'; + } + + let pathSegments: string[] = pathValue.split(seperatorChar); + for (let segment of pathSegments) { + if (segment.length === 0 || !path.isAbsolute(segment)) { + continue; + } + + const segmentPath = path.join(segment, fileName); + if (CoreClrDebugUtil.existsSync(segmentPath)) { + return true; + } + } + + return false; +} + +function ensureAd7EngineExists(channel: vscode.OutputChannel, outputDirectory: string) : Promise { + let filePath = path.join(outputDirectory, "coreclr.ad7Engine.json"); + return new Promise((resolve, reject) => { + fs.exists(filePath, (exists) => { + if (exists) { + return resolve(); + } else { + channel.appendLine(`${filePath} does not exist.`); + channel.appendLine(''); + // NOTE: The minimum build number is actually less than 1584, but this is the minimum + // build that I have tested. + channel.appendLine("Error: The .NET CLI did not correctly restore debugger files. Ensure that you have .NET CLI version 1.0.0 build #001584 or newer. You can check your .NET CLI version using 'dotnet --version'."); + return reject("The .NET CLI did not correctly restore debugger files."); + } + }); + }); +} + +function writeProjectJson(): Promise { + return new Promise(function(resolve, reject) { + var projectJson = createProjectJson(CoreClrDebugUtil.getPlatformRuntimeId()); + + fs.writeFile(path.join(_util.coreClrDebugDir(), 'project.json'), JSON.stringify(projectJson, null, 2), {encoding: 'utf8'}, function(err) { + if (err) { + reject(err.code); + } + else { + resolve(); + } + }); + }); +} + +function createProjectJson(targetRuntime: string): any +{ + let projectJson = { + name: "dummy", + compilationOptions: { + emitEntryPoint: true + }, + dependencies: { + "Microsoft.VisualStudio.clrdbg": "14.0.25201-preview-2911579", + "Microsoft.VisualStudio.clrdbg.MIEngine": "14.0.30401-preview-1", + "Microsoft.VisualStudio.OpenDebugAD7": "1.0.20405-preview-1", + "NETStandard.Library": "1.5.0-rc2-23931", + "Newtonsoft.Json": "7.0.1", + "Microsoft.VisualStudio.Debugger.Interop.Portable": "1.0.1", + "System.Collections.Specialized": "4.0.1-rc2-23931", + "System.Collections.Immutable": "1.2.0-rc2-23931", + "System.Diagnostics.Process" : "4.1.0-rc2-23931", + "System.Diagnostics.StackTrace": "4.0.1-rc2-23931", + "System.Dynamic.Runtime": "4.0.11-rc2-23931", + "Microsoft.CSharp": "4.0.1-rc2-23931", + "System.Threading.Tasks.Dataflow": "4.6.0-rc2-23931", + "System.Threading.Thread": "4.0.0-rc2-23931", + "System.Xml.XDocument": "4.0.11-rc2-23931", + "System.Xml.XmlDocument": "4.0.1-rc2-23931", + "System.Xml.XmlSerializer": "4.0.11-rc2-23931", + "System.ComponentModel": "4.0.1-rc2-23931", + "System.ComponentModel.Annotations": "4.1.0-rc2-23931", + "System.ComponentModel.EventBasedAsync": "4.0.11-rc2-23931", + "System.Runtime.Serialization.Primitives": "4.1.1-rc2-23931", + "System.Net.Http": "4.0.1-rc2-23931" + }, + frameworks: { + "netstandardapp1.5": { + imports: [ "dnxcore50", "portable-net45+win8" ] + } + }, + runtimes: { + } + } + + projectJson.runtimes[targetRuntime] = {}; + + return projectJson; +} + +function spawnChildProcess(process: string, args: string[], channel: vscode.OutputChannel, workingDirectory: string) : Promise { + const promise = new Promise(function(resolve, reject) { + const child = child_process.spawn(process, args, {cwd: workingDirectory}); + + child.stdout.on('data', (data) => { + channel.append(`${data}`); + }); + + child.stderr.on('data', (data) => { + channel.appendLine(`Error: ${data}`); + }); + + child.on('close', (code: number) => { + if (code != 0) { + channel.appendLine(`${process} exited with error code ${code}`); + reject(new Error(code.toString())); + } + else { + resolve(); + } + }); + }); + + return promise; +} \ No newline at end of file diff --git a/src/coreclr-debug/proxy.ts b/src/coreclr-debug/proxy.ts new file mode 100644 index 0000000000..d6a9c3bfd4 --- /dev/null +++ b/src/coreclr-debug/proxy.ts @@ -0,0 +1,109 @@ +/*--------------------------------------------------------------------------------------------- + * 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 path from 'path'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import * as child_process from 'child_process'; +import CoreClrDebugUtil from './util'; + +class ProxyErrorResponse implements DebugProtocol.ErrorResponse { + public body: { error?: DebugProtocol.Message }; + public request_seq: number; + public success: boolean; + public command: string; + public seq: number; + public type: string; + + constructor(public message: string) { + this.request_seq = 1; + this.seq = 1; + this.type = "response"; + this.success = false; + this.command = "initialize"; + } +} + +function serializeProtocolEvent(message: DebugProtocol.ProtocolMessage): string { + const payload: string = JSON.stringify(message); + const finalPayload: string = `Content-Length: ${payload.length}\r\n\r\n${payload}`; + return finalPayload; +} + +// The default extension manifest calls this proxy as the debugger program +// When installation of the debugger components finishes, the extension manifest is rewritten so that this proxy is no longer called +// If the debugger components have not finished downloading, the proxy displays an error message to the user +// If the debugger components have finished downloading, the manifest has been rewritten but has not been reloaded. +// This proxy will still be called and launch OpenDebugAD7 as a child process. +// During subsequent code sessions, the rewritten manifest will be loaded and this proxy will no longer be called. +function proxy() { + let util = new CoreClrDebugUtil(path.resolve(__dirname, '../../')); + + if (!CoreClrDebugUtil.existsSync(util.installCompleteFilePath())) { + if (CoreClrDebugUtil.existsSync(util.installBeginFilePath())) { + process.stdout.write(serializeProtocolEvent(new ProxyErrorResponse('The .NET Core Debugger is still being downloaded. See the Status Bar for more information.'))); + } else { + process.stdout.write(serializeProtocolEvent(new ProxyErrorResponse('Run \'Debug: Download .NET Core Debugger\' in the Command Palette or open a .NET project directory to download the .NET Core Debugger'))); + } + } + else + { + new Promise(function(resolve, reject) { + let processPath = path.join(util.debugAdapterDir(), "OpenDebugAD7" + CoreClrDebugUtil.getPlatformExeExtension()); + let args = process.argv.slice(2); + + // do not explicitly set a current working dir + // this seems to match what code does when OpenDebugAD7 is launched directly from the manifest + const child = child_process.spawn(processPath, args); + + // If we don't exit cleanly from the child process, log the error. + child.on('close', function(code: number) { + if (code !== 0) { + reject(new Error(code.toString())); + } else { + resolve(); + } + }); + + process.stdin.setEncoding('utf8'); + + child.on('error', function(data) { + util.logToFile(`Child error: ${data}`); + }); + + process.on('SIGTERM', function() { + child.kill(); + process.exit(0); + }); + + process.on('SIGHUP', function() { + child.kill(); + process.exit(0); + }); + + process.stdin.on('error', function(error) { + util.logToFile(`process.stdin error: ${error}`); + }); + + process.stdout.on('error', function(error) { + util.logToFile(`process.stdout error: ${error}`); + }); + + child.stdout.on('data', function(data) { + process.stdout.write(data); + }); + + process.stdin.on('data', function(data) { + child.stdin.write(data); + }); + + process.stdin.resume(); + }).catch(function(err) { + util.logToFile(`Promise failed: ${err}`); + }); + } +} + +proxy(); \ No newline at end of file diff --git a/src/coreclr-debug/util.ts b/src/coreclr-debug/util.ts new file mode 100644 index 0000000000..2d8e784723 --- /dev/null +++ b/src/coreclr-debug/util.ts @@ -0,0 +1,154 @@ +/*--------------------------------------------------------------------------------------------- + * 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 path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as child_process from 'child_process' + +let _extensionDir: string = ''; +let _coreClrDebugDir: string = ''; +let _debugAdapterDir: string = ''; +let _installLogPath: string = ''; +let _installBeginFilePath: string = ''; +let _installCompleteFilePath: string = ''; + +export default class CoreClrDebugUtil +{ + private _extensionDir: string = ''; + private _coreClrDebugDir: string = ''; + private _debugAdapterDir: string = ''; + private _installLogPath: string = ''; + private _installBeginFilePath: string = ''; + private _installCompleteFilePath: string = ''; + + constructor(extensionDir) { + _extensionDir = extensionDir; + _coreClrDebugDir = path.join(_extensionDir, 'coreclr-debug'); + _debugAdapterDir = path.join(_coreClrDebugDir, 'debugAdapters'); + _installLogPath = path.join(_coreClrDebugDir, 'install.log'); + _installBeginFilePath = path.join(_coreClrDebugDir, 'install.begin'); + _installCompleteFilePath = path.join(_debugAdapterDir, 'install.complete'); + } + + extensionDir(): string { + if (_extensionDir === '') + { + throw new Error('Failed to set extension directory'); + } + return _extensionDir; + } + + coreClrDebugDir(): string { + if (_coreClrDebugDir === '') { + throw new Error('Failed to set coreclrdebug directory'); + } + return _coreClrDebugDir; + } + + debugAdapterDir(): string { + if (_debugAdapterDir === '') { + throw new Error('Failed to set debugadpter directory'); + } + return _debugAdapterDir; + } + + installLogPath(): string { + if (_installLogPath === '') { + throw new Error('Failed to set install log path'); + } + return _installLogPath; + } + + installBeginFilePath(): string { + if (_installBeginFilePath === '') { + throw new Error('Failed to set install begin file path'); + } + return _installBeginFilePath; + } + + installCompleteFilePath(): string { + if (_installCompleteFilePath === '') + { + throw new Error('Failed to set install complete file path'); + } + return _installCompleteFilePath; + } + + static existsSync(path: string) : boolean { + try { + fs.accessSync(path, fs.F_OK); + return true; + } catch (err) { + if (err.code === 'ENOENT') { + return false; + } else { + throw Error(err.code); + } + } + } + + static getPlatformExeExtension() : string { + if (process.platform === 'win32') { + return '.exe'; + } + + return ''; + } + + static getPlatformLibExtension() : string { + switch (process.platform) { + case 'win32': + return '.dll'; + case 'darwin': + return '.dylib'; + case 'linux': + return '.so'; + default: + throw Error('Unsupported platform ' + process.platform); + } + } + + static getPlatformRuntimeId() : string { + switch(process.platform) { + case 'win32': + return 'win7-x64'; + case 'darwin': + return CoreClrDebugUtil.getDotnetRuntimeId(); + case 'linux': + return CoreClrDebugUtil.getDotnetRuntimeId(); + default: + throw Error('Unsupported platform ' + process.platform); + } + } + + static getDotnetRuntimeId() : string { + let out = child_process.execSync('dotnet --info').toString(); + + let lines = out.split('\n'); + let ridLine = lines.filter(function(value) { + return value.trim().startsWith('RID'); + }); + + if (ridLine.length < 1) { + throw new Error('Cannot obtain Runtime ID from dotnet cli'); + } + + let rid = ridLine[0].split(':')[1].trim(); + + if (!rid) { + throw new Error('Unable to determine Runtime ID'); + } + + return rid; + } + + /** Used for diagnostics only */ + logToFile(message: string): void { + let logFolder = path.resolve(this.coreClrDebugDir(), "extension.log"); + fs.writeFileSync(logFolder, `${message}${os.EOL}`, { flag: 'a' }); + } +} diff --git a/src/features/changeForwarding.ts b/src/features/changeForwarding.ts index c9d32fb7d6..f928556de7 100644 --- a/src/features/changeForwarding.ts +++ b/src/features/changeForwarding.ts @@ -7,7 +7,7 @@ import {Disposable, Uri, workspace} from 'vscode'; import {OmnisharpServer} from '../omnisharpServer'; -import * as proto from '../protocol'; +import * as serverUtils from '../omnisharpUtils'; function forwardDocumentChanges(server: OmnisharpServer): Disposable { @@ -22,10 +22,7 @@ function forwardDocumentChanges(server: OmnisharpServer): Disposable { return; } - server.makeRequest(proto.UpdateBuffer, { - Buffer: document.getText(), - Filename: document.fileName - }).catch(err => { + serverUtils.updateBuffer(server, {Buffer: document.getText(), Filename: document.fileName}).catch(err => { console.error(err); return err; }); @@ -38,9 +35,11 @@ function forwardFileChanges(server: OmnisharpServer): Disposable { if (!server.isRunning()) { return; } + let req = { Filename: uri.fsPath }; - server.makeRequest(proto.FilesChanged, [req]).catch(err => { - console.warn('[o] failed to forward file change event for ' + uri.fsPath, err); + + serverUtils.filesChanged(server, [req]).catch(err => { + console.warn(`[o] failed to forward file change event for ${uri.fsPath}`, err); return err; }); } diff --git a/src/features/codeActionProvider.ts b/src/features/codeActionProvider.ts index a5f50f41ae..d8455f7653 100644 --- a/src/features/codeActionProvider.ts +++ b/src/features/codeActionProvider.ts @@ -8,8 +8,9 @@ import {CodeActionProvider, CodeActionContext, Command, CancellationToken, TextDocument, WorkspaceEdit, TextEdit, Range, Uri, workspace, commands} from 'vscode'; import {OmnisharpServer} from '../omnisharpServer'; import AbstractProvider from './abstractProvider'; -import {TextChange, V2} from '../protocol'; +import * as protocol from '../protocol'; import {toRange2} from '../typeConvertion'; +import * as serverUtils from '../omnisharpUtils'; export default class OmnisharpCodeActionProvider extends AbstractProvider implements CodeActionProvider { @@ -37,32 +38,32 @@ export default class OmnisharpCodeActionProvider extends AbstractProvider implem return; } - let req: V2.GetCodeActionsRequest = { + let req: protocol.V2.GetCodeActionsRequest = { Filename: document.fileName, Selection: OmnisharpCodeActionProvider._asRange(range) - } + }; - return this._server.makeRequest(V2.GetCodeActions, req, token).then(response => { - return response.CodeActions.map(ca => { + return serverUtils.getCodeActions(this._server, req, token).then(response => { + return response.CodeActions.map(codeAction => { return { - title: ca.Name, + title: codeAction.Name, command: this._commandId, - arguments: [{ + arguments: [{ Filename: document.fileName, Selection: OmnisharpCodeActionProvider._asRange(range), - Identifier: ca.Identifier, + Identifier: codeAction.Identifier, WantsTextChanges: true }] }; }); }, (error) => { - return Promise.reject('Problem invoking \'GetCodeActions\' on OmniSharp server: ' + error); + return Promise.reject(`Problem invoking 'GetCodeActions' on OmniSharp server: ${error}`); }); } - private _runCodeAction(req: V2.RunCodeActionRequest): Promise { + private _runCodeAction(req: protocol.V2.RunCodeActionRequest): Promise { - return this._server.makeRequest(V2.RunCodeAction, req).then(response => { + return serverUtils.runCodeAction(this._server, req).then(response => { if (response && Array.isArray(response.Changes)) { @@ -86,7 +87,7 @@ export default class OmnisharpCodeActionProvider extends AbstractProvider implem }); } - private static _asRange(range: Range): V2.Range { + private static _asRange(range: Range): protocol.V2.Range { let {start, end} = range; return { Start: { Line: start.line + 1, Column: start.character + 1 }, diff --git a/src/features/codeLensProvider.ts b/src/features/codeLensProvider.ts index 510456b537..496b660d72 100644 --- a/src/features/codeLensProvider.ts +++ b/src/features/codeLensProvider.ts @@ -5,10 +5,11 @@ 'use strict'; -import {CancellationToken, CodeLens, SymbolKind, Range, Uri, TextDocument, CodeLensProvider, Position} from 'vscode'; -import {createRequest, toRange, toLocation} from '../typeConvertion'; +import {CancellationToken, CodeLens, Range, Uri, TextDocument, CodeLensProvider} from 'vscode'; +import {toRange, toLocation} from '../typeConvertion'; import AbstractSupport from './abstractProvider'; -import * as proto from '../protocol'; +import * as protocol from '../protocol'; +import * as serverUtils from '../omnisharpUtils'; class OmniSharpCodeLens extends CodeLens { @@ -31,16 +32,14 @@ export default class OmniSharpCodeLensProvider extends AbstractSupport implement provideCodeLenses(document: TextDocument, token: CancellationToken): CodeLens[] | Thenable { - return this._server.makeRequest(proto.CurrentFileMembersAsTree, { - Filename: document.fileName - }, token).then(tree => { - var ret: CodeLens[] = []; + return serverUtils.currentFileMembersAsTree(this._server, { Filename: document.fileName }, token).then(tree => { + let ret: CodeLens[] = []; tree.TopLevelTypeDefinitions.forEach(node => OmniSharpCodeLensProvider._convertQuickFix(ret, document.fileName, node)); return ret; }); } - private static _convertQuickFix(bucket: CodeLens[], fileName:string, node: proto.Node): void { + private static _convertQuickFix(bucket: CodeLens[], fileName:string, node: protocol.Node): void { if (node.Kind === 'MethodDeclaration' && OmniSharpCodeLensProvider.filteredSymbolNames[node.Location.Text]) { return; @@ -57,7 +56,7 @@ export default class OmniSharpCodeLensProvider extends AbstractSupport implement resolveCodeLens(codeLens: CodeLens, token: CancellationToken): Thenable { if (codeLens instanceof OmniSharpCodeLens) { - let req = { + let req = { Filename: codeLens.fileName, Line: codeLens.range.start.line + 1, Column: codeLens.range.start.character + 1, @@ -65,10 +64,11 @@ export default class OmniSharpCodeLensProvider extends AbstractSupport implement ExcludeDefinition: true }; - return this._server.makeRequest(proto.FindUsages, req, token).then(res => { + return serverUtils.findUsages(this._server, req, token).then(res => { if (!res || !Array.isArray(res.QuickFixes)) { return; } + let len = res.QuickFixes.length; codeLens.command = { title: len === 1 ? '1 reference' : `${len} references`, diff --git a/src/features/commands.ts b/src/features/commands.ts index 01239020ac..92e498df26 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -5,28 +5,26 @@ 'use strict'; -import * as proto from '../protocol'; import {OmnisharpServer} from '../omnisharpServer'; +import * as serverUtils from '../omnisharpUtils'; import findLaunchTargets from '../launchTargetFinder'; -import * as pathHelpers from '../pathHelpers'; import {runInTerminal} from 'run-in-terminal'; -import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; -const isWin = /^win/.test(process.platform); +const isWindows = process.platform === 'win32'; export default function registerCommands(server: OmnisharpServer, extensionPath: string) { let d1 = vscode.commands.registerCommand('o.restart', () => server.restart()); let d2 = vscode.commands.registerCommand('o.pickProjectAndStart', () => pickProjectAndStart(server)); - let d3 = vscode.commands.registerCommand('o.restore', () => dnxRestoreForAll(server)); - let d4 = vscode.commands.registerCommand('o.execute', () => dnxExecuteCommand(server)); - 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)); + let d3 = vscode.commands.registerCommand('o.showOutput', () => server.getChannel().show(vscode.ViewColumn.Three)); + let d4 = vscode.commands.registerCommand('dotnet.restore', () => dotnetRestoreAllProjects(server)); + + // register empty handler for csharp.installDebugger + // running the command activates the extension, which is all we need for installation to kickoff + let d5 = vscode.commands.registerCommand('csharp.downloadDebugger', () => { }); - return vscode.Disposable.from(d1, d2, d3, d4, d5, d6, d7, d8); + return vscode.Disposable.from(d1, d2, d3, d4, d5); } function pickProjectAndStart(server: OmnisharpServer) { @@ -59,57 +57,29 @@ interface Command { execute(): Thenable; } -let lastCommand: Command; - -function dnxExecuteLastCommand(server: OmnisharpServer) { - if (lastCommand) { - lastCommand.execute(); - } else { - dnxExecuteCommand(server); - } -} - -function dnxExecuteCommand(server: OmnisharpServer) { +export function dotnetRestoreAllProjects(server: OmnisharpServer) { if (!server.isRunning()) { return Promise.reject('OmniSharp server is not running.'); } - return server.makeRequest(proto.Projects).then(info => { + return serverUtils.requestWorkspaceInformation(server).then(info => { let commands: Command[] = []; - - info.Dnx.Projects.forEach(project => { - Object.keys(project.Commands).forEach(key => { - - commands.push({ - label: `dnx ${key} - (${project.Name || path.basename(project.Path)})`, - description: path.dirname(project.Path), - execute() { - lastCommand = this; - - let command = path.join(info.Dnx.RuntimePath, 'bin/dnx'); - let args = [key]; - - // dnx-beta[1-6] needs a leading dot, like 'dnx . run' - if (/-beta[1-6]/.test(info.Dnx.RuntimePath)) { - args.unshift('.'); - } - - if (isWin) { - command += '.exe'; - } - - return runInTerminal(command, args, { - cwd: path.dirname(project.Path), - env: { - // KRE_COMPILATION_SERVER_PORT: workspace.DesignTimeHostPort - } - }); - } - }); - }); - }); + + if ('DotNet' in info && info.DotNet.Projects.length > 0) { + for (let project of info.DotNet.Projects) { + commands.push({ + label: `dotnet restor - (${project.Name || path.basename(project.Path)})`, + description: path.dirname(project.Path), + execute() { + return runInTerminal('dotnet', ['restore'], { + cwd: path.dirname(project.Path) + }); + } + }); + } + } return vscode.window.showQuickPick(commands).then(command => { if (command) { @@ -119,153 +89,19 @@ function dnxExecuteCommand(server: OmnisharpServer) { }); } -export function dnxRestoreForAll(server: OmnisharpServer) { - - if (!server.isRunning()) { - return Promise.reject('OmniSharp server is not running.'); - } - - return server.makeRequest(proto.Projects).then(info => { - - let commands:Command[] = []; - - info.Dnx.Projects.forEach(project => { - commands.push({ - label: `dnu restore - (${project.Name || path.basename(project.Path)})`, - description: path.dirname(project.Path), - execute() { - - let command = path.join(info.Dnx.RuntimePath, 'bin/dnu'); - if (isWin) { - command += '.cmd'; - } - - return runInTerminal(command, ['restore'], { - cwd: path.dirname(project.Path) - }); - } - }); - }); - - return vscode.window.showQuickPick(commands).then(command => { - if(command) { - return command.execute(); - } - }); - }); -} - -export function dnxRestoreForProject(server: OmnisharpServer, fileName: string) { +export function dotnetRestoreForProject(server: OmnisharpServer, fileName: string) { - return server.makeRequest(proto.Projects).then((info):Promise => { - for(let project of info.Dnx.Projects) { - if (project.Path === fileName) { - let command = path.join(info.Dnx.RuntimePath, 'bin/dnu'); - if (isWin) { - command += '.cmd'; - } - - return runInTerminal(command, ['restore'], { - cwd: path.dirname(project.Path) - }); - } - } - - return Promise.reject(`Failed to execute restore, try to run 'dnu restore' manually for ${fileName}.`) - }); -} - -function dotnetRestore(server: OmnisharpServer) { - - if (!server.isRunning()) { - return Promise.reject('OmniSharp server is not running.'); - } - - let solutionPathOrFolder = server.getSolutionPathOrFolder(); - if (!solutionPathOrFolder) { - return Promise.reject('No solution or folder open.'); - } - - pathHelpers.getPathKind(solutionPathOrFolder).then(kind => { - if (kind === pathHelpers.PathKind.File) { - return path.dirname(solutionPathOrFolder); - } - else { - return solutionPathOrFolder; - } - }).then((solutionDirectory) => { - return runInTerminal('dotnet', ['restore'], { - cwd: solutionPathOrFolder - }); - }); -} - -function ensureDirectoryCreated(directoryPath: string) { - return pathHelpers.exists(directoryPath).then(e => { - if (e) { - return true; - } - else { - return pathHelpers.mkdir(directoryPath); - } - }); -} - -function getExpectedVsCodeFolderPath(solutionPathOrFolder: string): Promise { - return pathHelpers.getPathKind(solutionPathOrFolder).then(kind => { - if (kind === pathHelpers.PathKind.File) { - return path.join(path.dirname(solutionPathOrFolder), '.vscode'); - } - else { - return path.join(solutionPathOrFolder, '.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(vscodeFolderPath => { - let tasksJsonPath = path.join(vscodeFolderPath, 'tasks.json'); - - return pathHelpers.exists(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 pathHelpers.exists(templatePath).then(e => { - if (!e) { - return reject('Could not find template-tasks.json file in extension.'); - } - - return ensureDirectoryCreated(vscodeFolderPath).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 ${vscodeFolderPath} directory.`); - } - }); + return serverUtils.requestWorkspaceInformation(server).then(info => { + if ('DotNet' in info && info.DotNet.Projects.length > 0) { + for (let project of info.DotNet.Projects) { + if (project.Path === path.dirname(fileName)) { + return runInTerminal('dotnet', ['restore', fileName], { + cwd: path.dirname(project.Path) }); } - }); - }); - }); + } + } + + return Promise.reject(`Failed to execute restore, try to run 'dotnet restore' manually for ${fileName}.`); + }); } \ No newline at end of file diff --git a/src/features/completionItemProvider.ts b/src/features/completionItemProvider.ts index bc69fde325..cb26d97759 100644 --- a/src/features/completionItemProvider.ts +++ b/src/features/completionItemProvider.ts @@ -7,9 +7,10 @@ import {plain} from './documentation'; import AbstractSupport from './abstractProvider'; -import * as proto from '../protocol'; +import * as protocol from '../protocol'; +import * as serverUtils from '../omnisharpUtils'; import {createRequest} from '../typeConvertion'; -import {CompletionItemProvider, CompletionItem, CompletionItemKind, Uri, CancellationToken, TextDocument, Range, Position} from 'vscode'; +import {CompletionItemProvider, CompletionItem, CompletionItemKind, CancellationToken, TextDocument, Range, Position} from 'vscode'; export default class OmniSharpCompletionItemProvider extends AbstractSupport implements CompletionItemProvider { @@ -21,12 +22,12 @@ export default class OmniSharpCompletionItemProvider extends AbstractSupport imp wordToComplete = document.getText(new Range(range.start, position)); } - let req = createRequest(document, position); + let req = createRequest(document, position); req.WordToComplete = wordToComplete; req.WantDocumentationForEveryCompletionResult = true; req.WantKind = true; - return this._server.makeRequest(proto.AutoComplete, req).then(values => { + return serverUtils.autoComplete(this._server, req).then(values => { if (!values) { return; @@ -73,7 +74,7 @@ export default class OmniSharpCompletionItemProvider extends AbstractSupport imp } } -var _kinds: { [kind: string]: CompletionItemKind; } = Object.create(null); +const _kinds: { [kind: string]: CompletionItemKind; } = Object.create(null); _kinds['Variable'] = CompletionItemKind.Variable; _kinds['Struct'] = CompletionItemKind.Interface; _kinds['Interface'] = CompletionItemKind.Interface; diff --git a/src/features/definitionProvider.ts b/src/features/definitionProvider.ts index d03c1f7a00..34665cdfce 100644 --- a/src/features/definitionProvider.ts +++ b/src/features/definitionProvider.ts @@ -6,9 +6,9 @@ 'use strict'; import AbstractSupport from './abstractProvider'; -import * as Protocol from '../protocol'; +import * as serverUtils from '../omnisharpUtils'; import {createRequest, toLocation} from '../typeConvertion'; -import {Uri, TextDocument, Position, Location, CancellationToken, DefinitionProvider} from 'vscode'; +import {TextDocument, Position, Location, CancellationToken, DefinitionProvider} from 'vscode'; export default class CSharpDefinitionProvider extends AbstractSupport implements DefinitionProvider { @@ -16,7 +16,7 @@ export default class CSharpDefinitionProvider extends AbstractSupport implements let req = createRequest(document, position); - return this._server.makeRequest(Protocol.GoToDefinition, req, token).then(value => { + return serverUtils.goToDefinition(this._server, req, token).then(value => { if (value && value.FileName) { return toLocation(value); } diff --git a/src/features/diagnosticsProvider.ts b/src/features/diagnosticsProvider.ts index 8360a36f72..cebd8d9eb9 100644 --- a/src/features/diagnosticsProvider.ts +++ b/src/features/diagnosticsProvider.ts @@ -7,9 +7,10 @@ import {OmnisharpServer} from '../omnisharpServer'; import AbstractSupport from './abstractProvider'; -import * as proto from '../protocol'; -import {createRequest, toRange} from '../typeConvertion'; -import {Disposable, Uri, CancellationTokenSource, TextDocument, TextDocumentChangeEvent, Range, Diagnostic, DiagnosticCollection, DiagnosticSeverity, Location, workspace, languages} from 'vscode'; +import * as protocol from '../protocol'; +import * as serverUtils from '../omnisharpUtils'; +import {toRange} from '../typeConvertion'; +import {Disposable, Uri, CancellationTokenSource, TextDocument, Diagnostic, DiagnosticCollection, DiagnosticSeverity, workspace, languages} from 'vscode'; export class Advisor { @@ -41,7 +42,7 @@ export class Advisor { && !this._isHugeProject(); } - private _onProjectChange(info: proto.ProjectInformationResponse): void { + private _onProjectChange(info: protocol.ProjectInformationResponse): void { if (info.DnxProject && info.DnxProject.SourceFiles) { this._projectSourceFileCounts[info.DnxProject.Path] = info.DnxProject.SourceFiles.length; } @@ -67,8 +68,8 @@ export class Advisor { } private _isHugeProject(): boolean { - var sourceFileCount = 0; - for (var key in this._projectSourceFileCounts) { + let sourceFileCount = 0; + for (let key in this._projectSourceFileCounts) { sourceFileCount += this._projectSourceFileCounts[key]; if (sourceFileCount > 1000) { return true; @@ -151,8 +152,7 @@ class DiagnosticsProvider extends AbstractSupport { let source = new CancellationTokenSource(); let handle = setTimeout(() => { - let req: proto.Request = { Filename: document.fileName }; - this._server.makeRequest(proto.CodeCheck, req, source.token).then(value => { + serverUtils.codeCheck(this._server, { Filename: document.fileName }, source.token).then(value => { // (re)set new diagnostics for this document let diagnostics = value.QuickFixes.map(DiagnosticsProvider._asDiagnostic); @@ -176,7 +176,7 @@ class DiagnosticsProvider extends AbstractSupport { this._projectValidation = new CancellationTokenSource(); let handle = setTimeout(() => { - this._server.makeRequest(proto.CodeCheck, {}, this._projectValidation.token).then(value => { + serverUtils.codeCheck(this._server, { Filename: null }, this._projectValidation.token).then(value => { let quickFixes = value.QuickFixes.sort((a, b) => a.FileName.localeCompare(b.FileName)); let entries: [Uri, Diagnostic[]][] = []; @@ -208,7 +208,7 @@ class DiagnosticsProvider extends AbstractSupport { // --- data converter - private static _asDiagnostic(quickFix: proto.QuickFix): Diagnostic { + private static _asDiagnostic(quickFix: protocol.QuickFix): Diagnostic { let severity = DiagnosticsProvider._asDiagnosticSeverity(quickFix.LogLevel); let message = `${quickFix.Text} [${quickFix.Projects.map(n => DiagnosticsProvider._asProjectLabel(n)).join(', ') }]`; return new Diagnostic(toRange(quickFix), message, severity); @@ -226,7 +226,7 @@ class DiagnosticsProvider extends AbstractSupport { } private static _asProjectLabel(projectName: string): string { - var idx = projectName.indexOf('+'); + const idx = projectName.indexOf('+'); return projectName.substr(idx + 1); } } diff --git a/src/features/documentHighlightProvider.ts b/src/features/documentHighlightProvider.ts index 647a126531..e9efe90d3a 100644 --- a/src/features/documentHighlightProvider.ts +++ b/src/features/documentHighlightProvider.ts @@ -6,26 +6,27 @@ 'use strict'; import AbstractSupport from './abstractProvider'; -import * as proto from '../protocol'; +import * as protocol from '../protocol'; +import * as serverUtils from '../omnisharpUtils'; import {createRequest, toRange} from '../typeConvertion'; -import {DocumentHighlightProvider, DocumentHighlight, DocumentHighlightKind, Uri, CancellationToken, TextDocument, Position, Range} from 'vscode'; +import {DocumentHighlightProvider, DocumentHighlight, DocumentHighlightKind, CancellationToken, TextDocument, Position} from 'vscode'; export default class OmnisharpDocumentHighlightProvider extends AbstractSupport implements DocumentHighlightProvider { public provideDocumentHighlights(resource: TextDocument, position: Position, token: CancellationToken): Promise { - let req = createRequest(resource, position); + let req = createRequest(resource, position); req.OnlyThisFile = true; req.ExcludeDefinition = false; - return this._server.makeRequest(proto.FindUsages, req, token).then(res => { + return serverUtils.findUsages(this._server, req, token).then(res => { if (res && Array.isArray(res.QuickFixes)) { return res.QuickFixes.map(OmnisharpDocumentHighlightProvider._asDocumentHighlight); } }); } - private static _asDocumentHighlight(quickFix: proto.QuickFix): DocumentHighlight { + private static _asDocumentHighlight(quickFix: protocol.QuickFix): DocumentHighlight { return new DocumentHighlight(toRange(quickFix), DocumentHighlightKind.Read); } } diff --git a/src/features/documentSymbolProvider.ts b/src/features/documentSymbolProvider.ts index 9311631a66..0d220eaa38 100644 --- a/src/features/documentSymbolProvider.ts +++ b/src/features/documentSymbolProvider.ts @@ -6,16 +6,16 @@ 'use strict'; import AbstractSupport from './abstractProvider'; -import * as Protocol from '../protocol'; +import * as serverUtils from '../omnisharpUtils'; import {toDocumentSymbol} from '../typeConvertion'; -import {DocumentSymbolProvider, SymbolInformation, SymbolKind, Uri, TextDocument, Range, CancellationToken} from 'vscode'; +import {DocumentSymbolProvider, SymbolInformation, TextDocument, CancellationToken} from 'vscode'; export default class OmnisharpDocumentSymbolProvider extends AbstractSupport implements DocumentSymbolProvider { public provideDocumentSymbols(document: TextDocument, token: CancellationToken): Promise { - return this._server.makeRequest(Protocol.CurrentFileMembersAsTree, {Filename: document.fileName}, token).then(tree => { - var ret: SymbolInformation[] = []; + return serverUtils.currentFileMembersAsTree(this._server, { Filename: document.fileName }, token).then(tree => { + let ret: SymbolInformation[] = []; for (let node of tree.TopLevelTypeDefinitions) { toDocumentSymbol(ret, node); } diff --git a/src/features/documentation.ts b/src/features/documentation.ts index 46f25cd75b..7b955dc227 100644 --- a/src/features/documentation.ts +++ b/src/features/documentation.ts @@ -5,7 +5,7 @@ 'use strict'; -var _regExp = /<(\S*?).*?>((.|\r|\n)*?)<\/\1>/; +const _regExp = /<(\S*?).*?>((.|\r|\n)*?)<\/\1>/; /** * remove xml-tags from string @@ -16,7 +16,7 @@ export function plain(doc: string): string { return doc; } - var newDoc: string; + let newDoc: string; while (true) { newDoc = doc.replace(_regExp,(m, g1, g2, g3) => g2); diff --git a/src/features/formattingEditProvider.ts b/src/features/formattingEditProvider.ts index 38001dfe8f..d625c12b4e 100644 --- a/src/features/formattingEditProvider.ts +++ b/src/features/formattingEditProvider.ts @@ -6,14 +6,15 @@ 'use strict'; import AbstractSupport from './abstractProvider'; -import * as proto from '../protocol'; -import {Uri, DocumentRangeFormattingEditProvider, OnTypeFormattingEditProvider, FormattingOptions, CancellationToken, TextEdit, TextDocument, Range, Position} from 'vscode'; +import * as protocol from '../protocol'; +import * as serverUtils from '../omnisharpUtils'; +import {DocumentRangeFormattingEditProvider, FormattingOptions, CancellationToken, TextEdit, TextDocument, Range, Position} from 'vscode'; export default class FormattingSupport extends AbstractSupport implements DocumentRangeFormattingEditProvider { public provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): Promise { - let request = { + let request = { Filename: document.fileName, Line: range.start.line + 1, Column: range.start.character + 1, @@ -21,7 +22,7 @@ export default class FormattingSupport extends AbstractSupport implements Docume EndColumn: range.end.character + 1 }; - return this._server.makeRequest(proto.FormatRange, request, token).then(res => { + return serverUtils.formatRange(this._server, request, token).then(res => { if (res && Array.isArray(res.Changes)) { return res.Changes.map(FormattingSupport._asEditOptionation); } @@ -30,21 +31,21 @@ export default class FormattingSupport extends AbstractSupport implements Docume public provideOnTypeFormattingEdits(document: TextDocument, position: Position, ch: string, options: FormattingOptions, token: CancellationToken): Promise { - let request = { + let request = { Filename: document.fileName, Line: position.line + 1, Column: position.character + 1, Character: ch }; - return this._server.makeRequest(proto.FormatAfterKeystroke, request, token).then(res => { + return serverUtils.formatAfterKeystroke(this._server, request, token).then(res => { if (res && Array.isArray(res.Changes)) { return res.Changes.map(FormattingSupport._asEditOptionation); } }); } - private static _asEditOptionation(change: proto.TextChange): TextEdit { + private static _asEditOptionation(change: protocol.TextChange): TextEdit { return new TextEdit( new Range(change.StartLine - 1, change.StartColumn - 1, change.EndLine - 1, change.EndColumn - 1), change.NewText); diff --git a/src/features/hoverProvider.ts b/src/features/hoverProvider.ts index c5abb1c1a0..13ddcd6fce 100644 --- a/src/features/hoverProvider.ts +++ b/src/features/hoverProvider.ts @@ -7,18 +7,19 @@ import {plain} from './documentation'; import AbstractSupport from './abstractProvider'; -import * as Protocol from '../protocol'; +import * as protocol from '../protocol'; +import * as serverUtils from '../omnisharpUtils'; import {createRequest} from '../typeConvertion'; -import {HoverProvider, Hover, TextDocument, CancellationToken, Range, Position} from 'vscode'; +import {HoverProvider, Hover, TextDocument, CancellationToken, Position} from 'vscode'; export default class OmniSharpHoverProvider extends AbstractSupport implements HoverProvider { public provideHover(document: TextDocument, position: Position, token: CancellationToken): Promise { - let req = createRequest(document, position); + let req = createRequest(document, position); req.IncludeDocumentation = true; - return this._server.makeRequest(Protocol.TypeLookup, req, token).then(value => { + return serverUtils.typeLookup(this._server, req, token).then(value => { if (value && value.Type) { let contents = [plain(value.Documentation), { language: 'csharp', value: value.Type }]; return new Hover(contents); diff --git a/src/features/omnisharpStatus.ts b/src/features/omnisharpStatus.ts index 38176188ec..c32e4190e2 100644 --- a/src/features/omnisharpStatus.ts +++ b/src/features/omnisharpStatus.ts @@ -6,10 +6,10 @@ import * as vscode from 'vscode'; import {OmnisharpServer} from '../omnisharpServer'; -import {dnxRestoreForProject} from './commands'; +import {dotnetRestoreForProject} from './commands'; import {basename} from 'path'; -import * as proto from '../protocol'; - +import * as protocol from '../protocol'; +import * as serverUtils from '../omnisharpUtils'; export default function reportStatus(server: OmnisharpServer) { return vscode.Disposable.from( @@ -110,11 +110,11 @@ export function reportDocumentStatus(server: OmnisharpServer): vscode.Disposable render(); function updateProjectInfo() { - server.makeRequest(proto.Projects).then(info => { + serverUtils.requestWorkspaceInformation(server).then(info => { interface Project { Path: string; - SourceFiles: string[] + SourceFiles: string[]; } let fileNames: vscode.DocumentSelector[] = []; @@ -210,12 +210,12 @@ export function reportServerStatus(server: OmnisharpServer): vscode.Disposable{ let d2 = server.onMsBuildProjectDiagnostics(message => { - function asErrorMessage(message: proto.MSBuildDiagnosticsMessage) { + function asErrorMessage(message: protocol.MSBuildDiagnosticsMessage) { let value = `${message.FileName}(${message.StartLine},${message.StartColumn}): Error: ${message.Text}`; appendLine(value); } - function asWarningMessage(message: proto.MSBuildDiagnosticsMessage) { + function asWarningMessage(message: protocol.MSBuildDiagnosticsMessage) { let value = `${message.FileName}(${message.StartLine},${message.StartColumn}): Warning: ${message.Text}`; appendLine(value); } @@ -235,7 +235,7 @@ export function reportServerStatus(server: OmnisharpServer): vscode.Disposable{ return vscode.window.showInformationMessage(info, 'Restore').then(value => { if (value) { - dnxRestoreForProject(server, message.FileName); + dotnetRestoreForProject(server, message.FileName); } }); }); diff --git a/src/features/referenceProvider.ts b/src/features/referenceProvider.ts index 0815c8a837..f413a1dc85 100644 --- a/src/features/referenceProvider.ts +++ b/src/features/referenceProvider.ts @@ -6,19 +6,20 @@ 'use strict'; import AbstractSupport from './abstractProvider'; -import * as Protocol from '../protocol'; +import * as protocol from '../protocol'; +import * as serverUtils from '../omnisharpUtils'; import {createRequest, toLocation} from '../typeConvertion'; -import {ReferenceProvider, Location, Range, TextDocument, Uri, CancellationToken, Position} from 'vscode'; +import {ReferenceProvider, Location, TextDocument, CancellationToken, Position} from 'vscode'; export default class OmnisharpReferenceProvider extends AbstractSupport implements ReferenceProvider { public provideReferences(document: TextDocument, position: Position, options: { includeDeclaration: boolean;}, token: CancellationToken): Promise { - let req = createRequest(document, position); + let req = createRequest(document, position); req.OnlyThisFile = false; req.ExcludeDefinition = false; - return this._server.makeRequest(Protocol.FindUsages, req, token).then(res => { + return serverUtils.findUsages(this._server, req, token).then(res => { if (res && Array.isArray(res.QuickFixes)) { return res.QuickFixes.map(toLocation); } diff --git a/src/features/renameProvider.ts b/src/features/renameProvider.ts index 129a675d15..8db933cd6d 100644 --- a/src/features/renameProvider.ts +++ b/src/features/renameProvider.ts @@ -6,19 +6,20 @@ 'use strict'; import AbstractSupport from './abstractProvider'; -import * as proto from '../protocol'; -import {createRequest, toRange} from '../typeConvertion'; -import {RenameProvider, TextEdit, WorkspaceEdit, TextDocument, Uri, CancellationToken, Position, Range} from 'vscode'; +import * as protocol from '../protocol'; +import * as serverUtils from '../omnisharpUtils'; +import {createRequest} from '../typeConvertion'; +import {RenameProvider, WorkspaceEdit, TextDocument, Uri, CancellationToken, Position, Range} from 'vscode'; export default class OmnisharpRenameProvider extends AbstractSupport implements RenameProvider { public provideRenameEdits(document: TextDocument, position: Position, newName: string, token: CancellationToken): Promise { - let request = createRequest(document, position); - request.WantsTextChanges = true, - request.RenameTo = newName; + let req = createRequest(document, position); + req.WantsTextChanges = true; + req.RenameTo = newName; - return this._server.makeRequest(proto.Rename, request, token).then(response => { + return serverUtils.rename(this._server, req, token).then(response => { if (!response) { return; diff --git a/src/features/signatureHelpProvider.ts b/src/features/signatureHelpProvider.ts index eb673249c7..ca1cc99fd3 100644 --- a/src/features/signatureHelpProvider.ts +++ b/src/features/signatureHelpProvider.ts @@ -6,9 +6,9 @@ 'use strict'; import AbstractSupport from './abstractProvider'; -import * as Protocol from '../protocol'; +import * as serverUtils from '../omnisharpUtils'; import {createRequest} from '../typeConvertion'; -import {SignatureHelpProvider, SignatureHelp, SignatureInformation, ParameterInformation, Uri, CancellationToken, TextDocument, Position} from 'vscode'; +import {SignatureHelpProvider, SignatureHelp, SignatureInformation, ParameterInformation, CancellationToken, TextDocument, Position} from 'vscode'; export default class OmniSharpSignatureHelpProvider extends AbstractSupport implements SignatureHelpProvider { @@ -16,7 +16,11 @@ export default class OmniSharpSignatureHelpProvider extends AbstractSupport impl let req = createRequest(document, position); - return this._server.makeRequest(Protocol.SignatureHelp, req, token).then(res => { + return serverUtils.signatureHelp(this._server, req, token).then(res => { + + if (!res) { + return undefined; + } let ret = new SignatureHelp(); ret.activeSignature = res.ActiveSignature; diff --git a/src/features/workspaceSymbolProvider.ts b/src/features/workspaceSymbolProvider.ts index d144a3e1eb..5b8670071b 100644 --- a/src/features/workspaceSymbolProvider.ts +++ b/src/features/workspaceSymbolProvider.ts @@ -6,33 +6,31 @@ 'use strict'; import AbstractSupport from './abstractProvider'; -import * as Protocol from '../protocol'; -import {createRequest, toRange} from '../typeConvertion'; -import {CancellationToken, Uri, Range, WorkspaceSymbolProvider, SymbolInformation, SymbolKind} from 'vscode'; +import * as protocol from '../protocol'; +import * as serverUtils from '../omnisharpUtils'; +import {toRange} from '../typeConvertion'; +import {CancellationToken, Uri, WorkspaceSymbolProvider, SymbolInformation, SymbolKind} from 'vscode'; export default class OmnisharpWorkspaceSymbolProvider extends AbstractSupport implements WorkspaceSymbolProvider { public provideWorkspaceSymbols(search: string, token: CancellationToken): Promise { - return this._server.makeRequest(Protocol.FindSymbols, { - Filter: search, - Filename: '' - }, token).then(res => { + return serverUtils.findSymbols(this._server, { Filter: search, Filename: '' }, token).then(res => { if (res && Array.isArray(res.QuickFixes)) { return res.QuickFixes.map(OmnisharpWorkspaceSymbolProvider._asSymbolInformation); } }); } - private static _asSymbolInformation(symbolInfo: Protocol.SymbolLocation): SymbolInformation { + private static _asSymbolInformation(symbolInfo: protocol.SymbolLocation): SymbolInformation { return new SymbolInformation(symbolInfo.Text, OmnisharpWorkspaceSymbolProvider._toKind(symbolInfo), toRange(symbolInfo), Uri.file(symbolInfo.FileName)); } - private static _toKind(symbolInfo: Protocol.SymbolLocation): SymbolKind { + private static _toKind(symbolInfo: protocol.SymbolLocation): SymbolKind { switch (symbolInfo.Kind) { case 'Method': return SymbolKind.Method; diff --git a/src/launchTargetFinder.ts b/src/launchTargetFinder.ts index 1459af222f..1020f2e0d9 100644 --- a/src/launchTargetFinder.ts +++ b/src/launchTargetFinder.ts @@ -5,7 +5,6 @@ 'use strict'; import * as paths from 'path'; -import {EventEmitter} from 'events'; import {Uri, workspace} from 'vscode'; export interface LaunchTarget { @@ -34,7 +33,7 @@ function select(resources: Uri[], root: Uri): LaunchTarget[] { return []; } - var targets: LaunchTarget[] = [], + let targets: LaunchTarget[] = [], hasCsProjFiles = false, hasProjectJson = false, hasProjectJsonAtRoot = false; @@ -58,7 +57,7 @@ function select(resources: Uri[], root: Uri): LaunchTarget[] { // project.json files if (/project.json$/.test(resource.fsPath)) { - var dirname = paths.dirname(resource.fsPath); + const dirname = paths.dirname(resource.fsPath); hasProjectJson = true; hasProjectJsonAtRoot = hasProjectJsonAtRoot || dirname === root.fsPath; diff --git a/src/omnisharpDownload.ts b/src/omnisharpDownload.ts new file mode 100644 index 0000000000..dbe10610f9 --- /dev/null +++ b/src/omnisharpDownload.ts @@ -0,0 +1,120 @@ +/*--------------------------------------------------------------------------------------------- + * 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 tmp from 'tmp'; + +const Decompress = require('decompress'); +const Github = require('github-releases'); + +const OmnisharpRepo = 'OmniSharp/omnisharp-roslyn'; +const OmnisharpVersion = 'v1.9-alpha10'; +const DefaultInstallLocation = path.join(__dirname, '../.omnisharp'); + +tmp.setGracefulCleanup(); + +function getOmnisharpAssetName(): string { + switch (process.platform) { + case 'win32': + return 'omnisharp-win-x64-net451.zip'; + case 'darwin': + return 'omnisharp-osx-x64-netcoreapp1.0.tar.gz'; + case 'linux': + return 'omnisharp-linux-x64-netcoreapp1.0.tar.gz'; + default: + throw new Error(`Unsupported platform: ${process.platform}`); + } +} + +export function downloadOmnisharp(): Promise { + return new Promise((resolve, reject) => { + console.log(`[OmniSharp]: Installing to ${DefaultInstallLocation}`); + + const repo = new Github({ repo: OmnisharpRepo, token: null }); + const assetName = getOmnisharpAssetName(); + + console.log(`[OmniSharp] Looking for ${OmnisharpVersion}, ${assetName}...`); + + repo.getReleases({ tag_name: OmnisharpVersion }, (err, releases) => { + if (err) { + return reject(err); + } + + if (!releases.length) { + return reject(new Error(`OmniSharp release ${OmnisharpVersion} not found.`)); + } + + // Note: there should only be a single release, but use the first one + // if there are ever multiple results. Same thing for assets. + let foundAsset = null; + + for (var asset of releases[0].assets) { + if (asset.name === assetName) { + foundAsset = asset; + break; + } + } + + if (!foundAsset) { + return reject(new Error(`OmniSharp release ${OmnisharpVersion} asset, ${assetName} not found.`)); + } + + console.log(`[OmniSharp] Found it!`); + + repo.downloadAsset(foundAsset, (err, inStream) => { + if (err) { + return reject(err); + } + + tmp.file((err, tmpPath, fd, cleanupCallback) => { + if (err) { + return reject(err); + } + + console.log(`[OmniSharp] Downloading to ${tmpPath}...`); + + const outStream = fs.createWriteStream(null, { fd: fd }); + + outStream.once('error', err => reject(err)); + inStream.once('error', err => reject(err)); + + outStream.once('finish', () => { + // At this point, the asset has finished downloading. + + console.log(`[OmniSharp] Download complete!`); + + let decompress = new Decompress() + .src(tmpPath) + .dest(DefaultInstallLocation); + + if (path.extname(foundAsset.name).toLowerCase() === '.zip') { + decompress = decompress.use(Decompress.zip()); + console.log(`[OmniSharp] Unzipping...`); + } + else { + decompress = decompress.use(Decompress.targz()); + console.log(`[OmniSharp] Untaring...`); + } + + decompress.run((err, files) => { + if (err) { + return reject(err); + } + + console.log(`[OmniSharp] Done! ${files.length} files unpacked.`) + + return resolve(true); + }); + }); + + inStream.pipe(outStream); + }); + }); + }); + }); +} \ No newline at end of file diff --git a/src/omnisharpMain.ts b/src/omnisharpMain.ts index 72d0c9fba2..ec5e51e04b 100644 --- a/src/omnisharpMain.ts +++ b/src/omnisharpMain.ts @@ -22,64 +22,78 @@ import registerCommands from './features/commands'; import {StdioOmnisharpServer} from './omnisharpServer'; import forwardChanges from './features/changeForwarding'; import reportStatus from './features/omnisharpStatus'; -import findLaunchTargets from './launchTargetFinder'; -import {Disposable, ExtensionContext, DocumentSelector, languages, extensions} from 'vscode'; -import {installCoreClrDebug} from './coreclr-debug'; +import * as coreclrdebug from './coreclr-debug/main'; +import {addAssetsIfNecessary} from './assets'; +import * as vscode from 'vscode'; +import TelemetryReporter from 'vscode-extension-telemetry'; -export function activate(context: ExtensionContext): any { +export function activate(context: vscode.ExtensionContext): any { + + const extensionId = 'ms-vscode.csharp'; + const extension = vscode.extensions.getExtension(extensionId); + const extensionVersion = extension.packageJSON.version; + const aiKey = extension.packageJSON.contributes.debuggers[0].aiKey; + + const reporter = new TelemetryReporter(extensionId, extensionVersion, aiKey); - const _selector: DocumentSelector = { + const _selector: vscode.DocumentSelector = { language: 'csharp', scheme: 'file' // only files from disk }; - const server = new StdioOmnisharpServer(); + const server = new StdioOmnisharpServer(reporter); 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)); 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))); server.autoStart(context.workspaceState.get('lastSolutionPathOrFolder')); // stop server on deactivate - disposables.push(new Disposable(() => { + disposables.push(new vscode.Disposable(() => { advisor.dispose(); + server.reportAndClearTelemetry(); server.stop(); })); - + // install coreclr-debug - installCoreClrDebug(context); - + coreclrdebug.activate(context, reporter); + context.subscriptions.push(...disposables); } diff --git a/src/omnisharpPath.ts b/src/omnisharpPath.ts new file mode 100644 index 0000000000..03360a5c9f --- /dev/null +++ b/src/omnisharpPath.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; + +const runFileName = process.platform === 'win32' ? 'run.cmd' : 'run'; +const omnisharpFileName = process.platform === 'win32' ? 'omnisharp.cmd' : 'omnisharp'; +const omnisharpExeFileName = process.platform === 'win32' ? 'omnisharp.exe' : 'omnisharp'; + +function getLaunchFilePath(filePathOrFolder: string): Promise { + return fs.lstatAsync(filePathOrFolder).then(stats => { + // If a file path was passed, assume its the launch file. + if (stats.isFile()) { + return filePathOrFolder; + } + + // Otherwise, search the specified folder. + let candidate: string; + + candidate = path.join(filePathOrFolder, runFileName); + if (fs.existsSync(candidate)) { + return candidate; + } + + candidate = path.join(filePathOrFolder, omnisharpFileName); + if (fs.existsSync(candidate)) { + return candidate; + } + + candidate = path.join(filePathOrFolder, omnisharpExeFileName); + if (fs.existsSync(candidate)) { + return candidate; + } + + throw new Error(`Could not fnd launch file in ${filePathOrFolder}. Expected '${runFileName}', '${omnisharpFileName}', '${omnisharpExeFileName}'`); + }); +} + +function getLaunchPathFromSettings(): Promise { + const setting = vscode.workspace.getConfiguration('csharp').get('omnisharp'); + + if (setting) { + return getLaunchFilePath(setting) + .catch(err => { + vscode.window.showWarningMessage(`Invalid "csharp.omnisharp" use setting specified ('${setting}).`); + throw err; + }) + } + + return Promise.reject(new Error('OmniSharp use setting does not exists.')); +} + +function getLaunchPathFromDefaultInstallFolder(): Promise { + const installLocation = path.join(__dirname, '../.omnisharp'); + return getLaunchFilePath(installLocation); +} + +export function getOmnisharpLaunchFilePath(): Promise { + // Attempt to find launch file path first from settings, and then from the default install location. + + return getLaunchPathFromSettings() + .catch(getLaunchPathFromDefaultInstallFolder); +} \ No newline at end of file diff --git a/src/omnisharpServer.ts b/src/omnisharpServer.ts index c99701a8e1..a87c5e5e5a 100644 --- a/src/omnisharpServer.ts +++ b/src/omnisharpServer.ts @@ -7,14 +7,13 @@ import {EventEmitter} from 'events'; import {ChildProcess, exec} from 'child_process'; -import {request} from 'http'; import {dirname} from 'path'; import {ReadLine, createInterface} from 'readline'; import omnisharpLauncher from './omnisharpServerLauncher'; import {Disposable, CancellationToken, OutputChannel, workspace, window} from 'vscode'; import {ErrorMessage, UnresolvedDependenciesMessage, MSBuildProjectDiagnostics, ProjectInformationResponse} from './protocol'; import getLaunchTargets, {LaunchTarget} from './launchTargetFinder'; - +import TelemetryReporter from 'vscode-extension-telemetry'; enum ServerState { Starting, @@ -25,13 +24,89 @@ enum ServerState { interface Request { path: string; data?: any; - onSuccess: Function; - onError: Function; + onSuccess(value: any): void; + onError(err: any): void; _enqueued: number; } +module Events { + export const StateChanged = 'stateChanged'; + + export const StdOut = 'stdout'; + export const StdErr = 'stderr'; + + export const Error = 'Error'; + export const ServerError = 'ServerError'; + + export const UnresolvedDependencies = 'UnresolvedDependencies'; + export const PackageRestoreStarted = 'PackageRestoreStarted'; + export const PackageRestoreFinished = 'PackageRestoreFinished'; + + export const ProjectChanged = 'ProjectChanged'; + export const ProjectAdded = 'ProjectAdded'; + export const ProjectRemoved = 'ProjectRemoved'; + + export const MsBuildProjectDiagnostics = 'MsBuildProjectDiagnostics'; + + export const BeforeServerStart = 'BeforeServerStart'; + export const ServerStart = 'ServerStart'; + export const ServerStop = 'ServerStop'; + + export const MultipleLaunchTargets = 'server:MultipleLaunchTargets'; + + export const Started = 'started'; +} + +class Delays { + immediateDelays: number = 0; // 0-25 milliseconds + nearImmediateDelays: number = 0; // 26-50 milliseconds + shortDelays: number = 0; // 51-250 milliseconds + mediumDelays: number = 0; // 251-500 milliseconds + idleDelays: number = 0; // 501-1500 milliseconds + nonFocusDelays: number = 0; // 1501-3000 milliseconds + bigDelays: number = 0; // 3000+ milliseconds + + public report(elapsedTime: number) { + if (elapsedTime <= 25) { + this.immediateDelays += 1; + } + else if (elapsedTime <= 50) { + this.nearImmediateDelays += 1; + } + else if (elapsedTime <= 250) { + this.shortDelays += 1; + } + else if (elapsedTime <= 500) { + this.mediumDelays += 1; + } + else if (elapsedTime <= 1500) { + this.idleDelays += 1; + } + else if (elapsedTime <= 3000) { + this.nonFocusDelays += 1; + } + else { + this.bigDelays += 1; + } + } + + public toMeasures(): {[key: string]: number} { + return { + immedateDelays: this.immediateDelays, + nearImmediateDelays: this.nearImmediateDelays, + shortDelays: this.shortDelays, + mediumDelays: this.mediumDelays, + idleDelays: this.idleDelays, + nonFocusDelays: this.nonFocusDelays + }; + } +} + export abstract class OmnisharpServer { + private _reporter: TelemetryReporter; + private _requestDelays: { [requestName: string]: Delays }; + private _eventBus = new EventEmitter(); private _start: Promise; private _state: ServerState = ServerState.Stopped; @@ -43,9 +118,10 @@ export abstract class OmnisharpServer { protected _serverProcess: ChildProcess; protected _extraArgv: string[]; - constructor() { + constructor(reporter: TelemetryReporter) { this._extraArgv = []; this._channel = window.createOutputChannel('OmniSharp Log'); + this._reporter = reporter; } public isRunning(): boolean { @@ -59,9 +135,30 @@ export abstract class OmnisharpServer { private _setState(value: ServerState) : void { if (typeof value !== 'undefined' && value !== this._state) { this._state = value; - this._fireEvent('stateChanged', this._state); + this._fireEvent(Events.StateChanged, this._state); } } + + private _recordRequestDelay(requestName: string, elapsedTime: number) { + let delays = this._requestDelays[requestName]; + if (!delays) { + delays = new Delays(); + this._requestDelays[requestName] = delays; + } + + delays.report(elapsedTime); + } + + public reportAndClearTelemetry() { + for (var path in this._requestDelays) { + const eventName = 'omnisharp' + path; + const measures = this._requestDelays[path].toMeasures(); + + this._reporter.sendTelemetryEvent(eventName, null, measures); + } + + this._requestDelays = null; + } public getSolutionPathOrFolder(): string { return this._solutionPath; @@ -74,67 +171,67 @@ export abstract class OmnisharpServer { // --- eventing public onStdout(listener: (e: string) => any, thisArg?: any) { - return this._addListener('stdout', listener, thisArg); + return this._addListener(Events.StdOut, listener, thisArg); } public onStderr(listener: (e: string) => any, thisArg?: any) { - return this._addListener('stderr', listener, thisArg); + return this._addListener(Events.StdErr, listener, thisArg); } public onError(listener: (e: ErrorMessage) => any, thisArg?: any) { - return this._addListener('Error', listener, thisArg); + return this._addListener(Events.Error, listener, thisArg); } public onServerError(listener: (err: any) => any, thisArg?: any) { - return this._addListener('ServerError', listener, thisArg); + return this._addListener(Events.ServerError, listener, thisArg); } public onUnresolvedDependencies(listener: (e: UnresolvedDependenciesMessage) => any, thisArg?:any) { - return this._addListener('UnresolvedDependencies', listener, thisArg); + return this._addListener(Events.UnresolvedDependencies, listener, thisArg); } public onBeforePackageRestore(listener: () => any, thisArg?: any) { - return this._addListener('PackageRestoreStarted', listener, thisArg); + return this._addListener(Events.PackageRestoreStarted, listener, thisArg); } public onPackageRestore(listener: () => any, thisArg?: any) { - return this._addListener('PackageRestoreFinished', listener, thisArg); + return this._addListener(Events.PackageRestoreFinished, listener, thisArg); } public onProjectChange(listener: (e: ProjectInformationResponse) => any, thisArg?: any) { - return this._addListener('ProjectChanged', listener, thisArg); + return this._addListener(Events.ProjectChanged, listener, thisArg); } public onProjectAdded(listener: (e: ProjectInformationResponse) => any, thisArg?: any) { - return this._addListener('ProjectAdded', listener, thisArg); + return this._addListener(Events.ProjectAdded, listener, thisArg); } public onProjectRemoved(listener: (e: ProjectInformationResponse) => any, thisArg?: any) { - return this._addListener('ProjectRemoved', listener, thisArg); + return this._addListener(Events.ProjectRemoved, listener, thisArg); } public onMsBuildProjectDiagnostics(listener: (e: MSBuildProjectDiagnostics) => any, thisArg?: any) { - return this._addListener('MsBuildProjectDiagnostics', listener, thisArg); + return this._addListener(Events.MsBuildProjectDiagnostics, listener, thisArg); } public onBeforeServerStart(listener: (e:string) => any) { - return this._addListener('BeforeServerStart', listener); + return this._addListener(Events.BeforeServerStart, listener); } public onServerStart(listener: (e: string) => any) { - return this._addListener('ServerStart', listener); + return this._addListener(Events.ServerStart, listener); } public onServerStop(listener: () => any) { - return this._addListener('ServerStop', listener); + return this._addListener(Events.ServerStop, listener); } public onMultipleLaunchTargets(listener: (targets: LaunchTarget[]) => any, thisArg?: any) { - return this._addListener('server:MultipleLaunchTargets', listener, thisArg); + return this._addListener(Events.MultipleLaunchTargets, listener, thisArg); } public onOmnisharpStart(listener: () => any) { - return this._addListener('started', listener); + return this._addListener(Events.Started, listener); } private _addListener(event: string, listener: (e: any) => any, thisArg?: any): Disposable { @@ -161,22 +258,23 @@ export abstract class OmnisharpServer { this._setState(ServerState.Starting); this._solutionPath = solutionPath; - var cwd = dirname(solutionPath), + const cwd = dirname(solutionPath), argv = ['-s', solutionPath, '--hostPID', process.pid.toString(), 'dnx:enablePackageRestore=false'].concat(this._extraArgv); - this._fireEvent('stdout', `[INFO] Starting OmniSharp at '${solutionPath}'...\n`); - this._fireEvent('BeforeServerStart', solutionPath); + this._fireEvent(Events.StdOut, `[INFO] Starting OmniSharp at '${solutionPath}'...\n`); + this._fireEvent(Events.BeforeServerStart, solutionPath); return omnisharpLauncher(cwd, argv).then(value => { this._serverProcess = value.process; - this._fireEvent('stdout', `[INFO] Started OmniSharp from '${value.command}' with process id ${value.process.pid}...\n`); - this._fireEvent('ServerStart', solutionPath); + this._requestDelays = {}; + this._fireEvent(Events.StdOut, `[INFO] Started OmniSharp from '${value.command}' with process id ${value.process.pid}...\n`); this._setState(ServerState.Started); + this._fireEvent(Events.ServerStart, solutionPath); return this._doConnect(); }).then(_ => { this._processQueue(); }, err => { - this._fireEvent('ServerError', err); + this._fireEvent(Events.ServerError, err); throw err; }); } @@ -185,34 +283,38 @@ export abstract class OmnisharpServer { public stop(): Promise { - var ret: Promise; + let ret: Promise; if (!this._serverProcess) { // nothing to kill - ret = Promise.resolve(undefined); + ret = Promise.resolve(undefined); - } else if (/^win/.test(process.platform)) { + } + else if (process.platform === 'win32') { // when killing a process in windows its child // processes are *not* killed but become root // processes. Therefore we use TASKKILL.EXE ret = new Promise((resolve, reject) => { - var killer = exec(`taskkill /F /T /PID ${this._serverProcess.pid}`, function (err, stdout, stderr) { + const killer = exec(`taskkill /F /T /PID ${this._serverProcess.pid}`, function (err, stdout, stderr) { if (err) { return reject(err); } }); + killer.on('exit', resolve); killer.on('error', reject); }); - } else { + } + else { this._serverProcess.kill('SIGTERM'); - ret = Promise.resolve(undefined); + ret = Promise.resolve(undefined); } + return ret.then(_ => { this._start = null; this._serverProcess = null; this._setState(ServerState.Stopped); - this._fireEvent('ServerStop', this); + this._fireEvent(Events.ServerStop, this); return; }); } @@ -220,7 +322,7 @@ export abstract class OmnisharpServer { public restart(solutionPath: string = this._solutionPath): Promise { if (solutionPath) { return this.stop().then(() => { - this.start(solutionPath) + this.start(solutionPath); }); } } @@ -250,7 +352,7 @@ export abstract class OmnisharpServer { } } - this._fireEvent('server:MultipleLaunchTargets', targets); + this._fireEvent(Events.MultipleLaunchTargets, targets); return Promise.reject(undefined); } @@ -261,23 +363,28 @@ export abstract class OmnisharpServer { // --- requests et al - public makeRequest(path: string, data?: any, token?: CancellationToken): Promise { + public makeRequest(path: string, data?: any, token?: CancellationToken): Promise { if (this._getState() !== ServerState.Started) { - return Promise.reject('server has been stopped or not started'); + return Promise.reject('server has been stopped or not started'); } - + + let startTime: number; let request: Request; - let promise = new Promise((resolve, reject) => { + + let promise = new Promise((resolve, reject) => { + startTime = Date.now(); + request = { path, data, - onSuccess: resolve, - onError: reject, + onSuccess: value => resolve(value), + onError: err => reject(err), _enqueued: Date.now() }; + this._queue.push(request); - // this._statOnRequestStart(request); + if (this._getState() === ServerState.Started && !this._isProcessingQueue) { this._processQueue(); } @@ -295,7 +402,13 @@ export abstract class OmnisharpServer { }); } - return promise; + return promise.then(response => { + let endTime = Date.now(); + let elapsedTime = endTime - startTime; + this._recordRequestDelay(path, elapsedTime); + + return response; + }); } private _processQueue(): void { @@ -310,15 +423,13 @@ export abstract class OmnisharpServer { this._isProcessingQueue = true; // send next request and recurse when done - var thisRequest = this._queue.shift(); + const thisRequest = this._queue.shift(); this._makeNextRequest(thisRequest.path, thisRequest.data).then(value => { thisRequest.onSuccess(value); this._processQueue(); - // this._statOnRequestEnd(thisRequest, true); }, err => { thisRequest.onError(err); this._processQueue(); - // this._statOnRequestEnd(thisRequest, false); }).catch(err => { console.error(err); this._processQueue(); @@ -326,17 +437,6 @@ export abstract class OmnisharpServer { } protected abstract _makeNextRequest(path: string, data: any): Promise; - - // private _statOnRequestStart(request: Request): void { - // console.log(`[DEBUG] *enqueuing* request '${request.path}' (queue size is ${this._queue.length})\n`); - // } - - // private _statOnRequestEnd(request: Request, successfully: boolean): void { - // var duration = Date.now() - request._enqueued, - // state = successfully ? 'successfully' : 'with errors'; - - // console.log(`[DEBUG] request '${request.path}' finished *${state}* after ${duration}ms\n`); - // } } namespace WireProtocol { @@ -370,14 +470,13 @@ export class StdioOmnisharpServer extends OmnisharpServer { private static _seqPool = 1; private static StartupTimeout = 1000 * 60; - private static ResponsePacketTimeout = 1000 * 60 * 15; // helps debugging private _rl: ReadLine; private _activeRequest: { [seq: number]: { onSuccess: Function; onError: Function; } } = Object.create(null); private _callOnStop: Function[] = []; - constructor() { - super(); + constructor(reporter: TelemetryReporter) { + super(reporter); // extra argv this._extraArgv.push('--stdio'); @@ -400,18 +499,25 @@ export class StdioOmnisharpServer extends OmnisharpServer { terminal: false }); - var p = new Promise((resolve, reject) => { - var listener: Disposable; + const p = new Promise((resolve, reject) => { + let listener: Disposable; // timeout logic - var handle = setTimeout(() => { - listener && listener.dispose(); + const handle = setTimeout(() => { + if (listener) + { + listener.dispose(); + } + reject(new Error('Failed to start OmniSharp')); }, StdioOmnisharpServer.StartupTimeout); // handle started-event listener = this.onOmnisharpStart(() => { - listener && listener.dispose(); + if (listener) + { + listener.dispose(); + } clearTimeout(handle); resolve(this); }); @@ -424,16 +530,17 @@ export class StdioOmnisharpServer extends OmnisharpServer { private _startListening(): void { - var onLineReceived = (line: string) => { + const onLineReceived = (line: string) => { if (line[0] !== '{') { - this._fireEvent('stdout', `${line}\n`); + this._fireEvent(Events.StdOut, `${line}\n`); return; } - var packet: WireProtocol.Packet; + let packet: WireProtocol.Packet; try { packet = JSON.parse(line); - } catch (e) { + } + catch (e) { // not json return; } @@ -455,13 +562,14 @@ export class StdioOmnisharpServer extends OmnisharpServer { break; } }; + this._rl.addListener('line', onLineReceived); this._callOnStop.push(() => this._rl.removeListener('line', onLineReceived)); } private _handleResponsePacket(packet: WireProtocol.ResponsePacket): void { - var requestSeq = packet.Request_seq, + const requestSeq = packet.Request_seq, entry = this._activeRequest[requestSeq]; if (!entry) { @@ -482,8 +590,8 @@ export class StdioOmnisharpServer extends OmnisharpServer { if (packet.Event === 'log') { // handle log events - var entry = <{ LogLevel: string; Name: string; Message: string; }> packet.Body; - this._fireEvent('stdout', `[${entry.LogLevel}:${entry.Name}] ${entry.Message}\n`); + const entry = <{ LogLevel: string; Name: string; Message: string; }> packet.Body; + this._fireEvent(Events.StdOut, `[${entry.LogLevel}:${entry.Name}] ${entry.Message}\n`); return; } else { // fwd all other events @@ -493,18 +601,18 @@ export class StdioOmnisharpServer extends OmnisharpServer { protected _makeNextRequest(path: string, data: any): Promise { - var thisRequestPacket: WireProtocol.RequestPacket = { + const thisRequestPacket: WireProtocol.RequestPacket = { Type: 'request', Seq: StdioOmnisharpServer._seqPool++, Command: path, Arguments: data }; - return new Promise((c, e) => { + return new Promise((resolve, reject) => { this._activeRequest[thisRequestPacket.Seq] = { - onSuccess: c, - onError: e + onSuccess: value => resolve(value), + onError: err => reject(err) }; this._serverProcess.stdin.write(JSON.stringify(thisRequestPacket) + '\n'); diff --git a/src/omnisharpServerLauncher.ts b/src/omnisharpServerLauncher.ts index 618f26b71f..98838dbd3c 100644 --- a/src/omnisharpServerLauncher.ts +++ b/src/omnisharpServerLauncher.ts @@ -10,13 +10,27 @@ import {spawn, ChildProcess} from 'child_process'; import {workspace} from 'vscode'; import {satisfies} from 'semver'; import {join} from 'path'; +import {getOmnisharpLaunchFilePath} from './omnisharpPath'; +import {downloadOmnisharp} from './omnisharpDownload'; -var omnisharpEnv = 'OMNISHARP'; -var isWindows = /^win/.test(process.platform); +const isWindows = process.platform === 'win32'; -export default function launch(cwd: string, args: string[]):Promise < { process: ChildProcess, command: string } > { +export interface LaunchResult { + process: ChildProcess; + command: string; +} + +export function installOmnisharpIfNeeded(): Promise { + return getOmnisharpLaunchFilePath().catch(err => { + return downloadOmnisharp().then(_ => { + return getOmnisharpLaunchFilePath(); + }) + }); +} - return new Promise((resolve, reject) => { +export default function launch(cwd: string, args: string[]): Promise { + + return new Promise((resolve, reject) => { try { (isWindows ? launchWindows(cwd, args) : launchNix(cwd, args)).then(value => { @@ -38,8 +52,8 @@ export default function launch(cwd: string, args: string[]):Promise < { process: }); } -function launchWindows(cwd: string, args: string[]): Promise<{ process: ChildProcess, command: string }> { - return getOmnisharpPath().then(command => { +function launchWindows(cwd: string, args: string[]): Promise { + return installOmnisharpIfNeeded().then(command => { args = args.slice(0); args.unshift(command); @@ -63,19 +77,9 @@ function launchWindows(cwd: string, args: string[]): Promise<{ process: ChildPro }); } -function launchNix(cwd: string, args: string[]): Promise<{ process: ChildProcess, command: string }>{ - - return new Promise((resolve, reject) => { - hasMono('>=4.0.1').then(hasIt => { - if (!hasIt) { - reject(new Error('Cannot start Omnisharp because Mono version >=4.0.1 is required. See http://go.microsoft.com/fwlink/?linkID=534832#_20001')); - } else { - resolve(); - } - }); - }).then(_ => { - return getOmnisharpPath(); - }).then(command => { +function launchNix(cwd: string, args: string[]): Promise{ + + return installOmnisharpIfNeeded().then(command => { let process = spawn(command, args, { detached: false, // env: details.env, @@ -85,41 +89,31 @@ function launchNix(cwd: string, args: string[]): Promise<{ process: ChildProcess return { process, command - } - }); -} - -function getOmnisharpPath(): Promise { - - let pathCandidate: string; - - let config = workspace.getConfiguration(); - if (config.has('csharp.omnisharp')) { - // form config - pathCandidate = config.get('csharp.omnisharp'); - - } else if (typeof process.env[omnisharpEnv] === 'string') { - // form enviroment variable - console.warn('[deprecated] use workspace or user settings with "csharp.omnisharp":"/path/to/omnisharp"'); - pathCandidate = process.env[omnisharpEnv]; - - } else { - // bundled version of Omnisharp - pathCandidate = join(__dirname, '../bin/omnisharp') - if (isWindows) { - pathCandidate += '.cmd'; - } - } - - return new Promise((resolve, reject) => { - fileExists(pathCandidate, localExists => { - if (localExists) { - resolve(pathCandidate); - } else { - reject('OmniSharp does not exist at location: ' + pathCandidate); - } - }); - }); + }; + }); + + // return new Promise((resolve, reject) => { + // hasMono('>=4.0.1').then(hasIt => { + // if (!hasIt) { + // reject(new Error('Cannot start Omnisharp because Mono version >=4.0.1 is required. See http://go.microsoft.com/fwlink/?linkID=534832#_20001')); + // } else { + // resolve(); + // } + // }); + // }).then(_ => { + // return installOmnisharpIfNeeded(); + // }).then(command => { + // let process = spawn(command, args, { + // detached: false, + // // env: details.env, + // cwd + // }); + + // return { + // process, + // command + // }; + // }); } const versionRegexp = /(\d+\.\d+\.\d+)/; diff --git a/src/omnisharpUtils.ts b/src/omnisharpUtils.ts new file mode 100644 index 0000000000..409a51e239 --- /dev/null +++ b/src/omnisharpUtils.ts @@ -0,0 +1,75 @@ +/*--------------------------------------------------------------------------------------------- + * 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 {OmnisharpServer} from './omnisharpServer'; +import * as protocol from './protocol'; +import * as vscode from 'vscode'; + +export function autoComplete(server: OmnisharpServer, request: protocol.AutoCompleteRequest) { + return server.makeRequest(protocol.Requests.AutoComplete, request); +} + +export function codeCheck(server: OmnisharpServer, request: protocol.Request, token: vscode.CancellationToken) { + return server.makeRequest(protocol.Requests.CodeCheck, request, token); +} + +export function currentFileMembersAsTree(server: OmnisharpServer, request: protocol.Request, token: vscode.CancellationToken) { + return server.makeRequest(protocol.Requests.CurrentFileMembersAsTree, request, token); +} + +export function filesChanged(server: OmnisharpServer, requests: protocol.Request[]) { + return server.makeRequest(protocol.Requests.FilesChanged, requests); +} + +export function findSymbols(server: OmnisharpServer, request: protocol.FindSymbolsRequest, token: vscode.CancellationToken) { + return server.makeRequest(protocol.Requests.FindSymbols, request, token); +} + +export function findUsages(server: OmnisharpServer, request: protocol.FindUsagesRequest, token: vscode.CancellationToken) { + return server.makeRequest(protocol.Requests.FindUsages, request, token); +} + +export function formatAfterKeystroke(server: OmnisharpServer, request: protocol.FormatAfterKeystrokeRequest, token: vscode.CancellationToken) { + return server.makeRequest(protocol.Requests.FormatAfterKeystroke, request, token); +} + +export function formatRange(server: OmnisharpServer, request: protocol.FormatRangeRequest, token: vscode.CancellationToken) { + return server.makeRequest(protocol.Requests.FormatRange, request, token); +} + +export function getCodeActions(server: OmnisharpServer, request: protocol.V2.GetCodeActionsRequest, token: vscode.CancellationToken) { + return server.makeRequest(protocol.V2.Requests.GetCodeActions, request, token); +} + +export function goToDefinition(server: OmnisharpServer, request: protocol.Request, token: vscode.CancellationToken) { + return server.makeRequest(protocol.Requests.GoToDefinition, request); +} + +export function rename(server: OmnisharpServer, request: protocol.RenameRequest, token: vscode.CancellationToken) { + return server.makeRequest(protocol.Requests.Rename, request, token); +} + +export function requestWorkspaceInformation(server: OmnisharpServer) { + return server.makeRequest(protocol.Requests.Projects); +} + +export function runCodeAction(server: OmnisharpServer, request: protocol.V2.RunCodeActionRequest) { + return server.makeRequest(protocol.V2.Requests.RunCodeAction, request); +} + +export function signatureHelp(server: OmnisharpServer, request: protocol.Request, token: vscode.CancellationToken) { + return server.makeRequest(protocol.Requests.SignatureHelp, request, token); +} + +export function typeLookup(server: OmnisharpServer, request: protocol.TypeLookupRequest, token: vscode.CancellationToken) { + return server.makeRequest(protocol.Requests.TypeLookup, request, token); +} + +export function updateBuffer(server: OmnisharpServer, request: protocol.UpdateBufferRequest) { + return server.makeRequest(protocol.Requests.UpdateBuffer, request); +} + diff --git a/src/pathHelpers.ts b/src/pathHelpers.ts deleted file mode 100644 index 0c8b900a59..0000000000 --- a/src/pathHelpers.ts +++ /dev/null @@ -1,60 +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'; -import * as path from 'path'; - -export enum PathKind { - File, - Directory -} - -export function getPathKind(path: string): Promise { - return new Promise((resolve, reject) => - { - fs.lstat(path, (err, stats) => { - if (err) { - reject(err); - } - else if (stats.isFile()) { - resolve(PathKind.File); - } - else if (stats.isDirectory()) { - resolve(PathKind.Directory); - } - else { - reject(Error(`Path is not file or directory: ${path}`)); - } - }); - }); -} - -export function exists(path: string) { - return new Promise((resolve, reject) => { - fs.exists(path, exists => { - if (exists) { - resolve(true); - } - else { - resolve(false); - } - }) - }) -} - -export function mkdir(directoryPath: string) { - return new Promise((resolve, reject) => { - fs.mkdir(directoryPath, err => { - if (!err) { - resolve(true); - } - else { - reject(err); - } - }) - }) -} \ No newline at end of file diff --git a/src/protocol.ts b/src/protocol.ts index fcc83c0670..959d05a17d 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -5,51 +5,47 @@ 'use strict'; -export var GoToDefinition = '/gotoDefinition'; - -export var CodeCheck = '/codecheck'; - -export var AutoComplete = '/autocomplete'; - -export var CurrentFileMembersAsTree = '/currentfilemembersastree'; - -export var TypeLookup = '/typelookup'; - -export var AddToProject = '/addtoproject'; - -export var RemoveFromProject = '/removefromproject'; - -export var FindUsages = '/findusages'; - -export var FindSymbols = '/findsymbols'; - -export var CodeFormat = '/codeformat'; - -export var GetCodeActions = '/getcodeactions'; - -export var RunCodeAction = '/runcodeaction'; - -export var FormatAfterKeystroke = '/formatAfterKeystroke'; - -export var FormatRange = '/formatRange'; - -export var UpdateBuffer = '/updatebuffer'; - -export var ChangeBuffer = '/changebuffer'; - -export var Projects = '/projects'; - -export var Rename = '/rename'; - -export var FilesChanged = '/filesChanged'; - -export var SignatureHelp = '/signatureHelp'; +export module Requests { + export const AddToProject = '/addtoproject'; + export const AutoComplete = '/autocomplete'; + export const CodeCheck = '/codecheck'; + export const CodeFormat = '/codeformat'; + export const ChangeBuffer = '/changebuffer'; + export const CurrentFileMembersAsTree = '/currentfilemembersastree'; + export const FilesChanged = '/filesChanged'; + export const FindSymbols = '/findsymbols'; + export const FindUsages = '/findusages'; + export const FormatAfterKeystroke = '/formatAfterKeystroke'; + export const FormatRange = '/formatRange'; + export const GetCodeActions = '/getcodeactions'; + export const GoToDefinition = '/gotoDefinition'; + export const Projects = '/projects'; + export const RemoveFromProject = '/removefromproject'; + export const Rename = '/rename'; + export const RunCodeAction = '/runcodeaction'; + export const SignatureHelp = '/signatureHelp'; + export const TypeLookup = '/typelookup'; + export const UpdateBuffer = '/updatebuffer'; +} export interface Request { Filename: string; Line?: number; Column?: number; Buffer?: string; + Changes?: LinePositionSpanTextChange[]; +} + +export interface LinePositionSpanTextChange { + NewText: string; + StartLine: number; + StartColumn: number; + EndLine: number; + EndColumn: number; +} + +export interface UpdateBufferRequest extends Request { + FromDisk?: boolean; } export interface ChangeBufferRequest { @@ -267,13 +263,32 @@ export interface DnxFramework { export interface DotNetWorkspaceInformation { Projects: DotNetProject[]; + RuntimePath: string; } export interface DotNetProject { Path: string; + Name: string; + ProjectSearchPaths: string[]; + Configurations: DotNetConfiguration[]; + Frameworks: DotNetFramework[]; SourceFiles: string[]; } +export interface DotNetConfiguration { + Name: string; + CompilationOutputPath: string; + CompilationOutputAssemblyFile: string; + CompilationOutputPdbFile: string; + EmitEntryPoint?: boolean; +} + +export interface DotNetFramework { + Name: string; + FriendlyName: string; + ShortName: string; +} + export interface RenameRequest extends Request { RenameTo: string; WantsTextChanges?: boolean; @@ -347,9 +362,11 @@ export interface PackageDependency { } export namespace V2 { - - export var GetCodeActions = '/v2/getcodeactions'; - export var RunCodeAction = '/v2/runcodeaction'; + + export module Requests { + export const GetCodeActions = '/v2/getcodeactions'; + export const RunCodeAction = '/v2/runcodeaction'; + } export interface Point { Line: number; @@ -362,7 +379,7 @@ export namespace V2 { } export interface GetCodeActionsRequest extends Request { - Selection: Range + Selection: Range; } export interface OmniSharpCodeAction { @@ -422,5 +439,4 @@ export namespace V2 { Name: string; Version: string; } - } \ No newline at end of file diff --git a/src/typeConvertion.ts b/src/typeConvertion.ts index aa81c426ab..36a5eb6c76 100644 --- a/src/typeConvertion.ts +++ b/src/typeConvertion.ts @@ -22,16 +22,16 @@ export function toRange2(rangeLike: { StartLine: number; StartColumn: number; En return new vscode.Range(StartLine - 1, StartColumn - 1, EndLine - 1, EndColumn - 1); } -export function createRequest(document: vscode.TextDocument, where: vscode.Position | vscode.Range, includeBuffer:boolean = false): T { +export function createRequest(document: vscode.TextDocument, where: vscode.Position | vscode.Range, includeBuffer: boolean = false): T { let Line: number, Column: number; if (where instanceof vscode.Position) { Line = where.line + 1; Column = where.character + 1; - } else if(where instanceof vscode.Range) { + } else if (where instanceof vscode.Range) { Line = where.start.line + 1; - Column = where.start.character + 1 + Column = where.start.character + 1; } let request: proto.Request = { @@ -52,13 +52,13 @@ export function toDocumentSymbol(bucket: vscode.SymbolInformation[], node: proto if (node.ChildNodes) { for (let child of node.ChildNodes) { - toDocumentSymbol(bucket, child, ret.name) + toDocumentSymbol(bucket, child, ret.name); } } bucket.push(ret); } -var kinds: { [kind: string]: vscode.SymbolKind; } = Object.create(null); +let kinds: { [kind: string]: vscode.SymbolKind; } = Object.create(null); kinds['NamespaceDeclaration'] = vscode.SymbolKind.Namespace; kinds['ClassDeclaration'] = vscode.SymbolKind.Class; kinds['FieldDeclaration'] = vscode.SymbolKind.Field; diff --git a/src/typings/bluebird/bluebird.d.ts b/src/typings/bluebird/bluebird.d.ts new file mode 100644 index 0000000000..75f653440f --- /dev/null +++ b/src/typings/bluebird/bluebird.d.ts @@ -0,0 +1,781 @@ +// Type definitions for bluebird 2.0.0 +// Project: https://github.com/petkaantonov/bluebird +// Definitions by: Bart van der Schoor , falsandtru + +// ES6 model with generics overload was sourced and trans-multiplied from es6-promises.d.ts +// By: Campredon + +// Warning: recommended to use `tsc > v0.9.7` (critical bugs in earlier generic code): +// - https://github.com/borisyankov/DefinitelyTyped/issues/1563 + +// Note: replicate changes to all overloads in both definition and test file +// Note: keep both static and instance members inline (so similar) + +// TODO fix remaining TODO annotations in both definition and test + +// TODO verify support to have no return statement in handlers to get a Promise (more overloads?) + +declare var Promise: PromiseConstructor; + +interface PromiseConstructor { + /** + * Create a new promise. The passed in function will receive functions `resolve` and `reject` as its arguments which can be called to seal the fate of the created promise. + */ + new (callback: (resolve: (thenableOrResult?: T | PromiseLike) => void, reject: (error: any) => void) => void): Promise; + + // Ideally, we'd define e.g. "export class RangeError extends Error {}", + // but as Error is defined as an interface (not a class), TypeScript doesn't + // allow extending Error, only implementing it. + // However, if we want to catch() only a specific error type, we need to pass + // a constructor function to it. So, as a workaround, we define them here as such. + RangeError(): RangeError; + CancellationError(): Promise.CancellationError; + TimeoutError(): Promise.TimeoutError; + TypeError(): Promise.TypeError; + RejectionError(): Promise.RejectionError; + OperationalError(): Promise.OperationalError; + + /** + * Changes how bluebird schedules calls a-synchronously. + * + * @param scheduler Should be a function that asynchronously schedules + * the calling of the passed in function + */ + setScheduler(scheduler: (callback: (...args: any[]) => void) => void): void; + + /** + * Start the chain of promises with `Promise.try`. Any synchronous exceptions will be turned into rejections on the returned promise. + * + * Note about second argument: if it's specifically a true array, its values become respective arguments for the function call. Otherwise it is passed as is as the first argument for the function call. + * + * Alias for `attempt();` for compatibility with earlier ECMAScript version. + */ + try(fn: () => PromiseLike, args?: any[], ctx?: any): Promise; + try(fn: () => T, args?: any[], ctx?: any): Promise; + + attempt(fn: () => PromiseLike, args?: any[], ctx?: any): Promise; + attempt(fn: () => T, args?: any[], ctx?: any): Promise; + + /** + * Returns a new function that wraps the given function `fn`. The new function will always return a promise that is fulfilled with the original functions return values or rejected with thrown exceptions from the original function. + * This method is convenient when a function can sometimes return synchronously or throw synchronously. + */ + method(fn: Function): Function; + + /** + * Create a promise that is resolved with the given `value`. If `value` is a thenable or promise, the returned promise will assume its state. + */ + resolve(): Promise; + resolve(value: PromiseLike): Promise; + resolve(value: T): Promise; + + /** + * Create a promise that is rejected with the given `reason`. + */ + reject(reason: any): Promise; + reject(reason: any): Promise; + + /** + * Create a promise with undecided fate and return a `PromiseResolver` to control it. See resolution?: Promise(#promise-resolution). + */ + defer(): Promise.Resolver; + + /** + * Cast the given `value` to a trusted promise. If `value` is already a trusted `Promise`, it is returned as is. If `value` is not a thenable, a fulfilled is: Promise returned with `value` as its fulfillment value. If `value` is a thenable (Promise-like object, like those returned by jQuery's `$.ajax`), returns a trusted that: Promise assimilates the state of the thenable. + */ + cast(value: PromiseLike): Promise; + cast(value: T): Promise; + + /** + * Sugar for `Promise.resolve(undefined).bind(thisArg);`. See `.bind()`. + */ + bind(thisArg: any): Promise; + + /** + * See if `value` is a trusted Promise. + */ + is(value: any): boolean; + + /** + * Call this right after the library is loaded to enabled long stack traces. Long stack traces cannot be disabled after being enabled, and cannot be enabled after promises have alread been created. Long stack traces imply a substantial performance penalty, around 4-5x for throughput and 0.5x for latency. + */ + longStackTraces(): void; + + /** + * Returns a promise that will be fulfilled with `value` (or `undefined`) after given `ms` milliseconds. If `value` is a promise, the delay will start counting down when it is fulfilled and the returned promise will be fulfilled with the fulfillment value of the `value` promise. + */ + // TODO enable more overloads + delay(ms: number, value: PromiseLike): Promise; + delay(ms: number, value: T): Promise; + delay(ms: number): Promise; + + /** + * Returns a function that will wrap the given `nodeFunction`. Instead of taking a callback, the returned function will return a promise whose fate is decided by the callback behavior of the given node function. The node function should conform to node.js convention of accepting a callback as last argument and calling that callback with error as the first argument and success value on the second argument. + * + * If the `nodeFunction` calls its callback with multiple success values, the fulfillment value will be an array of them. + * + * If you pass a `receiver`, the `nodeFunction` will be called as a method on the `receiver`. + */ + promisify(func: (callback: (err: any, result: T) => void) => void, receiver?: any): () => Promise; + promisify(func: (arg1: A1, callback: (err: any, result: T) => void) => void, receiver?: any): (arg1: A1) => Promise; + promisify(func: (arg1: A1, arg2: A2, callback: (err: any, result: T) => void) => void, receiver?: any): (arg1: A1, arg2: A2) => Promise; + promisify(func: (arg1: A1, arg2: A2, arg3: A3, callback: (err: any, result: T) => void) => void, receiver?: any): (arg1: A1, arg2: A2, arg3: A3) => Promise; + promisify(func: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, callback: (err: any, result: T) => void) => void, receiver?: any): (arg1: A1, arg2: A2, arg3: A3, arg4: A4) => Promise; + promisify(func: (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, callback: (err: any, result: T) => void) => void, receiver?: any): (arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) => Promise; + promisify(nodeFunction: Function, receiver?: any): Function; + + /** + * Promisifies the entire object by going through the object's properties and creating an async equivalent of each function on the object and its prototype chain. The promisified method name will be the original method name postfixed with `Async`. Returns the input object. + * + * Note that the original methods on the object are not overwritten but new methods are created with the `Async`-postfix. For example, if you `promisifyAll()` the node.js `fs` object use `fs.statAsync()` to call the promisified `stat` method. + */ + // TODO how to model promisifyAll? + promisifyAll(target: Object, options?: Promise.PromisifyAllOptions): any; + + + /** + * Returns a promise that is resolved by a node style callback function. + */ + fromNode(resolver: (callback: (err: any, result?: any) => void) => void, options? : {multiArgs? : boolean}): Promise; + fromCallback(resolver: (callback: (err: any, result?: any) => void) => void, options? : {multiArgs? : boolean}): Promise; + + /** + * Returns a function that can use `yield` to run asynchronous code synchronously. This feature requires the support of generators which are drafted in the next version of the language. Node version greater than `0.11.2` is required and needs to be executed with the `--harmony-generators` (or `--harmony`) command-line switch. + */ + // TODO fix coroutine GeneratorFunction + coroutine(generatorFunction: Function): Function; + + /** + * Spawn a coroutine which may yield promises to run asynchronous code synchronously. This feature requires the support of generators which are drafted in the next version of the language. Node version greater than `0.11.2` is required and needs to be executed with the `--harmony-generators` (or `--harmony`) command-line switch. + */ + // TODO fix spawn GeneratorFunction + spawn(generatorFunction: Function): Promise; + + /** + * This is relevant to browser environments with no module loader. + * + * Release control of the `Promise` namespace to whatever it was before this library was loaded. Returns a reference to the library namespace so you can attach it to something else. + */ + noConflict(): typeof Promise; + + /** + * Add `handler` as the handler to call when there is a possibly unhandled rejection. The default handler logs the error stack to stderr or `console.error` in browsers. + * + * Passing no value or a non-function will have the effect of removing any kind of handling for possibly unhandled rejections. + */ + onPossiblyUnhandledRejection(handler: (reason: any) => any): void; + + /** + * Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled when all the items in the array are fulfilled. The promise's fulfillment value is an array with fulfillment values at respective positions to the original array. If any promise in the array rejects, the returned promise is rejected with the rejection reason. + */ + // TODO enable more overloads + // promise of array with promises of value + all(values: PromiseLike[]>): Promise; + // promise of array with values + all(values: PromiseLike): Promise; + // array with promises of value + all(values: PromiseLike[]): Promise; + // array with promises of different types + all(values: [PromiseLike, PromiseLike]): Promise<[T1, T2]>; + all(values: [PromiseLike, PromiseLike, PromiseLike]): Promise<[T1, T2, T3]>; + all(values: [PromiseLike, PromiseLike, PromiseLike, PromiseLike]): Promise<[T1, T2, T3, T4]>; + all(values: [PromiseLike, PromiseLike, PromiseLike, PromiseLike, PromiseLike]): Promise<[T1, T2, T3, T4, T5]>; + // array with values + all(values: T[]): Promise; + + /** + * Like ``Promise.all`` but for object properties instead of array items. Returns a promise that is fulfilled when all the properties of the object are fulfilled. The promise's fulfillment value is an object with fulfillment values at respective keys to the original object. If any promise in the object rejects, the returned promise is rejected with the rejection reason. + * + * If `object` is a trusted `Promise`, then it will be treated as a promise for object rather than for its properties. All other objects are treated for their properties as is returned by `Object.keys` - the object's own enumerable properties. + * + * *The original object is not modified.* + */ + // TODO verify this is correct + // trusted promise for object + props(object: Promise): Promise; + // object + props(object: Object): Promise; + + /** + * Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled when all the items in the array are either fulfilled or rejected. The fulfillment value is an array of ``PromiseInspection`` instances at respective positions in relation to the input array. + * + * *original: The array is not modified. The input array sparsity is retained in the resulting array.* + */ + // promise of array with promises of value + settle(values: PromiseLike[]>): Promise[]>; + // promise of array with values + settle(values: PromiseLike): Promise[]>; + // array with promises of value + settle(values: PromiseLike[]): Promise[]>; + // array with values + settle(values: T[]): Promise[]>; + + /** + * Like `Promise.some()`, with 1 as `count`. However, if the promise fulfills, the fulfillment value is not an array of 1 but the value directly. + */ + // promise of array with promises of value + any(values: PromiseLike[]>): Promise; + // promise of array with values + any(values: PromiseLike): Promise; + // array with promises of value + any(values: PromiseLike[]): Promise; + // array with values + any(values: T[]): Promise; + + /** + * Given an array, or a promise of an array, which contains promises (or a mix of promises and values) return a promise that is fulfilled or rejected as soon as a promise in the array is fulfilled or rejected with the respective rejection reason or fulfillment value. + * + * **Note** If you pass empty array or a sparse array with no values, or a promise/thenable for such, it will be forever pending. + */ + // promise of array with promises of value + race(values: PromiseLike[]>): Promise; + // promise of array with values + race(values: PromiseLike): Promise; + // array with promises of value + race(values: PromiseLike[]): Promise; + // array with values + race(values: T[]): Promise; + + /** + * Initiate a competetive race between multiple promises or values (values will become immediately fulfilled promises). When `count` amount of promises have been fulfilled, the returned promise is fulfilled with an array that contains the fulfillment values of the winners in order of resolution. + * + * If too many promises are rejected so that the promise can never become fulfilled, it will be immediately rejected with an array of rejection reasons in the order they were thrown in. + * + * *The original array is not modified.* + */ + // promise of array with promises of value + some(values: PromiseLike[]>, count: number): Promise; + // promise of array with values + some(values: PromiseLike, count: number): Promise; + // array with promises of value + some(values: PromiseLike[], count: number): Promise; + // array with values + some(values: T[], count: number): Promise; + + /** + * Like `Promise.all()` but instead of having to pass an array, the array is generated from the passed variadic arguments. + */ + // variadic array with promises of value + join(...values: PromiseLike[]): Promise; + // variadic array with values + join(...values: T[]): Promise; + + /** + * Map an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `mapper` function with the signature `(item, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well. + * + * If the `mapper` function returns promises or thenables, the returned promise will wait for all the mapped results to be resolved as well. + * + * *The original array is not modified.* + */ + // promise of array with promises of value + map(values: PromiseLike[]>, mapper: (item: T, index: number, arrayLength: number) => PromiseLike, options?: Promise.ConcurrencyOption): Promise; + map(values: PromiseLike[]>, mapper: (item: T, index: number, arrayLength: number) => U, options?: Promise.ConcurrencyOption): Promise; + + // promise of array with values + map(values: PromiseLike, mapper: (item: T, index: number, arrayLength: number) => PromiseLike, options?: Promise.ConcurrencyOption): Promise; + map(values: PromiseLike, mapper: (item: T, index: number, arrayLength: number) => U, options?: Promise.ConcurrencyOption): Promise; + + // array with promises of value + map(values: PromiseLike[], mapper: (item: T, index: number, arrayLength: number) => PromiseLike, options?: Promise.ConcurrencyOption): Promise; + map(values: PromiseLike[], mapper: (item: T, index: number, arrayLength: number) => U, options?: Promise.ConcurrencyOption): Promise; + + // array with values + map(values: T[], mapper: (item: T, index: number, arrayLength: number) => PromiseLike, options?: Promise.ConcurrencyOption): Promise; + map(values: T[], mapper: (item: T, index: number, arrayLength: number) => U, options?: Promise.ConcurrencyOption): Promise; + + /** + * Similar to `map` with concurrency set to 1 but guaranteed to execute in sequential order + * + * If the `mapper` function returns promises or thenables, the returned promise will wait for all the mapped results to be resolved as well. + * + * *The original array is not modified.* + */ + // promise of array with promises of value + mapSeries(values: PromiseLike[]>, mapper: (item: R, index: number, arrayLength: number) => U | PromiseLike): Promise; + + // promise of array with values + mapSeries(values: PromiseLike, mapper: (item: R, index: number, arrayLength: number) => U | PromiseLike): Promise; + + // array with promises of value + mapSeries(values: PromiseLike[], mapper: (item: R, index: number, arrayLength: number) => U | PromiseLike): Promise; + + // array with values + mapSeries(values: R[], mapper: (item: R, index: number, arrayLength: number) => U | PromiseLike): Promise; + + + /** + * Reduce an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `reducer` function with the signature `(total, current, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well. + * + * If the reducer function returns a promise or a thenable, the result for the promise is awaited for before continuing with next iteration. + * + * *The original array is not modified. If no `intialValue` is given and the array doesn't contain at least 2 items, the callback will not be called and `undefined` is returned. If `initialValue` is given and the array doesn't have at least 1 item, `initialValue` is returned.* + */ + // promise of array with promises of value + reduce(values: PromiseLike[]>, reducer: (total: U, current: T, index: number, arrayLength: number) => PromiseLike, initialValue?: U): Promise; + reduce(values: PromiseLike[]>, reducer: (total: U, current: T, index: number, arrayLength: number) => U, initialValue?: U): Promise; + + // promise of array with values + reduce(values: PromiseLike, reducer: (total: U, current: T, index: number, arrayLength: number) => PromiseLike, initialValue?: U): Promise; + reduce(values: PromiseLike, reducer: (total: U, current: T, index: number, arrayLength: number) => U, initialValue?: U): Promise; + + // array with promises of value + reduce(values: PromiseLike[], reducer: (total: U, current: T, index: number, arrayLength: number) => PromiseLike, initialValue?: U): Promise; + reduce(values: PromiseLike[], reducer: (total: U, current: T, index: number, arrayLength: number) => U, initialValue?: U): Promise; + + // array with values + reduce(values: T[], reducer: (total: U, current: T, index: number, arrayLength: number) => PromiseLike, initialValue?: U): Promise; + reduce(values: T[], reducer: (total: U, current: T, index: number, arrayLength: number) => U, initialValue?: U): Promise; + + /** + * Filter an array, or a promise of an array, which contains a promises (or a mix of promises and values) with the given `filterer` function with the signature `(item, index, arrayLength)` where `item` is the resolved value of a respective promise in the input array. If any promise in the input array is rejected the returned promise is rejected as well. + * + * The return values from the filtered functions are coerced to booleans, with the exception of promises and thenables which are awaited for their eventual result. + * + * *The original array is not modified. + */ + // promise of array with promises of value + filter(values: PromiseLike[]>, filterer: (item: T, index: number, arrayLength: number) => PromiseLike, option?: Promise.ConcurrencyOption): Promise; + filter(values: PromiseLike[]>, filterer: (item: T, index: number, arrayLength: number) => boolean, option?: Promise.ConcurrencyOption): Promise; + + // promise of array with values + filter(values: PromiseLike, filterer: (item: T, index: number, arrayLength: number) => PromiseLike, option?: Promise.ConcurrencyOption): Promise; + filter(values: PromiseLike, filterer: (item: T, index: number, arrayLength: number) => boolean, option?: Promise.ConcurrencyOption): Promise; + + // array with promises of value + filter(values: PromiseLike[], filterer: (item: T, index: number, arrayLength: number) => PromiseLike, option?: Promise.ConcurrencyOption): Promise; + filter(values: PromiseLike[], filterer: (item: T, index: number, arrayLength: number) => boolean, option?: Promise.ConcurrencyOption): Promise; + + // array with values + filter(values: T[], filterer: (item: T, index: number, arrayLength: number) => PromiseLike, option?: Promise.ConcurrencyOption): Promise; + filter(values: T[], filterer: (item: T, index: number, arrayLength: number) => boolean, option?: Promise.ConcurrencyOption): Promise; + + /** + * Iterate over an array, or a promise of an array, which contains promises (or a mix of promises and values) with the given iterator function with the signature (item, index, value) where item is the resolved value of a respective promise in the input array. Iteration happens serially. If any promise in the input array is rejected the returned promise is rejected as well. + * + * Resolves to the original array unmodified, this method is meant to be used for side effects. If the iterator function returns a promise or a thenable, the result for the promise is awaited for before continuing with next iteration. + */ + // promise of array with promises of value + each(values: PromiseLike[]>, iterator: (item: T, index: number, arrayLength: number) => U | PromiseLike): Promise; + // array with promises of value + each(values: PromiseLike[], iterator: (item: T, index: number, arrayLength: number) => U | PromiseLike): Promise; + // array with values OR promise of array with values + each(values: T[] | PromiseLike, iterator: (item: T, index: number, arrayLength: number) => U | PromiseLike): Promise; +} + +interface Promise extends PromiseLike, Promise.Inspection { + /** + * Promises/A+ `.then()` with progress handler. Returns a new promise chained from this promise. The new promise will be rejected or resolved dedefer on the passed `fulfilledHandler`, `rejectedHandler` and the state of this promise. + */ + then(onFulfill: (value: T) => U | PromiseLike, onReject?: (error: any) => U | PromiseLike, onProgress?: (note: any) => any): Promise; + then(onFulfill: (value: T) => U | PromiseLike, onReject?: (error: any) => void | PromiseLike, onProgress?: (note: any) => any): Promise; + + /** + * This is a catch-all exception handler, shortcut for calling `.then(null, handler)` on this promise. Any exception happening in a `.then`-chain will propagate to nearest `.catch` handler. + * + * Alias `.caught();` for compatibility with earlier ECMAScript version. + */ + catch(onReject?: (error: any) => T | PromiseLike | void | PromiseLike): Promise; + caught(onReject?: (error: any) => T | PromiseLike | void | PromiseLike): Promise; + + catch(onReject?: (error: any) => U | PromiseLike): Promise; + caught(onReject?: (error: any) => U | PromiseLike): Promise; + + /** + * This extends `.catch` to work more like catch-clauses in languages like Java or C#. Instead of manually checking `instanceof` or `.name === "SomeError"`, you may specify a number of error constructors which are eligible for this catch handler. The catch handler that is first met that has eligible constructors specified, is the one that will be called. + * + * This method also supports predicate-based filters. If you pass a predicate function instead of an error constructor, the predicate will receive the error as an argument. The return result of the predicate will be used determine whether the error handler should be called. + * + * Alias `.caught();` for compatibility with earlier ECMAScript version. + */ + catch(predicate: (error: any) => boolean, onReject: (error: any) => T | PromiseLike | void | PromiseLike): Promise; + caught(predicate: (error: any) => boolean, onReject: (error: any) => T | PromiseLike | void | PromiseLike): Promise; + + catch(predicate: (error: any) => boolean, onReject: (error: any) => U | PromiseLike): Promise; + caught(predicate: (error: any) => boolean, onReject: (error: any) => U | PromiseLike): Promise; + + catch(ErrorClass: Function, onReject: (error: any) => T | PromiseLike | void | PromiseLike): Promise; + caught(ErrorClass: Function, onReject: (error: any) => T | PromiseLike | void | PromiseLike): Promise; + + catch(ErrorClass: Function, onReject: (error: any) => U | PromiseLike): Promise; + caught(ErrorClass: Function, onReject: (error: any) => U | PromiseLike): Promise; + + + /** + * Like `.catch` but instead of catching all types of exceptions, it only catches those that don't originate from thrown errors but rather from explicit rejections. + */ + error(onReject: (reason: any) => PromiseLike): Promise; + error(onReject: (reason: any) => U): Promise; + + /** + * Pass a handler that will be called regardless of this promise's fate. Returns a new promise chained from this promise. There are special semantics for `.finally()` in that the final value cannot be modified from the handler. + * + * Alias `.lastly();` for compatibility with earlier ECMAScript version. + */ + finally(handler: () => PromiseLike): Promise; + finally(handler: () => U): Promise; + + lastly(handler: () => PromiseLike): Promise; + lastly(handler: () => U): Promise; + + /** + * Create a promise that follows this promise, but is bound to the given `thisArg` value. A bound promise will call its handlers with the bound value set to `this`. Additionally promises derived from a bound promise will also be bound promises with the same `thisArg` binding as the original promise. + */ + bind(thisArg: any): Promise; + + /** + * Like `.then()`, but any unhandled rejection that ends up here will be thrown as an error. + */ + done(onFulfilled: (value: T) => PromiseLike, onRejected: (error: any) => PromiseLike, onProgress?: (note: any) => any): void; + done(onFulfilled: (value: T) => PromiseLike, onRejected?: (error: any) => U, onProgress?: (note: any) => any): void; + done(onFulfilled: (value: T) => U, onRejected: (error: any) => PromiseLike, onProgress?: (note: any) => any): void; + done(onFulfilled?: (value: T) => U, onRejected?: (error: any) => U, onProgress?: (note: any) => any): void; + + /** + * Like `.finally()`, but not called for rejections. + */ + tap(onFulFill: (value: T) => PromiseLike): Promise; + tap(onFulfill: (value: T) => U): Promise; + + /** + * Shorthand for `.then(null, null, handler);`. Attach a progress handler that will be called if this promise is progressed. Returns a new promise chained from this promise. + */ + progressed(handler: (note: any) => any): Promise; + + /** + * Same as calling `Promise.delay(this, ms)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + delay(ms: number): Promise; + + /** + * Returns a promise that will be fulfilled with this promise's fulfillment value or rejection reason. However, if this promise is not fulfilled or rejected within `ms` milliseconds, the returned promise is rejected with a `Promise.TimeoutError` instance. + * + * You may specify a custom error message with the `message` parameter. + */ + timeout(ms: number, message?: string): Promise; + + /** + * Register a node-style callback on this promise. When this promise is is either fulfilled or rejected, the node callback will be called back with the node.js convention where error reason is the first argument and success value is the second argument. The error argument will be `null` in case of success. + * Returns back this promise instead of creating a new one. If the `callback` argument is not a function, this method does not do anything. + */ + nodeify(callback: (err: any, value?: T) => void, options?: Promise.SpreadOption): Promise; + nodeify(...sink: any[]): Promise; + + /** + * Marks this promise as cancellable. Promises by default are not cancellable after v0.11 and must be marked as such for `.cancel()` to have any effect. Marking a promise as cancellable is infectious and you don't need to remark any descendant promise. + */ + cancellable(): Promise; + + /** + * Cancel this promise. The cancellation will propagate to farthest cancellable ancestor promise which is still pending. + * + * That ancestor will then be rejected with a `CancellationError` (get a reference from `Promise.CancellationError`) object as the rejection reason. + * + * In a promise rejection handler you may check for a cancellation by seeing if the reason object has `.name === "Cancel"`. + * + * Promises are by default not cancellable. Use `.cancellable()` to mark a promise as cancellable. + */ + // TODO what to do with this? + cancel(reason?: any): Promise; + + /** + * Like `.then()`, but cancellation of the the returned promise or any of its descendant will not propagate cancellation to this promise or this promise's ancestors. + */ + fork(onFulfilled: (value: T) => PromiseLike, onRejected: (error: any) => PromiseLike, onProgress?: (note: any) => any): Promise; + fork(onFulfilled: (value: T) => PromiseLike, onRejected?: (error: any) => U, onProgress?: (note: any) => any): Promise; + fork(onFulfilled: (value: T) => U, onRejected: (error: any) => PromiseLike, onProgress?: (note: any) => any): Promise; + fork(onFulfilled?: (value: T) => U, onRejected?: (error: any) => U, onProgress?: (note: any) => any): Promise; + + /** + * Create an uncancellable promise based on this promise. + */ + uncancellable(): Promise; + + /** + * See if this promise can be cancelled. + */ + isCancellable(): boolean; + + /** + * See if this `promise` has been fulfilled. + */ + isFulfilled(): boolean; + + /** + * See if this `promise` has been rejected. + */ + isRejected(): boolean; + + /** + * See if this `promise` is still defer. + */ + isPending(): boolean; + + /** + * See if this `promise` is resolved -> either fulfilled or rejected. + */ + isResolved(): boolean; + + /** + * Get the fulfillment value of the underlying promise. Throws if the promise isn't fulfilled yet. + * + * throws `TypeError` + */ + value(): T; + + /** + * Get the rejection reason for the underlying promise. Throws if the promise isn't rejected yet. + * + * throws `TypeError` + */ + reason(): any; + + /** + * Synchronously inspect the state of this `promise`. The `PromiseInspection` will represent the state of the promise as snapshotted at the time of calling `.inspect()`. + */ + inspect(): Promise.Inspection; + + /** + * This is a convenience method for doing: + * + * + * promise.then(function(obj){ + * return obj[propertyName].call(obj, arg...); + * }); + * + */ + call(propertyName: string, ...args: any[]): Promise; + + /** + * This is a convenience method for doing: + * + * + * promise.then(function(obj){ + * return obj[propertyName]; + * }); + * + */ + // TODO find way to fix get() + // get(propertyName: string): Promise; + + /** + * Convenience method for: + * + * + * .then(function() { + * return value; + * }); + * + * + * in the case where `value` doesn't change its value. That means `value` is bound at the time of calling `.return()` + * + * Alias `.thenReturn();` for compatibility with earlier ECMAScript version. + */ + return(): Promise; + thenReturn(): Promise; + return(value: U): Promise; + thenReturn(value: U): Promise; + + /** + * Convenience method for: + * + * + * .then(function() { + * throw reason; + * }); + * + * Same limitations apply as with `.return()`. + * + * Alias `.thenThrow();` for compatibility with earlier ECMAScript version. + */ + throw(reason: Error): Promise; + thenThrow(reason: Error): Promise; + + /** + * Convert to String. + */ + toString(): string; + + /** + * This is implicitly called by `JSON.stringify` when serializing the object. Returns a serialized representation of the `Promise`. + */ + toJSON(): Object; + + /** + * Like calling `.then`, but the fulfillment value or rejection reason is assumed to be an array, which is flattened to the formal parameters of the handlers. + */ + // TODO how to model instance.spread()? like Q? + spread(onFulfill: Function, onReject?: (reason: any) => PromiseLike): Promise; + spread(onFulfill: Function, onReject?: (reason: any) => U): Promise; + /* + // TODO or something like this? + spread(onFulfill: (...values: W[]) => PromiseLike, onReject?: (reason: any) => PromiseLike): Promise; + spread(onFulfill: (...values: W[]) => PromiseLike, onReject?: (reason: any) => U): Promise; + spread(onFulfill: (...values: W[]) => U, onReject?: (reason: any) => PromiseLike): Promise; + spread(onFulfill: (...values: W[]) => U, onReject?: (reason: any) => U): Promise; + */ + /** + * Same as calling `Promise.all(thisPromise)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO type inference from array-resolving promise? + all(): Promise; + + /** + * Same as calling `Promise.props(thisPromise)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO how to model instance.props()? + props(): Promise; + + /** + * Same as calling `Promise.settle(thisPromise)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO type inference from array-resolving promise? + settle(): Promise[]>; + + /** + * Same as calling `Promise.any(thisPromise)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO type inference from array-resolving promise? + any(): Promise; + + /** + * Same as calling `Promise.some(thisPromise)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO type inference from array-resolving promise? + some(count: number): Promise; + + /** + * Same as calling `Promise.race(thisPromise, count)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO type inference from array-resolving promise? + race(): Promise; + + /** + * Same as calling `Promise.map(thisPromise, mapper)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO type inference from array-resolving promise? + map(mapper: (item: Q, index: number, arrayLength: number) => PromiseLike, options?: Promise.ConcurrencyOption): Promise; + map(mapper: (item: Q, index: number, arrayLength: number) => U, options?: Promise.ConcurrencyOption): Promise; + + /** + * Same as `Promise.mapSeries(thisPromise, mapper)`. + */ + // TODO type inference from array-resolving promise? + mapSeries(mapper: (item: Q, index: number, arrayLength: number) => U | PromiseLike): Promise; + + /** + * Same as calling `Promise.reduce(thisPromise, Function reducer, initialValue)`. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO type inference from array-resolving promise? + reduce(reducer: (memo: U, item: Q, index: number, arrayLength: number) => PromiseLike, initialValue?: U): Promise; + reduce(reducer: (memo: U, item: Q, index: number, arrayLength: number) => U, initialValue?: U): Promise; + + /** + * Same as calling ``Promise.filter(thisPromise, filterer)``. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + // TODO type inference from array-resolving promise? + filter(filterer: (item: U, index: number, arrayLength: number) => PromiseLike, options?: Promise.ConcurrencyOption): Promise; + filter(filterer: (item: U, index: number, arrayLength: number) => boolean, options?: Promise.ConcurrencyOption): Promise; + + /** + * Same as calling ``Promise.each(thisPromise, iterator)``. With the exception that if this promise is bound to a value, the returned promise is bound to that value too. + */ + each(iterator: (item: T, index: number, arrayLength: number) => U | PromiseLike): Promise; +} + +/** + * Don't use variable namespace such as variables, functions, and classes. + * If you use this namespace, it will conflict in es6. + */ +declare namespace Promise { + export interface RangeError extends Error { + } + export interface CancellationError extends Error { + } + export interface TimeoutError extends Error { + } + export interface TypeError extends Error { + } + export interface RejectionError extends Error { + } + export interface OperationalError extends Error { + } + + export interface ConcurrencyOption { + concurrency: number; + } + export interface SpreadOption { + spread: boolean; + } + export interface PromisifyAllOptions { + suffix?: string; + filter?: (name: string, func: Function, target?: any, passesDefaultFilter?: boolean) => boolean; + // The promisifier gets a reference to the original method and should return a function which returns a promise + promisifier?: (originalMethod: Function) => () => PromiseLike; + } + + export interface Resolver { + /** + * Returns a reference to the controlled promise that can be passed to clients. + */ + promise: Promise; + + /** + * Resolve the underlying promise with `value` as the resolution value. If `value` is a thenable or a promise, the underlying promise will assume its state. + */ + resolve(value: T): void; + resolve(): void; + + /** + * Reject the underlying promise with `reason` as the rejection reason. + */ + reject(reason: any): void; + + /** + * Progress the underlying promise with `value` as the progression value. + */ + progress(value: any): void; + + /** + * Gives you a callback representation of the `PromiseResolver`. Note that this is not a method but a property. The callback accepts error object in first argument and success values on the 2nd parameter and the rest, I.E. node js conventions. + * + * If the the callback is called with multiple success values, the resolver fullfills its promise with an array of the values. + */ + // TODO specify resolver callback + callback: (err: any, value: T, ...values: T[]) => void; + } + + export interface Inspection { + /** + * See if the underlying promise was fulfilled at the creation time of this inspection object. + */ + isFulfilled(): boolean; + + /** + * See if the underlying promise was rejected at the creation time of this inspection object. + */ + isRejected(): boolean; + + /** + * See if the underlying promise was defer at the creation time of this inspection object. + */ + isPending(): boolean; + + /** + * Get the fulfillment value of the underlying promise. Throws if the promise wasn't fulfilled at the creation time of this inspection object. + * + * throws `TypeError` + */ + value(): T; + + /** + * Get the rejection reason for the underlying promise. Throws if the promise wasn't rejected at the creation time of this inspection object. + * + * throws `TypeError` + */ + reason(): any; + } +} + +declare module 'bluebird' { + export = Promise; +} diff --git a/src/typings/del/del.d.ts b/src/typings/del/del.d.ts new file mode 100644 index 0000000000..372bf07a30 --- /dev/null +++ b/src/typings/del/del.d.ts @@ -0,0 +1,28 @@ +// Type definitions for del v2.2.0 +// Project: https://github.com/sindresorhus/del +// Definitions by: Asana , Aya Morisawa + +// /// +// /// + +declare module "del" { + import glob = require("glob"); + + function Del(pattern: string): Promise; + function Del(pattern: string, options: Del.Options): Promise; + + function Del(patterns: string[]): Promise; + function Del(patterns: string[], options: Del.Options): Promise; + + module Del { + function sync(pattern: string, options?: Options): string[]; + function sync(patterns: string[], options?: Options): string[]; + + interface Options extends glob.IOptions { + force?: boolean; + dryRun?: boolean; + } + } + + export = Del; +} diff --git a/src/typings/fs-extra-promise/fs-extra-promise.d.ts b/src/typings/fs-extra-promise/fs-extra-promise.d.ts new file mode 100644 index 0000000000..c57403c79f --- /dev/null +++ b/src/typings/fs-extra-promise/fs-extra-promise.d.ts @@ -0,0 +1,265 @@ +// Type definitions for fs-extra-promise +// Project: https://github.com/overlookmotel/fs-extra-promise +// Definitions by: midknight41 , Jason Swearingen + +// Imported from: https://github.com/soywiz/typescript-node-definitions/fs-extra.d.ts via TSD fs-extra definition + +/// +/// + +declare module "fs-extra-promise" { + import stream = require("stream"); + import Promise = require("bluebird"); + + export interface Stats { + isFile(): boolean; + isDirectory(): boolean; + isBlockDevice(): boolean; + isCharacterDevice(): boolean; + isSymbolicLink(): boolean; + isFIFO(): boolean; + isSocket(): boolean; + dev: number; + ino: number; + mode: number; + nlink: number; + uid: number; + gid: number; + rdev: number; + size: number; + blksize: number; + blocks: number; + atime: Date; + mtime: Date; + ctime: Date; + } + + export interface FSWatcher { + close(): void; + } + + export class ReadStream extends stream.Readable { } + export class WriteStream extends stream.Writable { } + + //extended methods + export function copy(src: string, dest: string, callback?: (err: Error) => void): void; + export function copy(src: string, dest: string, filter: (src: string) => boolean, callback?: (err: Error) => void): void; + + export function copySync(src: string, dest: string): void; + export function copySync(src: string, dest: string, filter: (src: string) => boolean): void; + + export function createFile(file: string, callback?: (err: Error) => void): void; + export function createFileSync(file: string): void; + + export function mkdirs(dir: string, callback?: (err: Error) => void): void; + export function mkdirp(dir: string, callback?: (err: Error) => void): void; + export function mkdirsSync(dir: string): void; + export function mkdirpSync(dir: string): void; + + export function outputFile(file: string, data: any, callback?: (err: Error) => void): void; + export function outputFileSync(file: string, data: any): void; + + export function outputJson(file: string, data: any, callback?: (err: Error) => void): void; + export function outputJSON(file: string, data: any, callback?: (err: Error) => void): void; + export function outputJsonSync(file: string, data: any): void; + export function outputJSONSync(file: string, data: any): void; + + export function readJson(file: string, callback?: (err: Error) => void): void; + export function readJson(file: string, options?: OpenOptions, callback?: (err: Error) => void): void; + export function readJSON(file: string, callback?: (err: Error) => void): void; + export function readJSON(file: string, options?: OpenOptions, callback?: (err: Error) => void): void; + + export function readJsonSync(file: string, options?: OpenOptions): void; + export function readJSONSync(file: string, options?: OpenOptions): void; + + export function remove(dir: string, callback?: (err: Error) => void): void; + export function removeSync(dir: string): void; + // export function delete(dir: string, callback?: (err: Error) => void): void; + // export function deleteSync(dir: string): void; + + export function writeJson(file: string, object: any, callback?: (err: Error) => void): void; + export function writeJson(file: string, object: any, options?: OpenOptions, callback?: (err: Error) => void): void; + export function writeJSON(file: string, object: any, callback?: (err: Error) => void): void; + export function writeJSON(file: string, object: any, options?: OpenOptions, callback?: (err: Error) => void): void; + + export function writeJsonSync(file: string, object: any, options?: OpenOptions): void; + export function writeJSONSync(file: string, object: any, options?: OpenOptions): void; + + export function rename(oldPath: string, newPath: string, callback?: (err: Error) => void): void; + export function renameSync(oldPath: string, newPath: string): void; + export function truncate(fd: number, len: number, callback?: (err: Error) => void): void; + export function truncateSync(fd: number, len: number): void; + export function chown(path: string, uid: number, gid: number, callback?: (err: Error) => void): void; + export function chownSync(path: string, uid: number, gid: number): void; + export function fchown(fd: number, uid: number, gid: number, callback?: (err: Error) => void): void; + export function fchownSync(fd: number, uid: number, gid: number): void; + export function lchown(path: string, uid: number, gid: number, callback?: (err: Error) => void): void; + export function lchownSync(path: string, uid: number, gid: number): void; + export function chmod(path: string, mode: number, callback?: (err: Error) => void): void; + export function chmod(path: string, mode: string, callback?: (err: Error) => void): void; + export function chmodSync(path: string, mode: number): void; + export function chmodSync(path: string, mode: string): void; + export function fchmod(fd: number, mode: number, callback?: (err: Error) => void): void; + export function fchmod(fd: number, mode: string, callback?: (err: Error) => void): void; + export function fchmodSync(fd: number, mode: number): void; + export function fchmodSync(fd: number, mode: string): void; + export function lchmod(path: string, mode: string, callback?: (err: Error) => void): void; + export function lchmod(path: string, mode: number, callback?: (err: Error) => void): void; + export function lchmodSync(path: string, mode: number): void; + export function lchmodSync(path: string, mode: string): void; + export function stat(path: string, callback?: (err: Error, stats: Stats) => void): void; + export function lstat(path: string, callback?: (err: Error, stats: Stats) => void): void; + export function fstat(fd: number, callback?: (err: Error, stats: Stats) => void): void; + export function statSync(path: string): Stats; + export function lstatSync(path: string): Stats; + export function fstatSync(fd: number): Stats; + export function link(srcpath: string, dstpath: string, callback?: (err: Error) => void): void; + export function linkSync(srcpath: string, dstpath: string): void; + export function symlink(srcpath: string, dstpath: string, type?: string, callback?: (err: Error) => void): void; + export function symlinkSync(srcpath: string, dstpath: string, type?: string): void; + export function readlink(path: string, callback?: (err: Error, linkString: string) => void): void; + export function realpath(path: string, callback?: (err: Error, resolvedPath: string) => void): void; + export function realpath(path: string, cache: string, callback: (err: Error, resolvedPath: string) => void): void; + export function realpathSync(path: string, cache?: boolean): string; + export function unlink(path: string, callback?: (err: Error) => void): void; + export function unlinkSync(path: string): void; + export function rmdir(path: string, callback?: (err: Error) => void): void; + export function rmdirSync(path: string): void; + export function mkdir(path: string, mode?: number, callback?: (err: Error) => void): void; + export function mkdir(path: string, mode?: string, callback?: (err: Error) => void): void; + export function mkdirSync(path: string, mode?: number): void; + export function mkdirSync(path: string, mode?: string): void; + export function readdir(path: string, callback?: (err: Error, files: string[]) => void): void; + export function readdirSync(path: string): string[]; + export function close(fd: number, callback?: (err: Error) => void): void; + export function closeSync(fd: number): void; + export function open(path: string, flags: string, mode?: string, callback?: (err: Error, fs: number) => void): void; + export function openSync(path: string, flags: string, mode?: string): number; + export function utimes(path: string, atime: number, mtime: number, callback?: (err: Error) => void): void; + export function utimesSync(path: string, atime: number, mtime: number): void; + export function futimes(fd: number, atime: number, mtime: number, callback?: (err: Error) => void): void; + export function futimesSync(fd: number, atime: number, mtime: number): void; + export function fsync(fd: number, callback?: (err: Error) => void): void; + export function fsyncSync(fd: number): void; + export function write(fd: number, buffer: NodeBuffer, offset: number, length: number, position: number, callback?: (err: Error, written: number, buffer: NodeBuffer) => void): void; + export function writeSync(fd: number, buffer: NodeBuffer, offset: number, length: number, position: number): number; + export function read(fd: number, buffer: NodeBuffer, offset: number, length: number, position: number, callback?: (err: Error, bytesRead: number, buffer: NodeBuffer) => void): void; + export function readSync(fd: number, buffer: NodeBuffer, offset: number, length: number, position: number): number; + export function readFile(filename: string, encoding: string, callback: (err: Error, data: string) => void): void; + export function readFile(filename: string, options: OpenOptions, callback: (err: Error, data: string) => void): void; + export function readFile(filename: string, callback: (err: Error, data: NodeBuffer) => void): void; + export function readFileSync(filename: string): NodeBuffer; + export function readFileSync(filename: string, encoding: string): string; + export function readFileSync(filename: string, options: OpenOptions): string; + export function writeFile(filename: string, data: any, encoding?: string, callback?: (err: Error) => void): void; + export function writeFile(filename: string, data: any, options?: OpenOptions, callback?: (err: Error) => void): void; + export function writeFileSync(filename: string, data: any, encoding?: string): void; + export function writeFileSync(filename: string, data: any, option?: OpenOptions): void; + export function appendFile(filename: string, data: any, encoding?: string, callback?: (err: Error) => void): void; + export function appendFile(filename: string, data: any, option?: OpenOptions, callback?: (err: Error) => void): void; + export function appendFileSync(filename: string, data: any, encoding?: string): void; + export function appendFileSync(filename: string, data: any, option?: OpenOptions): void; + export function watchFile(filename: string, listener: { curr: Stats; prev: Stats; }): void; + export function watchFile(filename: string, options: { persistent?: boolean; interval?: number; }, listener: { curr: Stats; prev: Stats; }): void; + export function unwatchFile(filename: string, listener?: Stats): void; + export function watch(filename: string, options?: { persistent?: boolean; }, listener?: (event: string, filename: string) => any): FSWatcher; + export function exists(path: string, callback?: (exists: boolean) => void): void; + export function existsSync(path: string): boolean; + export function ensureDir(path: string, cb: (err: Error) => void): void; + + export interface OpenOptions { + encoding?: string; + flag?: string; + } + + export interface ReadStreamOptions { + flags?: string; + encoding?: string; + fd?: number; + mode?: number; + autoClose?: boolean; + } + + export interface WriteStreamOptions { + flags?: string; + defaultEncounding?: string; + fd?: number; + mode?: number; + } + + export function createReadStream(path: string, options?: ReadStreamOptions): ReadStream; + export function createWriteStream(path: string, options?: WriteStreamOptions): WriteStream; + + + + //promisified versions + export function copyAsync(src: string, dest: string): Promise; + export function copyAsync(src: string, dest: string, filter: (src: string) => boolean): Promise; + + export function createFileAsync(file: string): Promise; + + export function mkdirsAsync(dir: string): Promise; + export function mkdirpAsync(dir: string): Promise; + + export function outputFileAsync(file: string, data: any): Promise; + + export function outputJsonAsync(file: string, data: any): Promise; + export function outputJSONAsync(file: string, data: any): Promise; + + export function readJsonAsync(file: string): Promise; + export function readJsonAsync(file: string, options?: OpenOptions): Promise; + export function readJSONAsync(file: string): Promise; + export function readJSONAsync(file: string, options?: OpenOptions): Promise; + + + export function removeAsync(dir: string): Promise; + // export function deleteAsync(dir: string):Promise; + + export function writeJsonAsync(file: string, object: any): Promise; + export function writeJsonAsync(file: string, object: any, options?: OpenOptions): Promise; + export function writeJSONAsync(file: string, object: any): Promise; + export function writeJSONAsync(file: string, object: any, options?: OpenOptions): Promise; + + export function renameAsync(oldPath: string, newPath: string): Promise; + export function truncateAsync(fd: number, len: number): Promise; + export function chownAsync(path: string, uid: number, gid: number): Promise; + export function fchownAsync(fd: number, uid: number, gid: number): Promise; + export function lchownAsync(path: string, uid: number, gid: number): Promise; + export function chmodAsync(path: string, mode: number): Promise; + export function chmodAsync(path: string, mode: string): Promise; + export function fchmodAsync(fd: number, mode: number): Promise; + export function fchmodAsync(fd: number, mode: string): Promise; + export function lchmodAsync(path: string, mode: string): Promise; + export function lchmodAsync(path: string, mode: number): Promise; + export function statAsync(path: string): Promise; + export function lstatAsync(path: string): Promise; + export function fstatAsync(fd: number): Promise; + export function linkAsync(srcpath: string, dstpath: string): Promise; + export function symlinkAsync(srcpath: string, dstpath: string, type?: string): Promise; + export function readlinkAsync(path: string): Promise; + export function realpathAsync(path: string): Promise; + export function realpathAsync(path: string, cache: string): Promise; + export function unlinkAsync(path: string): Promise; + export function rmdirAsync(path: string): Promise; + export function mkdirAsync(path: string, mode?: number): Promise; + export function mkdirAsync(path: string, mode?: string): Promise; + export function readdirAsync(path: string): Promise; + export function closeAsync(fd: number): Promise; + export function openAsync(path: string, flags: string, mode?: string): Promise; + export function utimesAsync(path: string, atime: number, mtime: number): Promise; + export function futimesAsync(fd: number, atime: number, mtime: number): Promise; + export function fsyncAsync(fd: number): Promise; + export function writeAsync(fd: number, buffer: NodeBuffer, offset: number, length: number, position: number): Promise<[number, NodeBuffer]>; + export function readAsync(fd: number, buffer: NodeBuffer, offset: number, length: number, position: number): Promise<[number, NodeBuffer]>; + export function readFileAsync(filename: string, encoding: string): Promise; + export function readFileAsync(filename: string, options: OpenOptions): Promise; + export function readFileAsync(filename: string): Promise; + export function writeFileAsync(filename: string, data: any, encoding?: string): Promise; + export function writeFileAsync(filename: string, data: any, options?: OpenOptions): Promise; + export function appendFileAsync(filename: string, data: any, encoding?: string): Promise; + export function appendFileAsync(filename: string, data: any, option?: OpenOptions): Promise; + + export function existsAsync(path: string): Promise; + export function ensureDirAsync(path: string): Promise; +} + diff --git a/src/typings/ref.d.ts b/src/typings/ref.d.ts index 07bc4a016a..32edf923c7 100644 --- a/src/typings/ref.d.ts +++ b/src/typings/ref.d.ts @@ -3,4 +3,5 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/// \ No newline at end of file +/// +/// \ No newline at end of file diff --git a/src/typings/tmp/tmp.d.ts b/src/typings/tmp/tmp.d.ts new file mode 100644 index 0000000000..b626734426 --- /dev/null +++ b/src/typings/tmp/tmp.d.ts @@ -0,0 +1,47 @@ +// Type definitions for tmp v0.0.28 +// Project: https://www.npmjs.com/package/tmp +// Definitions by: Jared Klopper + +declare module "tmp" { + + module tmp { + interface Options extends SimpleOptions { + mode?: number; + } + + interface SimpleOptions { + prefix?: string; + postfix?: string; + template?: string; + dir?: string; + tries?: number; + keep?: boolean; + unsafeCleanup?: boolean; + } + + interface SynchrounousResult { + name: string; + fd: number; + removeCallback: () => void; + } + + function file(callback: (err: any, path: string, fd: number, cleanupCallback: () => void) => void): void; + function file(config: Options, callback?: (err: any, path: string, fd: number, cleanupCallback: () => void) => void): void; + + function fileSync(config?: Options): SynchrounousResult; + + function dir(callback: (err: any, path: string, cleanupCallback: () => void) => void): void; + function dir(config: Options, callback?: (err: any, path: string, cleanupCallback: () => void) => void): void; + + function dirSync(config?: Options): SynchrounousResult; + + function tmpName(callback: (err: any, path: string) => void): void; + function tmpName(config: SimpleOptions, callback?: (err: any, path: string) => void): void; + + function tmpNameSync(config?: SimpleOptions): string; + + function setGracefulCleanup(): void; + } + + export = tmp; +} 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" - } - ] -}