diff --git a/.travis.yml b/.travis.yml index 4ecc32d232..d5f452ae91 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,6 +33,8 @@ script: - npm install - npm run tslint - npm run compile + # pr-check needs to run before test. test modifies package.json. + - npm run pr-check - npm run test after_failure: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3e0b3f45bb..0211aa40d3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Contribution Steps -* [Build and debug the extension](Documentation/Getting%20started.md#build-and-debug-the-cpptools-extension). +* [Build and debug the extension](Documentation/Building%20the%20Extension.md). * File an [issue](https://github.com/Microsoft/vscode-cpptools/issues) and a [pull request](https://github.com/Microsoft/vscode-cpptools/pulls) with the change and we will review it. * If the change affects functionality, add a line describing the change to [**CHANGELOG.md**](Extension/CHANGELOG.md). * Try and add a test in [**test/extension.test.ts**](Extension/test/unitTests/extension.test.ts). diff --git a/Documentation/LanguageServer/MinGW.md b/Documentation/LanguageServer/MinGW.md index 6e93cf3e5d..3c5f8bb7a7 100644 --- a/Documentation/LanguageServer/MinGW.md +++ b/Documentation/LanguageServer/MinGW.md @@ -1,12 +1,49 @@ For developers using MinGW on Windows, we recommend you start with the following **c_cpp_properties.json** template. Select "C/Cpp: Edit Configurations" from the command palette to create this file if you haven't already. -Note that you may have to change the MinGW version number to match what you have installed. Eg. `C:/MinGW/lib/gcc/mingw32/5.3.0/` instead of `C:/MinGW/lib/gcc/mingw32/6.3.0/`. +## With extension version 0.16.1 and higher: + +Starting with version 0.16.1, if you set the `compilerPath` property and change `intelliSenseMode` to `clang-x64`, you no longer need to copy the system include path or defines to `includePath` and `defines` to enable IntelliSense to work properly. However, `browse.path` still needs to be updated manually to add the system include paths to enable code browsing for system headers. For example: + +```json +{ + "configurations": [ + { + "name": "MinGW", + "intelliSenseMode": "clang-x64", + "compilerPath": "C:/MinGW/bin/gcc.exe", + "includePath": [ + "${workspaceRoot}", + ], + "defines": [ + "_DEBUG" + ], + "browse": { + "path": [ + "C:/MinGW/lib/gcc/mingw32/6.3.0/include", + "C:/MinGW/lib/gcc/mingw32/6.3.0/include-fixed", + "C:/MinGW/include/*" + "${workspaceRoot}", + ], + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "" + } + } + ], + "version": 3 +} +``` + +Note that the `browse.path` setting is not automatically updated at this time and you may have to change the MinGW version number to match what you have installed. If you are using a different MinGW distribution, the values for `compilerPath` and `browse.path` will likely be different than what is written here. + +## With extension version 0.16.0 and earlier: + +In earlier versions of the extension, the `includePath` and a some system defines need to be set in order for IntelliSense to work properly. Note that you may have to change the MinGW version number to match what you have installed. Eg. `C:/MinGW/lib/gcc/mingw32/5.3.0/` instead of `C:/MinGW/lib/gcc/mingw32/6.3.0/`. ```json { "configurations": [ { - "name": "Win32", + "name": "MinGW", "intelliSenseMode": "clang-x64", "includePath": [ "${workspaceRoot}", @@ -19,7 +56,6 @@ Note that you may have to change the MinGW version number to match what you have ], "defines": [ "_DEBUG", - "UNICODE", "__GNUC__=6", "__cdecl=__attribute__((__cdecl__))" ], @@ -28,6 +64,7 @@ Note that you may have to change the MinGW version number to match what you have "C:/MinGW/lib/gcc/mingw32/6.3.0/include", "C:/MinGW/lib/gcc/mingw32/6.3.0/include-fixed", "C:/MinGW/include/*" + "${workspaceRoot}", ], "limitSymbolsToIncludedHeaders": true, "databaseFilename": "" @@ -37,7 +74,7 @@ Note that you may have to change the MinGW version number to match what you have } ``` -The `includePath` above includes the system header paths that gcc uses in version 6.3.0 for C++ projects and matches the output of `gcc -v -E -x c++ -`. The `intelliSenseMode` should be set to **"clang-x64"** to get MinGW projects to work properly with IntelliSense. The `__GNUC__=#` define should match the major version of the toolchain in your installation (6 in this example). +The `includePath` above includes the system header paths that gcc uses in version 6.3.0 for C++ projects and matches the output of `"gcc -v -E -x c++ nul"`. The `intelliSenseMode` should be set to **"clang-x64"** to get MinGW projects to work properly with IntelliSense. The `__GNUC__=#` define should match the major version of the toolchain in your installation (6 in this example). For C projects, simply remove the C++ lines: @@ -45,7 +82,7 @@ For C projects, simply remove the C++ lines: { "configurations": [ { - "name": "Win32", + "name": "MinGW", "intelliSenseMode": "clang-x64", "includePath": [ "${workspaceRoot}", @@ -55,7 +92,6 @@ For C projects, simply remove the C++ lines: ], "defines": [ "_DEBUG", - "UNICODE", "__GNUC__=6", "__cdecl=__attribute__((__cdecl__))" ], @@ -64,6 +100,7 @@ For C projects, simply remove the C++ lines: "C:/MinGW/lib/gcc/mingw32/6.3.0/include", "C:/MinGW/lib/gcc/mingw32/6.3.0/include-fixed", "C:/MinGW/include/*" + "${workspaceRoot}", ], "limitSymbolsToIncludedHeaders": true, "databaseFilename": "" @@ -72,5 +109,3 @@ For C projects, simply remove the C++ lines: ] } ``` - -With these configurations, you should be all set up to use the new IntelliSense engine for linting, memberlist autocomplete, and quick info (tooltips). Add `"C_Cpp.intelliSenseEngine": "Default"` to your **settings.json** file to try out the new IntelliSense engine. diff --git a/Documentation/LanguageServer/c_cpp_properties.json.md b/Documentation/LanguageServer/c_cpp_properties.json.md index a391b01be9..23a2848a38 100644 --- a/Documentation/LanguageServer/c_cpp_properties.json.md +++ b/Documentation/LanguageServer/c_cpp_properties.json.md @@ -78,7 +78,7 @@ This list of paths will be used by the Tag Parser to search for headers included by your source files. The Tag Parser will automatically search all subfolders in these paths unless the path ends with a `/*` or `\*`. For example, `/usr/include` directs the Tag Parser to search the `include` folder and its subfolders for headers while `/usr/include/*` directs the Tag Parser not to look in any subfolders of `/usr/include`. * #### `limitSymbolsToIncludedHeaders` - When true, the Tag Parser will only parse code files that have been directly or indirectly included by a source file in `${workspaceRoot}`. When false, the Tag Parser will parse all code files found in the paths specified in the **path** list. + When true, the Tag Parser will only parse code files that have been directly or indirectly included by a source file in `${workspaceFolder}`. When false, the Tag Parser will parse all code files found in the paths specified in the **path** list. * #### `databaseFilename` - When set, this instructs the extension to save the Tag Parser's symbol database somewhere other than the workspace's default storage location. If a relative path is specified, it will be made relative to the workspace's default storage location, not the workspace folder itself. The `${workspaceRoot}` variable can be used to specify a path relative to the workspace folder (e.g. `$[workspaceRoot}/.vscode/browse.vc.db`) + When set, this instructs the extension to save the Tag Parser's symbol database somewhere other than the workspace's default storage location. If a relative path is specified, it will be made relative to the workspace's default storage location, not the workspace folder itself. The `${workspaceFolder}` variable can be used to specify a path relative to the workspace folder (e.g. `${workspaceFolder}/.vscode/browse.vc.db`) diff --git a/Extension/CHANGELOG.md b/Extension/CHANGELOG.md index 66fbd30ae0..b74af9b2d0 100644 --- a/Extension/CHANGELOG.md +++ b/Extension/CHANGELOG.md @@ -1,6 +1,24 @@ # C/C++ for Visual Studio Code Change Log -## Version 0.16.0-insiders2: March 23, 2018 +## Version 0.17.0: May 7, 2018 +* Auto-complete for headers after typing `#include`. [#802](https://github.com/Microsoft/vscode-cpptools/issues/802) +* Configuration improvements. [#1338](https://github.com/Microsoft/vscode-cpptools/issues/1338) + * Potentially addresses: [#368](https://github.com/Microsoft/vscode-cpptools/issues/368), [#410](https://github.com/Microsoft/vscode-cpptools/issues/410), [#1229](https://github.com/Microsoft/vscode-cpptools/issues/1229), [#1270](https://github.com/Microsoft/vscode-cpptools/issues/), [#1404](https://github.com/Microsoft/vscode-cpptools/issues/1404) +* Add support for querying system includes/defines from WSL and Cygwin compilers. [#1845](https://github.com/Microsoft/vscode-cpptools/issues/1845), [#1736](https://github.com/Microsoft/vscode-cpptools/issues/1736) +* Stop automatically adding `/usr/include` to the `includePath`. [#1819](https://github.com/Microsoft/vscode-cpptools/issues/1819) +* Fix wrong configuration being used if there are four or more. [#1599](https://github.com/Microsoft/vscode-cpptools/issues/1599) +* Fix `c_cpp_properties.json` requiring write access. [#1790](https://github.com/Microsoft/vscode-cpptools/issues/1790) +* Change file not found in `compile_commands.json` message from an error to a warning. [#1783](https://github.com/Microsoft/vscode-cpptools/issues/1783) +* Fix an IntelliSense crash during completion requests. [#1782](https://github.com/Microsoft/vscode-cpptools/issues/1782) +* Update the installed clang-format to 6.0. + +## Version 0.16.1: March 30, 2018 +* Fix random deadlock caused by logging code on Linux/Mac. [#1759](https://github.com/Microsoft/vscode-cpptools/issues/1759) +* Fix compiler from `compileCommands` not being queried for includes/defines if `compilerPath` isn't set on Windows. [#1754](https://github.com/Microsoft/vscode-cpptools/issues/1754) +* Fix OSX `UseShellExecute` I/O bug. [#1756](https://github.com/Microsoft/vscode-cpptools/issues/1756) +* Invalidate partially unzipped files from package manager. [#1757](https://github.com/Microsoft/vscode-cpptools/issues/1757) + +## Version 0.16.0: March 28, 2018 * Enable autocomplete for local and global scopes. [#13](https://github.com/Microsoft/vscode-cpptools/issues/13) * Add a setting to define multiline comment patterns: `C_Cpp.commentContinuationPatterns`. [#1100](https://github.com/Microsoft/vscode-cpptools/issues/1100), [#1539](https://github.com/Microsoft/vscode-cpptools/issues/1539) * Add a setting to disable inactive region highlighting: `C_Cpp.dimInactiveRegions`. [#1592](https://github.com/Microsoft/vscode-cpptools/issues/1592) diff --git a/Extension/c_cpp_properties.schema.json b/Extension/c_cpp_properties.schema.json index c36547e399..d4aac1e681 100644 --- a/Extension/c_cpp_properties.schema.json +++ b/Extension/c_cpp_properties.schema.json @@ -11,7 +11,7 @@ ], "properties": { "name": { - "description": "Configuration identifier. Mac, Linux, or Win32 are special identifiers for configurations that will be auto-selected on those platforms, but the identifier can be anything.", + "description": "Configuration identifier. Mac, Linux, and Win32 are special identifiers for configurations that will be auto-selected on those platforms, but the identifier can be anything.", "type": "string" }, "compilerPath": { @@ -24,7 +24,8 @@ "enum": [ "c89", "c99", - "c11" + "c11", + "${default}" ] }, "cppStandard": { @@ -35,7 +36,8 @@ "c++03", "c++11", "c++14", - "c++17" + "c++17", + "${default}" ] }, "compileCommands": { @@ -64,12 +66,13 @@ } }, "intelliSenseMode": { + "description": "If set, it overrides the default mode used by the IntelliSense engine. Windows defaults to msvc-x64 and Linux/Mac default to clang-x64.", "type": "string", "enum": [ "msvc-x64", - "clang-x64" - ], - "description": "If set, it overrides the default mode used by the IntelliSense engine. Windows defaults to msvc-x64 and Linux/Mac default to clang-x64." + "clang-x64", + "${default}" + ] }, "forcedInclude": { "description": "A list of files that should be included before any include file in a translation unit.", @@ -83,7 +86,10 @@ "properties": { "limitSymbolsToIncludedHeaders": { "description": "true to process only those files directly or indirectly included as headers, false to process all files under the specified include paths.", - "type": "boolean" + "type": [ + "boolean", + "string" + ] }, "databaseFilename": { "description": "Path to the generated symbol database. If a relative path is specified, it will be made relative to the workspace's default storage location.", diff --git a/Extension/gulpfile.js b/Extension/gulpfile.js index 9dc120c007..6bce747394 100644 --- a/Extension/gulpfile.js +++ b/Extension/gulpfile.js @@ -9,6 +9,7 @@ const gulp = require('gulp'); const env = require('gulp-env') const tslint = require('gulp-tslint'); const mocha = require('gulp-mocha'); +const fs = require('fs'); gulp.task('allTests', () => { gulp.start('unitTests'); @@ -73,4 +74,12 @@ gulp.task('tslint', () => { summarizeFailureOutput: false, emitError: false })) +}); + +gulp.task('pr-check', () => { + const packageJson = JSON.parse(fs.readFileSync('./package.json').toString()); + if (packageJson.activationEvents.length !== 1 && packageJson.activationEvents[0] !== '*') { + console.log('Please make sure to not check in package.json that has been rewritten by the extension activation. If you intended to have changes in package.json, please only check-in your changes. If you did not, please run `git checkout -- package.json`.'); + process.exit(1); + } }); \ No newline at end of file diff --git a/Extension/package-lock.json b/Extension/package-lock.json index c145d8bcad..944e1ba453 100644 --- a/Extension/package-lock.json +++ b/Extension/package-lock.json @@ -1,6 +1,6 @@ { "name": "cpptools", - "version": "0.15.0", + "version": "0.17.0-master", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -5100,9 +5100,9 @@ } }, "vscode": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.10.tgz", - "integrity": "sha512-MvFXXSGuhw0Q6GC6dQrnRc0ES+63wpttGIoYGBMQnoS9JFCCNC/rWfX0lBCHJyuKL2Q8CYg0ROsMEHbHVwEtVw==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.14.tgz", + "integrity": "sha512-acfn3fzGtTm7UjChAN7/YjsC0qIyJeuSrJwvm6qb7tLN6Geq1FmCz1JnBOc3kaY+HCLjQBAfwG/CsgnasOdXMw==", "dev": true, "requires": { "glob": "7.1.2", diff --git a/Extension/package.json b/Extension/package.json index 7786b78bac..583fb4c4bd 100644 --- a/Extension/package.json +++ b/Extension/package.json @@ -2,7 +2,7 @@ "name": "cpptools", "displayName": "C/C++", "description": "C/C++ IntelliSense, debugging, and code browsing.", - "version": "0.16.0-insiders2", + "version": "0.17.0-insiders", "publisher": "ms-vscode", "preview": true, "icon": "LanguageCCPP_color_128x.png", @@ -12,7 +12,7 @@ }, "license": "SEE LICENSE IN LICENSE.txt", "engines": { - "vscode": "^1.17.0" + "vscode": "^1.22.0" }, "bugs": { "url": "https://github.com/Microsoft/vscode-cpptools/issues", @@ -201,6 +201,16 @@ "description": "Instructs the extension when to use the \"files.exclude\" setting when determining which files should be added to the code navigation database while traversing through the paths in the \"browse.path\" array. \"checkFolders\" means that the exclusion filters will only be evaluated once per folder (individual files are not checked). \"checkFilesAndFolders\" means that the exclusion filters will be evaluated against every file and folder encountered. If your \"files.exclude\" setting only contains folders, then \"checkFolders\" is the best choice and will increase the speed at which the extension can initialize the code navigation database.", "scope": "resource" }, + "C_Cpp.preferredPathSeparator": { + "type": "string", + "enum": [ + "Forward Slash", + "Back Slash" + ], + "default": "Forward Slash", + "description": "The character used as a path separator for #include auto-completion results.", + "scope": "resource" + }, "C_Cpp.commentContinuationPatterns": { "type": "array", "default": [ @@ -229,6 +239,154 @@ }, "description": "Defines the editor behavior for when the Enter key is pressed inside a multiline or single line comment block.", "scope": "resource" + }, + "C_Cpp.default.includePath": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + }, + "default": null, + "description": "The value to use in a configuration if \"includePath\" is not specified, or the values to insert if \"${default}\" is present in \"includePath\".", + "scope": "resource" + }, + "C_Cpp.default.defines": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + }, + "default": null, + "description": "The value to use in a configuration if \"defines\" is not specified, or the values to insert if \"${default}\" is present in \"defines\".", + "scope": "resource" + }, + "C_Cpp.default.macFrameworkPath": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + }, + "default": null, + "description": "The value to use in a configuration if \"macFrameworkPath\" is not specified, or the values to insert if \"${default}\" is present in \"macFrameworkPath\".", + "scope": "resource" + }, + "C_Cpp.default.compileCommands": { + "type": [ + "string", + "null" + ], + "default": null, + "description": "The value to use in a configuration if \"compileCommands\" is either not specified, or set to \"${default}\".", + "scope": "resource" + }, + "C_Cpp.default.forcedInclude": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + }, + "default": null, + "description": "The value to use in a configuration if \"forcedInclude\" is not specified, or the values to insert if \"${default}\" is present in \"forcedInclude\".", + "scope": "resource" + }, + "C_Cpp.default.intelliSenseMode": { + "type": [ + "string", + "null" + ], + "enum": [ + "msvc-x64", + "clang-x64" + ], + "default": null, + "description": "The value to use in a configuration if \"intelliSenseMode\" is either not specified or set to \"${default}\".", + "scope": "resource" + }, + "C_Cpp.default.compilerPath": { + "type": [ + "string", + "null" + ], + "default": null, + "description": "The value to use in a configuration if \"compilerPath\" is either not specified or set to \"${default}\".", + "scope": "resource" + }, + "C_Cpp.default.cStandard": { + "type": [ + "string", + "null" + ], + "enum": [ + "c89", + "c99", + "c11" + ], + "default": null, + "description": "The value to use in a configuration if \"cStandard\" is either not specified or set to \"${default}\".", + "scope": "resource" + }, + "C_Cpp.default.cppStandard": { + "type": [ + "string", + "null" + ], + "enum": [ + "c++98", + "c++03", + "c++11", + "c++14", + "c++17" + ], + "default": null, + "description": "The value to use in a configuration if \"cppStandard\" is either not specified or set to \"${default}\".", + "scope": "resource" + }, + "C_Cpp.default.browse.path": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + }, + "default": null, + "description": "The value to use in a configuration if \"browse.path\" is not specified, or the values to insert if \"${default}\" is present in \"browse.path\".", + "scope": "resource" + }, + "C_Cpp.default.browse.databaseFilename": { + "type": [ + "string", + "null" + ], + "default": null, + "description": "The value to use in a configuration if \"browse.databaseFilename\" is either not specified or set to \"${default}\".", + "scope": "resource" + }, + "C_Cpp.default.browse.limitSymbolsToIncludedHeaders": { + "type": "boolean", + "default": true, + "description": "The value to use in a configuration if \"browse.limitSymbolsToIncludedHeaders\" is either not specified or set to \"${default}\".", + "scope": "resource" + }, + "C_Cpp.default.systemIncludePath": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + }, + "default": null, + "description": "The value to use for the system include path. If set, it overrides the system include path acquired via \"compilerPath\" and \"compileCommands\" settings.", + "scope": "resource" } } }, @@ -428,7 +586,7 @@ "ignoreFailures": { "type": "boolean", "description": "If true, failures from the command should be ignored. Default value is false.", - "default": "false" + "default": false } } }, @@ -453,7 +611,7 @@ "ignoreFailures": { "type": "boolean", "description": "If true, failures from the command should be ignored. Default value is false.", - "default": "" + "default": false } } }, @@ -476,7 +634,7 @@ "showDisplayString": { "type": "boolean", "description": "When a visualizerFile is specified, showDisplayString will enable the display string. Turning this option on can cause slower performance during debugging.", - "default": "true" + "default": true }, "environment": { "type": "array", @@ -533,12 +691,12 @@ "filterStdout": { "type": "boolean", "description": "Search stdout stream for server-started pattern and log stdout to debug output. Defaults to true.", - "default": "true" + "default": true }, "filterStderr": { "type": "boolean", "description": "Search stderr stream for server-started pattern and log stderr to debug output. Defaults to false.", - "default": "false" + "default": false }, "serverLaunchTimeout": { "type": "integer", @@ -553,7 +711,7 @@ "externalConsole": { "type": "boolean", "description": "If true, a console is launched for the debuggee. If false, no console is launched. Note this option is ignored in some cases for technical reasons.", - "default": "false" + "default": false }, "sourceFileMap": { "type": "object", @@ -673,7 +831,7 @@ "showDisplayString": { "type": "boolean", "description": "When a visualizerFile is specified, showDisplayString will enable the display string. Turning this option on can cause slower performance during debugging.", - "default": "true" + "default": true }, "additionalSOLibSearchPath": { "type": "string", @@ -712,12 +870,12 @@ "filterStdout": { "type": "boolean", "description": "Search stdout stream for server-started pattern and log stdout to debug output. Defaults to true.", - "default": "true" + "default": true }, "filterStderr": { "type": "boolean", "description": "Search stderr stream for server-started pattern and log stderr to debug output. Defaults to false.", - "default": "false" + "default": false }, "sourceFileMap": { "type": "object", @@ -825,7 +983,7 @@ "ignoreFailures": { "type": "boolean", "description": "If true, failures from the command should be ignored. Default value is false.", - "default": "false" + "default": false } } }, @@ -914,7 +1072,7 @@ "externalConsole": { "type": "boolean", "description": "If true, a console is launched for the debuggee. If false, no console is launched.", - "default": "false" + "default": false }, "sourceFileMap": { "type": "object", @@ -1109,6 +1267,7 @@ "integrationTests": "gulp integrationTests", "postinstall": "node ./node_modules/vscode/bin/install", "pretest": "tsc -p ./", + "pr-check": "gulp pr-check", "test": "gulp allTests", "tslint": "gulp tslint", "unitTests": "gulp unitTests", @@ -1127,7 +1286,7 @@ "tslint-no-unused-expression-chai": "0.0.3", "tslint": "5.8.0", "typescript": "^2.5.3", - "vscode": "^1.1.6" + "vscode": "^1.1.14" }, "dependencies": { "http-proxy-agent": "~2.0.0", @@ -1145,7 +1304,7 @@ "runtimeDependencies": [ { "description": "C/C++ language components (Linux / x86_64)", - "url": "https://go.microsoft.com/fwlink/?linkid=867977", + "url": "https://go.microsoft.com/fwlink/?linkid=872599", "platforms": [ "linux" ], @@ -1159,7 +1318,7 @@ }, { "description": "C/C++ language components (Linux / x86)", - "url": "https://go.microsoft.com/fwlink/?linkid=867978", + "url": "https://go.microsoft.com/fwlink/?linkid=872600", "platforms": [ "linux" ], @@ -1175,7 +1334,7 @@ }, { "description": "C/C++ language components (OS X)", - "url": "https://go.microsoft.com/fwlink/?linkid=867979", + "url": "https://go.microsoft.com/fwlink/?linkid=872601", "platforms": [ "darwin" ], @@ -1186,7 +1345,7 @@ }, { "description": "C/C++ language components (Windows)", - "url": "https://go.microsoft.com/fwlink/?linkid=867980", + "url": "https://go.microsoft.com/fwlink/?linkid=872602", "platforms": [ "win32" ], @@ -1194,7 +1353,7 @@ }, { "description": "ClangFormat (Linux / x86_64)", - "url": "https://go.microsoft.com/fwlink/?LinkID=848955", + "url": "https://go.microsoft.com/fwlink/?LinkID=872607", "platforms": [ "linux" ], @@ -1207,7 +1366,7 @@ }, { "description": "ClangFormat (Linux / x86)", - "url": "https://go.microsoft.com/fwlink/?LinkID=864640", + "url": "https://go.microsoft.com/fwlink/?LinkID=872608", "platforms": [ "linux" ], @@ -1222,7 +1381,7 @@ }, { "description": "ClangFormat (OS X)", - "url": "https://go.microsoft.com/fwlink/?LinkID=848956", + "url": "https://go.microsoft.com/fwlink/?LinkID=872609", "platforms": [ "darwin" ], @@ -1232,7 +1391,7 @@ }, { "description": "ClangFormat (Windows)", - "url": "https://go.microsoft.com/fwlink/?LinkID=848957", + "url": "https://go.microsoft.com/fwlink/?LinkID=872610", "platforms": [ "win32" ], diff --git a/Extension/src/Debugger/attachToProcess.ts b/Extension/src/Debugger/attachToProcess.ts index c9e0f3f2f3..65b5230b65 100644 --- a/Extension/src/Debugger/attachToProcess.ts +++ b/Extension/src/Debugger/attachToProcess.ts @@ -3,10 +3,15 @@ * See 'LICENSE' in the project root for license information. * ------------------------------------------------------------------------------------------ */ -import * as vscode from 'vscode'; import { execChildProcess } from '../common'; import { PsProcessParser } from './nativeAttach'; + +import * as debugUtils from './utils'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; import * as util from '../common'; +import * as vscode from 'vscode'; export interface AttachItem extends vscode.QuickPickItem { id: string; @@ -49,20 +54,47 @@ export class RemoteAttachPicker { private _channel: vscode.OutputChannel = null; - public ShowAttachEntries(args: any): Promise { + public ShowAttachEntries(config: any): Promise { return util.isExtensionReady().then(ready => { if (!ready) { util.displayExtensionNotReadyPrompt(); } else { this._channel.clear(); - let pipeTransport: any = args ? args.pipeTransport : null; + let pipeTransport: any = config ? config.pipeTransport : null; if (pipeTransport === null) { return Promise.reject(new Error("Chosen debug configuration does not contain pipeTransport")); } - let pipeProgram: string = pipeTransport.pipeProgram; + let pipeProgram: string = null; + + if (os.platform() === 'win32' && + pipeTransport.pipeProgram && + !fs.existsSync(pipeTransport.pipeProgram)) { + const pipeProgramStr: string = pipeTransport.pipeProgram.toLowerCase().trim(); + const expectedArch: debugUtils.ArchType = debugUtils.ArchType[process.arch]; + + // Check for pipeProgram + if (!fs.existsSync(config.pipeTransport.pipeProgram)) { + pipeProgram = debugUtils.ArchitectureReplacer.checkAndReplaceWSLPipeProgram(pipeProgramStr, expectedArch); + } + + // If pipeProgram does not get replaced and there is a pipeCwd, concatenate with pipeProgramStr and attempt to replace. + if (!pipeProgram && config.pipeTransport.pipeCwd) { + const pipeCwdStr: string = config.pipeTransport.pipeCwd.toLowerCase().trim(); + const newPipeProgramStr: string = path.join(pipeCwdStr, pipeProgramStr); + + if (!fs.existsSync(newPipeProgramStr)) { + pipeProgram = debugUtils.ArchitectureReplacer.checkAndReplaceWSLPipeProgram(newPipeProgramStr, expectedArch); + } + } + } + + if (!pipeProgram) { + pipeProgram = pipeTransport.pipeProgram; + } + let pipeArgs: string[] = pipeTransport.pipeArgs; let argList: string = RemoteAttachPicker.createArgumentList(pipeArgs); diff --git a/Extension/src/Debugger/configurationProvider.ts b/Extension/src/Debugger/configurationProvider.ts index 622d5a160f..93b31f8e6c 100644 --- a/Extension/src/Debugger/configurationProvider.ts +++ b/Extension/src/Debugger/configurationProvider.ts @@ -3,8 +3,11 @@ * See 'LICENSE' in the project root for license information. * ------------------------------------------------------------------------------------------ */ +import * as debugUtils from './utils'; import * as os from 'os'; +import * as path from 'path'; import * as vscode from 'vscode'; + import { IConfiguration, IConfigurationSnippet, DebuggerType, MIConfigurations, WindowsConfigurations, WSLConfigurations, PipeTransportConfigurations } from './configurations'; import { parse } from 'jsonc-parser'; @@ -34,6 +37,29 @@ abstract class CppConfigurationProvider implements vscode.DebugConfigurationProv return undefined; } + // Modify WSL config for OpenDebugAD7 + if (os.platform() === 'win32' && + config.pipeTransport && + config.pipeTransport.pipeProgram) { + let replacedPipeProgram: string = null; + const pipeProgramStr: string = config.pipeTransport.pipeProgram.toLowerCase().trim(); + + // OpenDebugAD7 is a 32-bit process. Make sure the WSL pipe transport is using the correct program. + replacedPipeProgram = debugUtils.ArchitectureReplacer.checkAndReplaceWSLPipeProgram(pipeProgramStr, debugUtils.ArchType.ia32); + + // If pipeProgram does not get replaced and there is a pipeCwd, concatenate with pipeProgramStr and attempt to replace. + if (!replacedPipeProgram && !path.isAbsolute(pipeProgramStr) && config.pipeTransport.pipeCwd) { + const pipeCwdStr: string = config.pipeTransport.pipeCwd.toLowerCase().trim(); + const newPipeProgramStr: string = path.join(pipeCwdStr, pipeProgramStr); + + replacedPipeProgram = debugUtils.ArchitectureReplacer.checkAndReplaceWSLPipeProgram(newPipeProgramStr, debugUtils.ArchType.ia32); + } + + if (replacedPipeProgram) { + config.pipeTransport.pipeProgram = replacedPipeProgram; + } + } + return config; } } diff --git a/Extension/src/Debugger/configurations.ts b/Extension/src/Debugger/configurations.ts index aca5382647..6a5e5322aa 100644 --- a/Extension/src/Debugger/configurations.ts +++ b/Extension/src/Debugger/configurations.ts @@ -218,7 +218,8 @@ export class WindowsConfigurations extends Configuration { } export class WSLConfigurations extends Configuration { - public bashPipeProgram = "C:\\\\Windows\\\\sysnative\\\\bash.exe"; + // Detects if the current VSCode is 32-bit and uses the correct bash.exe + public bashPipeProgram = process.arch === 'ia32' ? "${env:windir}\\\\sysnative\\\\bash.exe" : "${env:windir}\\\\system32\\\\bash.exe"; public GetLaunchConfiguration(): IConfigurationSnippet { let name: string = `(${this.MIMode}) Bash on Windows Launch`; diff --git a/Extension/src/Debugger/utils.ts b/Extension/src/Debugger/utils.ts new file mode 100644 index 0000000000..ef8b29f8ea --- /dev/null +++ b/Extension/src/Debugger/utils.ts @@ -0,0 +1,49 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. + * See 'LICENSE' in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +export enum ArchType { + ia32, + x64 +} + +export class ArchitectureReplacer { + public static checkAndReplaceWSLPipeProgram(pipeProgramStr: string, expectedArch: ArchType): string { + let replacedPipeProgram: string = null; + const winDir: string = process.env.WINDIR ? process.env.WINDIR.toLowerCase() : null; + const winDirAltDirSep: string = process.env.WINDIR ? process.env.WINDIR.replace('\\', '/').toLowerCase() : null; + const winDirEnv: string = "${env:windir}"; + + if (winDir && winDirAltDirSep && (pipeProgramStr.indexOf(winDir) === 0 || pipeProgramStr.indexOf(winDirAltDirSep) === 0 || pipeProgramStr.indexOf(winDirEnv) === 0)) { + if (expectedArch === ArchType.x64) { + const pathSep: string = ArchitectureReplacer.checkForFolderInPath(pipeProgramStr, "sysnative"); + if (pathSep) { + // User has sysnative but we expect 64 bit. Should be using System32 since sysnative is a 32bit concept. + replacedPipeProgram = pipeProgramStr.replace(`${pathSep}sysnative${pathSep}`, `${pathSep}system32${pathSep}`); + } + } else if (expectedArch === ArchType.ia32) { + const pathSep: string = ArchitectureReplacer.checkForFolderInPath(pipeProgramStr, "system32"); + if (pathSep) { + // User has System32 but we expect 32 bit. Should be using sysnative + replacedPipeProgram = pipeProgramStr.replace(`${pathSep}system32${pathSep}`, `${pathSep}sysnative${pathSep}`); + } + } + } + + return replacedPipeProgram; + } + + // Checks to see if the folder name is in the path using both win and unix style path seperators. + // Returns the path seperator it detected if the folder is in the path. + // Or else it returns empty string to indicate it did not find it in the path. + public static checkForFolderInPath(path: string, folder: string): string { + if (path.indexOf(`/${folder}/`) >= 0) { + return '/'; + } else if (path.indexOf(`\\${folder}\\`) >= 0) { + return '\\'; + } + + return ""; + } +} \ No newline at end of file diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index f3b67f54b0..6e133a4a75 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -145,13 +145,13 @@ function collectSettingsForTelemetry(filter: (key: string, val: string, settings if (filter(key, val, settings)) { previousCppSettings[key] = val; - switch (String(key).toLowerCase()) { + switch (key.toLowerCase()) { case "clang_format_path": { continue; } case "clang_format_style": case "clang_format_fallbackstyle": { - let newKey: string = String(key) + "2"; + let newKey: string = key + "2"; if (val) { switch (String(val).toLowerCase()) { case "visual studio": @@ -177,6 +177,9 @@ function collectSettingsForTelemetry(filter: (key: string, val: string, settings break; } default: { + if (key.startsWith("default.")) { + continue; // Don't log c_cpp_properties.json defaults since they may contain PII. + } result[key] = String(previousCppSettings[key]); break; } @@ -317,7 +320,7 @@ class DefaultClient implements Client { ui.bind(this); this.onReadyPromise = languageClient.onReady().then(() => { - this.configuration = new configs.CppProperties(this.RootPath); + this.configuration = new configs.CppProperties(this.RootUri); this.configuration.ConfigurationsChanged((e) => this.onConfigurationsChanged(e)); this.configuration.SelectionChanged((e) => this.onSelectedConfigurationChanged(e)); this.configuration.CompileCommandsChanged((e) => this.onCompileCommandsChanged(e)); @@ -397,7 +400,11 @@ class DefaultClient implements Client { dimInactiveRegions: settings.dimInactiveRegions, loggingLevel: settings.loggingLevel, workspaceParsingPriority: settings.workspaceParsingPriority, - exclusionPolicy: settings.exclusionPolicy + exclusionPolicy: settings.exclusionPolicy, + preferredPathSeparator: settings.preferredPathSeparator, + default: { + systemIncludePath: settings.defaultSystemIncludePath + } }, middleware: createProtocolFilter(this, allClients), // Only send messages directed at this client. errorHandler: { @@ -446,6 +453,7 @@ class DefaultClient implements Client { if (changedSettings["commentContinuationPatterns"]) { updateLanguageConfigurations(); } + this.configuration.onDidChangeSettings(); telemetry.logLanguageServerEvent("CppSettingsChange", changedSettings, null); } } diff --git a/Extension/src/LanguageServer/clientCollection.ts b/Extension/src/LanguageServer/clientCollection.ts index eb63d8fba9..6cb850b8b8 100644 --- a/Extension/src/LanguageServer/clientCollection.ts +++ b/Extension/src/LanguageServer/clientCollection.ts @@ -8,6 +8,7 @@ import * as vscode from 'vscode'; import * as util from '../common'; import * as telemetry from '../telemetry'; import * as cpptools from './client'; +import * as path from 'path'; const defaultClientKey: string = "@@default@@"; export interface ClientKey { @@ -82,7 +83,7 @@ export class ClientCollection { public checkOwnership(client: cpptools.Client, document: vscode.TextDocument): boolean { let owners: cpptools.Client[] = []; this.languageClients.forEach(languageClient => { - if (document.uri.fsPath.startsWith(languageClient.RootPath)) { + if (document.uri.fsPath.startsWith(languageClient.RootPath + path.sep)) { owners.push(languageClient); } }); diff --git a/Extension/src/LanguageServer/configurations.ts b/Extension/src/LanguageServer/configurations.ts index 397c16366d..5762f15aec 100644 --- a/Extension/src/LanguageServer/configurations.ts +++ b/Extension/src/LanguageServer/configurations.ts @@ -9,82 +9,32 @@ import * as fs from "fs"; import * as vscode from 'vscode'; import * as util from '../common'; import { PersistentFolderState } from './persistentState'; +import { CppSettings } from './settings'; const configVersion: number = 3; -let defaultSettings: string = `{ - "configurations": [ - { - "name": "Mac", - "includePath": [ - "/usr/include", - "/usr/local/include", - "$\{workspaceFolder\}" - ], - "defines": [], - "intelliSenseMode": "clang-x64", - "browse": { - "path": [ - "/usr/include", - "/usr/local/include", - "$\{workspaceFolder\}" - ], - "limitSymbolsToIncludedHeaders": true, - "databaseFilename": "" - }, - "macFrameworkPath": [ - "/System/Library/Frameworks", - "/Library/Frameworks" - ] - }, - { - "name": "Linux", - "includePath": [ - "/usr/include", - "/usr/local/include", - "$\{workspaceFolder\}" - ], - "defines": [], - "intelliSenseMode": "clang-x64", - "browse": { - "path": [ - "/usr/include", - "/usr/local/include", - "$\{workspaceFolder\}" - ], - "limitSymbolsToIncludedHeaders": true, - "databaseFilename": "" - } - }, - { - "name": "Win32", - "includePath": [ - "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include", - "$\{workspaceFolder\}" - ], - "defines": [ - "_DEBUG", - "UNICODE", - "_UNICODE" - ], - "intelliSenseMode": "msvc-x64", - "browse": { - "path": [ - "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include/*", - "$\{workspaceFolder\}" - ], - "limitSymbolsToIncludedHeaders": true, - "databaseFilename": "" - } - } - ], - "version": ${configVersion} +// No properties are set in the config since we want to apply vscode settings first (if applicable). +// That code won't trigger if another value is already set. +// The property defaults are moved down to applyDefaultIncludePathsAndFrameworks. +function getDefaultConfig(): Configuration { + if (process.platform === 'darwin') { + return { name: "Mac", browse: {} }; + } else if (process.platform === 'win32') { + return { name: "Win32", browse: {} }; + } else { + return { name: "Linux", browse: {} }; + } } -`; -export interface Browse { - path?: string[]; - limitSymbolsToIncludedHeaders?: boolean; - databaseFilename?: string; +function getDefaultCppProperties(): ConfigurationJson { + return { + configurations: [getDefaultConfig()], + version: configVersion + }; +} + +interface ConfigurationJson { + configurations: Configuration[]; + version: number; } export interface Configuration { @@ -101,20 +51,23 @@ export interface Configuration { browse?: Browse; } +export interface Browse { + path?: string[]; + limitSymbolsToIncludedHeaders?: boolean | string; + databaseFilename?: string; +} + export interface CompilerDefaults { compilerPath: string; cStandard: string; cppStandard: string; includes: string[]; frameworks: string[]; -} - -interface ConfigurationJson { - configurations: Configuration[]; - version: number; + intelliSenseMode: string; } export class CppProperties { + private rootUri: vscode.Uri; private propertiesFile: vscode.Uri = null; private readonly configFolder: string; private configurationJson: ConfigurationJson = null; @@ -127,25 +80,31 @@ export class CppProperties { private defaultCppStandard: string = null; private defaultIncludes: string[] = null; private defaultFrameworks: string[] = null; + private defaultIntelliSenseMode: string = null; private readonly configurationGlobPattern: string = "**/c_cpp_properties.json"; // TODO: probably should be a single file, not all files... private disposables: vscode.Disposable[] = []; private configurationsChanged = new vscode.EventEmitter(); private selectionChanged = new vscode.EventEmitter(); private compileCommandsChanged = new vscode.EventEmitter(); - // Any time the `defaultSettings` are parsed and assigned to `this.configurationJson`, + // Any time the default settings are parsed and assigned to `this.configurationJson`, // we want to track when the default includes have been added to it. private configurationIncomplete: boolean = true; - constructor(rootPath: string) { - console.assert(rootPath !== undefined); + constructor(rootUri: vscode.Uri) { + console.assert(rootUri !== undefined); + this.rootUri = rootUri; + let rootPath: string = rootUri ? rootUri.fsPath : ""; this.currentConfigurationIndex = new PersistentFolderState("CppProperties.currentConfigurationIndex", -1, rootPath); this.configFolder = path.join(rootPath, ".vscode"); - this.resetToDefaultSettings(this.currentConfigurationIndex.Value === -1); let configFilePath: string = path.join(this.configFolder, "c_cpp_properties.json"); if (fs.existsSync(configFilePath)) { this.propertiesFile = vscode.Uri.file(configFilePath); + this.parsePropertiesFile(); + } + if (!this.configurationJson) { + this.resetToDefaultSettings(this.CurrentConfiguration === -1); } this.configFileWatcher = vscode.workspace.createFileSystemWatcher(path.join(this.configFolder, this.configurationGlobPattern)); @@ -186,6 +145,7 @@ export class CppProperties { this.defaultCppStandard = compilerDefaults.cppStandard; this.defaultIncludes = compilerDefaults.includes; this.defaultFrameworks = compilerDefaults.frameworks; + this.defaultIntelliSenseMode = compilerDefaults.intelliSenseMode; // defaultPaths is only used when there isn't a c_cpp_properties.json, but we don't send the configuration changed event // to the language server until the default include paths and frameworks have been sent. @@ -204,8 +164,19 @@ export class CppProperties { this.compileCommandsChanged.fire(path); } + public onDidChangeSettings(): void { + // Default settings may have changed in a way that affects the configuration. + // Just send another message since the language server will sort out whether anything important changed or not. + if (!this.propertiesFile) { + this.resetToDefaultSettings(true); + this.handleConfigurationChange(); + } else if (!this.configurationIncomplete) { + this.handleConfigurationChange(); + } + } + private resetToDefaultSettings(resetIndex: boolean): void { - this.configurationJson = JSON.parse(defaultSettings); + this.configurationJson = getDefaultCppProperties(); if (resetIndex || this.CurrentConfiguration < 0 || this.CurrentConfiguration >= this.configurationJson.configurations.length) { this.currentConfigurationIndex.Value = this.getConfigIndexForPlatform(this.configurationJson); @@ -215,17 +186,40 @@ export class CppProperties { private applyDefaultIncludePathsAndFrameworks(): void { if (this.configurationIncomplete && this.defaultIncludes && this.defaultFrameworks) { - this.configurationJson.configurations[this.CurrentConfiguration].includePath = this.defaultIncludes; - this.configurationJson.configurations[this.CurrentConfiguration].browse.path = this.defaultIncludes; - if (process.platform === 'darwin') { - this.configurationJson.configurations[this.CurrentConfiguration].macFrameworkPath = this.defaultFrameworks; + let configuration: Configuration = this.configurationJson.configurations[this.CurrentConfiguration]; + let settings: CppSettings = new CppSettings(this.rootUri); + + // Anything that has a vscode setting for it will be resolved in updateServerOnFolderSettingsChange. + // So if a property is currently unset, but has a vscode setting, don't set it yet, otherwise the linkage + // to the setting will be lost if this configuration is saved into a c_cpp_properties.json file. + + // Only add settings from the default compiler if user hasn't explicitly set the corresponding VS Code setting. + + if (!settings.defaultIncludePath) { + // We don't add system includes to the includePath anymore. The language server has this information. + configuration.includePath = ["${workspaceFolder}"]; + } + if (!settings.defaultDefines) { + configuration.defines = (process.platform === 'win32') ? ["_DEBUG", "UNICODE", "_UNICODE"] : []; + } + if (!settings.defaultBrowsePath) { + // We don't add system includes to the browse.path anymore. The language server has this information. + configuration.browse.path = ["${workspaceFolder}"]; + } + if (!settings.defaultMacFrameworkPath && process.platform === 'darwin') { + configuration.macFrameworkPath = this.defaultFrameworks; + } + if (!settings.defaultCompilerPath && this.defaultCompilerPath) { + configuration.compilerPath = this.defaultCompilerPath; } - this.configurationJson.configurations[this.CurrentConfiguration].compilerPath = this.defaultCompilerPath; - if (this.defaultCStandard) { - this.configurationJson.configurations[this.CurrentConfiguration].cStandard = this.defaultCStandard; + if (!settings.defaultCStandard && this.defaultCStandard) { + configuration.cStandard = this.defaultCStandard; } - if (this.defaultCppStandard) { - this.configurationJson.configurations[this.CurrentConfiguration].cppStandard = this.defaultCppStandard; + if (!settings.defaultCppStandard && this.defaultCppStandard) { + configuration.cppStandard = this.defaultCppStandard; + } + if (!settings.defaultIntelliSenseMode) { + configuration.intelliSenseMode = this.defaultIntelliSenseMode; } this.configurationIncomplete = false; } @@ -279,7 +273,11 @@ export class CppProperties { public addToIncludePathCommand(path: string): void { this.handleConfigurationEditCommand((document: vscode.TextDocument) => { + this.parsePropertiesFile(); // Clear out any modifications we may have made internally. let config: Configuration = this.configurationJson.configurations[this.CurrentConfiguration]; + if (config.includePath === undefined) { + config.includePath = ["${default}"]; + } config.includePath.splice(config.includePath.length, 0, path); fs.writeFileSync(this.propertiesFile.fsPath, JSON.stringify(this.configurationJson, null, 4)); this.updateServerOnFolderSettingsChange(); @@ -295,38 +293,79 @@ export class CppProperties { this.onSelectionChanged(); } - private resolveAndSplit(paths: string[] | undefined): string[] { + private resolveDefaults(entries: string[], defaultValue: string[]): string[] { + let result: string[] = []; + entries.forEach(entry => { + if (entry === "${default}") { + result = result.concat(defaultValue); + } else { + result.push(entry); + } + }); + return result; + } + + private resolveAndSplit(paths: string[] | undefined, defaultValue: string[]): string[] { let result: string[] = []; if (paths) { paths.forEach(entry => { let entries: string[] = util.resolveVariables(entry).split(";").filter(e => e); + entries = this.resolveDefaults(entries, defaultValue); result = result.concat(entries); }); } return result; } + private resolveVariables(input: string | boolean, defaultValue: string | boolean): string | boolean { + if (input === undefined || input === "${default}") { + input = defaultValue; + } + if (typeof input === "boolean") { + return input; + } + return util.resolveVariables(input); + } + + private updateConfiguration(property: string[], defaultValue: string[]): string[]; + private updateConfiguration(property: string, defaultValue: string): string; + private updateConfiguration(property: string | boolean, defaultValue: boolean): boolean; + private updateConfiguration(property, defaultValue): any { + if (typeof property === "string" || typeof defaultValue === "string") { + return this.resolveVariables(property, defaultValue); + } else if (typeof property === "boolean" || typeof defaultValue === "boolean") { + return this.resolveVariables(property, defaultValue); + } else if (property instanceof Array || defaultValue instanceof Array) { + if (property) { + return this.resolveAndSplit(property, defaultValue); + } else if (property === undefined && defaultValue) { + return this.resolveAndSplit(defaultValue, []); + } + } + return property; + } + private updateServerOnFolderSettingsChange(): void { + let settings: CppSettings = new CppSettings(this.rootUri); for (let i: number = 0; i < this.configurationJson.configurations.length; i++) { let configuration: Configuration = this.configurationJson.configurations[i]; - if (configuration.includePath) { - configuration.includePath = this.resolveAndSplit(configuration.includePath); - } - if (configuration.browse && configuration.browse.path) { - configuration.browse.path = this.resolveAndSplit(configuration.browse.path); - } - if (configuration.macFrameworkPath) { - configuration.macFrameworkPath = this.resolveAndSplit(configuration.macFrameworkPath); - } - if (configuration.forcedInclude) { - configuration.forcedInclude = this.resolveAndSplit(configuration.forcedInclude); - } - if (configuration.compileCommands) { - configuration.compileCommands = util.resolveVariables(configuration.compileCommands); - } - if (configuration.compilerPath) { - configuration.compilerPath = util.resolveVariables(configuration.compilerPath); + + configuration.includePath = this.updateConfiguration(configuration.includePath, settings.defaultIncludePath); + configuration.defines = this.updateConfiguration(configuration.defines, settings.defaultDefines); + configuration.macFrameworkPath = this.updateConfiguration(configuration.macFrameworkPath, settings.defaultMacFrameworkPath); + configuration.forcedInclude = this.updateConfiguration(configuration.forcedInclude, settings.defaultForcedInclude); + configuration.compileCommands = this.updateConfiguration(configuration.compileCommands, settings.defaultCompileCommands); + configuration.compilerPath = this.updateConfiguration(configuration.compilerPath, settings.defaultCompilerPath); + configuration.cStandard = this.updateConfiguration(configuration.cStandard, settings.defaultCStandard); + configuration.cppStandard = this.updateConfiguration(configuration.cppStandard, settings.defaultCppStandard); + configuration.intelliSenseMode = this.updateConfiguration(configuration.intelliSenseMode, settings.defaultIntelliSenseMode); + + if (!configuration.browse) { + configuration.browse = {}; } + configuration.browse.path = this.updateConfiguration(configuration.browse.path, settings.defaultBrowsePath); + configuration.browse.limitSymbolsToIncludedHeaders = this.updateConfiguration(configuration.browse.limitSymbolsToIncludedHeaders, settings.defaultLimitSymbolsToIncludedHeaders); + configuration.browse.databaseFilename = this.updateConfiguration(configuration.browse.databaseFilename, settings.defaultDatabaseFilename); } this.updateCompileCommandsFileWatchers(); @@ -433,7 +472,7 @@ export class CppProperties { throw { message: "Invalid configuration file. There must be at least one configuration present in the array." }; } if (!this.configurationIncomplete && this.configurationJson && this.configurationJson.configurations && - this.CurrentConfiguration < this.configurationJson.configurations.length) { + this.CurrentConfiguration >= 0 && this.CurrentConfiguration < this.configurationJson.configurations.length) { for (let i: number = 0; i < newJson.configurations.length; i++) { if (newJson.configurations[i].name === this.configurationJson.configurations[this.CurrentConfiguration].name) { this.currentConfigurationIndex.Value = i; @@ -442,7 +481,7 @@ export class CppProperties { } } this.configurationJson = newJson; - if (this.CurrentConfiguration >= newJson.configurations.length) { + if (this.CurrentConfiguration < 0 || this.CurrentConfiguration >= newJson.configurations.length) { this.currentConfigurationIndex.Value = this.getConfigIndexForPlatform(newJson); } @@ -450,13 +489,29 @@ export class CppProperties { // the system includes were available. this.configurationIncomplete = false; + // Update intelliSenseMode, compilerPath, cStandard, and cppStandard with the defaults if they're missing. + // If VS Code settings exist for these properties, don't add them to c_cpp_properties.json let dirty: boolean = false; + let settings: CppSettings = new CppSettings(this.rootUri); for (let i: number = 0; i < this.configurationJson.configurations.length; i++) { let config: Configuration = this.configurationJson.configurations[i]; - if (config.intelliSenseMode === undefined) { + if (config.intelliSenseMode === undefined && !settings.defaultIntelliSenseMode) { dirty = true; config.intelliSenseMode = this.getIntelliSenseModeForPlatform(config.name); } + // Don't set the default if compileCommands exist, until it is fixed to have the correct value. + if (config.compilerPath === undefined && this.defaultCompilerPath && !config.compileCommands && !settings.defaultCompilerPath) { + config.compilerPath = this.defaultCompilerPath; + dirty = true; + } + if (!config.cStandard && this.defaultCStandard && !settings.defaultCStandard) { + config.cStandard = this.defaultCStandard; + dirty = true; + } + if (!config.cppStandard && this.defaultCppStandard && !settings.defaultCppStandard) { + config.cppStandard = this.defaultCppStandard; + dirty = true; + } } if (this.configurationJson.version !== configVersion) { @@ -473,23 +528,13 @@ export class CppProperties { } } - // Update the compilerPath, cStandard, and cppStandard with the default if they're missing. - let config: Configuration = this.configurationJson.configurations[this.CurrentConfiguration]; - if (config.compilerPath === undefined) { - config.compilerPath = this.defaultCompilerPath; - dirty = true; - } - if (!config.cStandard) { - config.cStandard = this.defaultCStandard; - dirty = true; - } - if (!config.cppStandard) { - config.cppStandard = this.defaultCppStandard; - dirty = true; - } - if (dirty) { - fs.writeFileSync(this.propertiesFile.fsPath, JSON.stringify(this.configurationJson, null, 4)); + try { + fs.writeFileSync(this.propertiesFile.fsPath, JSON.stringify(this.configurationJson, null, 4)); + } catch { + // Ignore write errors, the file may be under source control. Updated settings will only be modified in memory. + vscode.window.showWarningMessage('Attempt to update "' + this.propertiesFile.fsPath + '" failed (do you have write access?)'); + } } } catch (err) { vscode.window.showErrorMessage('Failed to parse "' + this.propertiesFile.fsPath + '": ' + err.message); diff --git a/Extension/src/LanguageServer/extension.ts b/Extension/src/LanguageServer/extension.ts index e034760cac..a5966cbdb0 100644 --- a/Extension/src/LanguageServer/extension.ts +++ b/Extension/src/LanguageServer/extension.ts @@ -47,14 +47,28 @@ export function activate(activationEventOccurred: boolean): void { // Check if an activation event has already occurred. if (activationEventOccurred) { - return onActivationEvent(); + onActivationEvent(); + return; } - + + // handle "workspaceContains:/.vscode/c_cpp_properties.json" activation event. + if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) { + for (let i: number = 0; i < vscode.workspace.workspaceFolders.length; ++i) { + let config: string = path.join(vscode.workspace.workspaceFolders[i].uri.fsPath, ".vscode/c_cpp_properties.json"); + if (fs.existsSync(config)) { + onActivationEvent(); + return; + } + } + } + + // handle "onLanguage:cpp" and "onLanguage:c" activation events. if (vscode.workspace.textDocuments !== undefined && vscode.workspace.textDocuments.length > 0) { for (let i: number = 0; i < vscode.workspace.textDocuments.length; ++i) { let document: vscode.TextDocument = vscode.workspace.textDocuments[i]; if (document.languageId === "cpp" || document.languageId === "c") { - return onActivationEvent(); + onActivationEvent(); + return; } } } diff --git a/Extension/src/LanguageServer/settings.ts b/Extension/src/LanguageServer/settings.ts index e09f31ef77..40a3557f9d 100644 --- a/Extension/src/LanguageServer/settings.ts +++ b/Extension/src/LanguageServer/settings.ts @@ -47,6 +47,20 @@ export class CppSettings extends Settings { public get workspaceParsingPriority(): boolean { return super.Section.get("workspaceParsingPriority"); } public get exclusionPolicy(): boolean { return super.Section.get("exclusionPolicy"); } public get commentContinuationPatterns(): (string | CommentPattern)[] { return super.Section.get<(string | CommentPattern)[]>("commentContinuationPatterns"); } + public get preferredPathSeparator(): string { return super.Section.get("preferredPathSeparator"); } + public get defaultIncludePath(): string[] { return super.Section.get("default.includePath"); } + public get defaultDefines(): string[] { return super.Section.get("default.defines"); } + public get defaultMacFrameworkPath(): string[] { return super.Section.get("default.macFrameworkPath"); } + public get defaultCompileCommands(): string { return super.Section.get("default.compileCommands"); } + public get defaultForcedInclude(): string[] { return super.Section.get("default.forcedInclude"); } + public get defaultIntelliSenseMode(): string { return super.Section.get("default.intelliSenseMode"); } + public get defaultCompilerPath(): string { return super.Section.get("default.compilerPath"); } + public get defaultCStandard(): string { return super.Section.get("default.cStandard"); } + public get defaultCppStandard(): string { return super.Section.get("default.cppStandard"); } + public get defaultBrowsePath(): string[] { return super.Section.get("default.browse.path"); } + public get defaultDatabaseFilename(): string { return super.Section.get("default.browse.databaseFilename"); } + public get defaultLimitSymbolsToIncludedHeaders(): boolean { return super.Section.get("default.browse.limitSymbolsToIncludedHeaders"); } + public get defaultSystemIncludePath(): string[] { return super.Section.get("default.systemIncludePath"); } public toggleSetting(name: string, value1: string, value2: string): void { let value: string = super.Section.get(name); diff --git a/Extension/src/abTesting.ts b/Extension/src/abTesting.ts index c3fd053245..e688aa5804 100644 --- a/Extension/src/abTesting.ts +++ b/Extension/src/abTesting.ts @@ -80,16 +80,17 @@ export function downloadCpptoolsJsonPkg(): Promise { export function processCpptoolsJson(cpptoolsString: string): Promise { let cpptoolsObject: any = JSON.parse(cpptoolsString); let intelliSenseEnginePercentage: number = cpptoolsObject.intelliSenseEngine_default_percentage; + let packageJson: any = util.getRawPackageJson(); - if (!util.packageJson.extensionFolderPath.includes(".vscode-insiders")) { - let prevIntelliSenseEngineDefault: any = util.packageJson.contributes.configuration.properties["C_Cpp.intelliSenseEngine"].default; + if (!packageJson.extensionFolderPath.includes(".vscode-insiders")) { + let prevIntelliSenseEngineDefault: any = packageJson.contributes.configuration.properties["C_Cpp.intelliSenseEngine"].default; if (util.extensionContext.globalState.get(userBucketString, userBucketMax + 1) <= intelliSenseEnginePercentage) { - util.packageJson.contributes.configuration.properties["C_Cpp.intelliSenseEngine"].default = "Default"; + packageJson.contributes.configuration.properties["C_Cpp.intelliSenseEngine"].default = "Default"; } else { - util.packageJson.contributes.configuration.properties["C_Cpp.intelliSenseEngine"].default = "Tag Parser"; + packageJson.contributes.configuration.properties["C_Cpp.intelliSenseEngine"].default = "Tag Parser"; } - if (prevIntelliSenseEngineDefault !== util.packageJson.contributes.configuration.properties["C_Cpp.intelliSenseEngine"].default) { - return util.writeFileText(util.getPackageJsonPath(), util.getPackageJsonString()); + if (prevIntelliSenseEngineDefault !== packageJson.contributes.configuration.properties["C_Cpp.intelliSenseEngine"].default) { + return util.writeFileText(util.getPackageJsonPath(), util.stringifyPackageJson(packageJson)); } } } \ No newline at end of file diff --git a/Extension/src/common.ts b/Extension/src/common.ts index 964b2f735d..19cc5e3f4e 100644 --- a/Extension/src/common.ts +++ b/Extension/src/common.ts @@ -20,18 +20,34 @@ export function setExtensionContext(context: vscode.ExtensionContext): void { extensionContext = context; } -export let packageJson: any = vscode.extensions.getExtension("ms-vscode.cpptools").packageJSON; +// Use this package.json to read values +export const packageJson: any = vscode.extensions.getExtension("ms-vscode.cpptools").packageJSON; + +// Use getRawPackageJson to read and write back to package.json +// This prevents obtaining any of VSCode's expanded variables. +let rawPackageJson: any = null; +export function getRawPackageJson(): any { + if (rawPackageJson === null) { + const fileContents: Buffer = fs.readFileSync(getPackageJsonPath()); + rawPackageJson = JSON.parse(fileContents.toString()); + } + return rawPackageJson; +} + +// This function is used to stringify the rawPackageJson. +// Do not use with util.packageJson or else the expanded +// package.json will be written back. +export function stringifyPackageJson(packageJson: string): string { + return JSON.stringify(packageJson, null, 2); +} export function getExtensionFilePath(extensionfile: string): string { return path.resolve(extensionContext.extensionPath, extensionfile); } + export function getPackageJsonPath(): string { return getExtensionFilePath("package.json"); } -export function getPackageJsonString(): string { - packageJson.main = "./out/src/main"; // Needs to be reset, because the relative path is removed by VS Code. - return JSON.stringify(packageJson, null, 2); -} // Extension is ready if install.lock exists and debugAdapters folder exist. export async function isExtensionReady(): Promise { @@ -408,4 +424,26 @@ export function checkDistro(platformInfo: PlatformInformation): void { // or SunOS (the other platforms supported by node) getOutputChannelLogger().appendLine(`Warning: Debugging has not been tested for this platform. ${getReadmeMessage()}`); } +} + +export async function unlinkPromise(fileName: string): Promise { + return new Promise((resolve, reject) => { + fs.unlink(fileName, err => { + if (err) { + return reject(err); + } + return resolve(); + }); + }); +} + +export async function renamePromise(oldName: string, newName: string): Promise { + return new Promise((resolve, reject) => { + fs.rename(oldName, newName, err => { + if (err) { + return reject(err); + } + return resolve(); + }); + }); } \ No newline at end of file diff --git a/Extension/src/main.ts b/Extension/src/main.ts index 54d19f7f28..0a085d0417 100644 --- a/Extension/src/main.ts +++ b/Extension/src/main.ts @@ -114,18 +114,22 @@ async function downloadAndInstallPackages(info: PlatformInformation): Promise { - outputChannelLogger.appendLine(''); - setInstallationStage('installPackages'); - await packageManager.InstallPackages(); + outputChannelLogger.appendLine(''); + setInstallationStage('downloadPackages'); + await packageManager.DownloadPackages(progress); - statusItem.dispose(); + outputChannelLogger.appendLine(''); + setInstallationStage('installPackages'); + await packageManager.InstallPackages(progress); + }); } function makeBinariesExecutable(): Promise { @@ -287,7 +291,9 @@ async function finalizeExtensionActivation(): Promise { function rewriteManifest(): Promise { // Replace activationEvents with the events that the extension should be activated for subsequent sessions. - util.packageJson.activationEvents = [ + let packageJson: any = util.getRawPackageJson(); + + packageJson.activationEvents = [ "onLanguage:cpp", "onLanguage:c", "onCommand:extension.pickNativeProcess", @@ -307,8 +313,9 @@ function rewriteManifest(): Promise { "onCommand:C_Cpp.ResumeParsing", "onCommand:C_Cpp.ShowParsingCommands", "onCommand:C_Cpp.TakeSurvey", - "onDebug" + "onDebug", + "workspaceContains:/.vscode/c_cpp_properties.json" ]; - return util.writeFileText(util.getPackageJsonPath(), util.getPackageJsonString()); + return util.writeFileText(util.getPackageJsonPath(), util.stringifyPackageJson(packageJson)); } diff --git a/Extension/src/packageManager.ts b/Extension/src/packageManager.ts index a2ae74f0b5..1ee9ae5647 100644 --- a/Extension/src/packageManager.ts +++ b/Extension/src/packageManager.ts @@ -67,24 +67,33 @@ export class PackageManager { public constructor( private platformInfo: PlatformInformation, - private outputChannel?: Logger, - private statusItem?: vscode.StatusBarItem) { + private outputChannel?: Logger) { // Ensure our temp files get cleaned up in case of error tmp.setGracefulCleanup(); } - public DownloadPackages(): Promise { + public DownloadPackages(progress: vscode.Progress<{message?: string; increment?: number}>): Promise { return this.GetPackages() .then((packages) => { - return this.BuildPromiseChain(packages, (pkg) => this.DownloadPackage(pkg)); + let count: number = 1; + return this.BuildPromiseChain(packages, (pkg): Promise => { + const p: Promise = this.DownloadPackage(pkg, `${count}/${packages.length}`, progress); + count += 1; + return p; + }); }); } - public InstallPackages(): Promise { + public InstallPackages(progress: vscode.Progress<{message?: string; increment?: number}>): Promise { return this.GetPackages() - .then((packages) => { - return this.BuildPromiseChain(packages, (pkg) => this.InstallPackage(pkg)); + .then((packages) => { + let count: number = 1; + return this.BuildPromiseChain(packages, (pkg): Promise => { + const p: Promise = this.InstallPackage(pkg, `${count}/${packages.length}`, progress); + count += 1; + return p; }); + }); } /** Builds a chain of promises by calling the promiseBuilder function once per item in the list. @@ -137,73 +146,77 @@ export class PackageManager { }); } - private DownloadPackage(pkg: IPackage): Promise { + private async DownloadPackage(pkg: IPackage, progressCount: string, progress: vscode.Progress<{message?: string; increment?: number}>): Promise { this.AppendChannel(`Downloading package '${pkg.description}' `); - this.SetStatusText("$(cloud-download) Downloading packages..."); - this.SetStatusTooltip(`Downloading package '${pkg.description}'...`); + progress.report({message: `Downloading ${progressCount}: ${pkg.description}`}); + + const tmpResult: tmp.SyncResult = await this.CreateTempFile(pkg); + await this.DownloadPackageWithRetries(pkg, tmpResult, progress); + } + private async CreateTempFile(pkg: IPackage): Promise { return new Promise((resolve, reject) => { tmp.file({ prefix: "package-" }, (err, path, fd, cleanupCallback) => { if (err) { return reject(new PackageManagerError('Error from temp.file', 'DownloadPackage', pkg, err)); } - resolve({ name: path, fd: fd, removeCallback: cleanupCallback }); + return resolve({ name: path, fd: fd, removeCallback: cleanupCallback }); }); - }) - .then((tmpResult) => { - pkg.tmpFile = tmpResult; - - let lastError: any = null; - let retryCount: number = 0; - let handleDownloadFailure: (num: any, error: any) => void = (num, error) => { - retryCount = num; - lastError = error; + }); + } + + private async DownloadPackageWithRetries(pkg: IPackage, tmpResult: tmp.SyncResult, progress: vscode.Progress<{message?: string; increment?: number}>): Promise { + pkg.tmpFile = tmpResult; + + let success: boolean = false; + let lastError: any = null; + let retryCount: number = 0; + const MAX_RETRIES: number = 5; + + // Retry the download at most MAX_RETRIES times with 2-32 seconds delay. + do { + try { + await this.DownloadFile(pkg.url, pkg, retryCount, progress); + success = true; + } catch (error) { + retryCount += 1; + lastError = error; + if (retryCount >= MAX_RETRIES) { + this.AppendChannel(` Failed to download ` + pkg.url); + throw error; + } else { + // This will skip the success = true. this.AppendChannel(` Failed. Retrying...`); - }; - // Retry the download at most 5 times with 2-32 seconds delay. - return this.DownloadFile(pkg.url, pkg, 0).catch((error) => { - handleDownloadFailure(1, error); - return this.DownloadFile(pkg.url, pkg, 1).catch((error) => { - handleDownloadFailure(2, error); - return this.DownloadFile(pkg.url, pkg, 2).catch((error) => { - handleDownloadFailure(3, error); - return this.DownloadFile(pkg.url, pkg, 3).catch((error) => { - handleDownloadFailure(4, error); - return this.DownloadFile(pkg.url, pkg, 4).catch((error) => { - handleDownloadFailure(5, error); - return this.DownloadFile(pkg.url, pkg, 5); // Last try, don't catch the error. - }); - }); - }); - }); - }).then(() => { - this.AppendLineChannel(" Done!"); - if (retryCount !== 0) { - // Log telemetry to see if retrying helps. - let telemetryProperties: { [key: string]: string } = {}; - telemetryProperties["success"] = `OnRetry${retryCount}`; - if (lastError instanceof PackageManagerError) { - let packageError: PackageManagerError = lastError; - telemetryProperties['error.methodName'] = packageError.methodName; - telemetryProperties['error.message'] = packageError.message; - if (packageError.pkg) { - telemetryProperties['error.packageName'] = packageError.pkg.description; - telemetryProperties['error.packageUrl'] = packageError.pkg.url; - } - if (packageError.errorCode) { - telemetryProperties['error.errorCode'] = packageError.errorCode; - } - } - Telemetry.logDebuggerEvent("acquisition", telemetryProperties); - } - }); - }); + continue; + } + } + } while (!success && retryCount < MAX_RETRIES); + + this.AppendLineChannel(" Done!"); + if (retryCount !== 0) { + // Log telemetry to see if retrying helps. + let telemetryProperties: { [key: string]: string } = {}; + telemetryProperties["success"] = `OnRetry${retryCount}`; + if (lastError instanceof PackageManagerError) { + let packageError: PackageManagerError = lastError; + telemetryProperties['error.methodName'] = packageError.methodName; + telemetryProperties['error.message'] = packageError.message; + if (packageError.pkg) { + telemetryProperties['error.packageName'] = packageError.pkg.description; + telemetryProperties['error.packageUrl'] = packageError.pkg.url; + } + if (packageError.errorCode) { + telemetryProperties['error.errorCode'] = packageError.errorCode; + } + } + Telemetry.logDebuggerEvent("acquisition", telemetryProperties); + } } // reloadCpptoolsJson in main.ts uses ~25% of this function. - private DownloadFile(urlString: any, pkg: IPackage, delay: number): Promise { + private DownloadFile(urlString: any, pkg: IPackage, delay: number, progress: vscode.Progress<{message?: string; increment?: number}>): Promise { let parsedUrl: url.Url = url.parse(urlString); let proxyStrictSSL: any = vscode.workspace.getConfiguration().get("http.proxyStrictSSL", true); @@ -236,7 +249,7 @@ export class PackageManager { } else { redirectUrl = response.headers.location[0]; } - return resolve(this.DownloadFile(redirectUrl, pkg, 0)); + return resolve(this.DownloadFile(redirectUrl, pkg, 0, progress)); } else if (response.statusCode !== 200) { // Download failed - print error message let errorMessage: string = `failed (error code '${response.statusCode}')`; @@ -250,7 +263,6 @@ export class PackageManager { contentLength = response.headers['content-length'][0]; } let packageSize: number = parseInt(contentLength, 10); - let downloadedBytes: number = 0; let downloadPercentage: number = 0; let dots: number = 0; let tmpFile: fs.WriteStream = fs.createWriteStream(null, { fd: pkg.tmpFile.fd }); @@ -258,15 +270,6 @@ export class PackageManager { this.AppendChannel(`(${Math.ceil(packageSize / 1024)} KB) `); response.on('data', (data) => { - downloadedBytes += data.length; - - // Update status bar item with percentage - let newPercentage: number = Math.ceil(100 * (downloadedBytes / packageSize)); - if (newPercentage !== downloadPercentage) { - this.SetStatusTooltip(`Downloading package '${pkg.description}'... ${downloadPercentage}%`); - downloadPercentage = newPercentage; - } - // Update dots after package name in output console let newDots: number = Math.ceil(downloadPercentage / 5); if (newDots > dots) { @@ -276,11 +279,11 @@ export class PackageManager { }); response.on('end', () => { - resolve(); + return resolve(); }); response.on('error', (error) => { - reject(new PackageManagerWebResponseError(response.socket, 'HTTP/HTTPS Response error', 'DownloadFile', pkg, error.stack, error.name)); + return reject(new PackageManagerWebResponseError(response.socket, 'HTTP/HTTPS Response error', 'DownloadFile', pkg, error.stack, error.name)); }); // Begin piping data from the response to the package file @@ -291,7 +294,7 @@ export class PackageManager { let request: ClientRequest = https.request(options, handleHttpResponse); request.on('error', (error) => { - reject(new PackageManagerError('HTTP/HTTPS Request error' + (urlString.includes("fwlink") ? ": fwlink" : ""), 'DownloadFile', pkg, error.stack, error.message)); + return reject(new PackageManagerError('HTTP/HTTPS Request error' + (urlString.includes("fwlink") ? ": fwlink" : ""), 'DownloadFile', pkg, error.stack, error.message)); }); // Execute the request @@ -300,11 +303,10 @@ export class PackageManager { }); } - private InstallPackage(pkg: IPackage): Promise { + private InstallPackage(pkg: IPackage, progressCount: string, progress: vscode.Progress<{message?: string; increment?: number}>): Promise { this.AppendLineChannel(`Installing package '${pkg.description}'`); - this.SetStatusText("$(desktop-download) Installing packages..."); - this.SetStatusTooltip(`Installing package '${pkg.description}'`); + progress.report({message: `Installing ${progressCount}: ${pkg.description}`}); return new Promise((resolve, reject) => { if (!pkg.tmpFile || pkg.tmpFile.fd === 0) { @@ -316,6 +318,15 @@ export class PackageManager { return reject(new PackageManagerError('Zip file error', 'InstallPackage', pkg, err)); } + // setup zip file events + zipfile.on('end', () => { + return resolve(); + }); + + zipfile.on('error', err => { + return reject(new PackageManagerError('Zip File Error', 'InstallPackage', pkg, err, err.code)); + }); + zipfile.readEntry(); zipfile.on('entry', (entry: yauzl.Entry) => { @@ -334,26 +345,52 @@ export class PackageManager { util.checkFileExists(absoluteEntryPath).then((exists: boolean) => { if (!exists) { // File - extract it - zipfile.openReadStream(entry, (err, readStream) => { + zipfile.openReadStream(entry, (err, readStream: fs.ReadStream) => { if (err) { return reject(new PackageManagerError('Error reading zip stream', 'InstallPackage', pkg, err)); } - mkdirp.mkdirp(path.dirname(absoluteEntryPath), { mode: 0o775 }, (err) => { + readStream.on('error', (err) => { + return reject(new PackageManagerError('Error in readStream', 'InstallPackage', pkg, err)); + }); + + mkdirp.mkdirp(path.dirname(absoluteEntryPath), { mode: 0o775 }, async (err) => { if (err) { return reject(new PackageManagerError('Error creating directory', 'InstallPackage', pkg, err, err.code)); } + // Create as a .tmp file to avoid partially unzipped files + // counting as completed files. + let absoluteEntryTempFile: string = absoluteEntryPath + ".tmp"; + if (fs.existsSync(absoluteEntryTempFile)) { + try { + await util.unlinkPromise(absoluteEntryTempFile); + } catch (err) { + return reject(new PackageManagerError(`Error unlinking file ${absoluteEntryTempFile}`, 'InstallPackage', pkg, err)); + } + } + // Make sure executable files have correct permissions when extracted let fileMode: number = (pkg.binaries && pkg.binaries.indexOf(absoluteEntryPath) !== -1) ? 0o755 : 0o664; - - let writeStream: fs.WriteStream = fs.createWriteStream(absoluteEntryPath, { mode: fileMode }); - readStream.pipe(writeStream); - writeStream.on('close', () => { + let writeStream: fs.WriteStream = fs.createWriteStream(absoluteEntryTempFile, { mode: fileMode }); + + writeStream.on('close', async () => { + try { + // Remove .tmp extension from the file. + await util.renamePromise(absoluteEntryTempFile, absoluteEntryPath); + } catch (err) { + return reject(new PackageManagerError(`Error renaming file ${absoluteEntryTempFile}`, 'InstallPackage', pkg, err)); + } // Wait till output is done writing before reading the next zip entry. // Otherwise, it's possible to try to launch the .exe before it is done being created. zipfile.readEntry(); }); + + writeStream.on('error', (err) => { + return reject(new PackageManagerError('Error in writeStream', 'InstallPackage', pkg, err)); + }); + + readStream.pipe(writeStream); }); }); } else { @@ -366,20 +403,11 @@ export class PackageManager { }); } }); - - zipfile.on('end', () => { - resolve(); - }); - - zipfile.on('error', err => { - reject(new PackageManagerError('Zip File Error', 'InstallPackage', pkg, err, err.code)); - }); - }); - }) - .then(() => { - // Clean up temp file - pkg.tmpFile.removeCallback(); }); + }).then(() => { + // Clean up temp file + pkg.tmpFile.removeCallback(); + }); } private AppendChannel(text: string): void { @@ -393,18 +421,4 @@ export class PackageManager { this.outputChannel.appendLine(text); } } - - private SetStatusText(text: string): void { - if (this.statusItem) { - this.statusItem.text = text; - this.statusItem.show(); - } - } - - private SetStatusTooltip(text: string): void { - if (this.statusItem) { - this.statusItem.tooltip = text; - this.statusItem.show(); - } - } } \ No newline at end of file diff --git a/Extension/test/integrationTests/languageServer.integration.test.ts b/Extension/test/integrationTests/languageServer.integration.test.ts index 267dafbf67..29af501c06 100644 --- a/Extension/test/integrationTests/languageServer.integration.test.ts +++ b/Extension/test/integrationTests/languageServer.integration.test.ts @@ -6,6 +6,8 @@ import * as vscode from 'vscode'; import * as assert from 'assert'; import { getLanguageConfigFromPatterns } from '../../src/LanguageServer/languageConfig'; +import * as config from '../../src/LanguageServer/configurations'; +import { CppSettings } from '../../src/LanguageServer/settings'; suite("multiline comment setting tests", function() { suiteSetup(async function() { @@ -74,4 +76,56 @@ suite("multiline comment setting tests", function() { assert.deepEqual(rules, defaultSLRules); }); -}); \ No newline at end of file +}); + +/* +suite("configuration tests", function() { + suiteSetup(async function() { + let extension: vscode.Extension = vscode.extensions.getExtension("ms-vscode.cpptools"); + if (!extension.isActive) { + await extension.activate(); + } + // Open a c++ file to start the language server. + await vscode.workspace.openTextDocument({ language: "cpp", content: "int main() { return 0; }"}); + }); + + suiteTeardown(async function() { + // Delete c_cpp_properties.json + }); + + + + test("Check default configuration", () => { + let rootUri: vscode.Uri; + if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) { + rootUri = vscode.workspace.workspaceFolders[0].uri; + } + assert.notEqual(rootUri, undefined, "Root Uri is not defined"); + if (rootUri) { + let cppProperties: config.CppProperties = new config.CppProperties(rootUri); + let configurations: config.Configuration[] = cppProperties.Configurations; + let defaultConfig: config.Configuration = config.getDefaultConfig(); + assert.deepEqual(configurations[0], defaultConfig); + console.log(JSON.stringify(configurations, null, 2)); + + // Need to set the CompilerDefaults before the CppProperties can be successfully modified. + cppProperties.CompilerDefaults = { + compilerPath: "/path/to/compiler", + cStandard: "c99", + cppStandard: "c++14", + frameworks: ["/path/to/framework"], + includes: ["/path/to/includes"] + }; + + configurations[0].cppStandard = "${default}"; + + let s: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("C_Cpp.default", rootUri); + let d: any = s.inspect("cppStandard"); + s.update("cppStandard", "c++11", vscode.ConfigurationTarget.WorkspaceFolder); + d = s.inspect("cppStandard"); + + cppProperties.onDidChangeSettings(); + } + }); +}); +*/ \ No newline at end of file diff --git a/launch.md b/launch.md index 5c2bf5b7a0..d49a5a7c03 100644 --- a/launch.md +++ b/launch.md @@ -70,7 +70,7 @@ Set or change the following options to control VS Code's behavior during debuggi The following options enable you to modify the state of the target application when it is launched: * #### `args` - JSON array of command line arguments to pass to the program when it is launched. Example `["arg1", "arg2]`. + JSON array of command line arguments to pass to the program when it is launched. Example `["arg1", "arg2"]`. If you are escaping characters you will need to double escape them. For example `["{\\\"arg\\\": true}]` will send `{"arg1": true}` to your application. * #### `cwd` Sets the the working directory of the application launched by the debugger.