diff --git a/Extension/.vscode/launch.json b/Extension/.vscode/launch.json index 838793545..022eaed81 100644 --- a/Extension/.vscode/launch.json +++ b/Extension/.vscode/launch.json @@ -24,7 +24,7 @@ "runtimeExecutable": "${execPath}", "args": [ "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test" + "--extensionTestsPath=${workspaceFolder}/out/test/unitTests" ], "stopOnEntry": false, "sourceMaps": true, diff --git a/Extension/CHANGELOG.md b/Extension/CHANGELOG.md index c096df08d..8516315e5 100644 --- a/Extension/CHANGELOG.md +++ b/Extension/CHANGELOG.md @@ -1,12 +1,31 @@ # C/C++ for Visual Studio Code Change Log -## Version 0.17.7: July 16, 2018 +## Version 0.17.8-insiders: August 6, 2018 +* Fix attach to process for systems without `bash` by using `sh` instead. [#569](https://github.com/Microsoft/vscode-cpptools/issues/569) + * Andy Neff (@andyneff) [PR 2340](https://github.com/Microsoft/vscode-cpptools/pull/2340) +* Fix IntelliSense crash after hover or completion with `_Complex` types. [#689](https://github.com/Microsoft/vscode-cpptools/issues/689), [#1112](https://github.com/Microsoft/vscode-cpptools/issues/1112) +* Add `C_Cpp.workspaceSymbols` setting with default `Just My Code` to filter out system header symbols. [#1119](https://github.com/Microsoft/vscode-cpptools/issues/1119), [#2320](https://github.com/Microsoft/vscode-cpptools/issues/2320) +* Add `C_Cpp.inactiveRegionForegroundColor` and `C_Cpp.inactiveRegionBackgroundColor` settings. [#1620](https://github.com/Microsoft/vscode-cpptools/issues/1620), [#2212](https://github.com/Microsoft/vscode-cpptools/issues/2212) + * John Patterson (@john-patterson) [PR 2308](https://github.com/Microsoft/vscode-cpptools/pull/2308) +* Fix Mac framework dependencies not being discovered. [#1913](https://github.com/Microsoft/vscode-cpptools/issues/1913) +* Fix red flame getting stuck after modifying `c_cpp_properties.json`. [#2077](https://github.com/Microsoft/vscode-cpptools/issues/2077) +* Add `gcc-x64` `intelliSenseMode` and send the correct clang or gcc version to our parser, fixing various IntelliSense errors. [#2175](https://github.com/Microsoft/vscode-cpptools/issues/2175), [#2299](https://github.com/Microsoft/vscode-cpptools/issues/2299), [#2317](https://github.com/Microsoft/vscode-cpptools/issues/2317) +* Make `Go to Definition` on the definition go to the declaration instead. [#2298](https://github.com/Microsoft/vscode-cpptools/issues/2298) +* Don't add empty `windowsSDKVersion` if none exists. [#2300](https://github.com/Microsoft/vscode-cpptools/issues/2300) +* Add multi-pass environment variable resolution allowing variables defined in terms of other variables. [#2322](https://github.com/Microsoft/vscode-cpptools/pull/2322) + * John Patterson (@john-patterson) [PR 2322](https://github.com/Microsoft/vscode-cpptools/pull/2322) +* Fix IntelliSense crash when the gcc-8 type_traits header is used. [#2323](https://github.com/Microsoft/vscode-cpptools/issues/2323), [#2328](https://github.com/Microsoft/vscode-cpptools/issues/2328) +* Limit configuration popups to one at a time. [#2324](https://github.com/Microsoft/vscode-cpptools/issues/2324) +* Allow users to use `~` for `${userProfile}` on Windows. [PR 2333](https://github.com/Microsoft/vscode-cpptools/pull/2333) +* Filter out buggy IntelliSense error `"= delete" can only appear on the first declaration of a function`. [#2352](https://github.com/Microsoft/vscode-cpptools/issues/2352) + +## Version 0.17.7: July 22, 2018 * Fix `Go to Definition` for code scoped with an aliased namespace. [#387](https://github.com/Microsoft/vscode-cpptools/issues/387) * Fix incorrect IntelliSense errors with template template-arguments. [#1014](https://github.com/Microsoft/vscode-cpptools/issues/1014) * Fix crash when using designated initializer lists. [#1440](https://github.com/Microsoft/vscode-cpptools/issues/1440) * Add `windowsSdkVersion` to `c_cpp_properties.json`. [#1585](https://github.com/Microsoft/vscode-cpptools/issues/1585) * Add `${vcpkgRoot}` variable. [#1817](https://github.com/Microsoft/vscode-cpptools/issues/1817) -* Fix dangling IntelliSense processes and stuck red flame. [#2075](https://github.com/Microsoft/vscode-cpptools/issues/2075), [#2077](https://github.com/Microsoft/vscode-cpptools/issues/2077), [#2169](https://github.com/Microsoft/vscode-cpptools/issues/2169) +* Fix dangling IntelliSense processes. [#2075](https://github.com/Microsoft/vscode-cpptools/issues/2075), [#2169](https://github.com/Microsoft/vscode-cpptools/issues/2169) * Fix incorrect IntelliSense errors when class template argument deduction is used. [#2101](https://github.com/Microsoft/vscode-cpptools/issues/2101) * Skip automatic parsing of source files in Mac system framework paths. [#2156](https://github.com/Microsoft/vscode-cpptools/issues/2156) * Fix `Edit Configurations...` not working after `c_cpp_properties.json` is deleted. [#2214](https://github.com/Microsoft/vscode-cpptools/issues/2214) @@ -15,8 +34,15 @@ * Add `Change Configuration Provider...` command. [#2224](https://github.com/Microsoft/vscode-cpptools/issues/2224) * Fix out-of-memory crash with `#include` code actions when no folder is open. [#2225](https://github.com/Microsoft/vscode-cpptools/issues/2225) * Fix `intelliSenseMode` with custom config providers on Windows. [#2228](https://github.com/Microsoft/vscode-cpptools/issues/2228) +* Fix formatting not working on Windows if the VC++ 2015 redist isn't installed. [#2232](https://github.com/Microsoft/vscode-cpptools/issues/2232) * Fix variables not resolving in `macFrameworkPath`. [#2234](https://github.com/Microsoft/vscode-cpptools/issues/2234) * Fix `Go to Definition` not working for macros followed by `.` or `->`. [#2245](https://github.com/Microsoft/vscode-cpptools/issues/2245) +* Fix `#include` autocomplete with Mac framework headers. [#2251](https://github.com/Microsoft/vscode-cpptools/issues/2251) +* Fix for debugging to support empty arguments for debuggee. [#2258](https://github.com/Microsoft/vscode-cpptools/issues/2258) +* Fix `Go to Definition` bug (missing symbols outside the workspace). [#2281](https://github.com/Microsoft/vscode-cpptools/issues/2281) +* Add a setting to silence configuration provider warnings. [#2292](https://github.com/Microsoft/vscode-cpptools/issues/2292) +* Fix for debugging async Visual C++ causing debugger to hang. +* Fix `main` snippet. ## Version 0.17.6: July 2, 2018 * Fix the database icon getting stuck with recursive includes. [#2104](https://github.com/Microsoft/vscode-cpptools/issues/2104) @@ -234,7 +260,7 @@ ## Version 0.13.0: September 25, 2017 * Reference highlighting is now provided by the extension for both IntelliSense engines. * Parameter help is now provided by both IntelliSense engines. -* Light bulbs (code actions) for #include errors now suggest potential paths to add to the `includePath` based on a recursive search of the `browse.path`. [#846](https://github.com/Microsoft/vscode-cpptools/issues/846) +* Light bulbs (code actions) for `#include` errors now suggest potential paths to add to the `includePath` based on a recursive search of the `browse.path`. [#846](https://github.com/Microsoft/vscode-cpptools/issues/846) * Browse database now removes old symbols when `browse.path` changes. [#262](https://github.com/Microsoft/vscode-cpptools/issues/262) * Add `*` on new lines after a multiline comment with `/**` is started. [#579](https://github.com/Microsoft/vscode-cpptools/issues/579) * Fix `Go to Definition`, completion, and parameter hints for partially scoped members. [#635](https://github.com/Microsoft/vscode-cpptools/issues/635) diff --git a/Extension/README.md b/Extension/README.md index 445b61684..bdd02a250 100644 --- a/Extension/README.md +++ b/Extension/README.md @@ -11,7 +11,7 @@ This preview release of the extension adds language support for C/C++ to Visual * Quick Info (Hover) * Error Squiggles * Debugging - * Support for debugging Windows (PDB, Mingw/Cygwin), Linux and OS X applications + * Support for debugging Windows (PDB, MinGW/Cygwin), Linux and macOS applications * Line by line code stepping * Breakpoints (including conditional and function breakpoints) * Variable inspection diff --git a/Extension/bin/msvc.64.darwin.json b/Extension/bin/msvc.64.darwin.json index 96872269f..a371f0095 100644 --- a/Extension/bin/msvc.64.darwin.json +++ b/Extension/bin/msvc.64.darwin.json @@ -1,6 +1,5 @@ { "defaults": [ - "--clang", "--pack_alignment", "8" ], diff --git a/Extension/bin/msvc.64.intel.clang.json b/Extension/bin/msvc.64.intel.clang.json index e8b9ba4f9..6072a9ca4 100644 --- a/Extension/bin/msvc.64.intel.clang.json +++ b/Extension/bin/msvc.64.intel.clang.json @@ -1,6 +1,5 @@ { "defaults": [ - "--clang", "--pack_alignment", "8" ], diff --git a/Extension/bin/msvc.64.linux.json b/Extension/bin/msvc.64.linux.json index e8b9ba4f9..6072a9ca4 100644 --- a/Extension/bin/msvc.64.linux.json +++ b/Extension/bin/msvc.64.linux.json @@ -1,6 +1,5 @@ { "defaults": [ - "--clang", "--pack_alignment", "8" ], diff --git a/Extension/c_cpp_properties.schema.json b/Extension/c_cpp_properties.schema.json index 0c399ffab..b3f0c2ee9 100644 --- a/Extension/c_cpp_properties.schema.json +++ b/Extension/c_cpp_properties.schema.json @@ -71,10 +71,11 @@ } }, "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.", + "description": "If set, it overrides the default mode used by the IntelliSense engine. Windows defaults to msvc-x64, Linux defaults to gcc-x64, and Mac default to clang-x64.", "type": "string", "enum": [ "msvc-x64", + "gcc-x64", "clang-x64", "${default}" ] diff --git a/Extension/cpp_snippets.json b/Extension/cpp_snippets.json index b036af953..ccb7dc811 100644 --- a/Extension/cpp_snippets.json +++ b/Extension/cpp_snippets.json @@ -67,7 +67,7 @@ }, "main": { "prefix": "main", - "body": "\nint main(int argc, char const *argv[])\n{\n\t${1:/* code */}\n\treturn 0;\n}\n", + "body": "main(int argc, char const *argv[])\n{\n\t${1:/* code */}\n\treturn 0;\n}\n", "description": "Code snippet for main()", "scope": "source.c, source.objc, source.c++, source.objc++" }, diff --git a/Extension/gulpfile.js b/Extension/gulpfile.js index 03ff32cb7..895d71788 100644 --- a/Extension/gulpfile.js +++ b/Extension/gulpfile.js @@ -12,43 +12,31 @@ const mocha = require('gulp-mocha'); const fs = require('fs'); const optionsSchemaGenerator = require('./out/tools/GenerateOptionsSchema'); -gulp.task('allTests', () => { - gulp.start('unitTests'); - gulp.start('integrationTests'); -}); - gulp.task('unitTests', () => { - gulp.src('./out/test/unitTests', {read: false}).pipe( - mocha({ - ui: "tdd" - }) - ).once('error', err => { - process.exit(1); - }) - .once('end', () => { - process.exit(); - }) + env.set({ + CODE_TESTS_PATH: "./out/test/unitTests", + }); + + gulp.src('./test/runVsCodeTestsWithAbsolutePaths.js', {read: false}) + .pipe(mocha({ ui: "tdd" })) + .once('error', err => process.exit(1)) + .once('end', () => process.exit()) }); gulp.task('integrationTests', () => { env.set({ CODE_TESTS_PATH: "./out/test/integrationTests", CODE_TESTS_WORKSPACE: "./test/integrationTests/testAssets/SimpleCppProject" - } - ); - gulp.src('./test/runVsCodeTestsWithAbsolutePaths.js', {read: false}).pipe( - mocha({ - ui: "tdd", - delay: true - }) - ).once('error', err => { - process.exit(1); - }) - .once('end', () => { - process.exit(); - }) + }); + + gulp.src('./test/runVsCodeTestsWithAbsolutePaths.js', {read: false}) + .pipe(mocha({ ui: "tdd" })) + .once('error', err => process.exit(1)) + .once('end', () => process.exit()) }); +gulp.task('allTests', ['unitTests', 'integrationTests']); + /// Misc Tasks const allTypeScript = [ 'src/**/*.ts', @@ -80,7 +68,7 @@ gulp.task('tslint', () => { gulp.task('pr-check', () => { const packageJson = JSON.parse(fs.readFileSync('./package.json').toString()); - if (packageJson.activationEvents.length !== 1 && packageJson.activationEvents[0] !== '*') { + 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); } diff --git a/Extension/package-lock.json b/Extension/package-lock.json index 8078f8107..7e63fffc7 100644 --- a/Extension/package-lock.json +++ b/Extension/package-lock.json @@ -1,6 +1,6 @@ { "name": "cpptools", - "version": "0.17.7-insiders", + "version": "0.17.8-insiders", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/Extension/package.json b/Extension/package.json index 4b2bd7c58..7752994f7 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.17.7-insiders", + "version": "0.17.8-insiders", "publisher": "ms-vscode", "preview": true, "icon": "LanguageCCPP_color_128x.png", @@ -154,6 +154,24 @@ "minimum": 0.1, "maximum": 1 }, + "C_Cpp.inactiveRegionForegroundColor": { + "type": [ + "string", + "null" + ], + "default": null, + "description": "Controls the font coloring of inactive preprocessor blocks. Input is in the form a hexadecimal color code or a valid Theme Color. If not set, this defaults to the syntax coloring scheme of the editor. This setting only applies when inactive region dimming is enabled.", + "scope": "resource" + }, + "C_Cpp.inactiveRegionBackgroundColor": { + "type": [ + "string", + "null" + ], + "default": null, + "description": "Controls the background coloring of inactive preprocessor blocks. Input is in the form a hexadecimal color code or a valid Theme Color. If not set, this defaults to transparent. This setting only applies when inactive region dimming is enabled.", + "scope": "resource" + }, "C_Cpp.formatting": { "type": "string", "enum": [ @@ -201,6 +219,16 @@ "description": "Controls whether parsing of the non-active workspace files uses sleeps to avoid using 100% CPU. The values highest/high/medium/low correspond to approximately 100/75/50/25% CPU usage.", "scope": "resource" }, + "C_Cpp.workspaceSymbols": { + "type": "string", + "enum": [ + "All", + "Just My Code" + ], + "default": "Just My Code", + "description": "The symbols to include in the query results when 'Go to Symbol in Workspace' is invoked", + "scope": "resource" + }, "C_Cpp.exclusionPolicy": { "type": "string", "enum": [ @@ -250,6 +278,16 @@ "description": "Defines the editor behavior for when the Enter key is pressed inside a multiline or single line comment block.", "scope": "resource" }, + "C_Cpp.configurationWarnings": { + "type": "string", + "enum": [ + "Enabled", + "Disabled" + ], + "default": "Enabled", + "description": "Determines whether pop up notifications will be shown when a configuration provider extension is unable to provide a configuration for a source file.", + "scope": "resource" + }, "C_Cpp.default.includePath": { "type": [ "array", @@ -324,6 +362,7 @@ ], "enum": [ "msvc-x64", + "gcc-x64", "clang-x64" ], "default": null, @@ -1366,7 +1405,7 @@ "runtimeDependencies": [ { "description": "C/C++ language components (Linux / x86_64)", - "url": "https://go.microsoft.com/fwlink/?linkid=2004596", + "url": "https://go.microsoft.com/fwlink/?linkid=2006256", "platforms": [ "linux" ], @@ -1380,7 +1419,7 @@ }, { "description": "C/C++ language components (Linux / x86)", - "url": "https://go.microsoft.com/fwlink/?linkid=2004498", + "url": "https://go.microsoft.com/fwlink/?linkid=2006255", "platforms": [ "linux" ], @@ -1396,7 +1435,7 @@ }, { "description": "C/C++ language components (OS X)", - "url": "https://go.microsoft.com/fwlink/?linkid=2004597", + "url": "https://go.microsoft.com/fwlink/?linkid=2006254", "platforms": [ "darwin" ], @@ -1407,7 +1446,7 @@ }, { "description": "C/C++ language components (Windows)", - "url": "https://go.microsoft.com/fwlink/?linkid=2004598", + "url": "https://go.microsoft.com/fwlink/?linkid=2006362", "platforms": [ "win32" ], @@ -1528,4 +1567,4 @@ "binaries": [] } ] -} +} \ No newline at end of file diff --git a/Extension/src/Debugger/attachToProcess.ts b/Extension/src/Debugger/attachToProcess.ts index 65b5230b6..3cc8afbce 100644 --- a/Extension/src/Debugger/attachToProcess.ts +++ b/Extension/src/Debugger/attachToProcess.ts @@ -120,7 +120,7 @@ export class RemoteAttachPicker { private getRemoteOSAndProcesses(pipeCmd: string): Promise { // Commands to get OS and processes - const command: string = `bash -c 'uname && if [ $(uname) == "Linux" ] ; then ${PsProcessParser.psLinuxCommand} ; elif [ $(uname) == "Darwin" ] ; ` + + const command: string = `sh -c 'uname && if [ $(uname) == "Linux" ] ; then ${PsProcessParser.psLinuxCommand} ; elif [ $(uname) == "Darwin" ] ; ` + `then ${PsProcessParser.psDarwinCommand}; fi'`; return execChildProcess(`${pipeCmd} "${command}"`, null, this._channel).then(output => { diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index d9688785e..f9d697d47 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -30,6 +30,7 @@ import { getTestHook, TestHook } from '../testHook'; import { getCustomConfigProviders, CustomConfigurationProviderCollection, CustomConfigurationProvider1 } from '../LanguageServer/customProviders'; let ui: UI; +const configProviderTimeout: number = 2000; interface NavigationPayload { navigation: string; @@ -352,6 +353,7 @@ class DefaultClient implements Client { dimInactiveRegions: settings.dimInactiveRegions, loggingLevel: settings.loggingLevel, workspaceParsingPriority: settings.workspaceParsingPriority, + workspaceSymbols: settings.workspaceSymbols, exclusionPolicy: settings.exclusionPolicy, preferredPathSeparator: settings.preferredPathSeparator, default: { @@ -427,28 +429,34 @@ class DefaultClient implements Client { if (!selectedProvider) { let ask: PersistentFolderState = new PersistentFolderState("Client.registerProvider", true, this.RootPath); if (ask.Value) { - let folderStr: string = (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 1) ? "the '" + this.Name + "'" : "this"; - const message: string = `${provider.name} would like to configure IntelliSense for ${folderStr} folder.`; - const allow: string = "Allow"; - const notNow: string = "Not Now"; - const dontAskAgain: string = "Don't Ask Again"; - vscode.window.showInformationMessage(message, allow, notNow, dontAskAgain).then(result => { - switch (result) { - case allow: { - this.configuration.updateCustomConfigurationProvider(provider.extensionId).then(() => { - telemetry.logLanguageServerEvent("customConfigurationProvider", { "providerId": provider.extensionId }); - }); - break; - } - case dontAskAgain: { - ask.Value = false; - break; - } - default: { - break; + ui.showConfigureCustomProviderMessage(() => { + let folderStr: string = (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 1) ? "the '" + this.Name + "'" : "this"; + const message: string = `${provider.name} would like to configure IntelliSense for ${folderStr} folder.`; + const allow: string = "Allow"; + const dontAllow: string = "Don't Allow"; + const askLater: string = "Ask Me Later"; + + return vscode.window.showInformationMessage(message, allow, dontAllow, askLater).then(result => { + switch (result) { + case allow: { + this.configuration.updateCustomConfigurationProvider(provider.extensionId).then(() => { + telemetry.logLanguageServerEvent("customConfigurationProvider", { "providerId": provider.extensionId }); + }); + ask.Value = false; + return true; + } + case dontAllow: { + ask.Value = false; + break; + } + default: { + break; + } } - } - }); + return false; + }); + }, + () => ask.Value = false); } } else if (selectedProvider === provider.extensionId) { telemetry.logLanguageServerEvent("customConfigurationProvider", { "providerId": provider.extensionId }); @@ -475,7 +483,7 @@ class DefaultClient implements Client { let task: () => Thenable = () => { return currentProvider.provideConfigurations(documentUris, tokenSource.token); }; - this.queueTaskWithTimeout(task, 1000, tokenSource).then(configs => this.sendCustomConfigurations(configs)); + this.queueTaskWithTimeout(task, configProviderTimeout, tokenSource).then(configs => this.sendCustomConfigurations(configs)); }); } @@ -508,17 +516,29 @@ class DefaultClient implements Client { return Promise.reject(""); }; - return this.queueTaskWithTimeout(provideConfigurationAsync, 1000, tokenSource).then( + return this.queueTaskWithTimeout(provideConfigurationAsync, configProviderTimeout, tokenSource).then( (configs: SourceFileConfigurationItem[]) => { if (configs && configs.length > 0) { this.sendCustomConfigurations(configs); } }, () => { - if (!this.isExternalHeader(document) && !vscode.debug.activeDebugSession) { + let settings: CppSettings = new CppSettings(this.RootUri); + if (settings.configurationWarnings === "Enabled" && !this.isExternalHeader(document) && !vscode.debug.activeDebugSession) { + const dismiss: string = "Dismiss"; + const disable: string = "Disable Warnings"; vscode.window.showInformationMessage( `'${providerName}' is unable to provide IntelliSense configuration information for '${document.uri.fsPath}'. ` + - `Settings from the '${configName}' configuration will be used instead.`); + `Settings from the '${configName}' configuration will be used instead.`, + dismiss, + disable).then(response => { + switch (response) { + case disable: { + settings.toggleSetting("configurationWarnings", "Enabled", "Disabled"); + break; + } + } + }); } }); } @@ -792,27 +812,31 @@ class DefaultClient implements Client { } else if (message.endsWith("IntelliSense Fallback")) { let showIntelliSenseFallbackMessage: PersistentState = new PersistentState("CPP.showIntelliSenseFallbackMessage", true); if (showIntelliSenseFallbackMessage.Value) { - let learnMorePanel: string = "Learn More"; - let dontShowAgain: string = "Don't Show Again"; - let fallbackMsg: string = this.configuration.VcpkgInstalled ? - "Update your IntelliSense settings or use Vcpkg to install libraries to help find missing headers." : - "Configure your IntelliSense settings to help find missing headers."; - vscode.window.showInformationMessage(fallbackMsg, learnMorePanel, dontShowAgain).then((value) => { - switch (value) { - case learnMorePanel: - let uri: vscode.Uri = vscode.Uri.parse(`https://go.microsoft.com/fwlink/?linkid=864631`); - vscode.commands.executeCommand('vscode.open', uri); - vscode.commands.getCommands(true).then((commands: string[]) => { - if (commands.indexOf("workbench.action.problems.focus") >= 0) { - vscode.commands.executeCommand("workbench.action.problems.focus"); - } - }); - break; - case dontShowAgain: - showIntelliSenseFallbackMessage.Value = false; - break; - } - }); + ui.showConfigureIncludePathMessage(() => { + let learnMorePanel: string = "Learn More"; + let dontShowAgain: string = "Don't Show Again"; + let fallbackMsg: string = this.configuration.VcpkgInstalled ? + "Update your IntelliSense settings or use Vcpkg to install libraries to help find missing headers." : + "Configure your IntelliSense settings to help find missing headers."; + return vscode.window.showInformationMessage(fallbackMsg, learnMorePanel, dontShowAgain).then((value) => { + switch (value) { + case learnMorePanel: + let uri: vscode.Uri = vscode.Uri.parse(`https://go.microsoft.com/fwlink/?linkid=864631`); + vscode.commands.executeCommand('vscode.open', uri); + vscode.commands.getCommands(true).then((commands: string[]) => { + if (commands.indexOf("workbench.action.problems.focus") >= 0) { + vscode.commands.executeCommand("workbench.action.problems.focus"); + } + }); + break; + case dontShowAgain: + showIntelliSenseFallbackMessage.Value = false; + break; + } + return true; + }); + }, + () => showIntelliSenseFallbackMessage.Value = false); } } } @@ -826,6 +850,8 @@ class DefaultClient implements Client { let decoration: vscode.TextEditorDecorationType = vscode.window.createTextEditorDecorationType({ opacity: settings.inactiveRegionOpacity.toString(), + backgroundColor: settings.inactiveRegionBackgroundColor, + color: settings.inactiveRegionForegroundColor, rangeBehavior: vscode.DecorationRangeBehavior.ClosedOpen }); @@ -867,8 +893,8 @@ class DefaultClient implements Client { return; } - let showCompileCommandsSelection: PersistentState = new PersistentState("CPP.showCompileCommandsSelection", true); - if (!showCompileCommandsSelection.Value) { + let ask: PersistentState = new PersistentState("CPP.showCompileCommandsSelection", true); + if (!ask.Value) { return; } @@ -876,30 +902,33 @@ class DefaultClient implements Client { let folderStr: string = (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 1) ? "the '" + this.Name + "'" : "this"; const message: string = `Would you like to use ${compileCommandStr} to auto-configure IntelliSense for ${folderStr} folder?`; - const yes: string = "Yes"; - const notNow: string = "Not Now"; - const dontAskAgain: string = "Don't Ask Again"; - vscode.window.showInformationMessage(message, yes, notNow, dontAskAgain).then((value) => { - switch (value) { - case yes: - if (params.paths.length > 1) { - ui.showCompileCommands(params.paths).then((index) => { + ui.showConfigureCompileCommandsMessage(() => { + const yes: string = "Yes"; + const no: string = "No"; + const askLater: string = "Ask Me Later"; + return vscode.window.showInformationMessage(message, yes, no, askLater).then(async (value) => { + switch (value) { + case yes: + if (params.paths.length > 1) { + let index: number = await ui.showCompileCommands(params.paths); if (index < 0) { - return; + return false; } this.configuration.setCompileCommands(params.paths[index]); - }); - } else { - this.configuration.setCompileCommands(params.paths[0]); - } - break; - case notNow: - break; - case dontAskAgain: - showCompileCommandsSelection.Value = false; - break; - } - }); + } else { + this.configuration.setCompileCommands(params.paths[0]); + } + return true; + case askLater: + break; + case no: + ask.Value = false; + break; + } + return false; + }); + }, + () => ask.Value = false); } /********************************************* @@ -1054,7 +1083,7 @@ class DefaultClient implements Client { public handleConfigurationProviderSelectCommand(): void { this.notifyWhenReady(() => { - ui.showConfigurationProviders() + ui.showConfigurationProviders(this.configuration.CurrentConfigurationProvider) .then(extensionId => { if (extensionId === undefined) { // operation was cancelled. diff --git a/Extension/src/LanguageServer/configurations.ts b/Extension/src/LanguageServer/configurations.ts index 3e6f3e598..8eedcc358 100644 --- a/Extension/src/LanguageServer/configurations.ts +++ b/Extension/src/LanguageServer/configurations.ts @@ -233,7 +233,7 @@ export class CppProperties { if (!settings.defaultMacFrameworkPath && process.platform === 'darwin') { configuration.macFrameworkPath = this.defaultFrameworks; } - if (!settings.defaultWindowsSdkVersion && process.platform === 'win32') { + if (!settings.defaultWindowsSdkVersion && this.defaultWindowsSdkVersion && process.platform === 'win32') { configuration.windowsSdkVersion = this.defaultWindowsSdkVersion; } if (!settings.defaultCompilerPath && this.defaultCompilerPath) { @@ -300,15 +300,19 @@ export class CppProperties { private getIntelliSenseModeForPlatform(name: string): string { // Do the built-in configs first. - if (name === "Linux" || name === "Mac") { + if (name === "Linux") { + return "gcc-x64"; + } else if (name === "Mac") { return "clang-x64"; } else if (name === "Win32") { return "msvc-x64"; } else if (process.platform === 'win32') { // Custom configs default to the OS's preference. return "msvc-x64"; - } else { + } else if (process.platform === 'darwin') { return "clang-x64"; + } else { + return "gcc-x64"; } } @@ -343,7 +347,11 @@ export class CppProperties { }); } else { let settings: CppSettings = new CppSettings(this.rootUri); - settings.update("default.configurationProvider", providerId); + if (providerId) { + settings.update("default.configurationProvider", providerId); + } else { + settings.update("default.configurationProvider", undefined); // delete the setting + } this.CurrentConfiguration.configurationProvider = providerId; resolve(); } @@ -513,6 +521,13 @@ export class CppProperties { this.resetToDefaultSettings(true); } this.applyDefaultIncludePathsAndFrameworks(); + let settings: CppSettings = new CppSettings(this.rootUri); + if (settings.defaultConfigurationProvider) { + this.configurationJson.configurations.forEach(config => { + config.configurationProvider = settings.defaultConfigurationProvider; + }); + settings.update("default.configurationProvider", undefined); // delete the setting + } edit.insert(document.uri, new vscode.Position(0, 0), JSON.stringify(this.configurationJson, null, 4)); vscode.workspace.applyEdit(edit).then((status) => { // Fix for issue 163 diff --git a/Extension/src/LanguageServer/settings.ts b/Extension/src/LanguageServer/settings.ts index 9fca99695..3d007346d 100644 --- a/Extension/src/LanguageServer/settings.ts +++ b/Extension/src/LanguageServer/settings.ts @@ -41,13 +41,17 @@ export class CppSettings extends Settings { public get errorSquiggles(): string { return super.Section.get("errorSquiggles"); } public get dimInactiveRegions(): boolean { return super.Section.get("dimInactiveRegions"); } public get inactiveRegionOpacity(): number { return super.Section.get("inactiveRegionOpacity"); } + public get inactiveRegionForegroundColor(): string { return super.Section.get("inactiveRegionForegroundColor"); } + public get inactiveRegionBackgroundColor(): string { return super.Section.get("inactiveRegionBackgroundColor"); } public get autoComplete(): string { return super.Section.get("autocomplete"); } public get loggingLevel(): string { return super.Section.get("loggingLevel"); } public get navigationLength(): number { return super.Section.get("navigation.length", 60); } public get autoAddFileAssociations(): boolean { return super.Section.get("autoAddFileAssociations"); } public get workspaceParsingPriority(): boolean { return super.Section.get("workspaceParsingPriority"); } + public get workspaceSymbols(): string { return super.Section.get("workspaceSymbols"); } public get exclusionPolicy(): boolean { return super.Section.get("exclusionPolicy"); } public get commentContinuationPatterns(): (string | CommentPattern)[] { return super.Section.get<(string | CommentPattern)[]>("commentContinuationPatterns"); } + public get configurationWarnings(): string { return super.Section.get("configurationWarnings"); } 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"); } diff --git a/Extension/src/LanguageServer/ui.ts b/Extension/src/LanguageServer/ui.ts index 72f3ea71b..2279d2246 100644 --- a/Extension/src/LanguageServer/ui.ts +++ b/Extension/src/LanguageServer/ui.ts @@ -17,11 +17,24 @@ interface KeyedQuickPickItem extends vscode.QuickPickItem { key: string; } +// Higher numbers mean greater priority. +enum ConfigurationPriority { + IncludePath = 1, + CompileCommands = 2, + CustomProvider = 3, +} + +interface ConfigurationResult { + configured: boolean; + priority: ConfigurationPriority; +} + export class UI { private navigationStatusBarItem: vscode.StatusBarItem; private configStatusBarItem: vscode.StatusBarItem; private browseEngineStatusBarItem: vscode.StatusBarItem; private intelliSenseStatusBarItem: vscode.StatusBarItem; + private configurationUIPromise: Thenable; constructor() { // 1000 = priority, it needs to be high enough to be on the left of the Ln/Col. @@ -164,14 +177,18 @@ export class UI { .then(selection => (selection) ? selection.index : -1); } - public showConfigurationProviders(): Thenable { + public showConfigurationProviders(currentProvider: string|null): Thenable { let options: vscode.QuickPickOptions = {}; options.placeHolder = "Select a Configuration Provider..."; let providers: CustomConfigurationProviderCollection = getCustomConfigProviders(); let items: KeyedQuickPickItem[] = []; providers.forEach(provider => { - items.push({ label: provider.name, description: "", key: provider.extensionId }); + let label: string = provider.name; + if (provider.extensionId === currentProvider) { + label += " (active)"; + } + items.push({ label: label, description: "", key: provider.extensionId }); }); items.push({ label: "(none)", description: "Disable the active configuration provider, if applicable.", key: "" }); @@ -219,6 +236,49 @@ export class UI { .then(selection => (selection) ? selection.index : -1); } + public showConfigureIncludePathMessage(prompt: () => Thenable, onSkip: () => void): void { + setTimeout(() => { + this.showConfigurationPrompt(ConfigurationPriority.IncludePath, prompt, onSkip); + }, 10000); + } + + public showConfigureCompileCommandsMessage(prompt: () => Thenable, onSkip: () => void): void { + setTimeout(() => { + this.showConfigurationPrompt(ConfigurationPriority.CompileCommands, prompt, onSkip); + }, 5000); + } + + public showConfigureCustomProviderMessage(prompt: () => Thenable, onSkip: () => void): void { + this.showConfigurationPrompt(ConfigurationPriority.CustomProvider, prompt, onSkip); + } + + private showConfigurationPrompt(priority: ConfigurationPriority, prompt: () => Thenable, onSkip: () => void): void { + let showPrompt: () => Thenable = async () => { + let configured: boolean = await prompt(); + return Promise.resolve({ + priority: priority, + configured: configured + }); + }; + + if (this.configurationUIPromise) { + this.configurationUIPromise = this.configurationUIPromise.then(result => { + if (priority > result.priority) { + return showPrompt(); + } else if (!result.configured) { + return showPrompt(); + } + onSkip(); + return Promise.resolve({ + priority: result.priority, + configured: true + }); + }); + } else { + this.configurationUIPromise = showPrompt(); + } + } + public dispose(): void { this.configStatusBarItem.dispose(); this.browseEngineStatusBarItem.dispose(); diff --git a/Extension/src/common.ts b/Extension/src/common.ts index 4a7c613d5..34c11c3c8 100644 --- a/Extension/src/common.ts +++ b/Extension/src/common.ts @@ -178,55 +178,60 @@ export function resolveVariables(input: string, additionalEnvironment: {[key: st } // Replace environment and configuration variables. - let regexp: RegExp = /\$\{((env|config|workspaceFolder)(.|:))?(.*?)\}/g; - let ret: string = input.replace(regexp, (match: string, ignored1: string, varType: string, ignored2: string, name: string) => { - // Historically, if the variable didn't have anything before the "." or ":" - // it was assumed to be an environment variable - if (varType === undefined) { - varType = "env"; - } - let newValue: string = undefined; - switch (varType) { - case "env": { - let v: string | string[] = additionalEnvironment[name]; - if (typeof v === "string") { - newValue = v; - } else if (input === match && v instanceof Array) { - newValue = v.join(";"); - } - if (!newValue) { - newValue = process.env[name]; - } - break; + let regexp: () => RegExp = () => /\$\{((env|config|workspaceFolder)(\.|:))?(.*?)\}/g; + let ret: string = input; + let cycleCache: Set = new Set(); + while (!cycleCache.has(ret)) { + cycleCache.add(ret); + ret = ret.replace(regexp(), (match: string, ignored1: string, varType: string, ignored2: string, name: string) => { + // Historically, if the variable didn't have anything before the "." or ":" + // it was assumed to be an environment variable + if (varType === undefined) { + varType = "env"; } - case "config": { - let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(); - if (config) { - newValue = config.get(name); + let newValue: string = undefined; + switch (varType) { + case "env": { + let v: string | string[] = additionalEnvironment[name]; + if (typeof v === "string") { + newValue = v; + } else if (input === match && v instanceof Array) { + newValue = v.join(";"); + } + if (!newValue) { + newValue = process.env[name]; + } + break; } - break; - } - case "workspaceFolder": { - // Only replace ${workspaceFolder:name} variables for now. - // We may consider doing replacement of ${workspaceFolder} here later, but we would have to update the language server and also - // intercept messages with paths in them and add the ${workspaceFolder} variable back in (e.g. for light bulb suggestions) - if (name && vscode.workspace && vscode.workspace.workspaceFolders) { - let folder: vscode.WorkspaceFolder = vscode.workspace.workspaceFolders.find(folder => folder.name.toLocaleLowerCase() === name.toLocaleLowerCase()); - if (folder) { - newValue = folder.uri.fsPath; + case "config": { + let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(); + if (config) { + newValue = config.get(name); + } + break; + } + case "workspaceFolder": { + // Only replace ${workspaceFolder:name} variables for now. + // We may consider doing replacement of ${workspaceFolder} here later, but we would have to update the language server and also + // intercept messages with paths in them and add the ${workspaceFolder} variable back in (e.g. for light bulb suggestions) + if (name && vscode.workspace && vscode.workspace.workspaceFolders) { + let folder: vscode.WorkspaceFolder = vscode.workspace.workspaceFolders.find(folder => folder.name.toLocaleLowerCase() === name.toLocaleLowerCase()); + if (folder) { + newValue = folder.uri.fsPath; + } } + break; } - break; + default: { assert.fail("unknown varType matched"); } } - default: { assert.fail("unknown varType matched"); } - } - return (newValue) ? newValue : match; - }); + return (newValue) ? newValue : match; + }); + } // Resolve '~' at the start of the path. - regexp = /^\~/g; - ret = ret.replace(regexp, (match: string, name: string) => { - let newValue: string = process.env.HOME; + regexp = () => /^\~/g; + ret = ret.replace(regexp(), (match: string, name: string) => { + let newValue: string = (process.platform === 'win32') ? process.env.USERPROFILE : process.env.HOME; return (newValue) ? newValue : match; }); diff --git a/Extension/src/packageManager.ts b/Extension/src/packageManager.ts index 5f636d61f..f6cda5c73 100644 --- a/Extension/src/packageManager.ts +++ b/Extension/src/packageManager.ts @@ -197,7 +197,7 @@ export class PackageManager { if (retryCount !== 0) { // Log telemetry to see if retrying helps. let telemetryProperties: { [key: string]: string } = {}; - telemetryProperties["success"] = `OnRetry${retryCount}`; + telemetryProperties["success"] = success ? `OnRetry${retryCount}` : 'false'; if (lastError instanceof PackageManagerError) { let packageError: PackageManagerError = lastError; telemetryProperties['error.methodName'] = packageError.methodName; diff --git a/Extension/test/unitTests/common.test.ts b/Extension/test/unitTests/common.test.ts new file mode 100644 index 000000000..fd6252bf9 --- /dev/null +++ b/Extension/test/unitTests/common.test.ts @@ -0,0 +1,226 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. + * See 'LICENSE' in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import * as assert from "assert"; +import { resolveVariables } from "../../src/common"; + +suite("Common Utility validation", () => { + suite("resolveVariables", () => { + const success: string = "success"; + const home: string = process.env.HOME || process.env.USERPROFILE; + + test("raw input", () => { + const input: string = "test"; + inputAndEnvironment(input, {}) + .shouldResolveTo(input); + }); + + test("raw input with tilde", () => { + inputAndEnvironment("~/test", {}) + .shouldResolveTo(`${home}/test`); + }); + + test("env input with tilde", () => { + inputAndEnvironment("${path}/test", { + path: home + }) + .shouldResolveTo(`${home}/test`); + }); + + test("solo env input resulting in array", () => { + inputAndEnvironment("${test}", { + test: ["foo", "bar"] + }) + .shouldResolveTo("foo;bar"); + }); + + test("mixed raw and env input resulting in array", () => { + const input: string = "baz${test}"; + resolveVariablesWithInput(input) + .withEnvironment({ + test: ["foo", "bar"] + }) + .shouldResolveTo(input); + }); + + test("solo env input not in env config finds process env", () => { + const processKey: string = `cpptoolstests_${Date.now()}`; + const input: string = "foo${" + processKey + "}"; + let actual: string; + try { + process.env[processKey] = "bar"; + actual = resolveVariables(input, {}); + } finally { + delete process.env[processKey]; + } + assert.equal(actual, "foobar"); + }); + + test("env input", () => { + resolveVariablesWithInput("${test}") + .withEnvironment({ + "test": success + }) + .shouldResolveTo(success); + }); + + test("env input mixed with plain text", () => { + resolveVariablesWithInput("${test}bar") + .withEnvironment({ + "test": "foo" + }) + .shouldResolveTo("foobar"); + }); + + test("env input with two variables", () => { + resolveVariablesWithInput("f${a}${b}r") + .withEnvironment({ + a: "oo", + b: "ba" + }) + .shouldResolveTo("foobar"); + }); + + test("env input not in env", () => { + const input: string = "${test}"; + resolveVariablesWithInput(input) + .withEnvironment({}) + .shouldResolveTo(input); + }); + + test("env with macro inside environment definition", () => { + resolveVariablesWithInput("${arm6.include}") + .withEnvironment({ + "envRoot": "apps/tool/buildenv", + "arm6.include": "${envRoot}/arm6/include" + }) + .shouldResolveTo("apps/tool/buildenv/arm6/include"); + }); + + test("env nested with half open variable", () => { + resolveVariablesWithInput("${arm6.include}") + .withEnvironment({ + "envRoot": "apps/tool/buildenv", + "arm6.include": "${envRoot/arm6/include" + }) + .shouldResolveTo("${envRoot/arm6/include"); + }); + + test("env nested with half closed variable", () => { + resolveVariablesWithInput("${arm6.include}") + .withEnvironment({ + "envRoot": "apps/tool/buildenv", + "arm6.include": "envRoot}/arm6/include" + }) + .shouldResolveTo("envRoot}/arm6/include"); + }); + + test("env nested with a cycle", () => { + resolveVariablesWithInput("${a}") + .withEnvironment({ + "a": "${b}", + "b": "${c}", + "c": "${a}" + }) + .shouldResolveTo("${a}"); + }); + + test("env input with 1 level of nested variables anchored at end", () => { + resolveVariablesWithInput("${foo${test}}") + .withEnvironment({ + "foobar": success, + "test": "bar" + }) + .shouldResolveTo("${foo${test}}"); + }); + + test("env input with 1 level of nested variables anchored in the middle", () => { + resolveVariablesWithInput("${f${test}r}") + .withEnvironment({ + "foobar": success, + "test": "ooba" + }) + .shouldResolveTo("${f${test}r}"); + }); + + test("env input with 1 level of nested variable anchored at front", () => { + resolveVariablesWithInput("${${test}bar}") + .withEnvironment({ + "foobar": success, + "test": "foo" + }) + .shouldResolveTo("${${test}bar}"); + }); + + test("env input with 3 levels of nested variables", () => { + resolveVariablesWithInput("${foo${a${b${c}}}}") + .withEnvironment({ + "foobar": success, + "a1": "bar", + "b2": "1", + "c": "2" + }) + .shouldResolveTo("${foo${a${b${c}}}}"); + }); + + test("env input contains env", () => { + resolveVariablesWithInput("${envRoot}") + .shouldLookupSymbol("envRoot"); + }); + + test("env input contains config", () => { + resolveVariablesWithInput("${configRoot}") + .shouldLookupSymbol("configRoot"); + }); + + test("env input contains workspaceFolder", () => { + resolveVariablesWithInput("${workspaceFolderRoot}") + .shouldLookupSymbol("workspaceFolderRoot"); + }); + + test("input contains env.", () => { + resolveVariablesWithInput("${env.Root}") + .shouldLookupSymbol("Root"); + }); + + test("input contains env:", () => { + resolveVariablesWithInput("${env:Root}") + .shouldLookupSymbol("Root"); + }); + + interface ResolveTestFlowEnvironment { + withEnvironment(additionalEnvironment: {[key: string]: string | string[]}): ResolveTestFlowAssert; + shouldLookupSymbol: (key: string) => void; + } + interface ResolveTestFlowAssert { + shouldResolveTo: (x: string) => void; + } + + function resolveVariablesWithInput(input: string): ResolveTestFlowEnvironment { + return { + withEnvironment: (additionalEnvironment: {[key: string]: string | string[]}) => { + return inputAndEnvironment(input, additionalEnvironment); + }, + shouldLookupSymbol: (symbol: string) => { + const environment: {[key: string]: string | string[]} = {}; + environment[symbol] = success; + return inputAndEnvironment(input, environment) + .shouldResolveTo(success); + } + }; + } + + function inputAndEnvironment(input: string, additionalEnvironment: {[key: string]: string | string[]}): ResolveTestFlowAssert { + return { + shouldResolveTo: (expected: string) => { + const actual: string = resolveVariables(input, additionalEnvironment); + const msg: string = `Expected ${expected}. Got ${actual} with input ${input} and environment ${JSON.stringify(additionalEnvironment)}.`; + assert.equal(actual, expected, msg); + } + }; + } + + }); +}); \ No newline at end of file diff --git a/launch.md b/launch.md index d49a5a7c0..6e27cafcd 100644 --- a/launch.md +++ b/launch.md @@ -101,6 +101,9 @@ You can change the behavior of GDB or LLDB by setting the following options. * #### `miDebuggerPath` The path to the debugger (such as gdb). When only the executable is specified, it will search the operating system's PATH variable for a debugger (GDB on Linux and Windows, LLDB on OS X). + +* #### `miDebuggerArgs` + Additional arguments to pass to the debugger (such as gdb). * #### `stopAtEntry` If set to true, the debugger should stop at the entry-point of the target (ignored on attach). Default value is `false`.