Compare commits

..
111 changed files with 867 additions and 6342 deletions
+5 -41
View File
@@ -7,44 +7,8 @@ on:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Use Node.js 16
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install Dependencies
run: yarn install
working-directory: Extension
- name: Compile Sources
run: yarn run compile
working-directory: Extension
- name: Run Linter
run: yarn run lint
working-directory: Extension
- name: Run unit tests
run: yarn test
working-directory: Extension
# # NOTE : We can't run the test that require the native binary files
# # yet -- there will be an update soon that allows the tester to
# # acquire them on-the-fly
# - name: Run simple vscode unit tests
# uses: GabrielBB/[email protected]
# with:
# run: yarn test --scenario=SingleRootProject
# working-directory: Extension
# - name: Run languageServer integration tests
# uses: GabrielBB/[email protected]
# with:
# run: yarn run integrationTests
# working-directory: Extension
job:
uses: ./.github/workflows/job-compile-and-test.yml
with:
runner-env: ubuntu-22.04
platform: linux
+6 -41
View File
@@ -7,44 +7,9 @@ on:
branches: [ main ]
jobs:
build:
runs-on: macos-12
steps:
- uses: actions/checkout@v3
- name: Use Node.js 16
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install Dependencies
run: yarn install --network-timeout 100000
working-directory: Extension
- name: Compile Sources
run: yarn run compile
working-directory: Extension
- name: Run Linter
run: yarn run lint
working-directory: Extension
- name: Run unit tests
run: yarn test
working-directory: Extension
# # NOTE : We can't run the test that require the native binary files
# # yet -- there will be an update soon that allows the tester to
# # acquire them on-the-fly
# - name: Run simple vscode unit tests
# uses: GabrielBB/[email protected]
# with:
# run: yarn test --scenario=SingleRootProject
# working-directory: Extension
# - name: Run languageServer integration tests
# uses: GabrielBB/[email protected]
# with:
# run: yarn run integrationTests
# working-directory: Extension
job:
uses: ./.github/workflows/job-compile-and-test.yml
with:
runner-env: macos-12
platform: mac
yarn-args: --network-timeout 100000
+5 -37
View File
@@ -7,40 +7,8 @@ on:
branches: [ main ]
jobs:
build:
runs-on: windows-2022
steps:
- uses: actions/checkout@v3
- name: Use Node.js 16
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install Dependencies
run: yarn install
working-directory: Extension
- name: Compile Sources
run: yarn run compile
working-directory: Extension
- name: Run Linter
run: yarn run lint
working-directory: Extension
- name: Run unit tests
run: yarn test
working-directory: Extension
# # NOTE : We can't run the test that require the native binary files
# # yet -- there will be an update soon that allows the tester to
# # acquire them on-the-fly
# - name: Run simple vscode unit tests
# run: yarn test --scenario=SingleRootProject
# working-directory: Extension
# - name: Run languageServer integration tests
# run: yarn run integrationTests
# working-directory: Extension
job:
uses: ./.github/workflows/job-compile-and-test.yml
with:
runner-env: windows-2022
platform: windows
@@ -0,0 +1,73 @@
# Reuable workflow for compiling and testing extension.
name: Compile and test extension
on:
workflow_call:
inputs:
runner-env:
required: true
type: string
platform:
# Expects 'mac', 'linux', or 'windows'
required: true
type: string
yarn-args:
type: string
jobs:
build:
runs-on: ${{ inputs.runner-env }}
steps:
- uses: actions/checkout@v3
- name: Use Node.js 16
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install Dependencies
run: yarn install ${{ inputs.yarn-args }}
working-directory: Extension
- name: Compile Sources
run: yarn run compile
working-directory: Extension
- name: Run Linter
run: yarn run lint
working-directory: Extension
- name: Run unit tests
run: yarn test
working-directory: Extension
# NOTE : We can't run the test that require the native binary files
# yet -- there will be an update soon that allows the tester to
# acquire them on-the-fly
# - name: Run languageServer integration tests
# if: ${{ inputs.platform == 'windows' }}
# run: yarn test --scenario=SingleRootProject
# working-directory: Extension
# - name: Run E2E IntelliSense features tests
# if: ${{ inputs.platform == 'windows' }}
# run: yarn test --scenario=MultirootDeadlockTest
# working-directory: Extension
# NOTE: For mac/linux run the tests with xvfb-action for UI support.
# Another way to start xvfb https://github.com/microsoft/vscode-test/blob/master/sample/azure-pipelines.yml
# - name: Run languageServer integration tests (xvfb)
# if: ${{ inputs.platform == 'mac' || inputs.platform == 'linux' }}
# uses: coactions/setup-xvfb@v1
# with:
# run: yarn test --scenario=SingleRootProject
# working-directory: Extension
# - name: Run E2E IntelliSense features tests (xvfb)
# if: ${{ inputs.platform == 'mac' || inputs.platform == 'linux' }}
# uses: coactions/setup-xvfb@v1
# with:
# run: yarn test --scenario=MultirootDeadlockTest
# working-directory: Extension
+3 -3
View File
@@ -19,7 +19,7 @@ parameters:
# Note: Make sure lldb_mi_commit is the same as the one in Extension/cgmanifest.json
# 'CommitHash' for lldb-mi.
lldb_mi_commit: 2388bd74133bc21eac59b2e2bf97f2a30770a315
lldb_mi_commit: 4fe9c663edce2447e114c71851694d8c529b982d
lldb_mi_additional_parameters: "-DUSE_LLDB_FRAMEWORK=1"
@@ -28,9 +28,9 @@ jobs:
timeoutInMinutes: 360
pool:
${{if eq(parameters['llvm_arch'], 'arm64')}}:
name: cpptoolsMacM1pool
vmImage: macos-13-arm64
${{ else }}:
vmImage: macOS-latest
vmImage: macOS-13
steps:
- task: CmdLine@2
displayName: 'Install Dependencies'
+2 -4
View File
@@ -21,8 +21,7 @@ module.exports = {
"eslint-plugin-jsdoc",
"@typescript-eslint/eslint-plugin",
"eslint-plugin-import",
"eslint-plugin-header",
"etc"
"eslint-plugin-header"
],
"rules": {
"indent": [
@@ -84,7 +83,6 @@ module.exports = {
"@typescript-eslint/prefer-function-type": "error",
"@typescript-eslint/prefer-namespace-keyword": "error",
"@typescript-eslint/semi": "error",
"etc/no-commented-out-code": "error",
"@typescript-eslint/triple-slash-reference": "error",
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/unified-signatures": "error",
@@ -92,7 +90,7 @@ module.exports = {
"@typescript-eslint/method-signature-style": ["error", "method"],
"@typescript-eslint/space-infix-ops": "error",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"@typescript-eslint/no-unnecessary-boolean-literal-compare": "error",
"arrow-body-style": "error",
"comma-dangle": "error",
-1
View File
@@ -13,7 +13,6 @@ bin/cpptools*
bin/*.dll
bin/.vs
bin/LICENSE.txt
bin/rg*
# ignore lock files
install.lock
-60
View File
@@ -1,60 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { parse } from 'semver';
import { $root, $switches, cyan, glob, green, note, updateFiles } from './common';
import { extensionsDir, installExtension, uninstallExtension } from './vscode';
export async function install(version: string) {
if (!version || parse(version)) {
note(`Attempting to install binaries from published vsix (${version ? version : $switches.includes('--pre-release') ? 'latest pre-release' : 'latest'})`);
}
// install it in the isolated vscodearea
note("Temporarily installing vscode-cpptools extension");
const { id, ver } = await installExtension('ms-vscode.cpptools', version);
// grab the binaries out
let files = [] as string[];
files.push(...await glob(`${extensionsDir}/${id}-${ver}*/bin/cpptools*`));
files.push(...await glob(`${extensionsDir}/${id}-${ver}*/bin/*.dll`));
files.push(...await glob(`${extensionsDir}/${id}-${ver}*/bin/*.exe`));
files.push(...await glob(`${extensionsDir}/${id}-${ver}*/LLVM/**`));
files.push(...await glob(`${extensionsDir}/${id}-${ver}*/debugAdapters/**`));
files = [...new Set(files)];
const extensionFolder = files[0].replace(/.bin.cpptools.*$/g, '');
note(`Copying files from vscode-cpptools extension ${ver} in '${extensionFolder}'`);
await updateFiles(files, $root, extensionFolder);
// remove the extension fromthe isolated vscode
note("Removing temporary vscode-cpptools extension");
await uninstallExtension('ms-vscode.cpptools');
}
export async function installbuild() {
//*
// cpptools-srv.exe
// cpptools-vcpkgsrv.exe
// cpptools.exe
// vcpkgsrvtest.exe
// xobjgen.exe
// search relative for the vcpkgsrv
//
// (grep --iglob **/vcpkgsrv/cpptools-vcpkgsrv.exe --max-depth 5 --ignore-case --files ../../ | Resolve-Path).Path
// base folder:
// ((grep --iglob **/vcpkgsrv/cpptools-vcpkgsrv.exe --max-depth 7 --ignore-case --files ../../) -replace "designtime.*",'' | Resolve-Path).Path
// typical locations for binaries
// ../../build/debug
//
}
export async function main() {
console.log(`use ${green(`yarn binary install ${cyan('[version] [--pre-release]')}`)}`);
}
+2 -2
View File
@@ -9,7 +9,7 @@ import { $args, $root, $scenario, assertAnyFile, brightGreen, gray, green, pwd }
import { resolve } from 'path';
import { getTestInfo } from '../test/common/selectTests';
import { environment, install, options } from "./vscode";
import { install, options } from "./vscode";
export { install, reset } from './vscode';
@@ -36,5 +36,5 @@ export async function main() {
const ARGS = [...args, ...options.launchArgs.filter(each => !each.startsWith('--extensions-dir=') && !each.startsWith('--user-data-dir=')), `--extensionDevelopmentPath=${$root}`, ...$args ];
verbose(gray(`${cli}\n ${ [...ARGS ].join('\n ')}`));
spawnSync(cli, ARGS, { encoding: 'utf-8', stdio: 'ignore', env:environment()});
spawnSync(cli, ARGS, { encoding: 'utf-8', stdio: 'ignore', env: { ...process.env, DONT_PROMPT_WSL_INSTALL:"1" } });
}
+4 -60
View File
@@ -119,17 +119,13 @@ export async function write(filePath: string, data: Buffer | string) {
await writeFile(filePath, data);
}
export async function updateFiles(files: string[], dest: string | Promise<string>, prefix?: string) {
export async function updateFiles(files: string[], dest: string | Promise<string>) {
const target = is.promise(dest) ? await dest : dest;
await Promise.all(files.map(async (each) => {
const sourceFile = await filepath.isFile(each, $root);
if (sourceFile) {
const targetFile = prefix ? resolve(target, each.replace(prefix, '.')) : resolve(target, each);
try {
await write(targetFile, await readFile(sourceFile));
} catch (e) {
verbose(`Error during update of '${targetFile}' ${e} `);
}
const targetFile = resolve(target, each);
await write(targetFile, await readFile(sourceFile));
}
}));
}
@@ -139,7 +135,7 @@ export async function go() {
// loop through the args and pick out the first non --arg and remove it from the $args and set $cmd
for (let i = 0; i < $args.length; i++) {
const each = $args[i];
if (require.main.exports[each]) {
if (!each.startsWith('--') && require.main.exports[each]) {
$cmd = each;
$args.splice(i, 1);
break;
@@ -345,55 +341,3 @@ export async function checkBinaries() {
}
return failing;
}
const sowrite = process.stdout.write.bind(process.stdout) as (...args: unknown[]) => boolean;
const sewrite = process.stderr.write.bind(process.stderr) as (...args: unknown[]) => boolean;
const filters = [
/^\[(.*)\].*/,
/^Unexpected token A/,
/Cannot register 'cmake.cmakePath'/,
/\[DEP0005\] DeprecationWarning/,
/--trace-deprecation/,
/Iconv-lite warning/,
/^Found existing install/
];
// remove unwanted messages from stdio
function filterStdio() {
process.stdout.write = function (...args: unknown[]) {
if (typeof args[0] === 'string') {
const text = args[0];
if (filters.some(each => text.match(each))) {
return true;
}
}
if (args[0] instanceof Buffer) {
const text = args[0].toString();
if (filters.some(each => text.match(each))) {
return true;
}
}
return sowrite(...args);
};
process.stderr.write = function (...args: unknown[]) {
if (typeof args[0] === 'string') {
const text = args[0];
if (filters.some(each => text.match(each))) {
return true;
}
}
if (args[0] instanceof Buffer) {
const text = args[0].toString();
if (filters.some(each => text.match(each))) {
return true;
}
}
return sewrite(...args);
};
}
filterStdio();
+54 -3
View File
@@ -19,11 +19,63 @@ import { install, isolated, options } from './vscode';
export { install, reset } from './vscode';
const sowrite = process.stdout.write.bind(process.stdout) as (...args: unknown[]) => boolean;
const sewrite = process.stderr.write.bind(process.stderr) as (...args: unknown[]) => boolean;
const filters = [
/^\[(.*)\].*/,
/^Unexpected token A/,
/Cannot register 'cmake.cmakePath'/,
/\[DEP0005\] DeprecationWarning/,
/--trace-deprecation/,
/Iconv-lite warning/,
/^Extension '/,
/^Found existing install/
];
// remove unwanted messages from stdio
function filterStdio() {
process.stdout.write = function (...args: unknown[]) {
if (typeof args[0] === 'string') {
const text = args[0];
if (filters.some(each => text.match(each))) {
return true;
}
}
if (args[0] instanceof Buffer) {
const text = args[0].toString();
if (filters.some(each => text.match(each))) {
return true;
}
}
return sowrite(...args);
};
process.stderr.write = function (...args: unknown[]) {
if (typeof args[0] === 'string') {
const text = args[0];
if (filters.some(each => text.match(each))) {
return true;
}
}
if (args[0] instanceof Buffer) {
const text = args[0].toString();
if (filters.some(each => text.match(each))) {
return true;
}
}
return sewrite(...args);
};
}
filterStdio();
async function unitTests() {
await assertAnyFolder('dist/test/unit', `The folder '${$root}/dist/test/unit is missing. You should run ${brightGreen("yarn compile")}\n\n`);
const mocha = await assertAnyFile(["node_modules/.bin/mocha.cmd", "node_modules/.bin/mocha"], `Can't find the mocha testrunner. You might need to run ${brightGreen("yarn install")}\n\n`);
const result = spawnSync(mocha, [`${$root}/dist/test/unit/**/*.test.js`, '--timeout', '30000'], { stdio:'inherit'});
verbose(`\n${green("NOTE:")} If you want to run a scenario test (end-to-end) use ${cmdSwitch('scenario=<NAME>')} \n\n`);
return result.status;
}
@@ -38,8 +90,7 @@ async function scenarioTests(assets: string, name: string, workspace: string) {
extensionTestsPath: resolve($root, 'dist/test/common/selectTests'),
launchArgs: workspace ? [...options.launchArgs, workspace] : options.launchArgs,
extensionTestsEnv: {
SCENARIO: assets,
DONT_PROMPT_WSL_INSTALL:"1"
SCENARIO: assets
}
});
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"module": "node16",
"moduleResolution": "node16",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
+1 -55
View File
@@ -3,16 +3,12 @@
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
/* eslint-disable @typescript-eslint/no-dynamic-delete */
import { downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath } from '@vscode/test-electron';
import { fail } from 'assert';
import { spawnSync } from 'child_process';
import { createHash } from 'crypto';
import { tmpdir } from 'os';
import { resolve } from 'path';
import { verbose } from '../src/Utility/Text/streams';
import { $switches, error, mkdir, readJson, rimraf, write } from './common';
import { mkdir, readJson, rimraf, write } from './common';
export const isolated = resolve(tmpdir(), '.vscode-test', createHash('sha256').update(__dirname).digest('hex').substring(0, 6));
export const extensionsDir = resolve(isolated, 'extensions');
@@ -56,57 +52,7 @@ export async function install() {
} catch (err: unknown) {
console.log(err);
}
}
export async function installExtension(name: string, version?: string) {
// eslint-disable-next-line prefer-const
let {cli, args} = await install();
args = [...args, '--install-extension', version ? `${name}@${version}` : name ];
if ($switches.includes('--pre-release')) {
args.push('--pre-release');
}
verbose({cli, args});
const result = spawnSync(cli, args, { encoding: 'utf-8', stdio: 'pipe', env:environment()});
verbose(result.stdout);
if (!result.status){
for (const line of result.output){
const [,id, ver] = /Extension '(.*)' v(.*?)\s/g.exec(line) ?? [];
if (id) {
return {id, ver};
}
}
}
error(result.stderr);
fail('Failed to install extension');
}
export async function uninstallExtension(name: string) {
// eslint-disable-next-line prefer-const
let {cli, args} = await install();
args = [...args, '--uninstall-extension', name];
const result = spawnSync(cli, args, { encoding: 'utf-8', stdio: 'pipe', env:environment()});
if (!result.status){
for (const line of result.output){
const [,id, ver] = /Extension '(.*)' v(.*?)\s/g.exec(line) ?? [];
if (id) {
return {id, ver};
break;
}
}
}
}
export async function extensions() {
// eslint-disable-next-line prefer-const, @typescript-eslint/no-unused-vars
let {cli, args} = await install();
}
/** returns a copy of the environment, with tweaks to ensure isolated instance works correctly in WSL and remote dev situations */
export function environment() {
const env = { ...process.env, DONT_PROMPT_WSL_INSTALL:"1" }; // this lets you run the native VSCODE instance, even if you're working in WSL
Object.keys(env).map(each => each.includes('VSCODE') && delete env[each]); // prevent VSCode remoting from hijacking the launching of the isolated vscode
return env;
}
export async function reset() {
-4
View File
@@ -13,12 +13,8 @@
"--skip-welcome",
"--skip-release-notes",
"--disable-workspace-trust",
"--disable-extensions",
"--extensionDevelopmentPath=${workspaceFolder}",
],
"env": {
//"VSCODE_CPP_DEBUG_VSCODE": "c:\\work\\vs\\src\\vc\\.vscode\\launch.json"
},
"sourceMaps": true,
"outFiles": [
"${workspaceFolder}/dist/**"
-18
View File
@@ -1,16 +1,11 @@
// Place your settings in this file to overwrite default and user settings.
{
"window.title": "CppTools <Typescript> ${separator}${activeEditorShort}${separator}${dirty}",
"workbench.colorTheme": "Hacker",
"files.exclude": {
"out": false // set this to true to hide the "out" folder with the compiled JS files
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
},
"files.associations": {
"**/bin/definitions/**/*.json": "jsonl",
},
"typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version
// if you install the mocha test explorer extension, you can run the unit tests from the test explorer UI
"testExplorer.useNativeTesting": true,
@@ -69,17 +64,4 @@
"directory": "./.scripts",
}
],
"[jsonl]": {
"editor.tabSize": 2,
},
"workbench.colorCustomizations": {
"[Hacker]": {
"sideBar.background": "#050a0a",
"menu.background": "#050a0a",
"editorWidget.background": "#050a0a",
"terminal.background": "#030303",
"activityBar.background": "#050a0a",
"activityBarBadge.foreground": "#eaeaea",
}
},
}
+1 -1
View File
@@ -22,7 +22,7 @@
"isDefault": true
},
"isBackground": true,
"problemMatcher": "$tsc-watch"
"problemMatcher": "$tsc-watch",
}
]
}
+22
View File
@@ -1,5 +1,27 @@
# C/C++ for Visual Studio Code Changelog
## Version 1.18.0: October 12, 2023
### New Features
* Add an 'Extract to Function' (or Member Function) code action after selecting code. [#1162](https://github.com/microsoft/vscode-cpptools/issues/1162)
* Currently, it's only enabled when `C_Cpp.experimentFeatures` is `true`. Also, 'Extract to Free Function' is disabled.
* Compiler acquisition improvements. [#10525](https://github.com/microsoft/vscode-cpptools/issues/10525)
### Enhancements
* Add setting `C_Cpp.refactoring.includeHeader` to customize whether or not to add an include header when doing a refactoring code action. [#11271](https://github.com/microsoft/vscode-cpptools/issues/11271)
* Update clang-format and clang-tidy to 17.0.2. [PR #11491](https://github.com/microsoft/vscode-cpptools/pull/11491)
### Bug Fixes
* Fix the debugger truncating long strings when inspecting values. [#1786](https://github.com/microsoft/vscode-cpptools/issues/1786)
* Switch to using `XDG_CACHE_HOME` on Linux for the default database path. [#10191](https://github.com/microsoft/vscode-cpptools/issues/10191)
* Fix incorrect status and commands with the tag parsing language status UI. [#10749](https://github.com/microsoft/vscode-cpptools/issues/10749)
* Fix an empty (`""`) `compilerPath` in a base configuration overriding the compiler specified by a custom configuration provider or a `compile_commands.json`. [#11373](https://github.com/microsoft/vscode-cpptools/issues/11373)
* Fix a startup crash when reading values from JSON (settings) that are not the type expected. [#11375](https://github.com/microsoft/vscode-cpptools/issues/11375)
* Fix a crash detected by crash telemetry. [#11401](https://github.com/microsoft/vscode-cpptools/issues/11401)
* Fix handling of an undefined `env` variable on Linux and macOS. [#11447](https://github.com/microsoft/vscode-cpptools/issues/11447)
* Fix multiple issues with querying `nvcc` (CUDA) as a compiler. [#11454](https://github.com/microsoft/vscode-cpptools/issues/11454)
* Fix an issue that could cause a C language standard to be applied to a C++ file, or vice versa.
* Remove `cpp` and `clang-cpp` preprocessors from the list of detectable compilers.
## Version 1.17.5: August 28, 2023
### Bug Fixes
* Fix a language server crash for platforms that don't support the IntelliSense cache (AutoPCH). [#10789](https://github.com/microsoft/vscode-cpptools/issues/10789)
+1 -1
View File
@@ -17,7 +17,7 @@ required to debug changes to any libraries licensed under the GNU Lesser General
---------------------------------------------------------
lldb-tools/lldb-mi 2388bd74133bc21eac59b2e2bf97f2a30770a315 - Apache-2.0 WITH LLVM-exception
lldb-tools/lldb-mi 4fe9c663edce2447e114c71851694d8c529b982d - Apache-2.0 WITH LLVM-exception
Copyright (c) 2010 Apple Inc.
@@ -1,20 +0,0 @@
{
// common query file for gcc-style compilers
"analysis": {
/*
"query": {
"-E -dM -v ${tmp:c}": {
"macro": "^#define (\\S*) ?(.*)?$", // match defines in output
"includePaths": "#include <...> search starts here:\n((?: [^\n]*\n)*)*", // match the lines for include folders
"target": "^Target: (.*)", // get the target
"cStandard": "^#define __STDC_VERSION__\\s*(.*)",
"cppStandard": "^#define __cplusplus\\s*(.*)",
},
"--version": {
"version": "^clang version (\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$"
}
}
}*/
// how do we specify args to trim off if the discovery is handled/initiated by someone else
}
}
@@ -1,86 +0,0 @@
{
"intellisense": {
"hostArchitecture": "${host:arch}",
},
"conditions": {
// example using a conditional to set the target platform based on the host platform
"${host:os} === 'linux'": { "intellisense.platform": "linux" },
"${host:os} === 'win32'": { "intellisense.platform": "windows" },
"${host:os} === 'darwin'": { "intellisense.platform": "macos" }
},
"analysis": {
// there are a lot of things that can be inferred from the macros that are defined by the compiler
// if they are specified, let's use them.
"expressions: PRIORITY=90 # things inferred from macros": {
/* if this is truthy */ /* then apply this to the intellisense configuration */
"${macro:_X86_}": { "architecture": "x86", "bits" : 32 },
"${macro:_x86_64_}": { "architecture": "x64", "bits" : 64 },
"${macro:__aarch64__}": { "architecture": "arm64", "bits" : 64 },
"${macro:__arm__}": { "architecture": "arm", "bits" : 32 },
"${macro:__AVR__}": { "architecture": "avr", "bits": 16 },
"${macro:winnt}": { "platform": "windows" },
"${macro:_WIN32}": { "platform": "windows" },
"${macro:_WIN64}": { "platform": "windows", "bits": 64, "architecture": "x64" },
"${macro:__linux__}": { "platform": "linux" },
"${macro:__APPLE__}": { "platform": "macos" },
"${macro:__INTPTR_WIDTH__}": { "bits": "${macro:.__INTPTR_WIDTH__}" },
"!${sizes.char} && ${macro:__CHAR_SIZE__}" : { "sizes.char": "${macro:__CHAR_SIZE__}" },
"!${sizes.short} && ${macro:__SIZEOF_SHORT__}": { "sizes.short": "${macro:__SIZEOF_SHORT__}"},
"!${sizes.short} && ${macro:__SHORT_SIZE__}": { "sizes.short": "${macro:__SHORT_SIZE__}"},
"!${sizes.int} && ${macro:__SIZEOF_INT__}": { "sizes.int": "${macro:__SIZEOF_INT__}"},
"!${sizes.int} && ${macro:__INT_SIZE__}": { "sizes.int": "${macro:__INT_SIZE__}"},
"!${sizes.long} && ${macro:__SIZEOF_LONG__}" : { "sizes.long": "${macro:__SIZEOF_LONG__}" },
"!${sizes.long} && ${macro:__LONG_SIZE__}" : { "sizes.long": "${macro:__LONG_SIZE__}" },
"!${sizes.longDouble} && ${macro:__SIZEOF_LONG_DOUBLE__}" : { "sizes.longDouble": "${macro:__SIZEOF_LONG_DOUBLE__}" },
"!${sizes.longDouble} && ${macro:__LONG_DOUBLE_SIZE__}" : { "sizes.longDouble": "${macro:__LONG_DOUBLE_SIZE__}" },
"!${sizes.float} && ${macro:__SIZEOF_FLOAT__}": { "sizes.float": "${macro:__SIZEOF_FLOAT__}"},
"!${sizes.float} && ${macro:__FLOAT_SIZE__}": { "sizes.float": "${macro:__FLOAT_SIZE__}"},
"!${sizes.double} && ${macro:__SIZEOF_DOUBLE__}": { "sizes.double": "${macro:__SIZEOF_DOUBLE__}"},
"!${sizes.double} && ${macro:__DOUBLE_SIZE__}": { "sizes.double": "${macro:__DOUBLE_SIZE__}"},
"!${sizes.pointer} && ${macro:__SIZEOF_POINTER__}": { "sizes.pointer": "${macro:__SIZEOF_POINTER__}"},
"!${sizes.pointer} && ${macro:__DEF_PTR_SIZE__}": { "sizes.pointer": "${macro:__DEF_PTR_SIZE__}"},
"!${sizes.digitsInLongMantissa} && ${macro:__LDBL_MANT_DIG__}": { "sizes.digitsInLongMantissa": "${macro:__LDBL_MANT_DIG__}"},
"!${types.wcharT} && ${macro:__WCHAR_TYPE__}": { "types.wcharT":"${macro:__WCHAR_TYPE__}"},
"!${types.wcharT} && ${macro:__WCHAR_T_TYPE__}": { "types.wcharT":"${macro:__WCHAR_T_TYPE__}"},
"!${types.sizeT} && ${macro:__SIZE_TYPE__}": { "types.sizeT":"${macro:__SIZE_TYPE__}"},
"!${types.sizeT} && ${macro:__SIZE_T_TYPE__}": { "types.sizeT":"${macro:__SIZE_T_TYPE__}"},
"!${types.ptrDiffT} && ${macro:__PTRDIFF_TYPE__}": { "types.ptrDiffT":"${macro:__PTRDIFF_TYPE__}"},
"!${types.ptrDiffT} && ${macro:__PTRDIFF_T_TYPE__}": { "types.ptrDiffT":"${macro:__PTRDIFF_T_TYPE__}"},
},
// when setting priority, it forces it to be higher or lower depending on the value.
// higher numbers mean that the block is applied later, lower numbers mean that the block is applied earlier
// the default number is the order in which it is loaded
"expressions: PRIORITY=100": {
"!${sizes.long}" : { "sizes.long": "${platform} === 'windows' ? 4 : ${bits} === 64 ? 8: 4" },
"!${sizes.longDouble}": { "sizes.longDouble": "${platform} === 'windows' || ${architecture} === 'arm' || (${architecture} === 'arm64' && ${platform} === 'macos' ) ? 8 : 16" },
"!${sizes.char}": { "sizes.char": 1},
"!${sizes.short}": { "sizes.short": 2},
"!${sizes.int}": { "sizes.int": 4},
"!${sizes.float}": { "sizes.float": 4},
"!${sizes.double}": { "sizes.double": 8},
"!${sizes.pointer}": { "sizes.pointer": "${bits} === 64 ? 8: 4" },
"!${sizes.digitsInLongMantissa}": { "sizes.digitsInLongMantissa": "${platform} === 'windows' || ${architecture} === 'arm' || (${architecture} === 'arm64' && ${platform} === 'macos' ) ? 53 : 64" },
"!${sizes.alignmentOfLongDouble}": { "sizes.alignmentOfLongDouble": "${platform} === 'windows' || ${architecture} === 'arm' || (${architecture} === 'arm64' && ${platform} === 'macos' ) ? 8 : 16" },
"!${sizes.defaultNewAlignment}": { "sizes.defaultNewAlignment": "${bits} === 64 ? 16 : 8" },
}
}
}
-121
View File
@@ -1,121 +0,0 @@
{
"analysis": { // affects
"queries: ONCE": { // we never need to do this more than once
// gets the version of the compiler by executing it with --version
"-dumpversion": {
"(?<version>.*)" : {"version": "${version}"}, // parse the version from the output
},
},
"tasks: ": [
// tasks are one-off, built-in functionality that can be executed during analysis
],
"commandLineArguments: C #" :{
"-std=(?<std>.+)" : { "standard":"${std}" },
"-ansi" : { "standard":"c90" },
},
"commandLineArguments: CPP # " :{
"-std=(?<std>.+)" : { "standard":"${std}" },
},
"commandLineArguments: PRIORITY=-10 # drop these (maybe we don't even care to do this?...)": {
"-o;.+": { }, // output file
"-c;.+": { }, // input file
"-param;${keyEqualsValue}": { }, // compiler parameter
"-aux-info;.+": { }, // aux info
"-MF;.+": { }, // output file for dependencies
"-MT;.+": { }, // target for dependencies
"-MQ;.+": { }, // target for dependencies
"-T;.+": { }, // link script
"-Xlinker;.+": { }, // pass to linker
"-Wl,.+": { }, // pass to linker
"-z;.+": { }, // pass to linker
"-Xpreprocessor;.+": { }, // pass to preprocessor
"-Xassembler;.+": { }, // pass to assembler
"-Wa,.+": { }, // pass to assembler,
"-fmacro-prefix-map=.*": { }, // macro prefix map
"-fdebug-prefix-map=.*": { }, // debug prefix map
},
"commandLineArguments: # " :{
"-B(?<prefix>.+)": { "queryArgument": "-B${prefix}" }, // binary prefix
"-fms-extensions" : { "parserArgument": "--ms_extensions" },
"-fno-ms-extensions" : { "remove:parserArgument": "--ms_extensions" },
"-m(?<arch>.+)": { "queryArgument" : "-m${arch}" },
"-I-": { "splitDir": true},
"-I(?<path>.+)": { "path.include": "${path}", "queryArgument": "-I${path}" },
"-I;(?<path>.+)": { "path.include": "${path}", "queryArgument": "-I${path}"},
"-isystem;(?<path>.+)": { "path.systemInclude": "${path}", "queryArgument": ["-isystem","${path}"] },
"-idirafter;(?<path>.+)": { "path.afterInclude": "${path}", "queryArgument": ["-idirafter","${path}"] },
"-iprefix;(?<path>.+)": { "prefixPath": "${path}", "queryArgument": ["-iprefix","${path}"] },
"-iwithprefix;(?<path>.+)": { "path.afterInclude": "${prefixPath}${path}", "queryArgument": ["-iwithprefix","${path}"] },
"-iwithprefixbefore;(?<path>.+)": { "path.include": "${prefixPath}${path}", "queryArgument": ["-iwithprefixbefore","${path}"] },
"-iquote;(?<path>.+)": { "path.quoteInclude": "${path}", "queryArgument": ["-iquote","${path}"] },
"-isysroot;(?<path>.+)": { "sysroot": "${path}" },
"-imultilib;(?<path>.+)": { "path.multilibpaths": "${path}" },
"-F(?<path>.+)": { "path.framework": "${path}" },
"-D${keyEqualsValue}" : { "macro": { "${key}": "${value}" } , "queryArgument": ["-D", "${key}=${value}"]},
"-D${keyNoValue}" : { "macro": { "${key}": 1 }, "queryArgument": ["-D", "${key}"]},
"-D;${keyEqualsValue}" : { "macro": { "${key}": "${value}" }, "queryArgument": ["-D", "${key}=${value}"]},
"-D;${keyNoValue}" : { "macro": { "${key}": 1 }, "queryArgument": ["-D", "${key}"]},
"-U${keyNoValue}" : { "macro": { "${key}": null }, "queryArgument": ["-U", "${key}"]},
"-U$;{keyNoValue}" : { "macro": { "${key}": null}, "queryArgument": ["-U", "${key}"]},
"-include;(?<path>.+)": { "path.forcedIncludeFile": "${path}" }, // forced include
"-fexperimental-library": { "macro": {"__has_feature(experimental_library)": 1 }},
"-undef": { "queryArgument": "-undef" },
"-x;(?<language>.+)": { "language": "${language}" }
},
"expressions: # environment variables" : {
"${env:CPATH}": { "path.include": "${env:CPATH}" },
"${language}=== 'c' && ${env:C_INCLUDE_PATH}": { "path.systemInclude": "${env:C_INCLUDE_PATH}" },
"${language}=== 'cpp' && ${env:CPLUS_INCLUDE_PATH}":{ "path.systemInclude": "${env:CPLUS_INCLUDE_PATH}" },
"${language}=== 'objc' && ${env:OBJC_INCLUDE_PATH}":{ "path.systemInclude": "${env:OBJC_INCLUDE_PATH}" },
},
// before the sizes are set, we need to query the compiler with the right args to get the macros again
// (so we can let things like -m32 or -m64 or -mthumb or other arch changes affect the macros )
"queries:":{
// ==== COMMAND LINE IS THE KEY ====
"-E -dM -v ${queryArgument} ${tmp:c}": {
/* regex on output */ /* apply this to the intellisense config*/
"^#define (?<key>\\S*) ?(?<value>.*)?$": { "macro":{ "${key}": "${value}" } },
"#include <...> search starts here:\n(?<path>(?: [^\n]*\n)*)*": { "path.builtInInclude": "${path}" },
"#include \"...\" search starts here:\n(?<path>(?: [^\n]*\n)*)*": { "path.quoteInclude": "${path}" },
"^COLLECT_GCC_OPTIONS=(?<opts>.*)": { "gcc.options" : "${opts}" },
"^Target: (?<target>.*)": { "gcc.target": "${target}" },
"^COMPILER_PATH=(?<cp>.*)": { "gcc.compiler.paths": "${cp}" },
"^LIBRARY_PATH=(?<lp>.*)": { "gcc.library.paths": "${lp}" },
"^#define __STDC_VERSION__\\s*(?<ver>.*)": { "cStandard": "${ver}" },
"^#define __cplusplus\\s*(?<ver>.*)": { "cppStandard": "${ver}" },
}
},
"expressions: ONCE # get parts of gcc version" : {
"${macro:__GNUC__}" : {
"MAJOR": "${macro:__GNUC__} >= 10 ? ${macro:__GNUC__} : '0'+${macro:__GNUC__}",
"MINOR": "${macro:__GNUC_MINOR__} >= 10 ? ${macro:__GNUC_MINOR__} : '0'+${macro:__GNUC_MINOR__}",
"PATCH": "${macro:__GNUC_PATCHLEVEL__} >= 10 ? ${macro:__GNUC_PATCHLEVEL__} : '0'+${macro:__GNUC_PATCHLEVEL__}"
}
},
"expressions: ONCE # gnu_version" : {
"${macro:__GNUC__}" : {
"parserArgument": [ "--gcc", "--gnu_version=${MAJOR}${MINOR}${PATCH}" ]
}
},
}
// how do we specify args to trim off if the discovery is handled/initiated by someone else
}
-170
View File
@@ -1,170 +0,0 @@
{
"analysis": {
// analysis steps are applied in order
"tasks: # handle environment variables" : [
"inline-environment-variables", // takes the $env:CL and $env:_CL_ variables and adds them to the command line
"inline-response-file", // takes the @file response file and adds it to the command line
"consume-lib-path", // adds $env:LIB to the lib paths
"remove-linker-arguments", // removes linker arguments from the command line
],
"expressions: #environment variables": {
"${env:INCLUDE}": { "path.environmentInclude": "${env:INCLUDE}" },
},
"commandLineArguments: # detect target language": { // looks thru args to find things that set certain states (ie, /Tc, /Tp for language selection )
"${-/}T[cC]" : { "language": "c",},
"${-/}T[pP]" : { "language": "c++" },
},
"commandLineArguments: NO_CONSUME # telemetry check for CLR usage": {
// don"t consume the parameter
"${-/}clr.*" : {"telemetry:cliDetected": {"cliDetection": true} },
"${-/}ZW.*" : {"telemetry:cxDetected": {"cxDetection": true} }
},
"commandLineArguments: # process most args": {
"${-/}I(?<path>.+)" : { "path.include": "${path}" },
"${-/}I;(?<path>.+)" : { "path.include": "${path}" },
"${-/}FI(?<path>.+)" : { "path.forcedIncludeFile": "${path}" }, // should we be validating the path exists against system and user include folders? -- cl_compiler_info.cpp@191
"${-/}FI;(?<path>.+)" : { "path.forcedIncludeFile": "${path}" },
"-include;(?<path>.+)" : { "path.forcedIncludeFile": "${path}" }, // This support for passing "-include" to cl.exe can be removed in the future, when Unreal Engine is updated to no longer pass it to cl.exe.
"${-/}external:I(?<path>.+)" : { "path.externalInclude": "${path}" },
"${-/}external:I;(?<path>.+)" : { "path.externalInclude": "${path}" },
"${-/}D;${keyEqualsValue}" : { "macro": { "${key}": "${value}" } },
"${-/}D;${keyNoValue}" : { "macro": { "${key}": 1 } },
"${-/}D${keyEqualsValue}" : { "macro": { "${key}": "${value}" } },
"${-/}D${keyNoValue}" : { "macro": { "${key}": 1 } },
"${-/}Zl" : { "macro": { "_VC_NODEFAULTLIB": 1 } },
"${-/}Zp(?<alignment>1|2|4|8|16)" : { "parserArgument": [ "--pack_alignment", "${alignment}" ] },
"${-/}Zp(?<alignment>.+)": { "warning": "unknown pack alignment ${alignment}", "parserArgument": [ "--pack_alignment", 1 ] },
"${-/}fp:except": { "macro": { "_M_FP_EXCEPT": 1} },
"${-/}fp:except-": { "macro": { "_M_FP_EXCEPT": null} },
"${-/}fp:fast": { "macro": { "_M_FP_FAST": 1} },
"${-/}fp:precise": { "macro": { "_M_FP_PRECISE": 1} },
"${-/}fp:strict": { "macro": { "_M_FP_STRICT": 1, "_M_FP_EXCEPT":1} },
"${-/}GR": { "parserArgument": "--rtti" },
"${-/}GR-": { "parserArgument": "--no_rtti" },
"${-/}headerUnit;(?<hu>.+)": { "parserArgument": ["--ms_header_unit","${hu}"] },
"${-/}headerUnit(?<hu>.+)": { "parserArgument": ["--ms_header_unit","${hu}"] },
"${-/}ifcSearchDir;(?<md>.+)": { "parserArgument": ["--modules_directory","${md}"] },
"${-/}ifcSearchDir(?<md>.+)": { "parserArgument": ["--modules_directory","${md}"] },
"${-/}J": { "parserArgument": "--unsiged_chars" , "macro": { "_CHAR_UNSIGNED": 1 } },
"${-/}LD": { "macro": { "_MT":1 } },
"${-/}MT": { "macro": { "_MT":1 } },
"${-/}LDd": { "macro": { "_MT":1, "_DEBUG":1 } },
"${-/}MTd": { "macro": { "_MT":1, "_DEBUG":1 } },
"${-/}MD": { "macro": { "_MT":1, "_DLL":1 } },
"${-/}MDd": { "macro": { "_MT":1, "_DLL":1, "_DEBUG":1 } },
"${-/}openmp": { "macro": { "_OPENMP":"200203" } },
"${-/}openmp:experimental": { "macro": { "_OPENMP":"2019" } },
"${-/}openmp:llvm": { "macro": { "_OPENMP":"200203", "_OPENMP_LLVM_RUNTIME":1 } },
"${-/}permissive": { "parserArgument": "--ms_permissive" },
"${-/}permissive-": { "parserArgument": "--no_ms_permissive" },
"${-/}reference;(?<path>.+)": { "parserArgument": ["--ms_mod_file_map","${path}"] },
"${-/}reference(?<path>.+)": { "parserArgument": ["--ms_mod_file_map","${path}"] },
"${-/}showIncludes": { "parserArgument": "--trace_includes" },
"${-/}stdIfcDir;(?<path>.+)": { "parserArgument": ["--std_modules_directory","${path}"] },
"${-/}stdIfcDir(?<path>.+)": { "parserArgument": ["--std_modules_directory","${path}"] },
"${-/}volatile:iso": { "macro": { "_VOLATILE_ISO":1 } },
"${-/}Za": { "parserArgument": "--alternative_tokens" },
"${-/}Zc:__cplusplus": { "parserArgument": "--ms_cplusplus_std_value" },
"${-/}Zc:__cplusplus-": { "parserArgument": "--no_ms_cplusplus_std_value" },
"${-/}Zc:char8_t": { "parserArgument": "--char8_t" },
"${-/}Zc:char8_t-": { "parserArgument": "--no_char8_t" },
"${-/}Zc:forScope": { "parserArgument": "--new_for_init" },
"${-/}Zc:forScope-": { "parserArgument": "--old_for_init" },
"${-/}Zc:wchar_t": { "parserArgument": "--wchar_t_keyword" },
"${-/}Zc:wchar_t-": { "parserArgument": "--no_wchar_t_keyword" },
"${-/}Zc:auto-": { "parserArgument": "--no_auto_type" },
"${-/}Zc:trigraphs": { "parserArgument": "--trigraphs" },
"${-/}Zc:trigraphs-": { "parserArgument": "--no_trigraphs" },
"${-/}Zc:rvalueCast": { "parserArgument": "--ms_rvalue_cast" },
"${-/}Zc:rvalueCast-": { "parserArgument": "--no_ms_rvalue_cast" },
"${-/}Zc:strictStrings": { "parserArgument": "--no_deprecated_string_conv" },
"${-/}Zc:strictStrings-": { "parserArgument": "--deprecated_string_conv" },
"${-/}Zc:noexceptTypes": { "parserArgument": "--exc_spec_in_func_type" },
"${-/}Zc:noexceptTypes-": { "parserArgument": "--no_exc_spec_in_func_type" },
"${-/}Zc:alignedNew": { "parserArgument": "--overaligned_allocation" },
"${-/}Zc:alignedNew-": { "parserArgument": "--no_overaligned_allocation" },
"${-/}Zc:referenceBinding": { "parserArgument": "--no_nonconst_ref_anachronism" },
"${-/}Zc:referenceBinding-": { "parserArgument": "--nonconst_ref_anachronism" },
"${-/}Zc:ternary": { "parserArgument": "--ms_strict_ternary" },
"${-/}Zc:ternary-": { "parserArgument": "--no_ms_strict_ternary", "remove:parserArgument": "--ms_strict_ternary" },
"${-/}Zc:twoPhase": { "parserArgument": "--no_dep_name" },
},
"commandLineArguments:CPP": {
"${-/}await": { "parserArgument": "--ms_await" },
"${-/}experimental:module" : { "parserArgument": "--modules" },
"${-/}experimental:preprocessor" : { "parserArgument": "--ms_std_preprocessor" },
"${-/}Zc:preprocessor" : { "parserArgument": "--ms_std_preprocessor" },
"${-/}experimental:preprocessor-" : { "parserArgument": "--no_ms_std_preprocessor" },
"${-/}Zc:preprocessor-" : { "parserArgument": "--no_ms_std_preprocessor" },
"${-/}std:(?<std>.+)" : { "standard":"${std}" },
"${-/}kernel": { "macro": { "_KERNEL_MODE": 1 }, "parserArgument": ["--no_exceptions","no_rtti"] },
},
"commandLineArguments: # CPP-CLI ??? todo - come back to this (cl_compiler_info.cpp@485 to )": {
// *** This section would not apply to ARM complier? <#if !VSCODE_ARM> -- see cl_compiler_info.cpp@485 --
// *** which I think may be a mistake, since being applied to ARM64 as a host isn"t right, we"re talking about a target?
// cpp/cli
"${-/}clr:initialAppDomain": { "macro": { "_CPPUNWIND":1 }},
"${-/}clr:netcore": { "parserArgument": ["--cppcli_netcore","--no_using_framework_directory", "--cppcli"],"macro":{"D_CLR_NETCORE":1, "_MANAGED":1, "_M_CEE_":"001"} },
"${-/}clr:newSyntax": { "parserArgument": "--cppcli", "macro": {"_CPPUNWIND":1, "_M_CEE_PURE":"001", "_MANAGED":1, "_M_CEE_":"001" }},
"${-/}clr:noAssembly": { "parserArgument": "--cppcli", "macro": { "_MANAGED":1, "_M_CEE_":"001" }},
"${-/}clr:nostdlib": { "parserArgument": ["--no_using_framework_directory"] },
"${-/}clr:nostdimport": { "parserArgument": ["--no_stdlib"] },
"${-/}clr": { "parserArgument": "--cppcli", "macro": { "_MANAGED":1, "_M_CEE_":"001", "_CPPUNWIND":1, }},
"${-/}clr:pure": { "parserArgument": "--cppcli", "macro": { "_MANAGED":1, "_M_CEE_":"001", "_CPPUNWIND":1, }},
"${-/}clr:safe": { "parserArgument": "--cppcli", "macro": { "_MANAGED":1, "_M_CEE_":"001", "_CPPUNWIND":1, "_M_CEE_PURE":"001", "_M_CEE_SAFE":"001" }},
"${-/}zw": { "parserArgument": "--cppcx" },
"${-/}zw:nostdlib": { "parserArgument": "--cppcx"},
},
"commandLineArguments:C" : {
"${-/}kernel": { "macro": { "_KERNEL_MODE": 1 } },
"${-/}std:(?<std>.+)" : { "cStandard":"${std}" },
},
"query" : {
"-E /Zc:preprocessor ${queryArgument} /PD ${tmp.c}": {
"^#define (?<key>\\S*) ?(?<value>.*)?$": { "macro":{ "${key}": "${value}" } },
}
},
"expressions: PRIORITY=85" : {
"${bits} === 64" : {
"parserArgument": [
"-D_MSC_EXTENSIONS",
"--microsoft",
"--microsoft_bugs",
"--microsoft_version", "1935",
"--pack_alignment", "8",
"-D_CPPUNWIND=1",
"-D_MSC_VER=1935",
"-D_MSC_FULL_VER=193532215",
"-D_MSC_BUILD=0",
"-D_M_X64=100",
"-D_M_AMD64=100"
]
}
},
"task:post-process": [
// see cl_compiler_info.cpp@617 - process zw directories
"zwCommandLineSwitch",
"experimentalModuleNegative",
],
}
}
@@ -1,18 +0,0 @@
{
"name": "GNU C++ [ARM]",
"import": "common/gcc.json",
"discover": {
"binary": "arm-none-eabi-g++",
"locations": [ "c:/nxp/", "c:/st/" ],
"match": {
"^(?<ver>\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$" : { "version": "${ver}" }
}
},
"intellisense": {
"compiler": "gcc",
"architecture": "arm",
"bits": 32,
"platform": "none"
}
}
@@ -1,17 +0,0 @@
{
"name": "GNU C [ARM]",
"import": "common/gcc.json",
"discover": {
"binary": "arm-none-eabi-gcc",
"locations": [ "c:/nxp/", "c:/st/" ],
"match": {
"^(?<ver>\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$" : { "version": "${ver}" }
}
},
"intellisense": {
"compiler": "gcc",
"architecture": "arm",
"bits": 32,
"platform": "none"
}
}
@@ -1,23 +0,0 @@
{
"name": "GNU C++ [AVR/Arduino]",
"import": [
"common/gcc.json",
"common/defaults.json"
],
"discover": {
"binary": "avr-g++",
"locations": [ "${env:home}/appdata/local" ],
"match": {
"^(?<ver>\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$" : { "version": "${ver}" }
}
},
"intellisense": {
"compiler": "gcc",
"architecture": "avr",
"platform": "none"
},
"conditions": {
"${host:os} === 'win32'": { "discover.locations": "${env:home}/appdata/local", }
}
}
@@ -1,18 +0,0 @@
{
"name": "Clang C++",
"import": [
"common/defaults.json",
"common/clang.json",
],
"discover": {
"binary": "clang++",
"match": {
"^clang (?<ver>\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$": { "version": "${ver}" }
}
},
"intellisense": {
"compiler": "clang",
"architecture": "x64",
"bits": 64,
}
}
@@ -1,18 +0,0 @@
{
"name": "GNU C++ [x64]",
"import": [
"common/gcc.json",
"common/defaults.json"
],
"discover": {
"binary": "g++",
"match": {
"^(?<ver>\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$" : { "version": "${ver}" }
}
},
"intellisense": {
"compiler": "gcc",
"architecture": "x64",
"bits": 64,
}
}
@@ -1,18 +0,0 @@
{
"name": "GNU C [x64]",
"import": [
"common/gcc.json",
"common/defaults.json"
],
"discover": {
"binary": "gcc",
"match": {
"^(?<ver>\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$" : { "version": "${ver}" }
}
},
"intellisense": {
"compiler": "gcc",
"architecture": "x64",
"bits": 64,
}
}
@@ -1,33 +0,0 @@
{
"name": "IAR C/C++ [ARM]",
"import": [
"common/defaults.json"
],
"discover": {
"binary": "iccarm",
"match": {
"^(?<ver>\\d{1,2}[.]\\d+[.]\\d+[.]\\d+)(?:/\\S*)?$": { "version": "${ver}" }
},
"expression: FOLDER" : {
"${binary}/../../" : { "IARPath" : "${binary}/../../" }
}
},
"intellisense": {
"compiler": "iar", // this just gets us the 'base' that EDG will think it is
"architecture": "arm", // we can have a common file that remaps this to something else if needed
"bits": 32, // another hint for later.
"platform": "none", // bare-metal target. (can be remaped to windows/macos/linux if needed to make EDG happy)
"path.systemInclude": [ // we can't get the include path from the compiler, so we had to explicity state it
"${IARPath}inc/cpp" // include folder for c++ headers in IAR
]
},
"analysis": {
"queries": {
"${tmp.c} --c++ --predef_macros ${tmp.stdout} --output ${tmp.o}": {
"^#define (?<key>\\S*) ?(?<value>.*)?$" : { "macro": { "${key}": "${value}" } },
"^#define __STDC_VERSION__\\s*(?<ver>.*)": { "cStandard": "${ver}" },
"^#define __cplusplus\\s*(?<ver>.*)": { "cppStandard": "${ver}" }
}
}
}
}
@@ -1,79 +0,0 @@
{
"name": "Microsoft Visual C++",
"import": [
"common/msvc.json",
"common/defaults.json"
],
"discover": {
"binary": "cl",
// Add the default paths.include for the WindowsSDK
"expression: FOLDER, OPTIONAL": {
"${KitsRoot10}/Include/${SDKVer}/um" : { "path.builtInInclude": "${KitsRoot10}/Include/${SDKVer}/um" },
"${KitsRoot10}/Include/${SDKVer}/ucrt" : { "path.builtInInclude": "${KitsRoot10}/Include/${SDKVer}/ucrt" },
"${KitsRoot10}/Include/${SDKVer}/shared" : { "path.builtInInclude": "${KitsRoot10}/Include/${SDKVer}/shared" },
"${KitsRoot10}/Include/${SDKVer}/winrt" : { "path.builtInInclude": "${KitsRoot10}/Include/${SDKVer}/winrt" },
"${KitsRoot10}/Include/${SDKVer}/cppwinrt" : { "path.builtInInclude": "${KitsRoot10}/Include/${SDKVer}/cppwinrt" }
},
// get the compiler version from the binary itself
"match: # detect the compiler version from the binary": {
"^(?<ver>\\d+[.]\\d+[.]\\d+)[.].*$" : { "version": "${ver}" }
},
// get the host architecture based on the string we can find inside the binary
"match: ONEOF # detect the host architecture from the binary" : {
"\\\\amd64\\\\": { "hostArchitecture": "x64" ,"bits": 64, "architecture": "x64"},
"\\\\i386\\\\" : { "hostArchitecture": "x86" , "bits": 32, "architecture": "x86" },
"\\\\arm64\\\\": { "hostArchitecture": "arm64" , "bits": 64, "architecture": "arm64" },
"\\\\x86_amd64\\\\": { "hostArchitecture": "x86" ,"bits": 64, "architecture": "x64"},
"\\\\x86_arm64\\\\": { "hostArchitecture": "x86", "bits": 64, "architecture": "arm64" },
"\\\\x86_arm\\\\": { "hostArchitecture": "x86", "bits": 32, "architecture": "arm" },
"\\\\amd64_x86\\\\": { "hostArchitecture": "x64" , "bits": 32, "architecture": "x86" },
"\\\\amd64_arm\\\\": { "hostArchitecture": "x64" , "bits": 32, "architecture": "arm"},
"\\\\amd64_arm64\\\\": { "hostArchitecture": "x64", "bits": 64, "architecture": "arm64" },
"\\\\arm64_amd64\\\\": { "hostArchitecture": "arm64" ,"bits": 64, "architecture": "x64"},
"\\\\arm64_x86\\\\": { "hostArchitecture": "arm64", "bits": 32, "architecture": "x86" },
"\\\\arm64_arm\\\\": { "hostArchitecture": "arm64" , "bits": 32, "architecture": "arm"},
},
// add the default paths.include for the Visual C++ compiler
"expression: ONEOF,FOLDER # find the base include folder": {
"${binary}/../include": { "path.builtInInclude": "${binary}/../include"},
"${binary}/../../include": { "path.builtInInclude": "${binary}/../../include"},
"${binary}/../../../include": { "path.builtInInclude": "${binary}/../../../include"},
"${binary}/../../../../include": { "path.builtInInclude": "${binary}/../../../../include" }
},
// add the atlmfc paths.include for the Visual C++ compiler
"expression: ONEOF,OPTIONAL,FOLDER # find the optional atlmfc include folder": {
"${binary}/../atlmfc/include": { "path.builtInInclude": "${binary}/../atlmfc/include" },
"${binary}/../../atlmfc/include": { "path.builtInInclude": "${binary}/../../atlmfc/include" },
"${binary}/../../../atlmfc/include": { "path.builtInInclude": "${binary}/../../../atlmfc/include" },
"${binary}/../../../../atlmfc/include": { "path.builtInInclude": "${binary}/../../../../atlmfc/include" }
}
},
"intellisense": {
// get the location of the SDK root from the registry
"KitsRoot10": "${HKLM:SOFTWARE/WOW6432Node/Microsoft/Windows Kits/Installed Roots;KitsRoot10}",
"KitsRoot81": "${HKLM:SOFTWARE/WOW6432Node/Microsoft/Windows Kits/Installed Roots;KitsRoot81}",
// set the default SDK version (todo: this should be able to be pulled in from the user's config)
"SDKVer": "10.0.22000.0",
"parserArgument": [
"--no_warnings",
"--rtti",
"--wchar_t_keyword",
"--edge",
"--exceptions",
"--error_limit", "25000",
"-D_EDG_COMPILER",
"-D_USE_DECLSPECS_FOR_SAL=1"
]
}
}
@@ -1,22 +0,0 @@
{
"name": "GNU C++ [xtensa-esp32]",
"import": [
"common/gcc.json",
"common/defaults.json"
],
"discover": {
"binary": "xtensa-esp32-elf-g++",
"locations": [ "${env:IDF_TOOLS_PATH}" ],
"match": {
"^(?<ver>\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$" : { "version": "${ver}" }
}
},
"intellisense": {
"compiler": "gcc",
"architecture": "xtensa",
"bits": 32,
"platform": "none",
"parserArgument": [],
}
}
@@ -1,22 +0,0 @@
{
"name": "GNU C [xtensa-esp32]",
"import": [
"common/gcc.json",
"common/defaults.json"
],
"discover": {
"binary": "xtensa-esp32-elf-gcc",
"locations": [ "${env:IDF_TOOLS_PATH}" ],
"match": {
"^(?<ver>\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$" : { "version": "${ver}" }
}
},
"intellisense": {
"compiler": "gcc",
"architecture": "xtensa",
"bits": 32,
"platform": "none",
"parserArgument": ["--c", "--c${cStandard}" ]
}
}
+1 -1
View File
@@ -16,7 +16,7 @@
"Type": "git",
"Git": {
"RepositoryUrl": "https://github.com/lldb-tools/lldb-mi",
"CommitHash": "2388bd74133bc21eac59b2e2bf97f2a30770a315"
"CommitHash": "4fe9c663edce2447e114c71851694d8c529b982d"
}
}
}
-83
View File
@@ -1,83 +0,0 @@
# Performance Notes
I moved the entire discover/identify process to a nodejs `worker_thread` (a background thread).
This is useful when the foreground app has cpu-intensive work or a lot of things in the event loop.
Technically, this wouldn't be super important to do if everything was *simply* async IO bound, but the startup
of the extension is kinda CPU intensive (as ), and moving the logic to a worker thread completely frees up the
foreground to do what it needs to.
# Results
| Initial State | selecting compiler | discovery in init? | time to identify | time to get intellisense |
| ------------- | ------------------ | ------------------ | ---------------- | ------------------------ |
| no cache | explicit path | no | 600-700 ms | 550-745 ms |
| no cache | explicit path | yes | 1000-1350 ms | 1625-1675 ms |
| no cache | by name/wildcard | no | 6900-7100 ms | 700-730 ms |
| no cache | by name/wildcard | yes | 5900-6100 ms | 700-720 ms |
| cached | explicit path | no | 6-7 ms | 6-15 ms |
| cached | explicit path | yes | 6-8 ms | 6-19 ms |
| cached | by name/wildcard | no | 6-7 ms | 6-15 ms |
| cached | by name/wildcard | yes | 6-7 ms | 6-15 ms |
1. "cached" means that the extension has already performed some discovery
or identification at some point, and it loaded the previously used data from
the cache.
2. "no cache" means that the extension has not performed any discovery or
identification at all, the extension has to identify the compiler from scratch.
3. "explicit path" means that in the `c_cpp_properties.json` file, the full path
is specified in the `compiler` field.
The compiler can be identified soley by path, and doesn't have to wait
for full discovery to complete. If the data is cached, it's a fast lookup.
4. "by name/wildcard" means that in the `c_cpp_properties.json` file, the compiler
is specified by name (with or without a wildcard).
The compiler can be identified quickly if there are cached entries, but if there isn't
we have to wait for full discovery to be done.
5. "discover in init = yes" means that the discovery process is initiated in the background
during initialization. If there are any cached entries, then it waits 5 seconds before kicking
it off in the background. If there are zero cached entries, then it kicks it off immediately.
6. "discover in init = no" means that the discovery process is not initiated in the background
during initialization. It is only kicked off when the extension needs to identify a compiler
and it misses the cache hit.
Notes :
- if the `compiler` field isn't in the intellisense, we're not activating any of this.
- when we enable the extension to prompt the user which compiler to use, we have to
have discovery done (~6-12 seconds) before we can prompt the user.
This may be a good reason to have discovery done in the background during initialization
every time.
- Cached data is stored in a single JSON file in the global storage path for the extension
- Cached data is loading on startup.
- Cached data is saved every time the extension does something that alters the data in the cache.
- discovery finds a compiler
- a (new/different) intellisense config is generated for a given command line
- a (new/different) query of a compiler is done
Questions:
- The cached data is globally stored for all instances of vscode.
- that means that currently, 'last-one-wins' is how it's being stored.
- I'm leaning towards making it load/merge/store whenever any instance is about to modify the cache file.
- this would make the cache more and more accurate over time, and effort by a single instance would benefit
all instances
- I *can* enable a staleness timeout on things in the cache (even partially, so like queries/analysis). Currently, this file would grow and grow over time
and get somewhat large. A missed cache hit could incur a hit of 500 to 2000 ms depending on how much work it has
to do.
- if we stale out the whole toolset entry, then we'd have to run identify on that one again the next time (more expensive, and could trigger a full discovery)
- if we stale out just the queries/analysis, then just those would be regenerated/rerun when the intellisense config (not as expensive, and doesn't require us to do discovery again)
+71 -9
View File
@@ -2,7 +2,7 @@
"name": "cpptools",
"displayName": "C/C++",
"description": "C/C++ IntelliSense, debugging, and code browsing.",
"version": "1.17.5-main",
"version": "1.18.0-main",
"publisher": "ms-vscode",
"icon": "LanguageCCPP_color_128x.png",
"readme": "README.md",
@@ -1789,6 +1789,7 @@
"bugprone-dangling-handle",
"bugprone-dynamic-static-initializers",
"bugprone-easily-swappable-parameters",
"bugprone-empty-catch",
"bugprone-exception-escape",
"bugprone-fold-init-type",
"bugprone-forward-declaration-namespace",
@@ -1806,9 +1807,12 @@
"bugprone-misplaced-pointer-arithmetic-in-alloc",
"bugprone-misplaced-widening-cast",
"bugprone-move-forwarding-reference",
"bugprone-multiple-*",
"bugprone-multiple-new-in-one-expression",
"bugprone-multiple-statement-macro",
"bugprone-narrowing-conversions",
"bugprone-no-escape",
"bugprone-non-zero-enum-to-bool-conversion",
"bugprone-not-null-terminated-result",
"bugprone-parent-virtual-call",
"bugprone-posix-return",
@@ -1837,6 +1841,7 @@
"bugprone-suspicious-semicolon",
"bugprone-suspicious-string-compare",
"bugprone-swapped-arguments",
"bugprone-switch-missing-default-case",
"bugprone-terminating-continue",
"bugprone-throw-keyword-missing",
"bugprone-too-small-loop-variable",
@@ -1846,6 +1851,7 @@
"bugprone-unhandled-*",
"bugprone-unhandled-exception-at-new",
"bugprone-unhandled-self-assignment",
"bugprone-unique-ptr-array-mismatch",
"bugprone-unused-raii",
"bugprone-unused-return-value",
"bugprone-use-after-move",
@@ -2005,6 +2011,7 @@
"cppcoreguidelines-*",
"cppcoreguidelines-avoid-*",
"cppcoreguidelines-avoid-c-arrays",
"cppcoreguidelines-avoid-capturing-lambda-coroutines",
"cppcoreguidelines-avoid-const-or-ref-data-members",
"cppcoreguidelines-avoid-do-while",
"cppcoreguidelines-avoid-goto",
@@ -2017,6 +2024,8 @@
"cppcoreguidelines-interfaces-global-init",
"cppcoreguidelines-macro-to-enum",
"cppcoreguidelines-macro-usage",
"cppcoreguidelines-misleading-capture-default-by-value",
"cppcoreguidelines-missing-std-forward",
"cppcoreguidelines-narrowing-conversions",
"cppcoreguidelines-no-malloc",
"cppcoreguidelines-non-private-member-variables-in-classes",
@@ -2034,6 +2043,7 @@
"cppcoreguidelines-pro-type-static-cast-downcast",
"cppcoreguidelines-pro-type-union-access",
"cppcoreguidelines-pro-type-vararg",
"cppcoreguidelines-rvalue-reference-param-not-moved",
"cppcoreguidelines-slicing",
"cppcoreguidelines-special-member-functions",
"cppcoreguidelines-virtual-class-destructor",
@@ -2124,11 +2134,14 @@
"llvmlibc-*",
"llvmlibc-callee-namespace",
"llvmlibc-implementation-in-namespace",
"llvmlibc-inline-function-decl",
"llvmlibc-restrict-system-libc-headers",
"misc-*",
"misc-confusable-identifiers",
"misc-const-correctness",
"misc-definitions-in-headers",
"misc-header-include-cycle",
"misc-include-cleaner",
"misc-misleading-*",
"misc-misleading-bidirectional",
"misc-misleading-identifier",
@@ -2170,6 +2183,7 @@
"modernize-replace-random-shuffle",
"modernize-return-braced-init-list",
"modernize-shrink-to-fit",
"modernize-type-traits",
"modernize-unary-static-assert",
"modernize-use-*",
"modernize-use-auto",
@@ -2184,6 +2198,7 @@
"modernize-use-noexcept",
"modernize-use-nullptr",
"modernize-use-override",
"modernize-use-std-print",
"modernize-use-trailing-return-type",
"modernize-use-transparent-functors",
"modernize-use-uncaught-exceptions",
@@ -2205,6 +2220,7 @@
"openmp-exception-escape",
"openmp-use-default-none",
"performance-*",
"performance-avoid-endl",
"performance-faster-string-find",
"performance-for-range-copy",
"performance-implicit-conversion-in-loop",
@@ -2218,7 +2234,10 @@
"performance-no-*",
"performance-no-automatic-move",
"performance-no-int-to-ptr",
"performance-noexcept-*",
"performance-noexcept-destructor",
"performance-noexcept-move-constructor",
"performance-noexcept-swap",
"performance-trivially-destructible",
"performance-type-promotion-in-math-fn",
"performance-unnecessary-*",
@@ -2229,7 +2248,9 @@
"portability-simd-intrinsics",
"portability-std-allocator-const",
"readability-*",
"readability-avoid-*",
"readability-avoid-const-params-in-decls",
"readability-avoid-unconditional-preprocessor-if",
"readability-braces-around-statements",
"readability-const-return-type",
"readability-container-*",
@@ -2255,6 +2276,7 @@
"readability-misplaced-array-index",
"readability-named-parameter",
"readability-non-const-parameter",
"readability-operators-representation",
"readability-qualified-auto",
"readability-redundant-*",
"readability-redundant-access-specifiers",
@@ -2353,6 +2375,7 @@
"bugprone-dangling-handle",
"bugprone-dynamic-static-initializers",
"bugprone-easily-swappable-parameters",
"bugprone-empty-catch",
"bugprone-exception-escape",
"bugprone-fold-init-type",
"bugprone-forward-declaration-namespace",
@@ -2370,9 +2393,12 @@
"bugprone-misplaced-pointer-arithmetic-in-alloc",
"bugprone-misplaced-widening-cast",
"bugprone-move-forwarding-reference",
"bugprone-multiple-*",
"bugprone-multiple-new-in-one-expression",
"bugprone-multiple-statement-macro",
"bugprone-narrowing-conversions",
"bugprone-no-escape",
"bugprone-non-zero-enum-to-bool-conversion",
"bugprone-not-null-terminated-result",
"bugprone-parent-virtual-call",
"bugprone-posix-return",
@@ -2401,6 +2427,7 @@
"bugprone-suspicious-semicolon",
"bugprone-suspicious-string-compare",
"bugprone-swapped-arguments",
"bugprone-switch-missing-default-case",
"bugprone-terminating-continue",
"bugprone-throw-keyword-missing",
"bugprone-too-small-loop-variable",
@@ -2410,6 +2437,7 @@
"bugprone-unhandled-*",
"bugprone-unhandled-exception-at-new",
"bugprone-unhandled-self-assignment",
"bugprone-unique-ptr-array-mismatch",
"bugprone-unused-raii",
"bugprone-unused-return-value",
"bugprone-use-after-move",
@@ -2569,6 +2597,7 @@
"cppcoreguidelines-*",
"cppcoreguidelines-avoid-*",
"cppcoreguidelines-avoid-c-arrays",
"cppcoreguidelines-avoid-capturing-lambda-coroutines",
"cppcoreguidelines-avoid-const-or-ref-data-members",
"cppcoreguidelines-avoid-do-while",
"cppcoreguidelines-avoid-goto",
@@ -2581,6 +2610,8 @@
"cppcoreguidelines-interfaces-global-init",
"cppcoreguidelines-macro-to-enum",
"cppcoreguidelines-macro-usage",
"cppcoreguidelines-misleading-capture-default-by-value",
"cppcoreguidelines-missing-std-forward",
"cppcoreguidelines-narrowing-conversions",
"cppcoreguidelines-no-malloc",
"cppcoreguidelines-non-private-member-variables-in-classes",
@@ -2598,6 +2629,7 @@
"cppcoreguidelines-pro-type-static-cast-downcast",
"cppcoreguidelines-pro-type-union-access",
"cppcoreguidelines-pro-type-vararg",
"cppcoreguidelines-rvalue-reference-param-not-moved",
"cppcoreguidelines-slicing",
"cppcoreguidelines-special-member-functions",
"cppcoreguidelines-virtual-class-destructor",
@@ -2688,11 +2720,14 @@
"llvmlibc-*",
"llvmlibc-callee-namespace",
"llvmlibc-implementation-in-namespace",
"llvmlibc-inline-function-decl",
"llvmlibc-restrict-system-libc-headers",
"misc-*",
"misc-confusable-identifiers",
"misc-const-correctness",
"misc-definitions-in-headers",
"misc-header-include-cycle",
"misc-include-cleaner",
"misc-misleading-*",
"misc-misleading-bidirectional",
"misc-misleading-identifier",
@@ -2734,6 +2769,7 @@
"modernize-replace-random-shuffle",
"modernize-return-braced-init-list",
"modernize-shrink-to-fit",
"modernize-type-traits",
"modernize-unary-static-assert",
"modernize-use-*",
"modernize-use-auto",
@@ -2748,6 +2784,7 @@
"modernize-use-noexcept",
"modernize-use-nullptr",
"modernize-use-override",
"modernize-use-std-print",
"modernize-use-trailing-return-type",
"modernize-use-transparent-functors",
"modernize-use-uncaught-exceptions",
@@ -2769,6 +2806,7 @@
"openmp-exception-escape",
"openmp-use-default-none",
"performance-*",
"performance-avoid-endl",
"performance-faster-string-find",
"performance-for-range-copy",
"performance-implicit-conversion-in-loop",
@@ -2782,7 +2820,10 @@
"performance-no-*",
"performance-no-automatic-move",
"performance-no-int-to-ptr",
"performance-noexcept-*",
"performance-noexcept-destructor",
"performance-noexcept-move-constructor",
"performance-noexcept-swap",
"performance-trivially-destructible",
"performance-type-promotion-in-math-fn",
"performance-unnecessary-*",
@@ -2793,7 +2834,9 @@
"portability-simd-intrinsics",
"portability-std-allocator-const",
"readability-*",
"readability-avoid-*",
"readability-avoid-const-params-in-decls",
"readability-avoid-unconditional-preprocessor-if",
"readability-braces-around-statements",
"readability-const-return-type",
"readability-container-*",
@@ -2819,6 +2862,7 @@
"readability-misplaced-array-index",
"readability-named-parameter",
"readability-non-const-parameter",
"readability-operators-representation",
"readability-qualified-auto",
"readability-redundant-*",
"readability-redundant-access-specifiers",
@@ -3307,6 +3351,15 @@
"apply": "first"
},
"when": "editorLangId =~ /^(c|(cuda-)?cpp)$/ && editorTextFocus && !(config.C_Cpp.intelliSenseEngine =~ /^[dD]isabled$/)"
},
{
"command": "editor.action.codeAction",
"key": "ctrl+shift+r ctrl+e",
"args": {
"kind": "refactor.extract.function",
"apply": "first"
},
"when": "editorLangId =~ /^(c|(cuda-)?cpp)$/ && editorTextFocus && !(config.C_Cpp.intelliSenseEngine =~ /^[dD]isabled$/)"
}
],
"debuggers": [
@@ -6206,13 +6259,25 @@
"languages": [
"c",
"cpp",
"cude-cpp"
"cuda-cpp"
],
"actions": {
"kind": "refactor.inline.macro",
"title": "%c_cpp.codeActions.refactor.inline.macro.title%",
"description": "%c_cpp.codeActions.refactor.inline.macro.description%"
}
},
{
"languages": [
"c",
"cpp",
"cuda-cpp"
],
"actions": {
"kind": "refactor.extract.function",
"title": "%c_cpp.codeActions.refactor.extract.function.title%",
"description": "%c_cpp.codeActions.refactor.extract.function.description%"
}
}
]
},
@@ -6253,8 +6318,8 @@
"@types/tmp": "^0.1.0",
"@types/which": "^1.3.2",
"@types/yauzl": "^2.9.1",
"@typescript-eslint/eslint-plugin": "^6.5.0",
"@typescript-eslint/parser": "^6.5.0",
"@typescript-eslint/eslint-plugin": "^6.1.0",
"@typescript-eslint/parser": "^6.1.0",
"eslint-plugin-header": "^3.1.1",
"@vscode/test-electron": "^2.3.3",
"@vscode/dts": "^0.4.0",
@@ -6263,7 +6328,6 @@
"eslint": "^8.45.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsdoc": "^46.4.4",
"eslint-plugin-etc": "^2.0.3",
"event-stream": "^4.0.1",
"fs-extra": "^8.1.0",
"gulp": "^4.0.2",
@@ -6278,7 +6342,7 @@
"parse5-traverse": "^1.0.3",
"ts-loader": "^8.1.0",
"ts-node": "10.9.1",
"typescript": "^5.2.2",
"typescript": "^5.1.3",
"@vscode/debugadapter": "^1.61.0",
"@vscode/debugprotocol": "^1.61.0",
"vscode-nls-dev": "^4.0.0-next.1",
@@ -6306,9 +6370,7 @@
"vscode-languageclient": "^8.1.0-next.4",
"vscode-nls": "^5.0.0",
"vscode-tas-client": "^0.1.27",
"which": "^2.0.2",
"https-proxy-agent": "^5.0.0",
"proxy-from-env": "^1.1.0"
"which": "^2.0.2"
},
"resolutions": {
"chokidar": "^3.5.3",
+2
View File
@@ -997,6 +997,8 @@
"c_cpp.walkthrough.customize.debugging.altText": "Image that shows Add Debug Configuration in the drop-down",
"c_cpp.codeActions.refactor.inline.macro.title": "Inline macro",
"c_cpp.codeActions.refactor.inline.macro.description": "Replace the macro invocation with the expanded code.",
"c_cpp.codeActions.refactor.extract.function.title": "Extract to function",
"c_cpp.codeActions.refactor.extract.function.description": "Extract the selected code to a free or member function.",
"c_cpp.configuration.refactoring.includeHeader.markdownDescription": "Controls whether to include the header file of a refactored function/symbol to its corresponding source file when doing a refactoring action, such as create declaration/definition.",
"c_cpp.configuration.refactoring.includeHeader.always.description": "Always include the header file if it is not included explicitly in its source file.",
"c_cpp.configuration.refactoring.includeHeader.ifNeeded.description": "Only include the header file if it is not included explicitly in its source file or through implicit inclusion.",
@@ -1,268 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { CommentArray, CommentObject, parse } from 'comment-json';
import { readFile } from 'fs/promises';
import { dirname } from 'path';
import { CancellationToken, Uri } from 'vscode';
import { SourceFileConfiguration, SourceFileConfigurationItem, Version, WorkspaceBrowseConfiguration } from 'vscode-cpptools';
import { CancellationTokenSource } from 'vscode-languageclient';
import { identifyToolset } from '../../ToolsetDetection/detection';
import { IntelliSenseConfiguration } from '../../ToolsetDetection/interfaces';
import { PriorityQueue } from '../../Utility/Async/priorityQueue';
import { returns } from '../../Utility/Async/returns';
import { filepath } from '../../Utility/Filesystem/filepath';
import { extractArgs } from '../../Utility/Process/commandLine';
import { is } from '../../Utility/System/guards';
import { getOrAdd } from '../../Utility/System/map';
import { elapsed } from '../../Utility/System/performance';
import { addNormalizedPath } from '../../Utility/System/set';
import { log } from '../../logger';
import { DefaultClient } from '../client';
import { Configuration } from '../configurations';
import { ConfigurationAdapter } from './configurationAdapter';
import { ExtendedBrowseInformation, IntellisenseConfigurationAdapter } from './interfaces';
type CompileCommand = CommentObject & {
directory: string;
file: string;
command?: string;
arguments?: string[];
output?: string;
};
// create a static cancelled token
const cts = new CancellationTokenSource();
cts.cancel();
const cancelled = cts.token;
export class CompileCommandsConfigurationAdapter extends ConfigurationAdapter implements IntellisenseConfigurationAdapter {
private intellisenseConfigurations = new PriorityQueue<IntelliSenseConfiguration>();
private sourceFiles: Uri[] = [];
isReady = true;
isValid = true;
get version() { return Version.latest; }
get name() { return "CompileCommandsProvider"; }
get extensionId() { return "built-in.compile-commands"; }
private parsing: Promise<void> | undefined;
private constructor(client: DefaultClient, private path: string, configuration: Configuration, private token?: CancellationToken) {
super(client, configuration);
log('CREATING CompileCommandsConfiguration');
}
async getSourceFiles(): Promise<Uri[]> {
await this.parsing;
return [...this.sourceFiles];
}
async getHeaderFiles(): Promise<Uri[]> {
return [];
}
// we store a static map of instances that are tied to the filename+timestamp
static instances = new Map<string, CompileCommandsConfigurationAdapter>();
static async getProvider(client: DefaultClient, path: string, configuration: Configuration, token: CancellationToken): Promise<IntellisenseConfigurationAdapter | undefined> {
const [jsonfile, stats] = await filepath.stats(path);
if (stats) {
const key = jsonfile + stats.mtime;
const result = getOrAdd(this.instances, key, () => new CompileCommandsConfigurationAdapter(client, jsonfile, configuration, token));
// delete old instances that have the same path
for (const [k, v] of this.instances) {
if (v.path === jsonfile && k !== key) {
v.token = cancelled;
this.instances.delete(k);
}
}
// start it updating...
void result.update();
// return the instance
return result;
}
return undefined;
}
update() {
return this.parsing ?? (this.parsing = this.updateAsync());
}
private async updateAsync() {
// reload the file
const content = await readFile(this.path, 'utf8');
const data = parse(content);
if (!is.array(data)) {
this.isValid = false;
return;
}
const userIntellisenseConfiguration = this.client.configuration?.CurrentConfiguration?.intellisense;
console.log(`${elapsed()} =>> Begin parsing compile_commands.json...`);
this.intellisenseConfigurations.once('item', (key, value) => {
// if we don't have a toolset selected in the configuration, let's
// steal the one from the first file we find.
if (!this.compiler) {
this.compiler = value.compilerPath;
if (!this.compilerArgs) {
this.compilerArgs = value.compilerArgs;
}
}
this.initialized.resolve();
});
this.intellisenseConfigurations.on('item', (key, value) => {
// propogate the event
this.emit('configuration', key, value);
});
this.intellisenseConfigurations.on('empty', () => {
this.emit('done');
this.intellisenseConfigurations.removeAllListeners();
});
for (const cmd of data as CommentArray<CompileCommand>) {
if (this.token?.isCancellationRequested) {
break;
}
// we need to make sure that all files are passed thru Uri because the Uri class can modify things like the
// drive letter on Windows (it lowercases it) -- so we normalize the path first.
const uri = Uri.file(cmd.file);
cmd.file = uri.fsPath;
const args = is.array(cmd.arguments) ? cmd.arguments : is.string(cmd.command) ? extractArgs(cmd.command) : [];
if (!args) {
continue;
}
// grab the compiler from the arguments
const tool = args.shift();
if (!tool) {
continue;
}
// we have a tool and some args,
this.sourceFiles.push(uri);
// add the source directory to the browse path
addNormalizedPath(this.browseInfo.browsePaths, dirname(cmd.file));
// start it processing
void this.intellisenseConfigurations.enqueue(cmd.file, async () => {
const toolset = await identifyToolset(tool);
if (!toolset) {
throw new Error("Unable to identify toolset");
}
const isense = await toolset.getIntellisenseConfiguration(args, {baseDirectory: cmd.directory, sourceFile : cmd.file, userIntellisenseConfiguration });
if ((isense.parserArgument?.length || 0) < 10) {
console.log("ouch");
}
// update the browse paths with info from the intellisense
this.mergeBrowseInfo(isense);
return isense;
});
}
console.log(`${elapsed()} =>> Completed parsing compile_commands.json...`);
}
async canProvideConfiguration(uri: Uri, token?: CancellationToken | undefined): Promise<boolean> {
await this.parsing;
if (this.token?.isCancellationRequested || token?.isCancellationRequested) {
return false;
}
return this.intellisenseConfigurations.has(uri.fsPath);
}
async provideConfigurations(uris: Uri[], token?: CancellationToken | undefined): Promise<SourceFileConfigurationItem[]> {
await this.parsing;
if (this.token?.isCancellationRequested || token?.isCancellationRequested) {
return [];
}
// if they pass in a zero length array, assume they mean 'all that are ready'
if (uris.length === 0) {
uris = this.intellisenseConfigurations.completedKeys.map(each => Uri.file(each));
}
const result = [] as SourceFileConfigurationItem[];
for (const uri of uris) {
const intellisense = await this.intellisenseConfigurations.get(uri.fsPath).catch(returns.undefined);
if (intellisense) {
// trim stuff not needed.
//* delete intellisense.path;
delete intellisense.macro;
result.push({
uri,
configuration: {
includePath: [],
defines: [],
intellisense,
enableNewIntellisense: true
} as SourceFileConfiguration
});
}
}
return result;
}
async canProvideBrowseConfiguration(token?: CancellationToken | undefined): Promise<boolean> {
await this.parsing;
if (this.token?.isCancellationRequested || token?.isCancellationRequested) {
return false;
}
return true;
}
async provideBrowseConfiguration(token?: CancellationToken | undefined): Promise<WorkspaceBrowseConfiguration | null> {
await this.parsing;
if (this.token?.isCancellationRequested || token?.isCancellationRequested) {
return null;
}
return { browsePath: [...this.browseInfo.browsePaths] };
}
async canProvideBrowseConfigurationsPerFolder(token?: CancellationToken | undefined): Promise<boolean> {
// todo: support multi-root workspaces
return false;
}
async provideFolderBrowseConfiguration(uri: Uri, token?: CancellationToken | undefined): Promise<WorkspaceBrowseConfiguration | null> {
// todo: support multi-root workspaces
return null;
}
dispose() {
this.token = cancelled;
CompileCommandsConfigurationAdapter.instances.delete(this.path);
}
override async getExtendedBrowseInformation(token: CancellationToken): Promise<ExtendedBrowseInformation> {
await this.parsing;
return super.getExtendedBrowseInformation(token);
}
}
@@ -1,155 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import EventEmitter = require('events');
import { CancellationToken, Uri } from 'vscode';
import { SourceFileConfigurationItem, Version, WorkspaceBrowseConfiguration } from 'vscode-cpptools';
import { identifyToolset } from '../../ToolsetDetection/detection';
import { IntelliSenseConfiguration } from '../../ToolsetDetection/interfaces';
import { ManualPromise } from '../../Utility/Async/manualPromise';
import { is } from '../../Utility/System/guards';
import { addNormalizedPath } from '../../Utility/System/set';
import { structuredClone } from '../../Utility/System/structuredClone';
import { DefaultClient } from '../client';
import { Configuration } from '../configurations';
import { ExtendedBrowseInformation, IntellisenseConfigurationAdapter } from './interfaces';
export abstract class ConfigurationAdapter extends EventEmitter implements IntellisenseConfigurationAdapter {
browseInfo = {
browsePaths: new Set<string>(),
systemPaths: new Set<string>(),
userFrameworks: new Set<string>(),
systemFrameworks: new Set<string>()
};
constructor(protected client: DefaultClient, protected configuration: Configuration) {
super();
}
abstract isReady: boolean;
abstract isValid: boolean;
abstract version: Version;
abstract name: string;
abstract extensionId: string;
abstract canProvideConfiguration(uri: Uri, token?: CancellationToken | undefined): Thenable<boolean>;
abstract provideConfigurations(uris: Uri[], token?: CancellationToken | undefined): Thenable<SourceFileConfigurationItem[]>;
abstract canProvideBrowseConfiguration(token?: CancellationToken | undefined): Thenable<boolean>;
abstract provideBrowseConfiguration(token?: CancellationToken | undefined): Thenable<WorkspaceBrowseConfiguration | null>;
abstract canProvideBrowseConfigurationsPerFolder(token?: CancellationToken | undefined): Thenable<boolean>;
abstract provideFolderBrowseConfiguration(uri: Uri, token?: CancellationToken | undefined): Thenable<WorkspaceBrowseConfiguration | null> ;
abstract dispose(): void ;
abstract getSourceFiles(): Promise<Uri[]> ;
abstract getHeaderFiles(): Promise<Uri[]> ;
#compilerArgs: string[] | undefined;
get compilerArgs(): string[] | undefined {
return this.#compilerArgs ?? (this.#compilerArgs = this.configuration.compilerArgs);
}
set compilerArgs(compilerArgs: string[] | undefined) {
this.#compilerArgs = compilerArgs;
}
initialized = new ManualPromise<void>();
#compiler: string | undefined;
get compiler(): string | undefined {
return this.#compiler ?? (this.#compiler = this.configuration.compiler ?? this.configuration.compilerPath);
}
set compiler(compiler: string | undefined) {
this.#compiler = compiler;
}
async getBaseConfiguration() {
await this.initialized;
// first we have to send the base config
const baseConfiguration = {
enableNewIntellisense: true,
...structuredClone(this.configuration)
// we don't want the server to handle any configuration provider or compile commands.
} as Configuration;
delete baseConfiguration.configurationProvider;
delete baseConfiguration.compileCommands;
delete baseConfiguration.compileCommandsInCppPropertiesJson;
baseConfiguration.browse = baseConfiguration.browse ?? {};
addNormalizedPath(this.browseInfo.browsePaths, baseConfiguration.browse.path);
await this.probeForBrowseInfo(this.compiler, this.compilerArgs);
baseConfiguration.browse.path = [...this.browseInfo.browsePaths];
// if we don't have a toolset, let's see if we can pick one;
/*!
if (!this.compiler) {
log(`No compiler specified for new Intellisense?`);
// can we get provided one?
} else {
// the user has specifically set the compiler name/path
const toolset = await identifyToolset(this.compiler);
if (toolset) {
baseConfiguration.intellisense = await toolset.getIntellisenseConfiguration(this.compilerArgs || [], { userIntellisenseConfiguration: baseConfiguration.intellisense});
}
}
*/
return baseConfiguration;
}
mergeBrowseInfo(intellisense: IntelliSenseConfiguration) {
// add include paths to the browse paths
addNormalizedPath(this.browseInfo.browsePaths, intellisense.path?.quoteInclude);
addNormalizedPath(this.browseInfo.browsePaths, intellisense.path?.include);
addNormalizedPath(this.browseInfo.browsePaths, intellisense.path?.afterInclude);
addNormalizedPath(this.browseInfo.browsePaths, intellisense.path?.externalInclude);
addNormalizedPath(this.browseInfo.browsePaths, intellisense.path?.environmentInclude);
// add system include and built-in paths to the system paths
addNormalizedPath(this.browseInfo.systemPaths, intellisense.path?.systemInclude);
addNormalizedPath(this.browseInfo.systemPaths, intellisense.path?.builtInInclude);
// add frameworks to the user frameworks.
addNormalizedPath(this.browseInfo.userFrameworks, intellisense.path?.framework);
}
async probeForBrowseInfo(compilerPath: string | undefined, compilerArgs: string[] | undefined): Promise<void> {
if (compilerPath) {
using toolset = await identifyToolset(compilerPath);
if (toolset) {
// todo: support compilerFragments args too
let intellisense = await toolset.getIntellisenseConfiguration(compilerArgs ?? [], { userIntellisenseConfiguration: is.object(this.configuration.intellisense) ? this.configuration.intellisense : undefined});
intellisense = toolset.harvestFromConfiguration(this.configuration, intellisense);
this.mergeBrowseInfo(intellisense);
}
}
}
override on(event: 'configuration', listener: (key: string, value: IntelliSenseConfiguration) => void): this;
override on(event: 'done', listener: () => void): this;
override on(eventName: string | symbol, listener: (...args: any[]) => void): this {
return super.on(eventName, listener);
}
override once(event: 'configuration', listener: (key: string, value: IntelliSenseConfiguration) => void): this;
override once(event: 'done', listener: () => void): this;
override once(eventName: string | symbol, listener: (...args: any[]) => void): this {
return super.once(eventName, listener);
}
async getExtendedBrowseInformation(_token: CancellationToken): Promise<ExtendedBrowseInformation> {
return {
browsePath: [...this.browseInfo.browsePaths],
systemPath: [...this.browseInfo.systemPaths],
userFrameworks: [...this.browseInfo.userFrameworks],
systemFrameworks: [...this.browseInfo.systemFrameworks]
};
}
}
@@ -1,38 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { CancellationToken, Uri } from 'vscode';
import { IntelliSenseConfiguration } from '../../ToolsetDetection/interfaces';
import { Configuration } from '../configurations';
import { CustomConfigurationProvider1 } from '../customProviders';
import EventEmitter = require('events');
export interface ExtendedBrowseInformation {
browsePath: string[];
systemPath: string[];
userFrameworks: string[];
systemFrameworks: string[];
}
export interface IntellisenseConfigurationAdapter extends EventEmitter, CustomConfigurationProvider1 {
readonly initialized: Promise<void>;
// source files, aka 'translation units'
getSourceFiles(): Promise<Uri[]>;
// header files, aka 'include'd files'
getHeaderFiles(): Promise<Uri[]>;
getExtendedBrowseInformation(token: CancellationToken): Promise<ExtendedBrowseInformation>;
getBaseConfiguration(token: CancellationToken): Promise<Configuration>;
// events
on(event: 'configuration', listener: (key: string, value: IntelliSenseConfiguration) => void): this;
on(event: 'done', listener: () => void): this;
once(event: 'configuration', listener: (key: string, value: IntelliSenseConfiguration) => void): this;
once(event: 'done', listener: () => void): this;
}
@@ -1,111 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { dirname } from 'path';
import { CancellationToken, Uri } from 'vscode';
import { SourceFileConfigurationItem, WorkspaceBrowseConfiguration } from 'vscode-cpptools';
import { getOrAdd } from '../../Utility/System/map';
import { addNormalizedPath } from '../../Utility/System/set';
import { DefaultClient, InternalWorkspaceBrowseConfiguration } from '../client';
import { Configuration } from '../configurations';
import { CustomConfigurationProvider1 } from '../customProviders';
import { ConfigurationAdapter } from './configurationAdapter';
import { ExtendedBrowseInformation, IntellisenseConfigurationAdapter } from './interfaces';
export class ProviderConfigurationAdapter extends ConfigurationAdapter implements IntellisenseConfigurationAdapter {
private constructor(client: DefaultClient, private provider: CustomConfigurationProvider1, configuration: Configuration) {
super(client, configuration);
}
static instances = new Map<CustomConfigurationProvider1, ProviderConfigurationAdapter>();
static async getProvider(client: DefaultClient, provider: CustomConfigurationProvider1 | undefined, configuration: Configuration): Promise<IntellisenseConfigurationAdapter | undefined> {
if (!provider) {
return undefined;
}
return getOrAdd(this.instances, provider, () => new ProviderConfigurationAdapter(client, provider, configuration));
}
async getSourceFiles(): Promise<Uri[]> {
if ('getSourceFiles' in this.provider) {
return (this.provider as any).getSourceFiles();
}
// if we don't have a provider that has 'getSourceFiles', we have to find the files ourselves.
return [];
}
async getHeaderFiles(): Promise<Uri[]> {
if ('getHeaderFiles' in this.provider) {
return (this.provider as any).getHeaderFiles();
}
// if we don't have a provider that has 'getSourceFiles', we have to find the files ourselves.
return [];
}
get isReady() { return this.provider.isReady; }
get isValid() { return this.provider.isValid; }
get version() { return this.provider.version; }
get name() { return this.provider.name; }
get extensionId() { return this.provider.extensionId; }
canProvideConfiguration(uri: Uri, token?: CancellationToken | undefined): Thenable<boolean> {
return this.provider.canProvideConfiguration(uri, token);
}
provideConfigurations(uris: Uri[], token?: CancellationToken | undefined): Thenable<SourceFileConfigurationItem[]> {
return this.provider.provideConfigurations(uris, token);
}
canProvideBrowseConfiguration(token?: CancellationToken | undefined): Thenable<boolean> {
return this.provider.canProvideBrowseConfiguration(token);
}
provideBrowseConfiguration(token?: CancellationToken | undefined): Thenable<WorkspaceBrowseConfiguration | null> {
return this.provider.provideBrowseConfiguration(token);
}
canProvideBrowseConfigurationsPerFolder(token?: CancellationToken | undefined): Thenable<boolean> {
return this.provider.canProvideBrowseConfigurationsPerFolder(token);
}
provideFolderBrowseConfiguration(uri: Uri, token?: CancellationToken | undefined): Thenable<WorkspaceBrowseConfiguration | null> {
return this.provider.provideFolderBrowseConfiguration(uri, token);
}
dispose() {
this.provider.dispose();
}
override async getExtendedBrowseInformation(token: CancellationToken): Promise<ExtendedBrowseInformation> {
const browseConfig = await this.provideBrowseConfiguration();
if (browseConfig) {
// got a browse config
// we expanded the kinds of paths to include the system, user frameworks, etc.
// so a very enlightened provider give us all the information we need
addNormalizedPath(this.browseInfo.browsePaths, browseConfig.browsePath);
addNormalizedPath(this.browseInfo.systemPaths, (browseConfig as InternalWorkspaceBrowseConfiguration).systemPath);
addNormalizedPath(this.browseInfo.userFrameworks, (browseConfig as InternalWorkspaceBrowseConfiguration).userFrameworks);
addNormalizedPath(this.browseInfo.systemFrameworks, (browseConfig as InternalWorkspaceBrowseConfiguration).systemFrameworks);
// if we have a compilerPath, we can use that to find the system paths.
await this.probeForBrowseInfo(browseConfig.compilerPath, browseConfig.compilerArgs);
}
// if we are not given any browsePaths, we have to find the browse paths ourselves.
if (this.browseInfo.browsePaths.size === 0) {
// we assume that any folders that source files are in are part of the browse path.
const sourceFiles = await this.getSourceFiles();
sourceFiles.map(each => addNormalizedPath(this.browseInfo.browsePaths, dirname(each.fsPath)));
// if we can get a compiler for the source files, we can use that to query for system paths.
const configurations = await this.provideConfigurations(sourceFiles, token);
// todo: I don't like this. we could have hundreds or even thousands of source files.
// todo: and add in cancellation support for this too.
await Promise.all(configurations.map(config => this.probeForBrowseInfo(config.configuration.compilerPath, config.configuration.compilerArgs)));
}
// we have to find the system/frameworks/etc paths ourselves.
return super.getExtendedBrowseInformation(token);
}
}
@@ -1,109 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { dirname } from "path";
import { CancellationToken, Uri } from 'vscode';
import { SourceFileConfiguration, SourceFileConfigurationItem, WorkspaceBrowseConfiguration } from "vscode-cpptools";
import { identifyToolset } from '../../ToolsetDetection/detection';
import { fastFind } from "../../Utility/Filesystem/ripgrep";
import { is } from '../../Utility/System/guards';
import { getOrAdd } from "../../Utility/System/map";
import { sources } from '../../constants';
import { DefaultClient } from "../client";
import { Configuration } from '../configurations';
import { ConfigurationAdapter } from './configurationAdapter';
import { ExtendedBrowseInformation, IntellisenseConfigurationAdapter } from './interfaces';
/* eslint-disable @typescript-eslint/no-unused-vars */
export class WorkspaceCofigurationAdapter extends ConfigurationAdapter implements IntellisenseConfigurationAdapter {
private sourceFiles!: Uri[];
private browsePath!: string[];
private ready: Promise<void>;
private constructor(client: DefaultClient, configuration: Configuration) {
super(client, configuration);
this.ready = this.init();
}
static instances = new Map<DefaultClient, WorkspaceCofigurationAdapter>();
static async getProvider(client: DefaultClient, configuration: Configuration): Promise<IntellisenseConfigurationAdapter> {
const provider = getOrAdd(this.instances, client, () => new WorkspaceCofigurationAdapter(client, configuration));
// ensure that if we've changed the configuration that we update this.
provider.configuration = configuration;
return provider;
}
async init() {
// scan for files in the workspace folder
await Promise.all([
// get all the source files (and the )
fastFind(sources, this.client.RootPath).then(results => {
this.sourceFiles = results.map(each => Uri.file(each));
this.browsePath = [...new Set(results.map(each => dirname(each)))];
})
]);
}
async getSourceFiles(): Promise<Uri[]> {
// scan for source (things that can be TUs) files in the workspace folder
await this.ready;
return this.sourceFiles;
}
async getHeaderFiles(): Promise<Uri[]> {
// scan for header files in the workspace folder
await this.ready;
return [];
}
get isReady() { return true; }
get isValid() { return true; }
get version() { return 7; }
get name() { return "WorkspaceConfigurationProvider"; }
get extensionId() { return "built-in.workspaace"; }
async canProvideConfiguration(uri: Uri, token?: CancellationToken | undefined): Promise<boolean> {
return true;
}
async provideConfigurations(uris: Uri[], token?: CancellationToken | undefined): Promise<SourceFileConfigurationItem[]> {
const compiler = this.configuration.compiler || this.configuration.compilerPath;
if (compiler) {
using toolset = await identifyToolset(compiler);
if (toolset) {
const intellisense = await toolset.getIntellisenseConfiguration(this.configuration.compilerArgs ?? [], { userIntellisenseConfiguration: is.object(this.configuration.intellisense) ? this.configuration.intellisense : undefined});
//* cfg.intellisense = toolset.harvestFromConfiguration(cfg, intellisense);
return uris.map(uri => ({
uri: uri,
configuration: { intellisense } as unknown as SourceFileConfiguration
} as SourceFileConfigurationItem));
}
}
return [];
}
async canProvideBrowseConfiguration(token?: CancellationToken | undefined): Promise<boolean> {
return true;
}
async provideBrowseConfiguration(token?: CancellationToken | undefined): Promise<WorkspaceBrowseConfiguration | null> {
return { browsePath: this.browsePath };
}
async canProvideBrowseConfigurationsPerFolder(token?: CancellationToken | undefined): Promise<boolean> {
return false;
}
async provideFolderBrowseConfiguration(uri: Uri, token?: CancellationToken | undefined): Promise<WorkspaceBrowseConfiguration | null> {
return { browsePath: [] };
}
dispose() {
}
override async getExtendedBrowseInformation(token: CancellationToken): Promise<ExtendedBrowseInformation> {
return super.getExtendedBrowseInformation(token);
}
}
@@ -28,17 +28,13 @@ interface CodeActionCommand {
edit?: TextEdit;
uri?: string;
range?: Range;
disabledReason?: string;
}
interface GetCodeActionsResult {
commands: CodeActionCommand[];
}
export interface CreateDeclDefnCommandArguments {
sender: string;
range: Range;
}
export const GetCodeActionsRequest: RequestType<GetCodeActionsRequestParams, GetCodeActionsResult, void> =
new RequestType<GetCodeActionsRequestParams, GetCodeActionsResult, void>('cpptools/getCodeActions');
@@ -49,6 +45,8 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
}
private static inlineMacroKind: vscode.CodeActionKind = vscode.CodeActionKind.RefactorInline.append("macro");
private static extractToFunctionKind: vscode.CodeActionKind = vscode.CodeActionKind.RefactorExtract.append("function");
private static expandSelectionKind: vscode.CodeActionKind = CodeActionProvider.extractToFunctionKind.append("expandSelection");
public async provideCodeActions(document: vscode.TextDocument, range: vscode.Range | vscode.Selection,
context: vscode.CodeActionContext, token: vscode.CancellationToken): Promise<(vscode.Command | vscode.CodeAction)[]> {
@@ -90,6 +88,7 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
this.client.configuration.CurrentConfiguration?.compilerPathInCppPropertiesJson !== undefined ||
!!this.client.configuration.CurrentConfiguration?.compileCommandsInCppPropertiesJson ||
!!this.client.configuration.CurrentConfiguration?.configurationProviderInCppPropertiesJson;
const hasExperimentalFeatures: boolean = new CppSettings().experimentalFeatures ?? false;
// Convert to vscode.CodeAction array
let hasInlineMacro: boolean = false;
@@ -196,12 +195,8 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
return;
} else if ((command.command === 'C_Cpp.CreateDeclarationOrDefinition' || command.command === 'C_Cpp.CopyDeclarationOrDefinition')
&& (command.arguments ?? []).length === 0 && command.range !== undefined) {
const args: CreateDeclDefnCommandArguments = {
sender: 'codeAction',
range: command.range
};
command.arguments = [];
command.arguments.push(args);
command.arguments.push({ sender: 'codeAction', range: command.range });
} else if (command.command === "C_Cpp.SelectIntelliSenseConfiguration") {
command.arguments = ['codeAction'];
hasSelectIntelliSenseConfiguration = true;
@@ -214,6 +209,20 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
} else {
return;
}
} else if (command.command === "C_Cpp.ExtractToFunction" ||
command.command === "C_Cpp.ExtractToMemberFunction") {
if (!hasExperimentalFeatures) {
return;
}
codeActionKind = CodeActionProvider.extractToFunctionKind;
} else if (command.command === "C_Cpp.ExtractToFreeFunction") {
// TODO: https://github.com/microsoft/vscode-cpptools/issues/11473 needs to be fixed.
return;
} else if (command.command === "C_Cpp.ExpandSelection") {
if (!hasExperimentalFeatures) {
return;
}
codeActionKind = CodeActionProvider.expandSelectionKind;
}
const vscodeCodeAction: vscode.CodeAction = {
title: title,
@@ -223,7 +232,8 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
arguments: command.arguments
},
edit: wsEdit,
kind: codeActionKind
kind: codeActionKind,
disabled: command.disabledReason ? { reason: command.disabledReason } : undefined
};
resultCodeActions.push(vscodeCodeAction);
};
@@ -238,11 +248,11 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
if (!editor) {
return false;
}
const result: vscode.Hover[] = <vscode.Hover[]>(await vscode.commands.executeCommand('vscode.executeHoverProvider', document.uri, range.start));
const result: vscode.Hover[] = (await vscode.commands.executeCommand('vscode.executeHoverProvider', document.uri, range.start)) as vscode.Hover[];
if (result.length === 0) {
return false;
}
const hoverResult: vscode.MarkdownString = <vscode.MarkdownString>result[0].contents[0];
const hoverResult: vscode.MarkdownString = result[0].contents[0] as vscode.MarkdownString;
if (!hoverResult.value.includes(localize("expands.to", "Expands to:"))) {
return false;
}
@@ -13,7 +13,8 @@ export class DocumentRangeFormattingEditProvider implements vscode.DocumentRange
this.client = client;
}
public async provideDocumentRangeFormattingEdits(document: vscode.TextDocument, range: vscode.Range, options: vscode.FormattingOptions, token: vscode.CancellationToken): Promise<vscode.TextEdit[]> {
public async provideDocumentRangeFormattingEdits(document: vscode.TextDocument, range: vscode.Range,
options: vscode.FormattingOptions, token: vscode.CancellationToken): Promise<vscode.TextEdit[]> {
const settings: CppSettings = new CppSettings(vscode.workspace.getWorkspaceFolder(document.uri)?.uri);
if (settings.formattingEngine === "disabled") {
return [];
@@ -58,4 +59,12 @@ export class DocumentRangeFormattingEditProvider implements vscode.DocumentRange
return configCallBack(editorConfigSettings);
}
}
// TODO: This is needed for correct Extract to function formatting.
/*
public async provideDocumentRangesFormattingEdits(_document: vscode.TextDocument, _ranges: vscode.Range[],
_options: vscode.FormattingOptions, _token: vscode.CancellationToken): Promise<vscode.TextEdit[]> {
return [];
}
*/
}
@@ -56,11 +56,12 @@ export class DocumentSymbolProvider implements vscode.DocumentSymbolProvider {
public async provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.SymbolInformation[] | vscode.DocumentSymbol[]> {
const client: Client = clients.getClientFor(document.uri);
if (client instanceof DefaultClient) {
const defaultClient: DefaultClient = <DefaultClient>client;
await client.enqueue(() => processDelayedDidOpen(document));
const params: GetDocumentSymbolRequestParams = {
uri: document.uri.toString()
};
const response: GetDocumentSymbolResult = await client.languageClient.sendRequest(GetDocumentSymbolRequest, params, token);
const response: GetDocumentSymbolResult = await defaultClient.languageClient.sendRequest(GetDocumentSymbolRequest, params, token);
if (token.isCancellationRequested || response.symbols === undefined) {
throw new vscode.CancellationError();
}
@@ -24,6 +24,16 @@ export class RenameProvider implements vscode.RenameProvider {
}
public async provideRenameEdits(document: vscode.TextDocument, position: vscode.Position, newName: string, _token: vscode.CancellationToken): Promise<vscode.WorkspaceEdit | undefined> {
// Bypass the normal rename processing during Extract to function,
// since we already know the locations of the required edits.
if (this.client.renameDataForExtractToFunction.length > 0) {
const workspaceEditResult: vscode.WorkspaceEdit = new vscode.WorkspaceEdit();
for (const renameData of this.client.renameDataForExtractToFunction) {
workspaceEditResult.replace(renameData.uri, renameData.range, newName);
}
this.client.renameDataForExtractToFunction = [];
return workspaceEditResult;
}
await this.client.ready;
workspaceReferences.cancelCurrentReferenceRequest(CancellationSender.NewRequest);
+316 -379
View File
@@ -4,7 +4,6 @@
* ------------------------------------------------------------------------------------------ */
'use strict';
/* eslint-disable @typescript-eslint/no-unused-vars */
import * as path from 'path';
import * as vscode from 'vscode';
@@ -28,31 +27,22 @@ import * as fs from 'fs';
import * as os from 'os';
import { SourceFileConfiguration, SourceFileConfigurationItem, Version, WorkspaceBrowseConfiguration } from 'vscode-cpptools';
import { IntelliSenseStatus, Status } from 'vscode-cpptools/out/testApi';
import { CancellationTokenSource, CloseAction, DidOpenTextDocumentParams, ErrorAction, LanguageClientOptions, NotificationType, Position, Range, RequestType, TextDocumentIdentifier } from 'vscode-languageclient';
import { CloseAction, DidOpenTextDocumentParams, ErrorAction, LanguageClientOptions, NotificationType, Position, Range, RequestType, TextDocumentIdentifier } from 'vscode-languageclient';
import { LanguageClient, ServerOptions } from 'vscode-languageclient/node';
import * as nls from 'vscode-nls';
import { DebugConfigurationProvider } from '../Debugger/configurationProvider';
import { CustomConfigurationProvider1, getCustomConfigProviders, isSameProviderExtensionId } from '../LanguageServer/customProviders';
import { identifyToolset } from '../ToolsetDetection/detection';
import { ManualPromise } from '../Utility/Async/manualPromise';
import { ManualSignal } from '../Utility/Async/manualSignal';
import { logAndReturn } from '../Utility/Async/returns';
import { LastKnownState } from '../Utility/System/equality';
import { is } from '../Utility/System/guards';
import { elapsed } from '../Utility/System/performance';
import { structuredClone } from '../Utility/System/structuredClone';
import * as util from '../common';
import { isWindows } from '../constants';
import { DebugProtocolParams, Logger, ShowWarningParams, getDiagnosticsChannel, getOutputChannelLogger, log, logDebugProtocol, logLocalized, showWarning } from '../logger';
import { DebugProtocolParams, Logger, ShowWarningParams, getDiagnosticsChannel, getOutputChannelLogger, logDebugProtocol, logLocalized, showWarning } from '../logger';
import { localizedStringCount, lookupString } from '../nativeStrings';
import { SessionState } from '../sessionState';
import * as telemetry from '../telemetry';
import { TestHook, getTestHook } from '../testHook';
import { CompileCommandsConfigurationAdapter } from './Intellisense/compileCommandsConfigurationAdapter';
import { ExtendedBrowseInformation, IntellisenseConfigurationAdapter } from './Intellisense/interfaces';
import { ProviderConfigurationAdapter } from './Intellisense/providerConfigurationAdapter';
import { WorkspaceCofigurationAdapter } from './Intellisense/workspaceConfigurationAdapter';
import {
CodeAnalysisDiagnosticIdentifiersAndUri,
RegisterCodeAnalysisNotifications,
@@ -74,6 +64,9 @@ import { ConfigurationType, LanguageStatusUI, getUI } from './ui';
import { handleChangedFromCppToC, makeVscodeLocation, makeVscodeRange } from './utils';
import minimatch = require("minimatch");
function deepCopy(obj: any) {
return JSON.parse(JSON.stringify(obj));
}
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
@@ -201,6 +194,21 @@ interface WorkspaceFolderParams {
workspaceFolderUri?: string;
}
interface SelectionParams {
uri: string;
range: Range;
}
export interface VsCodeUriAndRange {
uri: vscode.Uri;
range: vscode.Range;
}
interface WorkspaceEditResult {
workspaceEdits: WorkspaceEdit[];
errorText?: string;
}
interface TelemetryPayload {
event: string;
properties?: Record<string, string>;
@@ -253,11 +261,8 @@ interface InternalSourceFileConfiguration extends SourceFileConfiguration {
compilerArgsLegacy?: string[];
}
export interface InternalWorkspaceBrowseConfiguration extends WorkspaceBrowseConfiguration {
interface InternalWorkspaceBrowseConfiguration extends WorkspaceBrowseConfiguration {
compilerArgsLegacy?: string[];
systemPath?: string[];
userFrameworks?: string[];
systemFrameworks?: string[];
}
// Need to convert vscode.Uri to a string before sending it to the language server.
@@ -327,16 +332,16 @@ interface PublishRefactorDiagnosticsParams {
diagnostics: RefactorDiagnostic[];
}
export interface CreateDeclarationOrDefinitionParams {
uri: string;
range: Range;
export interface CreateDeclarationOrDefinitionParams extends SelectionParams {
copyToClipboard: boolean;
}
export interface CreateDeclarationOrDefinitionResult {
edit: WorkspaceEdit;
export interface CreateDeclarationOrDefinitionResult extends WorkspaceEditResult {
clipboardText?: string;
errorText?: string;
}
export interface ExtractToFunctionParams extends SelectionParams {
extractAsGlobal: boolean;
}
interface ShowMessageWindowParams {
@@ -381,9 +386,7 @@ export interface LocalizeSymbolInformation {
suffix: LocalizeStringParams;
}
export interface FormatParams {
uri: string;
range: Range;
export interface FormatParams extends SelectionParams {
character: string;
insertSpaces: boolean;
tabSize: number;
@@ -576,6 +579,7 @@ export const FormatDocumentRequest: RequestType<FormatParams, FormatResult, void
export const FormatRangeRequest: RequestType<FormatParams, FormatResult, void> = new RequestType<FormatParams, FormatResult, void>('cpptools/formatRange');
export const FormatOnTypeRequest: RequestType<FormatParams, FormatResult, void> = new RequestType<FormatParams, FormatResult, void>('cpptools/formatOnType');
const CreateDeclarationOrDefinitionRequest: RequestType<CreateDeclarationOrDefinitionParams, CreateDeclarationOrDefinitionResult, void> = new RequestType<CreateDeclarationOrDefinitionParams, CreateDeclarationOrDefinitionResult, void>('cpptools/createDeclDef');
const ExtractToFunctionRequest: RequestType<ExtractToFunctionParams, WorkspaceEditResult, void> = new RequestType<ExtractToFunctionParams, WorkspaceEditResult, void>('cpptools/extractToFunction');
const GoToDirectiveInGroupRequest: RequestType<GoToDirectiveInGroupParams, Position | undefined, void> = new RequestType<GoToDirectiveInGroupParams, Position | undefined, void>('cpptools/goToDirectiveInGroup');
const GenerateDoxygenCommentRequest: RequestType<GenerateDoxygenCommentParams, GenerateDoxygenCommentResult | undefined, void> = new RequestType<GenerateDoxygenCommentParams, GenerateDoxygenCommentResult, void>('cpptools/generateDoxygenComment');
const ChangeCppPropertiesRequest: RequestType<CppPropertiesParams, void, void> = new RequestType<CppPropertiesParams, void, void>('cpptools/didChangeCppProperties');
@@ -748,7 +752,7 @@ export interface Client {
onRegisterCustomConfigurationProvider(provider: CustomConfigurationProvider1): Thenable<void>;
updateCustomConfigurations(requestingProvider?: CustomConfigurationProvider1): Thenable<void>;
updateCustomBrowseConfiguration(requestingProvider?: CustomConfigurationProvider1): Thenable<void>;
provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean, provider?: CustomConfigurationProvider1): Promise<void>;
provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean): Promise<void>;
logDiagnostics(): Promise<void>;
rescanFolder(): Promise<void>;
toggleReferenceResultsView(): void;
@@ -794,6 +798,7 @@ export interface Client {
handleFixCodeAnalysisProblems(workspaceEdit: vscode.WorkspaceEdit, refreshSquigglesOnSave: boolean, identifiersAndUris: CodeAnalysisDiagnosticIdentifiersAndUri[]): Promise<void>;
handleDisableAllTypeCodeAnalysisProblems(code: string, identifiersAndUris: CodeAnalysisDiagnosticIdentifiersAndUri[]): Promise<void>;
handleCreateDeclarationOrDefinition(isCopyToClipboard: boolean, codeActionRange?: Range): Promise<void>;
handleExtractToFunction(extractAsGlobal: boolean): Promise<void>;
onInterval(): void;
dispose(): void;
addFileAssociations(fileAssociations: string, languageId: string): void;
@@ -935,12 +940,13 @@ export class DefaultClient implements Client {
public static updateClientConfigurations(): void {
clients.forEach(client => {
if (client instanceof DefaultClient) {
const defaultClient: DefaultClient = client as DefaultClient;
if (!client.isInitialized() || !compilerDefaults) {
// This can randomly get hit when adding/removing workspace folders.
return;
}
client.configuration.CompilerDefaults = compilerDefaults;
client.configuration.handleConfigurationChange();
defaultClient.configuration.CompilerDefaults = compilerDefaults;
defaultClient.configuration.handleConfigurationChange();
}
});
}
@@ -1241,8 +1247,8 @@ export class DefaultClient implements Client {
const workspaceFolder: vscode.WorkspaceFolder | undefined = this.rootFolder;
this.innerConfiguration = new configs.CppProperties(this, rootUri, workspaceFolder);
this.innerConfiguration.ConfigurationsChanged((e) => this.onConfigurationsChanged(e));
this.innerConfiguration.CompileCommandsChanged((e) => this.onCompileCommandsChanged(e));
this.innerConfiguration.SelectionChanged((e) => this.onSelectedConfigurationChanged(e));
this.innerConfiguration.CompileCommandsChanged((e) => this.onCompileCommandsChanged(e));
this.disposables.push(this.innerConfiguration);
this.innerLanguageClient = languageClient;
@@ -1297,16 +1303,10 @@ export class DefaultClient implements Client {
}, 15000);
}
});
// if we're in newIntellisenseMode, we don't want to ask the service for the compiler
if (!this.isNewIntellisense) {
log("init: calling requestCompiler for legacy mode");
// The configurations will not be sent to the language server until the default include paths and frameworks have been set.
// The event handlers must be set before this happens.
compilerDefaults = await this.requestCompiler();
}
// The configurations will not be sent to the language server until the default include paths and frameworks have been set.
// The event handlers must be set before this happens.
compilerDefaults = await this.requestCompiler();
DefaultClient.updateClientConfigurations();
clients.forEach(client => {
if (client instanceof DefaultClient) {
client.configStateReceived.compilers = true;
@@ -1695,10 +1695,7 @@ export class DefaultClient implements Client {
}
}
public async onDidOpenTextDocument(document: vscode.TextDocument) {
if (this.isNewIntellisense) {
await this.updatingNewIntellisense;
}
public onDidOpenTextDocument(document: vscode.TextDocument): void {
if (document.uri.scheme === "file") {
const uri: string = document.uri.toString();
openFileVersions.set(uri, document.version);
@@ -1775,8 +1772,6 @@ export class DefaultClient implements Client {
}
public async updateCustomConfigurations(requestingProvider?: CustomConfigurationProvider1): Promise<void> {
this.ensureNotNewIntellisense();
log("updateCustomConfigurations: Legacy Mode");
await this.ready;
if (!this.configurationProvider) {
@@ -1805,14 +1800,10 @@ export class DefaultClient implements Client {
public async updateCustomBrowseConfiguration(requestingProvider?: CustomConfigurationProvider1): Promise<void> {
await this.ready;
this.ensureNotNewIntellisense();
console.log("updateCustomBrowseConfiguration: Legacy Mode");
if (!this.configurationProvider) {
return;
}
console.log("updateCustomBrowseConfiguration");
const currentProvider: CustomConfigurationProvider1 | undefined = getCustomConfigProviders().get(this.configurationProvider);
if (!currentProvider || !currentProvider.isReady || (requestingProvider && requestingProvider.extensionId !== currentProvider.extensionId)) {
return;
@@ -1925,34 +1916,23 @@ export class DefaultClient implements Client {
return this.languageClient.sendNotification(RescanFolderNotification);
}
public async provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean, provider?: CustomConfigurationProvider1): Promise<void> {
if (this.isNewIntellisense) {
await this.sendNewIntellisenseConfigurationForFile(docUri);
void this.languageClient.sendNotification(FinishedRequestCustomConfig, { uri: requestFile });
return;
}
// in newIntellisenseMode, this should not be getting called (the server shouldn't ask, and we're handling the files ourselves elsewhere.)
this.ensureNotNewIntellisense();
log("provideCustomConfiguration: Legacy Mode");
const onFinished = () => {
public async provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean): Promise<void> {
const onFinished: () => void = () => {
if (requestFile) {
void this.languageClient.sendNotification(FinishedRequestCustomConfig, { uri: requestFile });
}
};
if (!provider) {
const providerId: string | undefined = this.configurationProvider;
if (!providerId) {
onFinished();
return;
}
provider = getCustomConfigProviders().get(providerId);
telemetry.logLanguageServerEvent('provideCustomConfiguration', { providerId });
const providerId: string | undefined = this.configurationProvider;
if (!providerId) {
onFinished();
return;
}
const provider: CustomConfigurationProvider1 | undefined = getCustomConfigProviders().get(providerId);
if (!provider || !provider.isReady) {
onFinished();
return;
}
telemetry.logLanguageServerEvent('provideCustomConfiguration', { providerId });
void this.provideCustomConfigurationAsync(docUri, requestFile, replaceExisting, onFinished, provider);
}
@@ -2003,45 +1983,38 @@ export class DefaultClient implements Client {
if (configs && configs.length > 0 && configs[0]) {
const fileConfiguration: configs.Configuration | undefined = this.configuration.CurrentConfiguration;
if (fileConfiguration?.mergeConfigurations) {
configs.forEach(config => {
if (fileConfiguration.includePath) {
fileConfiguration.includePath.forEach(p => {
if (!config.configuration.includePath.includes(p)) {
config.configuration.includePath.push(p);
}
});
}
// do we have an actual configuration?
if (fileConfiguration) {
if (fileConfiguration.defines) {
fileConfiguration.defines.forEach(d => {
if (!config.configuration.defines.includes(d)) {
config.configuration.defines.push(d);
}
});
}
// if the user has additional (legacy) configuration they want merged, do it here.
if (fileConfiguration.mergeConfigurations) {
configs.forEach(config => {
if (fileConfiguration.includePath) {
fileConfiguration.includePath.forEach(p => {
if (!config.configuration.includePath.includes(p)) {
config.configuration.includePath.push(p);
if (!config.configuration.forcedInclude) {
config.configuration.forcedInclude = [];
}
if (fileConfiguration.forcedInclude) {
fileConfiguration.forcedInclude.forEach(i => {
if (config.configuration.forcedInclude) {
if (!config.configuration.forcedInclude.includes(i)) {
config.configuration.forcedInclude.push(i);
}
});
}
if (fileConfiguration.defines) {
fileConfiguration.defines.forEach(d => {
if (!config.configuration.defines.includes(d)) {
config.configuration.defines.push(d);
}
});
}
if (!config.configuration.forcedInclude) {
config.configuration.forcedInclude = [];
}
if (fileConfiguration.forcedInclude) {
fileConfiguration.forcedInclude.forEach(i => {
if (config.configuration.forcedInclude) {
if (!config.configuration.forcedInclude.includes(i)) {
config.configuration.forcedInclude.push(i);
}
}
});
}
});
}
}
});
}
});
}
return configs as SourceFileConfigurationItem[];
}
@@ -2049,11 +2022,9 @@ export class DefaultClient implements Client {
return null;
}
};
try {
const configs: SourceFileConfigurationItem[] | null | undefined = await this.callTaskWithTimeout(provideConfigurationAsync, configProviderTimeout, tokenSource);
if (configs?.length) {
if (configs && configs.length > 0) {
this.sendCustomConfigurations(configs, provider.version);
}
onFinished();
@@ -2291,7 +2262,8 @@ export class DefaultClient implements Client {
this.languageClient.onNotification(RequestCustomConfig, (requestFile: string) => {
const client: Client = clients.getClientFor(vscode.Uri.file(requestFile));
if (client instanceof DefaultClient) {
void client.handleRequestCustomConfig(requestFile);
const defaultClient: DefaultClient = client as DefaultClient;
void defaultClient.handleRequestCustomConfig(requestFile);
}
});
this.languageClient.onNotification(PublishIntelliSenseDiagnosticsNotification, publishIntelliSenseDiagnostics);
@@ -2802,231 +2774,10 @@ export class DefaultClient implements Client {
private doneInitialCustomBrowseConfigurationCheck: boolean = false;
private async patchConfigurationForNewIntellisense(cfg: configs.Configuration | (util.Mutable<SourceFileConfiguration> & configs.NewIntelliSense), compiler?: string): Promise<void> {
// currently, the new IntelliSense requires a compiler for the configuration
// we're not going to guess it here.
// so either they sent us one, or we look in the configuration for 'compiler' (best), or fallback to 'compilerPath' (ok)
compiler = compiler || cfg.compiler || cfg.compilerPath;
if (cfg.enableNewIntellisense) {
(cfg as any).compileCommands = undefined;
if (compiler) {
try {
using toolset = await identifyToolset(compiler);
if (toolset) {
const intellisense = await toolset.getIntellisenseConfiguration(cfg.compilerArgs ?? [], { userIntellisenseConfiguration: is.object(cfg.intellisense) ? cfg.intellisense : undefined});
cfg.intellisense = toolset.harvestFromConfiguration(cfg, intellisense);
}
log(JSON.stringify(cfg, null, 2));
return;
} catch (e) {
log(`Unable to identify toolset from compilerPath: ${cfg.compiler} - ${e}`);
}
}
}
// not using the new IntelliSense
// make sure that the configuration is not marked as newIntellisense
cfg.enableNewIntellisense = false;
cfg.compiler = undefined;
cfg.intellisense = undefined;
}
// we need to store the last relevant version of these we used so we can tell if they've changed
private lastKnownState = new class extends LastKnownState {
browseInfo: ExtendedBrowseInformation | null = null;
configuration = {} as configs.Configuration;
sourceFiles = [] as string[];
headerFiles = [] as string[];
}();
private async sendBrowsePath(configuration: configs.Configuration) {
const provider = await this.getAdapter(configuration);
const cancelToken = this.updateIntellisenseCancelToken;
if (cancelToken.isCancellationRequested) {
return;
}
const browseInfo = await provider.getExtendedBrowseInformation(cancelToken);
if (this.lastKnownState.unchanged('browseInfo', browseInfo)) {
return;
}
log('Sending Browse Path Information');
// send the custom browse config to the server
return this.languageClient.sendNotification(CustomBrowseConfigurationNotification, {
workspaceFolderUri: this.RootUri?.toString(),
browseConfiguration: browseInfo // we only want to pass in the folders - which ensures the service doesn't 'think' about anything
}).catch(logAndReturn.undefined);
}
public get isNewIntellisense() {
return !!this.configuration.CurrentConfiguration?.enableNewIntellisense;
}
private ensureNotNewIntellisense() {
if (this.isNewIntellisense) {
// eslint-disable-next-line no-debugger
debugger;
throw new Error("This method should not be called when using the new IntelliSense.");
}
}
async sendNewIntellisenseConfigurationForFile(fileUri: vscode.Uri) {
await this.updatingNewIntellisense;
// this is kind of like provideCustomConfiguration
// except that we're going to generate the configuration for the file
// and send it (if it hasn't changed from whence last it was sent)
// ask the provider for the configuration
const base = this.configuration.CurrentConfiguration;
if (!base) {
// this should not happen.
// eslint-disable-next-line no-debugger
debugger;
log("ERROR: ============== No base configuration for new intellisense ====================");
return;
}
const adapter = await this.getAdapter(base);
if (adapter) {
await adapter.initialized;
return this.sendFileConfigurations(adapter, [fileUri]);
}
}
async getAdapter(configuration: configs.Configuration): Promise<IntellisenseConfigurationAdapter> {
let adapter: IntellisenseConfigurationAdapter | undefined;
if (configuration.compileCommands) {
// if they are using compile commands, use the provider for that.
adapter = await CompileCommandsConfigurationAdapter.getProvider(this, configuration.compileCommands, configuration, this.updateIntellisenseCancelToken);
}
if (!adapter && configuration.configurationProvider) {
// can we get a provider from the configuration provider?
adapter = await ProviderConfigurationAdapter.getProvider(this, getCustomConfigProviders().get(configuration.configurationProvider), configuration);
}
if (!adapter) {
// no provider, use the workspace
adapter = await WorkspaceCofigurationAdapter.getProvider(this, configuration);
}
return adapter;
}
#updateIntellisenseCancelTokenSource: CancellationTokenSource | undefined;
private get updateIntellisenseCancelToken() {
return (this.#updateIntellisenseCancelTokenSource ?? (this.#updateIntellisenseCancelTokenSource = new CancellationTokenSource())).token;
}
private cancelUpdateIntellisense() {
if (this.#updateIntellisenseCancelTokenSource) {
this.#updateIntellisenseCancelTokenSource.cancel();
this.#updateIntellisenseCancelTokenSource = undefined;
}
}
private filterUnchanged(configurationItems: SourceFileConfigurationItem[], filter = true) {
return filter ?
configurationItems.
map(each => ({ uri: each.uri.toString(), configuration: each.configuration })).
filter(({uri, configuration}) => this.lastKnownState.changed(uri as any, configuration)) :
configurationItems.
map(each => ({ uri: each.uri.toString(), configuration: each.configuration }));
}
private async sendFileConfigurations(adapter: IntellisenseConfigurationAdapter, uris: vscode.Uri[] = []) {
const configurations = await adapter.provideConfigurations(uris);
if (uris.length && configurations.length !== uris.length) {
// we didn't get a configuration for everything requested.
for (const each of uris) {
if (!configurations.find(c => c.uri.toString() === each.toString())) {
log(`Unable to provide configuration for ${each.fsPath}`);
}
}
}
// if we're only going to send one configuration (ie, because we're opening a document) - don't filter out previously sent configurations
// because if a configuration was sent before it could be used, the server will have forgotten it.
const configurationItems = this.filterUnchanged(configurations, uris.length !== 1);
if (configurationItems.length > 0) {
log(`Sending file configuration for ${configurationItems.length} files: ${configurationItems[0].uri.toString()}...`);
return this.languageClient.sendNotification(CustomConfigurationNotification, { configurationItems, workspaceFolderUri: this.RootUri?.toString() }).catch(logAndReturn.undefined);
}
}
/** This sends the base configuration to the language client */
private async sendBaseConfiguration(adapter: IntellisenseConfigurationAdapter) {
return this.languageClient.sendRequest(ChangeCppPropertiesRequest, {
configurations: [await adapter.getBaseConfiguration(this.updateIntellisenseCancelToken)],
currentConfiguration: 0,
workspaceFolderUri: this.RootUri?.toString(),
isReady: true
});
}
// temporary - to see if we can stop it from doing things before we're ready.
updatingNewIntellisense = new ManualSignal<void>();
/**
* This gets called when the configuration changes
*/
private async onIntellisenseConfigurationChanged(configuration: configs.Configuration, force = false): Promise<void> {
log(`${elapsed()} updating (NEW) intellisense for ${configuration.name}`);
if (this.lastKnownState.unchanged('configuration', configuration) || force) {
// if the configuration hasn't changed, we don't need to do anything
// todo: if something dependent has changed (like the compile commands, how do we know?)
log(`${elapsed()} ========= SKIPPING UPDATE INTELLISENS (config hasn't changed) =========`);
return;
}
this.updatingNewIntellisense.reset();
// if we had a previous update in progress, cancel it.
this.cancelUpdateIntellisense();
// get the provider right away so that if it has to do any work, it can get started
const adapter = await this.getAdapter(configuration);
log(`${elapsed()} ========= SENDING BASE CONFIG (includes some browse path) =========`);
await this.sendBaseConfiguration(adapter);
void this.sendBrowsePath(configuration);
// first, let's send the configuration for files that are open in the editor (how?)
// then, we send the configuration for the known files that are ready
// then as the rest is done, send those too.
log(`${elapsed()} ========= SENDING configurations for files that are ready right now =========`);
void this.sendFileConfigurations(adapter, []);
// when it's done, stop listening.
adapter.once('done', () => {
log(`${elapsed()} ========= SENDING the remaining configurations for the rest of the files =========`);
adapter.removeAllListeners();
void this.sendFileConfigurations(adapter, []);
// send a flag with this to say "yeah, delete what you want" - since we're giveing the whole browse path
// see browse_engine.cpp:1512
void this.sendBrowsePath(configuration);
});
log(`${elapsed()} ========= DONE UPDATE INTELLISENSE =========`);
this.updatingNewIntellisense.resolve();
this.#updateIntellisenseCancelTokenSource = undefined;
}
private async onConfigurationsChanged(cppProperties: configs.CppProperties): Promise<void> {
if (!cppProperties.Configurations) {
return;
}
// redirct to use new intellisense if that's what's selected
const current = this.configuration.CurrentConfiguration ?? cppProperties.Configurations[0];
if (current?.enableNewIntellisense) {
return this.onIntellisenseConfigurationChanged(current);
}
const configurations: configs.Configuration[] = cppProperties.Configurations;
const params: CppPropertiesParams = {
configurations: [],
@@ -3037,8 +2788,8 @@ export class DefaultClient implements Client {
const settings: CppSettings = new CppSettings(this.RootUri);
// Clone each entry, as we make modifications before sending it, and don't
// want to add those modifications to the original objects.
params.configurations = await Promise.all(configurations.map(async (c) => {
const modifiedConfig: configs.Configuration = structuredClone(c);
configurations.forEach((c) => {
const modifiedConfig: configs.Configuration = deepCopy(c);
// Separate compiler path and args before sending to language client
const compilerPathAndArgs: util.CompilerPathAndArgs =
util.extractCompilerPathAndArgs(!!settings.legacyCompilerArgsBehavior, c.compilerPath, c.compilerArgs);
@@ -3050,11 +2801,10 @@ export class DefaultClient implements Client {
modifiedConfig.compilerArgs = compilerPathAndArgs.allCompilerArgs;
}
return modifiedConfig;
}));
params.configurations.push(modifiedConfig);
});
await this.languageClient.sendRequest(ChangeCppPropertiesRequest, params);
if (!!this.lastCustomBrowseConfigurationProviderId && !!this.lastCustomBrowseConfiguration && !!this.lastCustomBrowseConfigurationProviderVersion) {
if (!this.doneInitialCustomBrowseConfigurationCheck) {
// Send the last custom browse configuration we received from this provider.
@@ -3069,7 +2819,6 @@ export class DefaultClient implements Client {
}
const configName: string | undefined = configurations[params.currentConfiguration].name ?? "";
this.model.activeConfigName.setValueIfActive(configName);
const newProvider: string | undefined = this.configuration.CurrentConfigurationProvider;
if (!isSameProviderExtensionId(newProvider, this.configurationProvider)) {
if (this.configurationProvider) {
@@ -3098,19 +2847,10 @@ export class DefaultClient implements Client {
}
private async onCompileCommandsChanged(path: string): Promise<void> {
// if we're using new Intellisense, we don't want to send this.
// instead, we'd like to let the updateIntellisense method handle it.
if (this.configuration.CurrentConfiguration?.enableNewIntellisense) {
log('OnCompileCommands: Using New intellisense');
return this.onIntellisenseConfigurationChanged(this.configuration.CurrentConfiguration);
}
log('OnCompileCommands: Using Legacy intellisense');
const params: FileChangedParams = {
uri: vscode.Uri.file(path).toString(),
workspaceFolderUri: this.RootUri?.toString()
};
await this.ready;
return this.languageClient.sendNotification(ChangeCompileCommandsNotification, params);
}
@@ -3133,8 +2873,6 @@ export class DefaultClient implements Client {
}
private sendCustomConfigurations(configs: any, providerVersion: Version): void {
this.ensureNotNewIntellisense();
log('sendCustomConfigurations: Using Legacy intellisense');
// configs is marked as 'any' because it is untrusted data coming from a 3rd-party. We need to sanitize it before sending it to the language server.
if (!configs || !(configs instanceof Array)) {
console.warn("discarding invalid SourceFileConfigurationItems[]: " + configs);
@@ -3149,13 +2887,6 @@ export class DefaultClient implements Client {
const sanitized: SourceFileConfigurationItemAdapter[] = [];
configs.forEach(item => {
if (this.isSourceFileConfigurationItem(item, providerVersion)) {
const itemConfig: util.Mutable<InternalSourceFileConfiguration & configs.NewIntelliSense> = structuredClone(item.configuration);
// In legacy mode, make sure new Intellisense content isn't sent
itemConfig.intelliSenseMode = undefined;
itemConfig.enableNewIntellisense = undefined;
itemConfig.compiler = undefined;
let uri: string;
if (util.isString(item.uri) && !item.uri.startsWith("file://")) {
// If the uri field is a string, it may actually contain an fsPath.
@@ -3164,16 +2895,15 @@ export class DefaultClient implements Client {
uri = item.uri.toString();
}
this.configurationLogging.set(uri, JSON.stringify(item.configuration, null, 4));
/// if (settings.loggingLevel === "Debug") {
/// out.appendLine(` uri: ${uri}`);
/// out.appendLine(` config: ${JSON.stringify(item.configuration, null, 2)}`);
/// }
if (settings.loggingLevel === "Debug") {
out.appendLine(` uri: ${uri}`);
out.appendLine(` config: ${JSON.stringify(item.configuration, null, 2)}`);
}
if (item.configuration.includePath.some(path => path.endsWith('**'))) {
console.warn("custom include paths should not use recursive includes ('**')");
}
// Separate compiler path and args before sending to language client
const itemConfig: util.Mutable<InternalSourceFileConfiguration> = deepCopy(item.configuration);
if (util.isString(itemConfig.compilerPath)) {
const compilerPathAndArgs: util.CompilerPathAndArgs = util.extractCompilerPathAndArgs(
providerVersion < Version.v6,
@@ -3190,7 +2920,6 @@ export class DefaultClient implements Client {
itemConfig.compilerArgs = compilerPathAndArgs.allCompilerArgs;
}
}
sanitized.push({
uri,
configuration: itemConfig
@@ -3203,8 +2932,11 @@ export class DefaultClient implements Client {
if (sanitized.length === 0) {
return;
}
const params = { configurationItems: sanitized, workspaceFolderUri: this.RootUri?.toString() };
out.appendLine(`${CustomConfigurationNotification}:\n${JSON.stringify(params, null, 2)}`);
const params: CustomConfigurationParams = {
configurationItems: sanitized,
workspaceFolderUri: this.RootUri?.toString()
};
void this.languageClient.sendNotification(CustomConfigurationNotification, params).catch(logAndReturn.undefined);
}
@@ -3221,9 +2953,6 @@ export class DefaultClient implements Client {
}
private sendCustomBrowseConfiguration(config: any, providerId: string | undefined, providerVersion: Version, timeoutOccured?: boolean): void {
this.ensureNotNewIntellisense();
log('sendCustomBrowseConfiguration: Using Legacy intellisense');
const rootFolder: vscode.WorkspaceFolder | undefined = this.RootFolder;
if (!rootFolder
|| !this.lastCustomBrowseConfiguration
@@ -3257,7 +2986,7 @@ export class DefaultClient implements Client {
}
const browseConfig: InternalWorkspaceBrowseConfiguration = config as InternalWorkspaceBrowseConfiguration;
sanitized = structuredClone(browseConfig);
sanitized = deepCopy(browseConfig);
if (!this.isWorkspaceBrowseConfiguration(sanitized) || sanitized.browsePath.length === 0) {
console.log("Received an invalid browse configuration from configuration provider: " + JSON.stringify(sanitized));
const configValue: WorkspaceBrowseConfiguration | undefined = this.lastCustomBrowseConfiguration.Value;
@@ -3611,7 +3340,7 @@ export class DefaultClient implements Client {
const result: CreateDeclarationOrDefinitionResult = await this.languageClient.sendRequest(CreateDeclarationOrDefinitionRequest, params);
// Create/Copy returned no result.
if (result.edit === undefined) {
if (result.workspaceEdits === undefined) {
// The only condition in which result.edit would be undefined is a
// server-initiated cancellation, in which case the object is actually
// a ResponseError. https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#responseMessage
@@ -3634,26 +3363,32 @@ export class DefaultClient implements Client {
return vscode.env.clipboard.writeText(result.clipboardText);
}
const workspaceEdits: vscode.WorkspaceEdit = new vscode.WorkspaceEdit();
let workspaceEdits: vscode.WorkspaceEdit = new vscode.WorkspaceEdit();
let modifiedDocument: vscode.Uri | undefined;
let lastEdit: vscode.TextEdit | undefined;
let selectionPositionAdjustment: number = 0;
for (const file in result.edit.changes) {
const uri: vscode.Uri = vscode.Uri.file(file);
for (const workspaceEdit of result.workspaceEdits) {
const uri: vscode.Uri = vscode.Uri.file(workspaceEdit.file);
// At most, there will only be two text edits:
// 1.) an edit for: #include header file
// 2.) an edit for: definition or declaration
for (const edit of result.edit.changes[file]) {
const range: vscode.Range = makeVscodeRange(edit.range);
for (const edit of workspaceEdit.edits) {
let range: vscode.Range = makeVscodeRange(edit.range);
// Get new lines from an edit for: #include header file.
if (lastEdit && lastEdit.newText.includes("#include") && lastEdit.range.isEqual(range)) {
if (lastEdit && lastEdit.newText.length < 300 && lastEdit.newText.includes("#include") && lastEdit.range.isEqual(range)) {
// Destination file is empty.
// The edit positions for #include header file and definition or declaration are the same.
selectionPositionAdjustment = (lastEdit.newText.match(/\n/g) ?? []).length;
const selectionPositionAdjustment = (lastEdit.newText.match(/\n/g) ?? []).length;
range = new vscode.Range(new vscode.Position(range.start.line + selectionPositionAdjustment, range.start.character),
new vscode.Position(range.end.line + selectionPositionAdjustment, range.end.character));
}
lastEdit = new vscode.TextEdit(range, edit.newText);
const position: vscode.Position = new vscode.Position(edit.range.start.line, edit.range.start.character);
workspaceEdits.insert(uri, position, edit.newText);
workspaceEdits.insert(uri, range.start, edit.newText);
if (edit.newText.length < 300 && edit.newText.includes("#pragma once")) {
// Commit this so that it can be undone separately, to avoid leaving an empty file,
// which causes the next refactor to not add the #pragma once.
await vscode.workspace.applyEdit(workspaceEdits);
workspaceEdits = new vscode.WorkspaceEdit();
}
}
modifiedDocument = uri;
}
@@ -3679,7 +3414,7 @@ export class DefaultClient implements Client {
numNewlines++; // Increase the format range.
}
const selectionPosition: vscode.Position = new vscode.Position(startLine + selectionPositionAdjustment, 0);
const selectionPosition: vscode.Position = new vscode.Position(startLine, 0);
const selectionRange: vscode.Range = new vscode.Range(selectionPosition, selectionPosition);
await vscode.window.showTextDocument(modifiedDocument, { selection: selectionRange });
@@ -3711,6 +3446,207 @@ export class DefaultClient implements Client {
await vscode.workspace.applyEdit(formatEdits);
}
public async handleExtractToFunction(extractAsGlobal: boolean): Promise<void> {
const editor: vscode.TextEditor | undefined = vscode.window.activeTextEditor;
if (!editor || editor.selection.isEmpty) {
return;
}
// TODO: Show a quick pick to get the name before generating the code.
// That would allow the formatting to be done without waiting for the rename.
// Also, it's less error prone and eliminates a class of bugs in which the
// rename position can be incorrect.
const params: ExtractToFunctionParams = {
uri: editor.document.uri.toString(),
range: {
start: {
character: editor.selection.start.character,
line: editor.selection.start.line
},
end: {
character: editor.selection.end.character,
line: editor.selection.end.line
}
},
extractAsGlobal
};
const result: WorkspaceEditResult = await this.languageClient.sendRequest(ExtractToFunctionRequest, params);
if (result.workspaceEdits === undefined) {
// The only condition in which result.edit would be undefined is a
// server-initiated cancellation, in which case the object is actually
// a ResponseError. https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#responseMessage
return;
}
// Handle error messaging
if (result.errorText) {
void vscode.window.showErrorMessage(result.errorText);
return;
}
let workspaceEdits: vscode.WorkspaceEdit = new vscode.WorkspaceEdit();
let replaceEditRange: vscode.Range | undefined;
let hasProcessedReplace: boolean = false;
const formatUriAndRanges: VsCodeUriAndRange[] = [];
this.renameDataForExtractToFunction = [];
let lineOffset: number = 0;
let headerFileLineOffset: number = 0;
let isSourceFile: boolean = true;
// There will be 4-5 text edits:
// - A #pragma once added to a new header file (optional)
// - Add #include for header file (optional)
// - Add the new function declaration (in the source or header file)
// - Replace the selected code with the new function call,
// plus possibly extra declarations beforehand,
// plus possibly extra return value handling afterwards.
// - Add the new function definition (below the selection)
for (const workspaceEdit of result.workspaceEdits) {
if (hasProcessedReplace) {
isSourceFile = false;
lineOffset = 0;
}
const uri: vscode.Uri = vscode.Uri.file(workspaceEdit.file);
let nextLineOffset: number = 0;
for (const edit of workspaceEdit.edits) {
let range: vscode.Range = makeVscodeRange(edit.range);
if (!isSourceFile && headerFileLineOffset) {
range = new vscode.Range(new vscode.Position(range.start.line + headerFileLineOffset, range.start.character),
new vscode.Position(range.end.line + headerFileLineOffset, range.end.character));
}
const isReplace: boolean = !range.isEmpty;
lineOffset += nextLineOffset;
nextLineOffset = (edit.newText.match(/\n/g) ?? []).length;
let rangeStartLine: number = range.start.line + lineOffset;
// Find the editType.
if (isReplace) {
hasProcessedReplace = true;
workspaceEdits.replace(uri, range, edit.newText);
} else {
workspaceEdits.insert(uri, range.start, edit.newText);
if (edit.newText.length < 300) { // Avoid searching large code edits
if (isSourceFile && !hasProcessedReplace && edit.newText.includes("#include")) {
continue;
}
if (edit.newText.includes("#pragma once")) {
// Commit this so that it can be undone separately, to avoid leaving an empty file,
// which causes the next refactor to not add the #pragma once.
await vscode.workspace.applyEdit(workspaceEdits);
headerFileLineOffset = nextLineOffset;
workspaceEdits = new vscode.WorkspaceEdit();
continue;
}
}
}
let rangeStartCharacter: number = 0;
if (edit.newText.startsWith("\r\n\r\n")) {
rangeStartCharacter = 4;
rangeStartLine += 2;
} else if (edit.newText.startsWith("\n\n")) {
rangeStartCharacter = 2;
rangeStartLine += 2;
} else if (edit.newText.startsWith("\r\n")) {
rangeStartCharacter = 2;
rangeStartLine += 1;
} else if (edit.newText.startsWith("\n")) {
rangeStartCharacter = 1;
rangeStartLine += 1;
}
formatUriAndRanges.push({uri, range: new vscode.Range(
new vscode.Position(rangeStartLine + (nextLineOffset < 0 ? nextLineOffset : 0), range.start.character),
new vscode.Position(rangeStartLine + (nextLineOffset < 0 ? 0 : nextLineOffset),
isReplace ? range.end.character :
range.end.character + edit.newText.length - rangeStartCharacter))});
const newFunctionString: string = "NewFunction";
// Handle additional declaration lines added before the new function call.
let currentText: string = edit.newText.substring(rangeStartCharacter);
let currentTextNextLineStart: number = currentText.indexOf("\n");
let currentTextNewFunctionStart: number = currentText.indexOf(newFunctionString);
let currentTextNextLineStartUpdated: boolean = false;
while (currentTextNextLineStart !== -1 && currentTextNextLineStart < currentTextNewFunctionStart) {
++rangeStartLine;
currentText = currentText.substring(currentTextNextLineStart + 1);
currentTextNextLineStart = currentText.indexOf("\n");
currentTextNewFunctionStart = currentText.indexOf(newFunctionString);
currentTextNextLineStartUpdated = true;
}
rangeStartCharacter = (rangeStartCharacter === 0 && !currentTextNextLineStartUpdated ? range.start.character : 0) +
currentTextNewFunctionStart;
if (rangeStartCharacter < 0) {
// newFunctionString is missing -- unexpected error.
void vscode.window.showErrorMessage(`${localize("invalid.edit",
"Extract to function failed. An invalid edit was generated: '{0}'", edit.newText)}`);
continue;
}
const currentEditRange: vscode.Range = new vscode.Range(
new vscode.Position(rangeStartLine, rangeStartCharacter),
new vscode.Position(rangeStartLine, rangeStartCharacter + newFunctionString.length));
if (isReplace) {
replaceEditRange = currentEditRange;
nextLineOffset -= range.end.line - range.start.line;
}
this.renameDataForExtractToFunction.push({ uri, range: currentEditRange });
}
}
if (replaceEditRange === undefined || formatUriAndRanges.length === 0) {
return;
}
// Apply the extract to function text edits.
await vscode.workspace.applyEdit(workspaceEdits);
const firstUri: vscode.Uri = formatUriAndRanges[0].uri;
await vscode.window.showTextDocument(firstUri, { selection: replaceEditRange });
await vscode.commands.executeCommand("editor.action.rename", firstUri, replaceEditRange.start);
// Format the new text edits.
const formatEdits: vscode.WorkspaceEdit = new vscode.WorkspaceEdit();
for (const formatUriAndRange of formatUriAndRanges) {
const settings: OtherSettings = new OtherSettings(vscode.workspace.getWorkspaceFolder(formatUriAndRange.uri)?.uri);
const formatOptions: vscode.FormattingOptions = {
insertSpaces: settings.editorInsertSpaces ?? true,
tabSize: settings.editorTabSize ?? 4
};
const doFormat = async () => {
const versionBeforeFormatting: number | undefined = openFileVersions.get(formatUriAndRange.uri.toString());
if (versionBeforeFormatting === undefined) {
return true;
}
// TODO: Somehow invoke multiple range formatting (see https://github.com/microsoft/vscode/issues/193836).
// Maybe call DocumentRangeFormattingEditProvider.provideDocumentRangesFormattingEdits directly.
const formatTextEdits: vscode.TextEdit[] | undefined = await vscode.commands.executeCommand<vscode.TextEdit[] | undefined>(
"vscode.executeFormatRangeProvider", formatUriAndRange.uri, formatUriAndRange.range, formatOptions);
if (!formatTextEdits || formatTextEdits.length === 0 || versionBeforeFormatting === undefined) {
return true;
}
// Only apply formatting if the document version hasn't changed to prevent
// stale formatting results from being applied.
const versionAfterFormatting: number | undefined = openFileVersions.get(formatUriAndRange.uri.toString());
if (versionAfterFormatting === undefined || versionAfterFormatting > versionBeforeFormatting) {
return false;
}
formatEdits.set(formatUriAndRange.uri, formatTextEdits);
return true;
};
if (!await doFormat())
{
await doFormat(); // Try again;
}
}
if (formatEdits.size > 0) {
await vscode.workspace.applyEdit(formatEdits);
}
}
public renameDataForExtractToFunction: VsCodeUriAndRange[] = [];
public onInterval(): void {
// These events can be discarded until the language client is ready.
// Don't queue them up with this.notifyWhenLanguageClientReady calls.
@@ -3848,7 +3784,7 @@ class NullClient implements Client {
onRegisterCustomConfigurationProvider(provider: CustomConfigurationProvider1): Thenable<void> { return Promise.resolve(); }
updateCustomConfigurations(requestingProvider?: CustomConfigurationProvider1): Thenable<void> { return Promise.resolve(); }
updateCustomBrowseConfiguration(requestingProvider?: CustomConfigurationProvider1): Thenable<void> { return Promise.resolve(); }
provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean, provider?: CustomConfigurationProvider1): Promise<void> { return Promise.resolve(); }
provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean): Promise<void> { return Promise.resolve(); }
logDiagnostics(): Promise<void> { return Promise.resolve(); }
rescanFolder(): Promise<void> { return Promise.resolve(); }
toggleReferenceResultsView(): void { }
@@ -3894,6 +3830,7 @@ class NullClient implements Client {
handleFixCodeAnalysisProblems(workspaceEdit: vscode.WorkspaceEdit, refreshSquigglesOnSave: boolean, identifiersAndUris: CodeAnalysisDiagnosticIdentifiersAndUri[]): Promise<void> { return Promise.resolve(); }
handleDisableAllTypeCodeAnalysisProblems(code: string, identifiersAndUris: CodeAnalysisDiagnosticIdentifiersAndUri[]): Promise<void> { return Promise.resolve(); }
handleCreateDeclarationOrDefinition(isCopyToClipboard: boolean, codeActionRange?: Range): Promise<void> { return Promise.resolve(); }
handleExtractToFunction(extractAsGlobal: boolean): Promise<void> { return Promise.resolve(); }
onInterval(): void { }
dispose(): void {
this.booleanEvent.dispose();
+14 -14
View File
@@ -74,7 +74,7 @@ interface CodeActionAllInfo {
interface CodeAnalysisDiagnosticRelatedInformation {
location: Location;
message: string;
workspaceEdit?: WorkspaceEdit;
workspaceEdits?: WorkspaceEdit[];
}
interface CodeAnalysisDiagnostic {
@@ -83,7 +83,7 @@ interface CodeAnalysisDiagnostic {
severity: vscode.DiagnosticSeverity;
localizeStringParams: LocalizeStringParams;
relatedInformation?: CodeAnalysisDiagnosticRelatedInformation[];
workspaceEdit?: WorkspaceEdit;
workspaceEdits?: WorkspaceEdit[];
}
interface CodeAnalysisDiagnosticIdentifier {
@@ -274,18 +274,18 @@ export function publishCodeAnalysisDiagnostics(params: PublishCodeAnalysisDiagno
kind: vscode.CodeActionKind.QuickFix
}
};
const workspaceEdit: CodeActionWorkspaceEdit = {};
if (d.workspaceEdit) {
workspaceEdit.workspaceEdit = new vscode.WorkspaceEdit();
for (const [uriStr, edits] of Object.entries(d.workspaceEdit.changes)) {
workspaceEdit.workspaceEdit.set(vscode.Uri.parse(uriStr, true), makeVscodeTextEdits(edits));
const codeActionWorkspaceEdit: CodeActionWorkspaceEdit = {};
if (d.workspaceEdits) {
codeActionWorkspaceEdit.workspaceEdit = new vscode.WorkspaceEdit();
for (const workspaceEdit of d.workspaceEdits) {
codeActionWorkspaceEdit.workspaceEdit.set(vscode.Uri.parse(workspaceEdit.file, true), makeVscodeTextEdits(workspaceEdit.edits));
}
const fixThisCodeAction: vscode.CodeAction = {
title: localize("fix.this.problem", "Fix this {0} problem", d.code),
command: {
title: 'FixThisCodeAnalysisProblem',
command: 'C_Cpp.FixThisCodeAnalysisProblem',
arguments: [ nextVersion, workspaceEdit.workspaceEdit, true, [ identifiersAndUri ] ]
arguments: [ nextVersion, codeActionWorkspaceEdit.workspaceEdit, true, [ identifiersAndUri ] ]
},
kind: vscode.CodeActionKind.QuickFix
};
@@ -297,19 +297,19 @@ export function publishCodeAnalysisDiagnostics(params: PublishCodeAnalysisDiagno
const rootAndRelatedWorkspaceEdits: CodeActionWorkspaceEdit[] = [];
const rootAndRelatedIdentifiersAndUris: CodeAnalysisDiagnosticIdentifiersAndUri[] = [];
rootAndRelatedIdentifiersAndUris.push(identifiersAndUri);
if (workspaceEdit.workspaceEdit !== undefined) {
rootAndRelatedWorkspaceEdits.push(workspaceEdit);
if (codeActionWorkspaceEdit.workspaceEdit !== undefined) {
rootAndRelatedWorkspaceEdits.push(codeActionWorkspaceEdit);
}
if (d.relatedInformation) {
diagnostic.relatedInformation = [];
for (const info of d.relatedInformation) {
diagnostic.relatedInformation.push(new vscode.DiagnosticRelatedInformation(makeVscodeLocation(info.location), info.message));
if (info.workspaceEdit === undefined) {
if (info.workspaceEdits === undefined) {
continue;
}
const relatedWorkspaceEdit: vscode.WorkspaceEdit = new vscode.WorkspaceEdit();
for (const [uriStr, edits] of Object.entries(info.workspaceEdit.changes)) {
relatedWorkspaceEdit.set(vscode.Uri.parse(uriStr, true), makeVscodeTextEdits(edits));
for (const workspaceEdit of info.workspaceEdits) {
relatedWorkspaceEdit.set(vscode.Uri.parse(workspaceEdit.file, true), makeVscodeTextEdits(workspaceEdit.edits));
}
const relatedIdentifier: CodeAnalysisDiagnosticIdentifier = { range: info.location.range, code: d.code };
const relatedIdentifiersAndUri: CodeAnalysisDiagnosticIdentifiersAndUri = {
@@ -378,7 +378,7 @@ export function publishCodeAnalysisDiagnostics(params: PublishCodeAnalysisDiagno
docPage = `checks${checksGroup}/${checksPage}.html`;
}
// TODO: This should be checking the clang-tidy version used to better support usage of older versions.
const primaryDocUri: vscode.Uri = vscode.Uri.parse(`https://releases.llvm.org/16.0.0/tools/clang/tools/extra/docs/clang-tidy/${docPage}`);
const primaryDocUri: vscode.Uri = vscode.Uri.parse(`https://releases.llvm.org/17.0.1/tools/clang/tools/extra/docs/clang-tidy/${docPage}`);
diagnostic.code = { value: identifier.code, target: primaryDocUri };
if (new CppSettings().clangTidyCodeActionShowDocumentation) {
+2 -1
View File
@@ -17,5 +17,6 @@ export interface TextEdit {
}
export interface WorkspaceEdit {
changes: { [uri: string]: TextEdit[] };
file: string;
edits: TextEdit[];
}
+13 -22
View File
@@ -13,7 +13,6 @@ import { setTimeout } from 'timers';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import * as which from 'which';
import { IntelliSenseConfiguration } from '../ToolsetDetection/interfaces';
import { logAndReturn, returns } from '../Utility/Async/returns';
import * as util from '../common';
import { isWindows } from '../constants';
@@ -61,16 +60,7 @@ export interface ConfigurationJson {
enableConfigurationSquiggles?: boolean;
}
export interface NewIntelliSense {
/** will trigger the use of the new intellisense when the compiler is set. */
enableNewIntellisense?: boolean;
/** select the compiler for new mode (full path, filename of binary in ${env:PATH} or compiler name (supports wildcards) ) */
compiler?: string;
/** new IntelliSense configuration */
intellisense?: IntelliSenseConfiguration;
}
export interface Configuration extends NewIntelliSense{
export interface Configuration {
name: string;
compilerPathInCppPropertiesJson?: string;
compilerPath?: string;
@@ -772,8 +762,14 @@ export class CppProperties {
}
paths = this.resolveDefaults(paths, defaultValue);
paths.forEach(entry => {
const entries: string[] = util.resolveVariables(entry, env).split(path.delimiter).map(e => glob ? this.resolvePath(e, false) : e).filter(e => e);
resolvedVariables.push(...entries);
const resolvedVariable: string = util.resolveVariables(entry, env);
if (resolvedVariable.includes("env:")) {
// Do not futher try to resolve a "${env:VAR}"
resolvedVariables.push(resolvedVariable);
} else {
const entries: string[] = resolvedVariable.split(path.delimiter).map(e => glob ? this.resolvePath(e, false) : e).filter(e => e);
resolvedVariables.push(...entries);
}
});
if (!glob) {
return resolvedVariables;
@@ -914,12 +910,6 @@ export class CppProperties {
const env: Environment = this.ExtendedEnvironment;
for (let i: number = 0; i < this.configurationJson.configurations.length; i++) {
const configuration: Configuration = this.configurationJson.configurations[i];
if (configuration.compiler) {
// we are going to skip all the magic when it's a new intellisense config
continue;
}
configuration.compilerPathInCppPropertiesJson = configuration.compilerPath;
configuration.compileCommandsInCppPropertiesJson = configuration.compileCommands;
configuration.configurationProviderInCppPropertiesJson = configuration.configurationProvider;
@@ -1503,7 +1493,7 @@ export class CppProperties {
return success;
}
public resolvePath(input_path: string | undefined, replaceAsterisks: boolean = true): string {
private resolvePath(input_path: string | undefined, replaceAsterisks: boolean = true): string {
if (!input_path || input_path === "${default}") {
return "";
}
@@ -1527,8 +1517,9 @@ export class CppProperties {
result = result.replace(/\*/g, "");
}
// Make sure all paths result to an absolute path
if (!path.isAbsolute(result) && this.rootUri) {
// Make sure all paths result to an absolute path.
// Do not add the root path to an unresolved env variable.
if (!result.includes("env:") && !path.isAbsolute(result) && this.rootUri) {
result = path.join(this.rootUri.fsPath, result);
}
@@ -6,7 +6,6 @@
import * as vscode from 'vscode';
import { CustomConfigurationProvider, SourceFileConfigurationItem, Version, WorkspaceBrowseConfiguration } from 'vscode-cpptools';
import { InternalWorkspaceBrowseConfiguration } from './client';
import * as ext from './extension';
import { CppSettings } from './settings';
@@ -19,10 +18,6 @@ export interface CustomConfigurationProvider1 extends CustomConfigurationProvide
readonly version: Version;
}
export function isInternalWorkspaceBrowseConfiguration(config: WorkspaceBrowseConfiguration | null | undefined): config is InternalWorkspaceBrowseConfiguration {
return !!config && ('systemPath' in config || 'userFrameworks' in config || 'systemFrameworks' in config);
}
const oldCmakeToolsExtensionId: string = "vector-of-bool.cmake-tools";
const newCmakeToolsExtensionId: string = "ms-vscode.cmake-tools";
+27 -17
View File
@@ -9,11 +9,10 @@ import * as StreamZip from 'node-stream-zip';
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import { Range } from 'vscode-languageclient';
import * as nls from 'vscode-nls';
import { remote } from '../ToolsetDetection/detection';
import { logAndReturn } from '../Utility/Async/returns';
import * as util from '../common';
import { log } from '../logger';
import { PlatformInformation } from '../platform';
import * as telemetry from '../telemetry';
import { Client, DefaultClient, DoxygenCodeActionCommandArguments, openFileVersions } from './client';
@@ -344,27 +343,18 @@ export async function processDelayedDidOpen(document: vscode.TextDocument): Prom
if (!client.TrackedDocuments.has(document)) {
// If not yet tracked, process as a newly opened file. (didOpen is sent to server in client.takeOwnership()).
client.TrackedDocuments.add(document);
clients.timeTelemetryCollector.setDidOpenTime(document.uri);
// Work around vscode treating ".C" or ".H" as c, by adding this file name to file associations as cpp
if (document.languageId === "c" && shouldChangeFromCToCpp(document)) {
const baseFileName: string = path.basename(document.fileName);
const mappingString: string = baseFileName + "@" + document.fileName;
client.addFileAssociations(mappingString, "cpp");
void client.sendDidChangeSettings();
client.sendDidChangeSettings();
document = await vscode.languages.setTextDocumentLanguage(document, "cpp");
}
// if we are in newIntellisense mode, we have to ensure that the configuration for the file is sent asap.
if (client instanceof DefaultClient && client.isNewIntellisense) {
log(`Processing delayed didOpen for newIntellisense mode: ${document.uri.toString()}`);
await client.sendNewIntellisenseConfigurationForFile(document.uri);
} else {
log(`Processing delayed didOpen for Legacy Mode: ${document.uri.toString()}`);
await client.provideCustomConfiguration(document.uri, undefined);
}
await client.provideCustomConfiguration(document.uri, undefined);
// client.takeOwnership() will call client.TrackedDocuments.add() again, but that's ok. It's a Set.
void client.onDidOpenTextDocument(document);
client.onDidOpenTextDocument(document);
await client.takeOwnership(document);
return true;
}
@@ -447,6 +437,10 @@ export function registerCommands(enabled: boolean): void {
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.CreateDeclarationOrDefinition', enabled ? onCreateDeclarationOrDefinition : onDisabledCommand));
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.CopyDeclarationOrDefinition', enabled ? onCopyDeclarationOrDefinition : onDisabledCommand));
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.RescanCompilers', enabled ? onRescanCompilers : onDisabledCommand));
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ExtractToFunction', enabled ? () => onExtractToFunction(false, false) : onDisabledCommand));
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ExtractToFreeFunction', enabled ? () => onExtractToFunction(true, false) : onDisabledCommand));
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ExtractToMemberFunction', enabled ? () => onExtractToFunction(false, true) : onDisabledCommand));
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ExpandSelection', enabled ? (r: Range) => onExpandSelection(r) : onDisabledCommand));
}
function onDisabledCommand() {
@@ -754,6 +748,25 @@ async function onCreateDeclarationOrDefinition(args?: any): Promise<void> {
return getActiveClient().handleCreateDeclarationOrDefinition(false, args?.range);
}
async function onExtractToFunction(extractAsGlobal: boolean, extractAsMemberFunction: boolean): Promise<void> {
if (extractAsGlobal) {
telemetry.logLanguageServerEvent('ExtractToFreeFunction');
} else if (extractAsMemberFunction) {
telemetry.logLanguageServerEvent('ExtractToMemberFunction');
} else {
telemetry.logLanguageServerEvent('ExtractToFunction');
}
return getActiveClient().handleExtractToFunction(extractAsGlobal);
}
function onExpandSelection(r: Range) {
const activeTextEditor: vscode.TextEditor | undefined = vscode.window.activeTextEditor;
if (activeTextEditor) {
activeTextEditor.selection = new vscode.Selection(new vscode.Position(r.start.line, r.start.character), new vscode.Position(r.end.line, r.end.character));
telemetry.logLanguageServerEvent('ExpandSelection');
}
}
function onAddToIncludePath(path: string): void {
if (isFolderOpen()) {
// This only applies to the active client. It would not make sense to add the include path
@@ -1085,9 +1098,6 @@ function handleMacCrashFileRead(err: NodeJS.ErrnoException | undefined | null, d
}
export function deactivate(): Thenable<void> {
// shutdown the worker thread if it's running
remote.terminate();
clients.timeTelemetryCollector.clear();
console.log("deactivating extension");
telemetry.logLanguageServerEvent("LanguageServerShutdown");
@@ -8,7 +8,6 @@ import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { structuredClone } from '../Utility/System/structuredClone';
import * as util from '../common';
import * as telemetry from '../telemetry';
import * as config from './configurations';
@@ -17,6 +16,10 @@ import { getLocalizedHtmlPath } from './localization';
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
function deepCopy(obj: any) {
return JSON.parse(JSON.stringify(obj));
}
// TODO: share ElementId between SettingsPanel and SettingsApp. Investigate why SettingsApp cannot import/export
const elementId: { [key: string]: string } = {
// Basic settings
@@ -225,7 +228,7 @@ export class SettingsPanel {
}
private updateWebview(configSelection: string[], configuration: config.Configuration, errors: config.ConfigurationErrors | null): void {
this.configValues = structuredClone(configuration); // Copy configuration values
this.configValues = deepCopy(configuration); // Copy configuration values
this.isIntelliSenseModeDefined = this.configValues.intelliSenseMode !== undefined;
if (this.panel && this.initialized) {
void this.panel.webview.postMessage({ command: 'setKnownCompilers', compilers: this.compilerPaths });
+1 -1
View File
@@ -32,7 +32,7 @@ export class TargetLeafNode extends LabelLeafNode {
super(name);
}
override async getTreeItem(): Promise<TreeItem> {
async getTreeItem(): Promise<TreeItem> {
const item: TreeItem = await super.getTreeItem();
const removable: boolean = await isWritable(this.sshConfigHostInfo.file);
if (_activeTarget === this.name) {
+2 -3
View File
@@ -10,10 +10,9 @@ import * as path from 'path';
import {
Configuration, ConfigurationDirective,
ConfigurationEntry,
Type as ConfigurationEntryType,
HostConfigurationDirective,
HostConfigurationDirective, parse,
ResolvedConfiguration,
parse
Type as ConfigurationEntryType
} from 'ssh-config';
import { promisify } from 'util';
import * as vscode from 'vscode';
@@ -1,264 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-dynamic-delete */
import { parse as parseJson } from 'comment-json';
import { readFile } from 'fs/promises';
import { dirname, resolve } from 'path';
import { accumulator } from '../../Utility/Async/iterators';
import { AsyncMap } from '../../Utility/Async/map';
import { FastFinder } from '../../Utility/Filesystem/ripgrep';
import { is } from '../../Utility/System/guards';
import { CustomResolver, evaluateExpression } from '../../Utility/Text/taggedLiteral';
import { DeepPartial, DefinitionFile, IntelliSense, IntelliSenseConfiguration, PartialDefinitionFile, PkgMgr } from '../interfaces';
import { strings } from '../strings';
import { mergeObjects } from './objectMerge';
// iterates recursively over the parsed data and transform all the keys that are just
// identifiers and dots into nested objects
function transform(obj: any): any {
if (typeof obj === 'object' && obj !== null) {
for (const [key, value] of Object.entries(obj)) {
if (is.string(key) && /^[a-zA-Z0-9\$._]+$/.test(key)) {
const parts = key.split('.');
if (parts.length > 1) {
const first = parts.shift()!;
if (!obj[first]) {
obj[first] = {};
}
obj[first][parts.join('.')] = value;
transform(obj[first]);
delete obj[key];
}
}
transform(value);
}
}
return obj;
}
function parse(text: string) {
try {
return transform(parseJson(text));
} catch (e: any) {
if (e.message) {
console.error(e.message);
}
}
return undefined;
}
function isToolsetDefinition(definition: any): definition is DefinitionFile {
// stub for now - we can add a schema validator once we're sure the schema is stable
return true;
}
function isPartialToolsetDefinition(definition: any): definition is DefinitionFile {
// stub for now - we can add a schema validator once we're sure the schema is stable
return true;
}
const compilerDefintions = new AsyncMap<string, DefinitionFile>();
const partialDefinitions = new AsyncMap<string, PartialDefinitionFile>();
export function formatIntelliSenseBlock<T extends DeepPartial<IntelliSenseConfiguration> | DeepPartial<IntelliSense>>(intellisense?: T): T {
if (!intellisense) {
return {} as T;
}
const p = intellisense.path = intellisense.path || {};
// expand out the include paths
p.quoteInclude = strings(p.quoteInclude);
p.include = strings(p.include);
p.systemInclude = strings(p.systemInclude);
p.builtInInclude = strings(p.builtInInclude);
p.afterInclude = strings(p.afterInclude);
p.externalInclude = strings(p.externalInclude);
p.framework = strings(p.framework);
p.environmentInclude = strings(p.environmentInclude);
p.forcedIncludeFile = strings(p.forcedIncludeFile);
intellisense.parserArgument = strings(intellisense.parserArgument);
intellisense.compilerArg = strings(intellisense.compilerArg);
intellisense.macro = intellisense.macro || {};
for (const [key, value] of Object.entries(intellisense)) {
if (key.startsWith('message') || key.startsWith('remove')) {
// replace with strings array
(intellisense as any)[key] = strings(value);
}
}
return intellisense;
}
/** coerce the collections from OneOrMore<*> to Array<*> in the defintion */
function formatDefinitionBlock(definition: DefinitionFile) {
// definition.intellisense.* members
formatIntelliSenseBlock(definition.intellisense);
// definition.package.* = strings(definition.package.*);
if (definition.package) {
for (const key of Object.keys(definition.package)) {
definition.package[key as PkgMgr] = strings(definition.package[key as PkgMgr]);
}
}
// definition.analysis.* members
if (definition.analysis) {
for (const [key, value] of Object.entries(definition.analysis)) {
if (key.startsWith('task')) {
// replace with strings array
(definition.analysis as any)[key] = strings(value);
}
}
}
if (definition.discover) {
definition.discover.binary = strings(definition.discover.binary);
definition.discover.locations = strings(definition.discover.locations);
}
}
async function loadDefinition(definitionFile: string): Promise<DefinitionFile | undefined> {
return compilerDefintions.getOrAdd(definitionFile, async () => {
try {
const definition = parse(await readFile(definitionFile, 'utf8'));
if (!isToolsetDefinition(definition)) {
console.error(`The definition file ${definitionFile} is not a valid toolset definition.`);
return;
}
formatDefinitionBlock(definition);
if (definition.import) {
const files = strings(definition.import);
for (const file of files) {
// there should be a partial definition file that matches this expression
const partialFile = resolve(dirname(definitionFile), file);
await loadPartialDefinition(partialFile);
if (partialDefinitions.has(partialFile)) {
const partial = partialDefinitions.get(partialFile)!;
if (!isPartialToolsetDefinition(partial)) {
continue;
}
mergeObjects(definition, partial);
formatDefinitionBlock(definition);
}
}
}
if (definition.conditions) {
// eslint-disable-next-line prefer-const
for (let [expression, part] of Object.entries(definition.conditions)) {
if (is.string(part) || is.array(part)) {
const files = strings(part);
part = {};
for (const file of files) {
// there should be a partial definition file that matches this expression
const partialFile = resolve(dirname(definitionFile), file);
await loadPartialDefinition(partialFile);
if (partialDefinitions.has(partialFile)) {
const partial = partialDefinitions.get(partialFile)!;
if (!isPartialToolsetDefinition(partial)) {
continue;
}
mergeObjects(part, partial);
formatDefinitionBlock(definition);
}
}
}
if (isPartialToolsetDefinition(part)) {
// replace the location with the contents
definition.conditions[expression] = part;
}
}
}
compilerDefintions.set(definitionFile, definition);
return definition;
} catch (e: any) {
if (e.message) {
console.warn(`Error loading compiler definition file: ${definitionFile} - ${e.message}`);
}
}
compilerDefintions.delete(definitionFile);
return undefined;
});
}
async function loadPartialDefinition(definitionFile: string) {
if (!partialDefinitions.has(definitionFile)) {
const definition = parse(await readFile(definitionFile, 'utf8'));
if (!isPartialToolsetDefinition(definition)) {
console.error(`Error loading partial compiler definition file: ${definitionFile} - Invalid definition file.`);
return;
}
if (definition.import) {
const files = strings(definition.import);
for (const file of files) {
// there should be a partial definition file that matches this expression
const partialFile = resolve(dirname(definitionFile), file);
await loadPartialDefinition(partialFile);
if (partialDefinitions.has(partialFile)) {
const partial = partialDefinitions.get(partialFile)!;
if (!isPartialToolsetDefinition(partial)) {
continue;
}
mergeObjects(definition, partial);
formatDefinitionBlock(definition);
}
}
}
partialDefinitions.set(definitionFile, definition);
}
}
export function resetCompilerDefinitions() {
compilerDefintions.clear();
partialDefinitions.clear();
}
export async function* loadCompilerDefinitions(configurationFolders: Set<string>): AsyncIterable<DefinitionFile> {
// find all the definition files in the specified configuration folders.
const result = accumulator<DefinitionFile>();
const definitionFiles = new FastFinder(['toolset.*.json']).scan(...configurationFolders);
const all = [];
for await (const file of definitionFiles) {
all.push(loadDefinition(file).then(each => result.add(each)));
}
void Promise.all(all).then(() => result.complete());
yield* result;
}
export async function runConditions(definition: DefinitionFile, resolver: CustomResolver): Promise<boolean> {
let conditionsRan = false;
if (definition.conditions) {
for (const [expression, part] of Object.entries(definition.conditions)) {
if (await evaluateExpression(expression, definition, resolver)) {
// the condition is true!
// which means something changed...
conditionsRan = true;
// remove the condition from the definition so we don't re-run it
delete definition.conditions[expression];
// merge the part into the main document
mergeObjects(definition, part as any);
formatDefinitionBlock(definition);
// we should also run the conditions again, in case the new definition has more conditions
await runConditions(definition, resolver);
}
}
return conditionsRan;
}
return false;
}
@@ -1,450 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as os from 'os';
import { basename, resolve } from 'path';
import { rcompare } from 'semver';
import { accumulator } from '../../Utility/Async/iterators';
import { ManualPromise } from '../../Utility/Async/manualPromise';
import { sleep, then } from '../../Utility/Async/sleep';
import { filepath, filterToFolders, pathsFromVariable } from '../../Utility/Filesystem/filepath';
import { FastFinder, ripGrep } from '../../Utility/Filesystem/ripgrep';
import { Cache } from '../../Utility/System/cache';
import { is } from '../../Utility/System/guards';
import { structuredClone } from '../../Utility/System/structuredClone';
import { verbose } from '../../Utility/Text/streams';
import { render } from '../../Utility/Text/taggedLiteral';
import { isWindows } from '../../constants';
import { DefinitionFile, IntelliSense, IntelliSenseConfiguration } from '../interfaces';
import { getActions, strings } from '../strings';
import { loadCompilerDefinitions, resetCompilerDefinitions, runConditions } from './definition';
import { createResolver } from './resolver';
import { Toolset, loadToolsetData, persistToolsetData, settings } from './toolset';
import escapeStringRegExp = require('escape-string-regexp');
let initialized: ManualPromise | undefined;
const discoveringInProgress = new Map<DefinitionFile, Promise<void>>();
let discovering: Promise<any> | undefined;
const configurationFolders = new Set<string>();
const cache = new Cache<Record<string, string>>();
async function searchInsideBinary(compilerPath: string, rx: string | Promise<string>) {
if (is.promise(rx)) {
rx = await rx;
}
return cache.getOrAdd(compilerPath + rx, async () => {
for await (const match of ripGrep(compilerPath, rx as string, { binary: true, ignoreCase: true })) {
const rxResult = new RegExp(rx as string, 'i').exec(match.lines.text.replace(/\0/g, ''));
if (rxResult) {
return rxResult.groups || {};
}
}
return undefined;
});
}
async function discover(compilerPath: string, definition: DefinitionFile): Promise<Toolset | undefined> {
// normalize the path separators to be forward slashes.
compilerPath = resolve(compilerPath);
let toolset = settings.discoveredToolsets.get(compilerPath);
if (toolset) {
return toolset;
}
// toolset was not previously discovered for this binary, so, discover it now.
// clone the definition so it can be modified without affecting the original
definition = structuredClone(definition);
// create toolset object for the result.
toolset = new Toolset(compilerPath, definition);
const intellisense = definition.intellisense as IntelliSense;
const requirements = getActions<Record<string, IntelliSenseConfiguration>>(definition.discover as any, [
['match', ['optional', 'priority', 'oneof']],
['expression', ['oneof', 'optional', 'priority', 'folder', 'file']]
]);
nextBlock:
for (const { action, block, flags } of requirements) {
switch (action) {
case 'match':
// valid flags : 'oneof', 'optional'
if (flags.has('oneof')) {
// run them in parallel, but take the first winning result in order
for (const [rawRx, isense] of Object.entries(block)) {
const result = await searchInsideBinary(compilerPath, render(rawRx, {}, toolset.resolver));
if (result) {
await toolset.applyToConfiguration(toolset.default, isense, result);
// first one wins, exit the block
// await Promise.all(results); // wait for all the results to complete?
continue nextBlock;
}
}
// if this is optional, we can move to the next entry
if (flags.has('optional')) {
continue nextBlock;
}
// if we got here, none matched, so this whole toolset is not a match
return;
} else {
for (const [rawRx, isense] of Object.entries(block)) {
const r = await searchInsideBinary(compilerPath, render(rawRx, {}, toolset.resolver));
if (r) {
await toolset.applyToConfiguration(toolset.default, isense, r);
continue;
}
// not found, but not a problem
if (flags.has('optional')) {
continue;
}
// not found, and not optional, so this whole toolset is not a match
return;
}
}
break;
case 'expression':
// verifies that the expression is true
// valid flags : 'oneof', 'optional', 'priority', 'folder', 'file'
for (const [expr, isense] of Object.entries(block)) {
const value = await render(expr, {}, toolset.resolver);
if (value) {
if (flags.has('folder')) {
if (await filepath.isFolder(value)) {
await toolset.applyToConfiguration(intellisense, isense);
if (flags.has('oneof')) {
// first one wins, exit the block
continue nextBlock;
}
// a success, move to the next entry
continue;
}
// not a match
if (flags.has('optional') || flags.has('oneof')) {
// didn't find it, but it's optional (or we can still find a match later?), so we can move to the next entry
continue;
}
// should be a folder match, and not optional. this toolset is not a match
return;
}
if (flags.has('file')) {
if (await filepath.isFile(value)) {
await toolset.applyToConfiguration(intellisense, isense);
if (flags.has('oneof')) {
// first one wins, exit the block
continue nextBlock;
}
// a success, move to the next entry
continue;
}
// not a match
if (flags.has('optional') || flags.has('oneof')) {
// didn't find it, but it's optional (or we can still find a match later?), so we can move to the next entry
continue;
}
// should be a file match, and not optional. this toolset is not a match
return;
}
// it's a truthy value, so it's a match
await toolset.applyToConfiguration(intellisense, isense);
if (flags.has('oneof')) {
// first one wins, exit the block
continue nextBlock;
}
// a success, move to the next entry
continue;
}
// we didn't get a match
if (flags.has('optional') || flags.has('oneof')) {
// didn't find it, but it's optional (or we can still find a match later?), so we can move to the next entry
continue;
}
// no match, the whole toolset is not a match
return;
}
break;
}
}
settings.discoveredToolsets.set(compilerPath, toolset);
void persistToolsetData();
return toolset;
}
async function getWellKnownBinariesFromPath() {
// create the finder
const finder = new FastFinder(['cl'], { executable: true, executableExtensions: ['.exe'] });
// start scanning the folders in the $PATH
finder.scan(...await filterToFolders(pathsFromVariable('PATH')));
for await (const compilerPath of finder) {
await identify(compilerPath);
}
}
/**
* This will search for toolsets based on the definitions
* Calling this forces a reset of the compiler definitions and discovered toolsets -- ideally this shouldn't need to be called
* more than the initial time
*/
export async function initialize(configFolders: string[], options?: { quick?: boolean; storagePath?: string }) {
if (initialized) {
// wait for an existing initialize to complete
await initialized;
}
initialized = new ManualPromise();
const forceReset = !options?.quick;
settings.globalStoragePath = options?.storagePath;
if (forceReset) {
// if initialize is called more than once, we need to reset the compiler definitions and list of discovered toolsets
// (options.quick=true should only be used with tests)
resetCompilerDefinitions();
settings.discoveredToolsets.clear();
discoveringInProgress.clear();
}
// add the configuration folders to the list of folders to scan
configFolders.forEach(each => configurationFolders.add(each));
await loadToolsetData();
// if we have zero entries, then we're going to prioritize finding well known compilers on the PATH.
if (settings.discoveredToolsets.size === 0) {
try {
await getWellKnownBinariesFromPath();
} catch {
// ignore any failures during this process
}
}
initialized.resolve();
// find the well-known compilers on the path
if (forceReset) {
// we kick off the discovery in the background but we wait
// a few seconds to give the intelliSense engine an opportunity to start up
// and perhaps get a few requests for previously discovered toolsets.
// but we really do want to start the discovery so that it's in progress in the background
// for the next time it's needed.
void sleep(5000).then(() => getToolsets());
}
}
/**
* Async scan for all compilers using the definitions (toolset.*.json) in the given folders
* (iterate over this with `for await`)
*
* UNUSED-- TARGET FOR DELETION
* /
export async function* detectToolsets(): AsyncIterable<Toolset> {
const results = accumulator<Toolset>();
for await (const definition of loadCompilerDefinitions(configurationFolders)) {
results.add(searchForToolsets(definition));
}
results.complete();
yield* results;
}
*/
/** Returns the discovered toolsets all at once
*
* If the discovery has been done before, it will just return the cached results.
* If it hasn't, it will run the discovery process and then return all the results.
*
* To reset the cache, call initialize() before calling this.
*/
export async function getToolsets() {
if (!initialized) {
throw new Error('Compiler detection has not been initialized. Call initialize() before calling this.');
}
// ensure that init is done
await initialized;
// this exponentially/asychnronously searches for toolsets using the configuration folders
for await (const definition of loadCompilerDefinitions(configurationFolders)) {
// have we started searching with this definition yet?
const searching = discoveringInProgress.get(definition);
// yeah, we're already searching, so skip this one
if (is.promise(searching)) {
continue;
}
// nope, we haven't started searching yet, so start it now
discoveringInProgress.set(definition, then(async () => {
for await (const toolset of searchForToolsets(definition)) {
if (toolset) {
verbose(`Detected Compiler ${toolset.name}`);
}
}
}));
}
// wait for the inProgress searches to complete
discovering = Promise.all(discoveringInProgress.values());
await discovering;
// return the results
return settings.discoveredToolsets;
}
function lookupToolset(name: string) {
// simple lookup first
const result = settings.discoveredToolsets.get(name);
if (result) {
return result;
}
// if the name isn't wildcarded, and it's not a full path, then we just look at the filenames
if (name.match(/[\\\/*?]/) === null) {
for (const toolset of settings.discoveredToolsets.values()) {
if (name === basename(toolset.compilerPath)) {
return toolset;
}
}
}
// check if the candidate is a name of a toolset (* AND ? are supported)
const rx = new RegExp(escapeStringRegExp(name).replace(/\\\*/g, '.*'));
// iterate over the discovered toolsets starting with the highest versions
for (const toolset of [...settings.discoveredToolsets.values()].sort((a, b) => rcompare(a.version ?? "0.0.0", b.version ?? "0.0.0"))) {
// return the first match given the regex
if (rx.exec(toolset.name)) {
return toolset;
}
}
}
const identifyInProgress = new Map<string, Promise<Toolset | undefined>>();
/**
* Given a path to a binary, identify the compiler
* @param candidate the path to the binary to identify
* @returns a Toolset or undefined.
*/
export async function identifyToolset(candidate: string): Promise<Toolset | undefined> {
if (!initialized) {
throw new Error('Compiler detection has not been initialized. Call initialize() before calling this.');
}
await initialized;
// quick check if the given path is already in the discovered toolsets
const toolset = lookupToolset(candidate);
if (toolset) {
return toolset;
}
// check if we're already identifying this candidate
if (identifyInProgress.get(candidate)) {
return identifyInProgress.get(candidate);
}
// set this candidate to in-progress.
const promise = new ManualPromise<Toolset | undefined>();
identifyInProgress.set(candidate, promise);
// get file info for the candidate (is it even a file?)
const fileInfo = await filepath.info(candidate);
if (!fileInfo?.isFile) {
// if it's not a file let's quickly check for a match in the discovered toolsets
const toolset = lookupToolset(candidate);
if (toolset) {
return promise.resolve(toolset);
}
// we didn't find it, but the discovery may not be done yet, (or hasn't been done).
// make sure discovery is complete before doing another lookup.
await (is.promise(discovering) ? discovering : getToolsets());
return promise.resolve(lookupToolset(candidate));
}
if (fileInfo.isExecutable) {
// otherwise, let's use the definitions to try to identify it.
return identify(candidate).then((result) => promise.resolve(result));
}
// otherwise...
return promise.resolve(undefined);
}
async function identify(candidate: string, name?: string): Promise<Toolset | undefined> {
const bn = basename(candidate);
for await (const definition of loadCompilerDefinitions(configurationFolders)) {
if (!name || definition.name === name) {
const resolver = createResolver(definition);
await runConditions(definition, resolver);
if (strings(definition.discover.binary).includes(basename(bn, isWindows ? '.exe' : undefined))) {
const toolset = await discover(candidate, definition);
if (toolset) {
return toolset;
}
}
}
}
return undefined;
}
/** Given a specific definition file, detect a compiler
*
* If a path to candidate is passed in then we will only check that path.
*
* Otherwise, it will scan the $PATH, $ProgramFiles* and locations specified in the definition file.
*/
async function* searchForToolsets(definition: DefinitionFile): AsyncIterable<Toolset | undefined> {
// run the conditions once before we start.
const resolver = createResolver(definition);
await runConditions(definition, resolver);
// create the finder
const finder = new FastFinder(strings(definition.discover.binary), { executable: true, executableExtensions: ['.exe'] });
// start scanning the folders in the $PATH
finder.scan(...await filterToFolders(pathsFromVariable('PATH')));
// add any folders that the definition specifies (expand any variables)
finder.scan(10, ...await render(strings(definition.discover.locations), {}, resolver));
// add any platform folders
switch (os.platform()) {
case 'win32':
finder.scan(10, ...['ProgramFiles', 'ProgramW6432', 'ProgramFiles(x86)', 'ProgramFiles(Arm)'].map(each => process.env[each]).filter(each => each) as string[]);
break;
case 'linux':
finder.scan(10, '/usr/lib/');
break;
case 'darwin':
break;
}
const results = accumulator<Toolset>();
// kick off each discovery asynchronously
for await (const compilerPath of finder) {
results.add(discover(compilerPath, definition));
}
results.complete();
// return them as they complete.
yield* results;
}
@@ -1,126 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
/* eslint-disable @typescript-eslint/no-dynamic-delete */
import { is } from '../../Utility/System/guards';
import { structuredClone } from '../../Utility/System/structuredClone';
import { strings } from '../strings';
function isMergeble(item: any): boolean {
return item !== null && typeof item === 'object' && !is.array(item);
}
export function replaceOrInsert(original: Record<string, any>, key: string, value: any): Record<string, any> {
return Object.keys(original).reduce((result, existingKey, index) => {
if (!index) {
result[key] = value;
}
if (existingKey !== key) {
result[existingKey] = original[existingKey];
}
return result;
}, {} as Record<string, any>);
}
function expandArray(value: any): any {
return is.array(value) ? value.map(each => expandArray(each)).flat() : is.string(value) && value.includes('\u0007') ? value.split('\u0007') : value;
}
export function mergeObjects<T extends Record<string, any>>(input: T, dataToMerge: Record<string, any>, options?: {uniqueArrays?: boolean}): T {
const target: any = input;
const uniqueArrays = options?.uniqueArrays ?? false;
if (is.promise(input) || is.promise(dataToMerge)) {
throw new Error('Should not get promises here!');
}
if (isMergeble(target) && isMergeble(dataToMerge)) {
for (let [key, value] of Object.entries(dataToMerge)) {
if (key === '') {
continue;
}
if (key.startsWith('remove:')) {
key = key.substring(7);
if (target[key]) {
const v: string[] = strings(value);
if (is.array(target[key])) {
target.key[key] = target[key].filter((each: string) => !v.includes(each));
} else if (is.string(target[key]) && v.includes(target[key])) {
delete target[key];
}
}
continue;
}
const prepend: boolean = key.startsWith('prepend:');
if (prepend) {
key = key.substring(8);
}
// if this is supposed to be an array, lets expand it now.
value = expandArray(value);
// if there isn't a target value, just assign a copy of the source value
if (target[key] === undefined) {
target[key] = structuredClone(value);
continue;
}
if (target[key] === value) {
//* console.log(`Same Same -> '${key}' => '${value}'`);
continue;
}
// if the source value is null, we're going to delete the target value
if (value === null) {
delete target[key];
continue;
}
// if the source value is empty/undefined, we're going to leave the target value as is
if (value === undefined) {
continue;
}
// if the source value is an array, the target is going to be an array.
if (is.array(value)) {
// arrays are appended
if (target[key] === undefined) {
// no target value, just assign
target[key] = [...value];
} else if (is.array(target[key])) {
// target value is an array, append or prepend
if (prepend) {
target[key].unshift(...value);
} else {
target[key].push(...value);
}
if (uniqueArrays) {
target[key] = [...new Set(target[key])];
}
} else if (is.string(target[key])) {
// strings are converted to arrays
target[key] = [value, ...value];
}
continue;
}
// if the source value is an object, we're going to merge that with the target
if (isMergeble(value)) {
mergeObjects(target[key], value, { uniqueArrays : key === 'path' });
continue;
}
// otherwise,
target[key] = value;
}
}
return target;
}
@@ -1,101 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as os from 'os';
import { homedir } from 'os';
import { basename, delimiter, sep } from 'path';
import { Cache } from '../../Utility/System/cache';
import { readKey } from '../../Utility/System/registry';
import { CustomResolver } from '../../Utility/Text/taggedLiteral';
import { DefinitionFile, IntelliSenseConfiguration } from '../interfaces';
export function createResolver(definition: DefinitionFile, compilerPath: string = ''): CustomResolver {
// cache values/registry reads for the duration of the resolver.
// (that is, scoped to the current definition)
// this will drastically speed up resolution if an expensive variable is used repeatedly.
const valueCache = new Cache<any>();
// the resolver function
return async (prefix: string, expression: string): Promise<string> => {
const cacheKey = `${prefix}:${expression}`;
const cached = valueCache.get(cacheKey);
if (cached) {
return cached;
}
function cache(value: any) {
return valueCache.set(cacheKey, value);
}
switch (prefix) {
case 'env':
// make sure ${env:HOME} is expanded to the user's home directory always
if (expression.toLowerCase() === 'home') {
return cache(homedir());
}
return cache(process.env[expression] || '');
case 'definition':
return cache((definition as any)[expression] || '');
case 'HKLM':
case 'HKCU':
const [path, value] = expression.split(';');
return cache((await readKey(prefix, path))?.properties[value]?.toString() || '');
case 'host':
switch (expression) {
case 'os':
case 'platform':
return cache(os.platform());
case 'arch':
case 'architecture':
return cache(os.arch());
}
break;
case 'compilerPath':
switch (expression) {
case 'basename':
return cache(process.platform === 'win32' ? basename(compilerPath, '.exe') : basename(compilerPath));
}
break;
case '':
switch (expression) {
case 'cwd':
return cache(process.cwd()); // fake, this should come from the host.
case 'pathSeparator':
return sep;
case 'pathDelimiter':
return delimiter;
case 'name':
return definition.name;
case 'binary':
case 'compilerPath':
return compilerPath;
default:
// if the request was looking for a value in the intellisense configuration, we'll try to resolve that
if (definition.intellisense && expression in definition.intellisense) {
return cache((definition.intellisense as any)[expression as keyof IntelliSenseConfiguration]);
}
}
break;
default:
if (definition.intellisense?.[expression]) {
return definition.intellisense[expression];
}
return '';
}
return '';
};
}
@@ -1,621 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable prefer-const */
import { unlinkSync, writeFileSync } from 'fs';
import { readFile, writeFile } from 'fs/promises';
import { delimiter, dirname, resolve } from 'path';
import { filepath, mkdir, tmpFile } from '../../Utility/Filesystem/filepath';
import { Command, CommandFunction, cmdlineToArray } from '../../Utility/Process/program';
import { is } from '../../Utility/System/guards';
import { CustomResolver, evaluateExpression, recursiveRender, render } from '../../Utility/Text/taggedLiteral';
import { formatIntelliSenseBlock } from './definition';
import { parse } from 'comment-json';
import { sleep } from '../../Utility/Async/sleep';
import { Cache, isExpired, isLater } from '../../Utility/System/cache';
import { structuredClone } from '../../Utility/System/structuredClone';
import { CStandard, CppStandard, DeepPartial, DefinitionFile, IntelliSense, IntelliSenseConfiguration, Language, OneOrMore } from '../interfaces';
import { getActions, strings } from '../strings';
import { mergeObjects } from './objectMerge';
import { createResolver } from './resolver';
import { log } from './worker';
function isC(language?: string): boolean {
return language === 'c';
}
function isCpp(language?: string): boolean {
return language === 'cpp' || language === 'c++';
}
export let settings = {
globalStoragePath: undefined as string | undefined,
discoveredToolsets: new Cache<Toolset>(Cache.OneWeek),
get toolsetFilePath() {
return settings.globalStoragePath ? resolve(settings.globalStoragePath, 'detected-toolsets.json') : '';
},
timestamp: 0
};
/** Trims out empty elements during serialization to JSON */
function trim(key: string, value: any) {
if (is.array(value) && value.length === 0) {
// empty arrays
return undefined;
}
if (is.object(value) && Object.keys(value).length === 0) {
// empty objects
return undefined;
}
return value;
}
export async function persistToolsetData() {
// we can only store data if the globalStoragePath is set
// eslint-disable-next-line no-constant-condition
if (1 !== 1 && settings.globalStoragePath) {
// ensure the folder is created
await mkdir(settings.globalStoragePath);
// check to see if we have a file already
const info = await filepath.info(settings.toolsetFilePath);
if (info?.isFile && info.timestamp > settings.timestamp) {
// we do have a file, and it's newer than the last time we wrote to it.
// so we should merge that data before we write to it.
await loadToolsetData();
}
// ok, serialize out the data we have
const contents = {} as Record<string, [number, Record<string, any>]>;
for (const [path, [timeout, toolset]] of settings.discoveredToolsets.cacheEntries()) {
contents[path] = [timeout, toolset.serialize()];
}
log(`persisting ${Object.keys(contents).length} toolsets to ${settings.toolsetFilePath}`);
// write out the file
await writeFile(settings.toolsetFilePath, JSON.stringify(contents, trim));
// set our timestamp to now
settings.timestamp = Date.now();
}
}
function mergeToolsetData(newer: Toolset, older: Toolset) {
for (const [key, [timeout, query]] of older.cachedQueries.cacheEntries()) {
newer.cachedQueries.set(key, query, timeout);
}
for (const [key, [timeout, analysis]] of older.cachedAnalysis.cacheEntries()) {
newer.cachedAnalysis.set(key, analysis, timeout);
}
return newer;
}
export async function loadToolsetData() {
if (!settings.globalStoragePath) {
log(`GlobalStoragePath not set - can't load toolsets`);
return false;
}
const location = resolve(settings.globalStoragePath, 'detected-toolsets.json');
const cachePath = await filepath.isFile(location);
if (!cachePath) {
log(`No cached toolset file at ${location} - can't load toolsets`);
return false;
}
log(`loading toolsets from ${settings.toolsetFilePath}`);
const entries = parse(await readFile(cachePath, 'utf8')) as Record<string, any>;
if (!is.object(entries)) {
return false;
}
for (const [path, [timeout, serializedToolset]] of Object.entries(entries)) {
// in the event that something throws, we'll skip it an move on.
try {
// if the entry is expired, skip it completely.
if (isExpired(timeout)) {
continue;
}
// if we have one, let's see if it's newer than the one we have.
const current = settings.discoveredToolsets.getCacheEntry(path);
if (!current) {
// we don't have one currently, so let's just deserialize this one
const toolset = settings.discoveredToolsets.set(path, await Toolset.deserialize(serializedToolset), timeout);
log(`Loaded toolset: ${toolset?.name}`);
continue;
}
// there is a current one.
// ok, merge the current one and the deserialized one, and then set the cache to the merged one.
let toolset = await Toolset.deserialize(serializedToolset);
log(`Loaded toolset (merging): ${toolset?.name}`);
if (toolset) {
if (isLater(timeout, current[0])) {
// merge the current one into the deserialized one
settings.discoveredToolsets.set(path, mergeToolsetData(toolset, current[1]), timeout);
} else {
// merge the deserialized one into the current one
settings.discoveredToolsets.set(path, mergeToolsetData(current[1], toolset), timeout);
}
}
} catch {
// ignore deserialization failures
}
return true;
}
}
type Entries = {
action: string;
block: Record<string, IntelliSenseConfiguration>;
flags: Map<string, string | boolean>;
priority: number;
comment?: string | undefined;
}[];
/**
* The Toolset is the final results of the [discovery+query] process
*
* This is the contents that we're going to eventually pass to the back end.
*/
export class Toolset {
cachedQueries = new Cache<string>(Cache.OneWeek);
cachedAnalysis = new Cache<IntelliSenseConfiguration>(Cache.OneWeek);
resolver: CustomResolver;
cmd: Promise<CommandFunction>;
rxResolver: (prefix: string, expression: string) => any;
get default() {
return this.definition.intellisense as IntelliSense;
}
get version() {
return this.definition.version || this.definition.intellisense?.version;
}
get name() {
return `${this.definition.name}/${this.version}/${this.default.architecture}/${this.default.hostArchitecture || process.arch}`;
}
serialize() {
return {
name: this.name,
compilerPath: this.compilerPath,
definition: this.definition,
queries: this.cachedQueries.cacheEntries(),
analysis: this.cachedAnalysis.cacheEntries()
};
}
static async deserialize(obj: Record<string, any>) {
try {
const { compilerPath, definition, queries, analysis } = obj;
const result = new Toolset(compilerPath, definition);
result.cachedQueries = new Cache(queries, Cache.OneWeek);
result.cachedAnalysis = new Cache(analysis, Cache.OneWeek);
return result;
} catch {
return undefined;
}
}
constructor(readonly compilerPath: string, readonly definition: DefinitionFile) {
this.resolver = createResolver(definition, compilerPath);
this.definition.intellisense = this.definition.intellisense || {};
this.cmd = new Command(this.compilerPath, { env: { PATH: `${dirname(this.compilerPath)}${delimiter}${process.env.PATH}` } });
this.rxResolver = async (prefix: string, expression: string) => {
if (!prefix) {
switch (expression.toLowerCase()) {
case '-/':
case '/-':
return '[\\-\\/]';
case 'key':
case 'keynovalue':
return '(?<key>[^=]+)';
case 'value':
return '(?<value>.+)';
case 'keyequalsvalue':
return '(?<key>[^=]+)=(?<value>.+)';
}
}
return this.resolver(prefix, expression);
};
}
async applyToConfiguration(intellisenseConfiguration: IntelliSenseConfiguration | IntelliSense, partial: DeepPartial<IntelliSenseConfiguration>, data: Record<string, any> = intellisenseConfiguration) {
mergeObjects(intellisenseConfiguration, await recursiveRender(formatIntelliSenseBlock(partial), data, this.resolver));
}
async query(command: string, queries: Record<string, DeepPartial<IntelliSenseConfiguration>>, intellisenseConfiguration: IntelliSenseConfiguration) {
// check if we've handled this command before.
const key = await render(command, {}, this.resolver);
let text = this.cachedQueries.get(key);
if (!text) {
// prepare the command to run
const cmd = await this.cmd;
const tmpFiles = new Array<string>();
let stdout = '';
let stderr = '';
const commandLine = await render(command, {}, async (prefix, expression) => {
if (prefix === 'tmp') {
// creating temp files
const tmp = tmpFile('tmp.', `.${expression}`);
writeFileSync(tmp, '');
tmpFiles.push(tmp);
switch (expression) {
case 'stdout':
stdout = tmp;
break;
case 'stderr':
stderr = tmp;
break;
}
return tmp;
}
return this.resolver(prefix, expression);
});
// parse the arguments and replace any tmp files with actual files
const args = cmdlineToArray(commandLine);
// execute the command line now.
const out = await cmd(...args);
text = [...out.stdio.all(), ...out.error.all()].join('\n');
if (stdout) {
text += await readFile(stdout, 'utf8');
}
if (stderr) {
text += await readFile(stderr, 'utf8');
}
for (const tmp of tmpFiles) {
text = text!.replace(new RegExp(tmp, 'g'), '');
unlinkSync(tmp);
}
this.cachedQueries.set(key, text);
void persistToolsetData();
}
// now we can process the queries
for (const [rxes, isense] of Object.entries(queries)) {
for (const rx of strings(rxes)) {
for (const match of [...text.matchAll(new RegExp(rx, 'gm'))]) {
if (match?.groups) {
// transform multi-line values into arrays
const data = {} as Record<string, any>;
for (let [variable, value] of Object.entries(match.groups)) {
value = value || '';
data[variable] = value.includes('\n') ?
value.split('\n').map(each => each.trim()).filter(each => each) :
value;
}
await this.applyToConfiguration(intellisenseConfiguration, isense, data);
}
}
}
}
}
async runTasks(block: OneOrMore<string>, commandLineArgs: string[]) {
for (const task of strings(block)) {
switch (task) {
case 'inline-environment-variables':
const CL = process.env.CL;
const _CL_ = process.env['_CL_'];
if (CL) {
commandLineArgs.push(...cmdlineToArray(CL));
}
if (_CL_) {
commandLineArgs.unshift(...cmdlineToArray(_CL_));
}
break;
case 'inline-response-file':
// scan thru the command line arguments and look for @file
// and replace it with the contents of the file
for (let i = 0; i < commandLineArgs.length; i++) {
if (commandLineArgs[i].startsWith('@')) {
const file = commandLineArgs[i].slice(1);
const contents = await readFile(file, 'utf8');
commandLineArgs.splice(i, 1, ...cmdlineToArray(contents));
}
}
break;
case 'consume-lib-path':
break;
case 'remove-linker-arguments':
const link = commandLineArgs.findIndex(each => /^[\/-]link$/i.exec(each));
if (link !== -1) {
commandLineArgs.length = link; // drop it and all that follow
}
break;
case 'zwCommandLineSwitch':
break;
case 'experimentalModuleNegative':
break;
case 'verifyIncludes':
break;
}
}
}
async processComamndLineArgs(block: Record<string, any>, commandLineArgs: string[], intellisenseConfiguration: IntelliSenseConfiguration, flags: Map<string, any>) {
// get all the regular expressions and the results to apply
let allEngineeredRegexes: [RegExp[], any][] = [];
for (const [engineeredRx, result] of Object.entries(block)) {
const rxes: RegExp[] = [];
for (const rx of engineeredRx.split(';')) {
rxes.push(new RegExp(await render(`^${rx}$`, {}, this.rxResolver)));
}
allEngineeredRegexes.push([rxes, result]);
}
const keptArgs = new Array<string>();
nextArg:
while (commandLineArgs.length) {
nextRx:
for (const [engineeredRegexSet, isense] of allEngineeredRegexes) {
const capturedData = {};
for (const result of engineeredRegexSet.map((rx, index) => rx.exec(commandLineArgs[index]))) {
if (result === null) {
continue nextRx; // something didn't match, we don't care.
}
if (result.groups) {
mergeObjects(capturedData, result.groups);
}
}
// now we can apply the results to the intellisenseConfiguration
await this.applyToConfiguration(intellisenseConfiguration, isense, capturedData);
// remove the args used from the command line
const usedArgs = commandLineArgs.splice(0, engineeredRegexSet.length);
// but if the no_consume flag set, we should keep the args in the KeptArgs list
if (flags.get('no_consume')) {
// remove the arguments from the command line
keptArgs.push(...usedArgs);
}
continue nextArg;
}
// if we got here after running the expressions, we did not have a match.
// so we can just assume that something else will look at them later
keptArgs.push(commandLineArgs.shift()!);
}
return keptArgs;
}
async ensurePathsAreLegit(obj: Record<string, any>) {
for (let [key, value] of Object.entries(obj)) {
const k = key.toLowerCase();
// if it's a *path(s), let's make sure they are real
if (['path', 'paths', 'file', 'files'].find(each => k.endsWith(each))) {
if (is.string(value)) {
// if we started with a string, let's check if it's a concatenated path first.
const values = value.split(delimiter);
if (values.length <= 1) {
obj[key] = await filepath.exists(render(value as string, {}, this.resolver)) || value;
continue;
}
// concatenated path (with delimiters)
value = values;
}
// if it's an array, let's check each value now.
if (is.array(value)) {
obj[key] = [...new Set(await Promise.all(value.map(each => each && filepath.exists(render(each as string, {}, this.resolver)))))].filter(each => each);
}
}
// if it's a nested object, let's recurse
if (is.object(value)) {
await this.ensurePathsAreLegit(value);
}
}
}
private async process(entries: Entries, compilerArgs: string[], intellisenseConfiguration: IntelliSenseConfiguration) {
for (const { action, block, flags } of entries) {
// If the flags specifies 'C' and the language is not 'c', then we should skip this section.
if (flags.get('c') && !isC(intellisenseConfiguration.lanugage)) {
continue;
}
// If the flags specifies 'c++' and the language is not 'c++', then we should skip this section.
if (flags.get('cpp') || flags.get('c++') && !isCpp(intellisenseConfiguration.lanugage)) {
continue;
}
switch (action) {
case 'task':
await this.runTasks(block as unknown as OneOrMore<string>, compilerArgs /* , intellisenseConfiguration */);
break;
case 'command':
// commandLineArguments
compilerArgs = await this.processComamndLineArgs(block, compilerArgs, intellisenseConfiguration, flags);
break;
case 'quer':
for (const [command, queries] of Object.entries(block as Record<string, Record<string, DeepPartial<IntelliSenseConfiguration>>>)) {
await this.query(command, queries, intellisenseConfiguration);
}
break;
case 'expression':
for (const [expr, isense] of Object.entries(block as Record<string, DeepPartial<IntelliSenseConfiguration>>)) {
if (await evaluateExpression(expr, intellisenseConfiguration, this.resolver)) {
await this.applyToConfiguration(intellisenseConfiguration, isense);
}
}
break;
default:
break;
}
}
return compilerArgs;
}
current: Promise<IntelliSenseConfiguration> | undefined;
async _getIntellisenseConfiguration(compilerArgs: string[], options?: { baseDirectory?: string; sourceFile?: string; language?: Language; standard?: CppStandard | CStandard; userIntellisenseConfiguration?: IntelliSenseConfiguration }): Promise<IntelliSenseConfiguration> {
while (this.current) {
await this.current;
await sleep(10);
}
this.current = this._getIntellisenseConfiguration(compilerArgs, options);
const result = await this.current;
this.current = undefined;
return result;
}
/**
* Processes the analysis section of the definition file given a command line to work with
*/
async getIntellisenseConfiguration(compilerArgs: string[], options?: { baseDirectory?: string; sourceFile?: string; language?: Language; standard?: CppStandard | CStandard; userIntellisenseConfiguration?: IntelliSenseConfiguration }): Promise<IntelliSenseConfiguration> {
let entries: Entries = [];
const userIntellisenseConfiguration = options?.userIntellisenseConfiguration ? structuredClone(options.userIntellisenseConfiguration) : {};
// if we have an analysis section, we're going to need to get it ready
if (this.definition.analysis) {
entries = getActions<Record<string, IntelliSenseConfiguration>>(this.definition.analysis as any, [
['task', ['priority', 'c', 'cpp', 'c++', 'once']],
['command', ['priority', 'c', 'cpp', 'c++', 'no_consume', 'once']],
['quer', ['priority', 'c', 'cpp', 'c++', 'once']],
['expression', ['priority', 'c', 'cpp', 'c++', 'once']]
]);
}
const early = entries.filter(each => each.priority < 0);
let intellisenseConfiguration = {
...structuredClone(this.definition.intellisense),
language: options?.language,
standard: options?.standard,
compilerPath: this.compilerPath
} as IntelliSenseConfiguration;
// process the 'early' steps before we generate the cache key so that we can filter out useless args
if (early.length) {
compilerArgs = await this.process(early, compilerArgs, intellisenseConfiguration);
}
const late = entries.filter(each => each.priority >= 0);
const cacheKey = compilerArgs.join(' ');
const i = this.cachedAnalysis.get(cacheKey);
if (i) {
intellisenseConfiguration = structuredClone(i);
// after getting the cached results, merge in user settings (which are not cached here)
if (options?.userIntellisenseConfiguration) {
await this.applyToConfiguration(intellisenseConfiguration, userIntellisenseConfiguration);
// before we go, let's make sure that any *paths are unique, and that they are all absolute
await this.ensurePathsAreLegit(intellisenseConfiguration);
}
this.postProcessIntellisense(intellisenseConfiguration);
return intellisenseConfiguration;
}
// (late) Analysis phase
compilerArgs = await this.process(late, compilerArgs, intellisenseConfiguration);
// before we go, let's make sure that any *paths are unique, and that they are all absolute
await this.ensurePathsAreLegit(intellisenseConfiguration);
// render any variables that are left (if therer are value that are specified explicity in definition that reference variables, this is when they get resolved)
intellisenseConfiguration = await recursiveRender(intellisenseConfiguration, intellisenseConfiguration, this.resolver);
// cache the results
this.cachedAnalysis.set(cacheKey, intellisenseConfiguration);
void persistToolsetData();
intellisenseConfiguration = structuredClone(intellisenseConfiguration);
// after the cached results, merge in user settings (since the user can change those at any time)
if (options?.userIntellisenseConfiguration) {
await this.applyToConfiguration(intellisenseConfiguration, userIntellisenseConfiguration);
// before we go, let's make sure that any *paths are unique, and that they are all absolute
await this.ensurePathsAreLegit(intellisenseConfiguration);
}
this.postProcessIntellisense(intellisenseConfiguration);
/// this.postProcessIntellisense(intellisenseConfiguration);
return intellisenseConfiguration;
}
/** the final steps to producing the parser args for EDG */
postProcessIntellisense(intellisense: IntelliSense) {
const args = [];
// turn the macros into -D flags
if (intellisense.macro) {
for (const [name, value] of Object.entries(intellisense.macro)) {
args.push(`-D${name}=${value}`);
}
}
// generate the two sets of include paths that EDG supports:
// --inlcude_directory and --sys_include
for (const each of intellisense.path?.builtInInclude ?? []) {
args.push('--sys_include', each);
/// args.push(`-I${each}`);
}
for (const each of intellisense.path?.systemInclude ?? []) {
args.push('--sys_include', each);
}
for (const each of intellisense.path?.externalInclude ?? []) {
args.push('--sys_include', each);
}
for (const each of intellisense.path?.include ?? []) {
args.push('--include_directory', each);
}
for (const each of intellisense.path?.environmentInclude ?? []) {
args.push('--include_directory', each);
}
intellisense.parserArgument = strings(intellisense.parserArgument).concat(args);
intellisense.queryArgument = undefined;
intellisense.parserArgument.unshift('cpfe', "--no_warnings", "--edge", "--error_limit", "25000", "-D_EDG_COMPILER");
for (let i = 0; i < intellisense.parserArgument.length; i++) {
const arg = intellisense.parserArgument[i];
if (arg.startsWith('--c') && arg.length > 5) {
intellisense.parserArgument[i] = '--c' + arg.substring(5, 7);
}
}
return intellisense;
}
}
@@ -1,26 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { parentPort } from 'worker_threads';
import { getByRef, ref, startRemoting, unref } from '../../Utility/System/snare';
import { getToolsets, identifyToolset, initialize } from './discovery';
import { Toolset } from './toolset';
/** This is the SNARE remote call interface dispatcher that the worker thread supports */
const remote = parentPort ? startRemoting(parentPort, {
unref,
initialize,
getToolsets: () => getToolsets().then(toolsets => toolsets.entries()),
identifyToolset: (candidate: string) => ref(identifyToolset(candidate)),
"Toolset.getIntellisenseConfiguration": (identity: number, compilerArgs: string[], options: any) => getByRef<Toolset>(identity).getIntellisenseConfiguration(compilerArgs, options)
}) : undefined; //: fail("Remoting: Failed to start remote thread - no parent port");
export function log(text: string) {
if (!remote) {
console.log(text);
return;
}
remote.notify('console.log', text);
}
-111
View File
@@ -1,111 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { Configuration, NewIntelliSense } from '../LanguageServer/configurations';
import { MarshalByReference, startRemoting } from '../Utility/System/snare';
import { CStandard, CppStandard, IntelliSenseConfiguration, Language, SizeOf } from './interfaces';
import { appendUnique } from './strings';
import { resolve } from 'path';
import { SHARE_ENV, Worker, isMainThread } from 'worker_threads';
import { SourceFileConfiguration } from 'vscode-cpptools';
import { is } from '../Utility/System/guards';
import { Mutable } from '../common';
import { getOutputChannel } from '../logger';
// this code must only run in the main thread.
if (!isMainThread) {
throw new Error("Remoting: Failed to start host thread responder - not on main thread");
}
// starts the worker thread and returns the RemoteConnection object
export const remote = startRemoting(new Worker(resolve(__dirname.substring(0, __dirname.lastIndexOf('dist')), "dist", "src", "ToolsetDetection", "Service", 'worker.js'), {stderr:true, stdout: true, env: SHARE_ENV}), {
// this is the functions we expose to the worker
"console.log": (text: string) => {
try {
getOutputChannel().appendLine(text);
} catch {
// fall back to console?
console.log(text);
}
}
});
function coerceIntellisense(intellisense: IntelliSenseConfiguration) {
if (intellisense) {
if (is.string(intellisense.bits)) {
intellisense.bits = parseInt(intellisense.bits);
}
if (is.object(intellisense.sizes)) {
for (const [key, value] of Object.entries(intellisense.sizes) as [keyof SizeOf, any][]) {
intellisense.sizes[key] = parseInt(value);
}
}
}
return intellisense;
}
/**
* This is a byref proxy to the toolset.
*
* As with all byref proxies, it is a reference to an object that lives in the worker thread.
* It is important to call .dispose() when you are done with it, as this enables the worker
* thread to release the object and free up resources.
*
*/
export class Toolset extends MarshalByReference {
async getIntellisenseConfiguration(compilerArgs: string[], options?: { baseDirectory?: string; sourceFile?: string; language?: Language; standard?: CppStandard | CStandard; userIntellisenseConfiguration?: IntelliSenseConfiguration }): Promise<IntelliSenseConfiguration> {
return coerceIntellisense(await this.remote.request('Toolset.getIntellisenseConfiguration', this.instance, compilerArgs, options));
}
harvestFromConfiguration(configuration: Configuration | (Mutable<SourceFileConfiguration> & NewIntelliSense), intellisense: IntelliSenseConfiguration) {
// includePath
intellisense.path!.include = appendUnique(intellisense.path!.include, configuration.includePath);
// macFrameworkPath
intellisense.path!.framework = appendUnique(intellisense.path!.framework, (configuration as any).macFrameworkPath);
// cStandard
// cppStandard
// defines
for (const define of configuration.defines || []) {
const [,key, value] = /^([^=]+)=*(.*)?$/.exec(define) ?? [];
if (key && value) {
intellisense.defines[key] = value;
}
}
// forcedInclude
intellisense.path!.forcedIncludeFile = appendUnique(intellisense.path!.forcedIncludeFile, configuration.forcedInclude);
return coerceIntellisense(intellisense);
}
}
/**
* Makes a remote call to the identifyToolset function in the worker thread.
*
* @param candidate one of:
* - the path to the compiler executable to identify
* - a name of a binary on the PATH
* - a name of a toolset definition (supports wildcards)
* @returns a Promise to either a valid toolset or undefined if there was no match..
*/
export function identifyToolset(candidate: string): Promise<Toolset | undefined> {
return remote.marshall(Toolset, remote.request('identifyToolset', candidate));
}
/** Makes a remote call to initialize the toolset detection system */
export async function initialize(configFolders: string[], options?: { quick?: boolean; storagePath?: string }): Promise<void> {
return remote.request('initialize', configFolders, options);
}
/** Makes a remote call to get the list of toolsets from the worker thread */
export async function getToolsets(): Promise<Map<string, string>> {
return new Map(await remote.request('getToolsets'));
}
@@ -1,309 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
/* eslint-disable @typescript-eslint/ban-types */
// Deep Partial implementation
export type Primitive = string | number | boolean | bigint | symbol | undefined | null | Date | Function | RegExp;
export type DeepPartial<T> =
T extends Primitive | Function | Date ? T :
{
[P in keyof T]?:
T[P] extends (infer U)[] ? DeepPartial<U>[] :
T[P] extends readonly (infer V)[] ? readonly DeepPartial<V>[] :
T[P] extends Primitive ? T[P] :
DeepPartial<T[P]>
} | T;
/** An Expression supports tempate variable substitution (ie `the workspace is $ {workspaceFolder}, the PATH is $ {env:PATH} `) */
export type Expression = string;
/** A Conditional is an Expression that is used to conditially apply configuation based on a specific condition being met */
export type Conditional = Expression;
/** One or more (as a type or an array of a type) */
export type OneOrMore<T> = T | T[];
/** A regular expression in a string
*
* take care that the string is properly escaped (ie, backslashes)
*/
export type RegularExpression = string;
/** Discovery requirements operations */
export type Operation = 'match' | 'folder' | 'file' | 'regex';
/** officially supported standards (c++) */
export type CppStandard = 'c++98' | 'c++03' | 'c++11' | 'c++14' | 'c++17' | 'c++20' | 'c++23';
/** officially supported standards (c) */
export type CStandard = 'c89' | 'c99' | 'c11' | 'c17' | 'c23';
/** Package manager names */
export type PkgMgr = 'apt' | 'brew' | 'winget' | 'yum' | 'rpm' | 'dpkg';
/** Language selection */
export type Language = 'c' | 'cpp' | 'c++' | 'cuda';
/** A package definition */
export type Package = Partial<Record<PkgMgr, OneOrMore<string>>>;
/** A query definition - the 'active' requirements to get settings from a binary */
export type Query = Record<Expression, Record<string, OneOrMore<Expression>>>;
/** #define macro definitions */
export type Macros = Record<string, string | number | boolean | null>;
/** the target 'platform' (aka OS) */
export type Platform =
'windows' | // windows
'linux' | // linux
'macos' | // apple osx/darwin
'ios' | // apple ios
'none' | // bare metal
'android' | // android
'wasm' | // wasm
'unknown'; // don't know what it is
/** The Target CPU/Processor architecture */
export type Architecture =
'arm' | // arm aka aarch32
'arm64' | // 64bit arm, aka aarch64
'avr' | // AVR (arduino)
'x64' | // x86_64 aka amd64 aka x64
'x86' | // x86 (32bit)
'riscv' | // riscv
'ia64' | // ia64
'mips' | // mips
'ppc' | // ppc
'sparc' | // sparc
'wasm' | // wasm
'unknown'; // don't know what it is
/** The "well-known" compiler. At the moment, some back end parts make assumptions base on this */
export type CompilerVariant = 'msvc' | 'clang' | 'gcc';
/** The (passive) requirements to discover a binary */
export interface Discover {
binary: OneOrMore<RegularExpression>;
locations?: OneOrMore<string>;
/** 'match' examines the binary file itself to search for strings via regex */
[key: `match:${string}`]: Record<RegularExpression, any>;
[key: `match#${string}`]: Record<RegularExpression, any>;
[key: `matches:${string}`]: Record<RegularExpression, any>;
[key: `matches#${string}`]: Record<RegularExpression, any>;
match?: Record<RegularExpression, any>;
matches?: Record<RegularExpression, any>;
/** Expressions are evaluated, and if 'truthy' will apply the Installisense block */
[key: `expressions:${string}`]: Record<Expression, any>;
[key: `expressions#${string}`]: Record<Expression, any>;
[key: `expression:${string}`]: Record<Expression, any>;
[key: `expression#${string}`]: Record<Expression, any>;
expressions?: Record<Expression, any>;
expression?: Record<Expression, any>;
}
export interface SizeOf {
char?: number;
short?: number;
int?: number;
long?: number;
float?: number;
double?: number;
longDouble?: number;
pointer?: number;
/** number of digits in long double manitissa (64/53) */
digitsInLongDoubleMantissa?: number;
/** alignment of the long double */
alignmentOfLongDouble?: number;
/** In C++17 mode, the alignment beyond which new and delete expressions will use the versions of
allocation and deallocation functions with an alignment parameter */
defaultNewAlignment?: number;
}
export interface TypeAliases {
wcharT?: string;
sizeT?: string;
ptrDiffT?: string;
}
export interface Paths {
/** specified `-iquote` paths - Used for ONLY #include "..." */
quoteInclude?: OneOrMore<Expression>;
/** standard specified include paths (ie, `-I`) */
include?: OneOrMore<Expression>;
/** specified `-isystem` paths */
systemInclude?: OneOrMore<Expression>;
/** Directories specified that are built into the compiler (usually thru interrogation) */
builtInInclude?: OneOrMore<Expression>;
/** specified `-idirafter` paths */
afterInclude?: OneOrMore<Expression>;
/** specified `-external:I` paths (MSVC) */
externalInclude?: OneOrMore<Expression>;
/** specified `-F` paths (MacOS) */
framework?: OneOrMore<Expression>;
/** paths that are specified via environment variables (ie `INCLUDE`) */
environmentInclude?: OneOrMore<Expression>;
/** paths that are forcibly #included */
forcedIncludeFile?: OneOrMore<Expression>;
}
/** the Intellisense interface represents the things that a given toolset supports/exposes */
export interface IntelliSense {
/** meta-property: telemetry entries to track */
[key: `telemetry:${string}`]: Record<string, string | number | boolean>;
/** meta-property:error/warnings/info encountered */
[key: `message:${string}`]: OneOrMore<string>;
/** any unstructured data that is added by definitions */
[key: `${string}`]: any;
/** #define macros that are specified so that the backend understands how to handle the code */
macro?: Macros;
/** Include folders */
path?: Paths;
/** the C++ standard that this toolset supports */
cppStandard?: CppStandard | number;
/** the C Standard that this toolset supports */
cStandard?: CStandard | number;
/** refined arguments that are passed to the language parser (edg) */
parserArgument?: OneOrMore<string>;
/** Well-known compiler variant (currently, just the three) */
compiler?: CompilerVariant;
/** the target platform */
platform?: Platform;
/** The target CPU/Processor architecture */
architecture?: Architecture;
/** architecture bits? */
bits?: number;
/** sizes of the various types */
sizes?: SizeOf;
/** type aliases (what is the 'type' for a given type ) */
types?: TypeAliases;
/** additional arguments that are being passed to the compiler (unprocessed) */
compilerArg?: OneOrMore<string>; // arguments that are assumed to be passed to the compiler on the command line
}
export interface IntelliSenseConfiguration extends IntelliSense {
/** meta-property: things can be removed via remove:<setting> ... */
[key: `remove:${string}`]: OneOrMore<string>;
/** meta-property: things can be prepended to a collection via prepend:<setting> ... */
[key: `prepend:${string}`]: OneOrMore<string>;
/** the selected language for this configuration */
lanugage?: Language;
/** the selected/overridden lanaguage standard version of this configuration */
standard?: CppStandard | CStandard;
/** the full path to the compiler */
compilerPath: string;
}
/** The interface for the toolset.XXX.json file */
export interface DefinitionFile {
/** The cosmetic name for the toolkit */
name: string;
/** The cosmetic version for the toolkit */
version?: string;
/** files to automatically load and merge */
import?: OneOrMore<string>;
/** Describes the steps to find this toolkit */
discover: Discover;
/** Analysis steps to take the gathered data and transform it for the backend */
analysis?: Analysis;
/** Explicitly declared settings about this toolset */
intellisense?: DeepPartial<IntelliSense>;
/** The package identities if we are interested in bootstrapping it. */
package?: Package;
/** Conditional events that allow us to overlay additional configuration when a condition is met */
conditions?: Record<string, OneOrMore<string> | PartialDefinitionFile>;
}
/** Analysis phase declarations */
export interface Analysis {
/** Custom steps to trigger (ie, specific built-in actions) */
[key: `tasks:${string}`]: OneOrMore<string>;
[key: `tasks#${string}`]: OneOrMore<string>;
[key: `task:${string}`]: OneOrMore<string>;
[key: `task#${string}`]: OneOrMore<string>;
tasks?: Record<string, OneOrMore<string>>;
task?: Record<string, OneOrMore<string>>;
/**
* A map of <engineered regex sequences> to <what to apply when it matches>
*
* "engineered regex sequence" is a packed string that is semicolon separated regular expressions
* each regular expression will have '^'' and '$'' added to assume that a full argument must be matched
* tagged template literals (${}) are processed before anything else, ( which we can use for built-in macros)
* after that, the seqence of regular expressions is split
* when there are more than one, all of the regular expressions should match arguments in order (from the current arg)
* so "-D;(?<val>.*)" would be valid if a -D parameter was followed by anything.
*
* the analysis phase is run, and the compiler args are run thru the list of the regular expressions
* if a match is found, the data is applied to the toolset block, and the args are consumed/dropped
* (unless keep:true is in the apply block)
*
* Since they are run in order, the first match wins, and the args are consumed (unless 'keep:true' is specified).
*/
[key: `commandLineArguments:${string}`]: Record<string, any>;
[key: `commandLineArguments#${string}`]: Record<string, any>;
[key: `commandLineArgument:${string}`]: Record<string, any>;
[key: `commandLineArgument#${string}`]: Record<string, any>;
commandLineArgument?: Record<string, any>;
commandLineArguments?: Record<string, any>;
/** Expressions are evaluated, and if 'truthy' will apply the InstallisenseConfiguration block */
[key: `expressions:${string}`]: Record<string, any>;
[key: `expressions#${string}`]: Record<string, any>;
[key: `expression:${string}`]: Record<string, any>;
[key: `expression#${string}`]: Record<string, any>;
expressions?: Record<string, any>;
expression?: Record<string, any>;
/** Query steps to ask the compiler (by executing it) about its settings */
[key: `queries:${string}`]: Record<Expression, Record<string, any>>;
[key: `queries#${string}`]: Record<Expression, Record<string, any>>;
[key: `query:${string}`]: Record<Expression, Record<string, any>>;
[key: `query#${string}`]: Record<Expression, Record<string, any>>;
queries?: Record<Expression, Record<string, any>>;
query?: Record<Expression, Record<string, any>>;
}
/** A partial definition file */
export type PartialDefinitionFile = DeepPartial<DefinitionFile>;
-122
View File
@@ -1,122 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { normalize } from 'path';
import { is } from '../Utility/System/guards';
import { OneOrMore } from './interfaces';
export function strings(input: OneOrMore<string> | undefined | Set<string> | (string | undefined)[]): string[] {
if (!input) {
return [];
}
if (input instanceof Set) {
return [...input];
}
if (is.string(input)) {
return [input];
}
return input as string[];
}
/** pushes one or more paths to the array if they aren't in there already. */
export function appendUniquePath(collection: string[] | Set<string>, elements: (string | undefined)[] | string | undefined) {
if (!elements) {
return collection;
}
for (let path of is.string(elements) ? [elements] : elements) {
// skip empty values
if (!path) {
continue;
}
// normalize path first.
path = normalize(path);
// drop trailing slashes
path = path.endsWith('\\') ? path.substring(0, path.length - 1) : path;
// append if not present.
// sets are smart
if (is.set(collection)) {
collection.add(path);
continue;
}
// arrays we have to look
if (!collection.includes(path)) {
collection.push(path);
}
}
return collection;
}
export function appendUnique(collection: string[] | string | undefined, elements: (string | undefined)[] | string | undefined) {
if (!elements) {
return collection;
}
if (!collection) {
collection = [];
}
if (is.string(collection)) {
collection = [collection];
}
for (let path of is.string(elements) ? [elements] : elements) {
// skip empty values
if (!path) {
continue;
}
// normalize path first.
path = normalize(path);
// drop trailing slashes
path = path.endsWith('\\') ? path.substring(0, path.length - 1) : path;
// append if not present.
if (!collection.includes(path)) {
collection.push(path);
}
}
return collection;
}
export function getActions<T>(obj: any, actions: [string, string[]][]) {
if (!obj || typeof obj !== 'object') {
return [];
}
return Object.entries(obj).map(([expression, block], ndx) => {
const [, act, flag, comment] = /^([a-zA-Z]{4})(?:[a-zA-Z]*)(?:[:])?(.*?)(#.*?)?$/.exec(expression) || [];
// coerce the action to be one of the valid actions, or empty string.
const [action, validFlags] = actions.find(each => each[0].startsWith(act.toLowerCase())) || ['', []];
// extract the flags
const flags = new Map();
for (const each of flag.split(',')) {
// eslint-disable-next-line prefer-const
let [,key, value] = /^([^=]+)=*(.*)?$/.exec(each) ?? [];
if (!key) {
continue;
}
key = key.toLowerCase().trim();
if (validFlags.includes(key)) {
flags.set(key, value?.trim() ?? true);
}
}
// get the priority
const priority = parseInt(flags.get('priority') ?? '0') || ndx;
return { action, block, flags, priority, comment } as const;
}).sort((a, b) => a.priority - b.priority).filter(each => each.action) as { action: string; block: T; flags: Map<string, string | boolean>; priority: number; comment?: string }[];
}
+1 -3
View File
@@ -42,9 +42,8 @@ export class ManualPromise<T = void> implements Promise<T> {
/**
* A method to manually resolve the Promise.
*/
public resolve: (value?: T | PromiseLike<T> | undefined) => T = (v) => {
public resolve: (value?: T | PromiseLike<T> | undefined) => void = (v) => {
void v; /* */
return v as T;
};
/**
@@ -90,7 +89,6 @@ export class ManualPromise<T = void> implements Promise<T> {
}
this.state = 'resolved';
r(v as any);
return v as T;
};
this.reject = (e: any) => {
if ((global as any).DEVMODE && this.state !== 'pending') {
@@ -1,166 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import EventEmitter = require('events');
import { ManualPromise } from './manualPromise';
export class PriorityQueue<T> extends EventEmitter {
private queued = new Map<string, () => Promise<T>>();
private inProgress = new Map<string, Promise<T>>();
private completed = new Map<string, T>();
private failed = new Map<string, any>();
constructor(private maxParallel: number = 10) {
super();
}
get completedKeys() {
return [...this.completed.keys()];
}
get keys() {
return [...this.queued.keys(), ...this.inProgress.keys(), ...this.completed.keys(), ...this.failed.keys()];
}
has(key: string) {
return this.queued.has(key) || this.inProgress.has(key) || this.completed.has(key) || this.failed.has(key);
}
/** returns the number of items not yet processed */
get length() {
return this.queued.size + this.inProgress.size;
}
/** returns the total number of items regardless of status */
get size() {
return this.failed.size + this.completed.size + this.length;
}
// removes a task from the queue, regardless if it is queued, in progress, or completed
async reset(key: string) {
// if it's currently running, we have to wait for it to finish what it was doing
// so that we can remove it from the results.
if (this.inProgress.has(key)) {
await this.inProgress.get(key);
}
// regardless, remove it from the queue entirely
this.queued.delete(key);
this.completed.delete(key);
this.failed.delete(key);
}
async enqueue(key: string, task: () => Promise<T>): Promise<T> {
// reset this if it was already queued
await this.reset(key);
// returning a promise to the thing we're queueing
const result = new ManualPromise<T>();
// add it to the queue, and make it resolve the promise when it's done
this.queued.set(key, () => {
void task().then(result.resolve, result.reject);
return result;
});
// start the queue if it's not already running
if (this.inProgress.size === 0) {
void this.start();
}
// return the promise to the result
return result;
}
async get(key: string): Promise<T | undefined> {
// if it's already completed
const value = this.completed.get(key);
if (value !== undefined) {
return value;
}
// if it's in progress
const p = this.inProgress.get(key);
if (p !== undefined) {
return p;
}
// if it failed, throw that to the consumer.
const f = this.failed.get(key);
if (f !== undefined) {
throw f;
}
// if it's queued, let's run it right away and return that result
const task = this.queued.get(key);
// if we do have a task, run it thru exec
// so that if we ask for the value again
// it will cache the result
return task ? this.exec(key, task) : undefined;
}
async getOrEnqueue(key: string, task: () => Promise<T>): Promise<T> {
const result = await this.get(key);
return result === undefined ? this.enqueue(key, task) : result;
}
private exec(key: string, task: () => Promise<T>) {
this.queued.delete(key);
const result = new ManualPromise<T>();
this.inProgress.set(key, result);
task().then((value: T) => {
// when we're done, remove the task from the in progress list
this.inProgress.delete(key);
this.completed.set(key, value);
this.emit('item', key, value);
result.resolve(value);
}, (reason: any) => {
this.inProgress.delete(key);
this.completed.delete(key);
this.failed.set(key, reason);
result.reject(reason);
});
return result;
}
private async start() {
while (this.queued.size || this.inProgress.size) {
if (this.inProgress.size > this.maxParallel || this.queued.size === 0) {
// if we reached the max parallel tasks, or we have nothing left to queue, wait for one to complete
await Promise.any(this.inProgress.values());
}
//* const {value, done} = this.queued.entries().next();
// if (!done) {
// void this.exec(value[0], value[1]);
//}
// grab the first task from the queue
for (const [key, task] of this.queued.entries()) {
void this.exec(key, task);
break;
}
}
this.emit('empty', this);
}
override on(event: 'item', listener: (key: string, value: T) => void): this;
override on(event: 'empty', listener: (queue: PriorityQueue<T>) => void): this;
override on(eventName: string | symbol, listener: (...args: any[]) => void): this {
return super.on(eventName, listener);
}
override once(event: 'item', listener: (key: string, value: T) => void): this;
override once(event: 'empty', listener: (queue: PriorityQueue<T>) => void): this;
override once(eventName: string | symbol, listener: (...args: any[]) => void): this {
return super.once(eventName, listener);
}
}
+3 -2
View File
@@ -3,6 +3,7 @@
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/unified-signatures */
import { readFile } from 'node:fs/promises';
@@ -80,7 +81,7 @@ async function dispatch<TResult>(event: Event<any, TResult>): Promise<void> {
resultValue = r as TResult | EventStatus;
if (is.cancelled(resultValue)) {
event.completed.resolve(resultValue); // the event has been cancelled
return event.completed.resolve(resultValue); // the event has been cancelled
}
}
} catch (e: any) {
@@ -134,7 +135,7 @@ async function dispatch<TResult>(event: Event<any, TResult>): Promise<void> {
// wait for all the async handlers to complete
// and return the first result that isn't 'Continue'
event.completed.resolve((await Promise.all(results)).find((each: any) => each !== Continue));
return event.completed.resolve((await Promise.all(results)).find((each: any) => each !== Continue));
}
function* getHandlers<TResult>(event: Event<any, TResult>, category: Map<string, Subscriber[]>): Iterable<[Callback, string[]]> {
+3 -2
View File
@@ -10,7 +10,7 @@ export class requests {
static readonly get = 'get';
}
// event names should be like [noun]-[verb]
// [noun]-[verb]
export class events {
static readonly writing = 'writing';
static readonly reading = 'reading';
@@ -27,7 +27,8 @@ export class channels {
}
/** Notifications */
// should be like [state] or [pastTenseVerb]-[noun]
// [state]
// [pastTenseVerb]-[noun]
export class notifications {
static readonly ready = 'ready';
static readonly exited = 'exited';
@@ -1,398 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
/* eslint-disable @typescript-eslint/method-signature-style */
/* eslint-disable @typescript-eslint/no-var-requires */
/**
* The code here is borrowed from https://github.com/microsoft/vscode-ripgrep
*
* The original code is used internally and not designed to be consumed via npm.
* Since we needed the same functionality, I've borrowed the code and typescript-ified it.
*
*/
import { resolve } from 'path';
import { verbose } from '../Text/streams';
import { filepath, mkdir } from './filepath';
const path = require('path');
const fs = require('fs');
const os = require('os');
const https = require('https');
const util = require('util');
const url = require('url');
const child_process = require('child_process');
const proxy_from_env = require('proxy-from-env');
const fsExists = util.promisify(fs.exists);
const tmpDir = path.join(os.tmpdir(), `vscode-ripgrep-cache`);
const fsUnlink = util.promisify(fs.unlink);
const fsMkdir = util.promisify(fs.mkdir);
const isWindows = os.platform() === 'win32';
const REPO = 'microsoft/ripgrep-prebuilt';
function isGithubUrl(_url: any) {
return url.parse(_url).hostname === 'api.github.com';
}
function downloadWin(url: any, dest: any, opts: { headers: { [x: string]: any }; proxy: string | URL }) {
return new Promise((resolve, reject) => {
let userAgent;
if (opts.headers['user-agent']) {
userAgent = opts.headers['user-agent'];
delete opts.headers['user-agent'];
}
const headerValues = Object.keys(opts.headers)
.map(key => `\\"${key}\\"=\\"${opts.headers[key]}\\"`)
.join('; ');
const headers = `@{${headerValues}}`;
verbose('Downloading with Invoke-WebRequest');
dest = sanitizePathForPowershell(dest);
let iwrCmd = `[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -URI ${url} -UseBasicParsing -OutFile ${dest} -Headers ${headers}`;
if (userAgent) {
iwrCmd += ' -UserAgent ' + userAgent;
}
if (opts.proxy) {
iwrCmd += ' -Proxy ' + opts.proxy;
try {
const { username, password } = new URL(opts.proxy);
if (username && password) {
const decodedPassword = decodeURIComponent(password);
iwrCmd += ` -ProxyCredential (New-Object PSCredential ('${username}', (ConvertTo-SecureString '${decodedPassword}' -AsPlainText -Force)))`;
}
} catch (err) {
reject(err);
}
}
iwrCmd = `powershell "${iwrCmd}"`;
child_process.exec(iwrCmd, (err: any) => {
if (err) {
reject(err);
return;
}
resolve(undefined);
});
});
}
function download(_url: { version: string; token: string | undefined; target: string; destDir: any; force: boolean }, dest?: any, opts?: any) {
const proxy = proxy_from_env.getProxyForUrl(url.parse(_url));
if (proxy !== '') {
const HttpsProxyAgent = require('https-proxy-agent');
opts = {
...opts,
"agent": new HttpsProxyAgent(proxy),
proxy
};
}
if (isWindows) {
// This alternative strategy shouldn't be necessary but sometimes on Windows the file does not get closed,
// so unzipping it fails, and I don't know why.
return downloadWin(_url, dest, opts);
}
if (opts.headers && opts.headers.authorization && !isGithubUrl(_url)) {
delete opts.headers.authorization;
}
return new Promise((resolve, reject) => {
verbose(`Download options: ${JSON.stringify(opts)}`);
const outFile = fs.createWriteStream(dest);
const mergedOpts = {
...url.parse(_url),
...opts
};
https.get(mergedOpts, (response: { statusCode: string | number; headers: Record<string, any>; pipe: (arg0: any) => void }) => {
verbose('statusCode: ' + response.statusCode);
if (response.statusCode === 302) {
verbose('Following redirect to: ' + response.headers.location);
return download(response.headers.location, dest, opts)
.then(resolve, reject);
} else if (response.statusCode !== 200) {
reject(new Error('Download failed with ' + response.statusCode));
return;
}
response.pipe(outFile);
outFile.on('finish', () => {
resolve(undefined);
});
}).on('error', async (err: any) => {
await fsUnlink(dest);
reject(err);
});
});
}
function get(_url: string, opts: any) {
verbose(`GET ${_url}`);
const proxy = proxy_from_env.getProxyForUrl(url.parse(_url));
if (proxy !== '') {
const HttpsProxyAgent = require('https-proxy-agent');
opts = {
...opts,
"agent": new HttpsProxyAgent(proxy)
};
}
return new Promise((resolve, reject) => {
let result = '';
opts = {
...url.parse(_url),
...opts
};
https.get(opts, (response: { statusCode: string | number; on: any }) => {
if (response.statusCode !== 200) {
reject(new Error('Request failed: ' + response.statusCode));
}
response.on('data', (d: any) => {
result += d.toString();
});
response.on('end', () => {
resolve(result);
});
response.on('error', (e: any) => {
reject(e);
});
});
});
}
function getApiUrl(repo: string, tag: any) {
return `https://api.github.com/repos/${repo}/releases/tags/${tag}`;
}
/**
* @param opts
* @param assetName
* @param downloadFolder
*/
async function getAssetFromGithubApi(opts: Record<string, any>, assetName: string, downloadFolder: any) {
const assetDownloadPath = path.join(downloadFolder, assetName);
// We can just use the cached binary
if (!opts.force && await fsExists(assetDownloadPath)) {
verbose('Using cached download: ' + assetDownloadPath);
return assetDownloadPath;
}
const downloadOpts = {
headers: {
'user-agent': 'vscode-ripgrep'
} as Record<string, any>
}as Record<string, any>;
if (opts.token) {
downloadOpts.headers.authorization = `token ${opts.token}`;
}
verbose(`Finding release for ${opts.version}`);
const release = await get(getApiUrl(REPO, opts.version), downloadOpts) as string;
let jsonRelease;
try {
jsonRelease = JSON.parse(release);
} catch (e) {
throw new Error('Malformed API response: ' + (e as any)?.stack);
}
if (!jsonRelease.assets) {
throw new Error('Bad API response: ' + JSON.stringify(release));
}
const asset = jsonRelease.assets.find((a: { name: any }) => a.name === assetName);
if (!asset) {
throw new Error('Asset not found with name: ' + assetName);
}
verbose(`Downloading from ${asset.url}`);
verbose(`Downloading to ${assetDownloadPath}`);
downloadOpts.headers.accept = 'application/octet-stream';
await download(asset.url, assetDownloadPath, downloadOpts);
}
function unzipWindows(zipPath: any, destinationDir: any) {
return new Promise((resolve, reject) => {
zipPath = sanitizePathForPowershell(zipPath);
destinationDir = sanitizePathForPowershell(destinationDir);
const expandCmd = 'powershell -ExecutionPolicy Bypass -Command Expand-Archive ' + ['-Path', zipPath, '-DestinationPath', destinationDir, '-Force'].join(' ');
child_process.exec(expandCmd, (err: any, _stdout: any, stderr: string | undefined) => {
if (err) {
reject(err);
return;
}
if (stderr) {
verbose(stderr);
reject(new Error(stderr));
return;
}
verbose('Expand-Archive completed');
resolve(undefined);
});
});
}
// Handle whitespace in filepath as powershell split's path with whitespaces
function sanitizePathForPowershell(path: string) {
path = path.replace(/ /g, '` '); // replace whitespace with "` " as solution provided here https://stackoverflow.com/a/18537344/7374562
return path;
}
function untar(zipPath: any, destinationDir: any) {
return new Promise((resolve, reject) => {
const unzipProc = child_process.spawn('tar', ['xvf', zipPath, '-C', destinationDir], { stdio: 'inherit' });
unzipProc.on('error', (err: any) => {
reject(err);
});
unzipProc.on('close', (code: number) => {
verbose(`tar xvf exited with ${code}`);
if (code !== 0) {
reject(new Error(`tar xvf exited with ${code}`));
return;
}
resolve(undefined);
});
});
}
async function unzipRipgrep(zipPath: any, destinationDir: any) {
if (isWindows) {
await unzipWindows(zipPath, destinationDir);
} else {
await untar(zipPath, destinationDir);
}
const expectedName = path.join(destinationDir, 'rg');
if (await fsExists(expectedName)) {
return expectedName;
}
if (await fsExists(expectedName + '.exe')) {
return expectedName + '.exe';
}
throw new Error(`Expecting rg or rg.exe unzipped into ${destinationDir}, didn't find one.`);
}
async function dl (opts: Record<string, any>) {
if (!opts.version) {
return Promise.reject(new Error('Missing version'));
}
if (!opts.target) {
return Promise.reject(new Error('Missing target'));
}
const extension = isWindows ? '.zip' : '.tar.gz';
const assetName = ['ripgrep', opts.version, opts.target].join('-') + extension;
if (!await fsExists(tmpDir)) {
await fsMkdir(tmpDir);
}
const assetDownloadPath = path.join(tmpDir, assetName);
try {
await getAssetFromGithubApi(opts, assetName, tmpDir);
} catch (e) {
verbose('Deleting invalid download cache');
try {
await fsUnlink(assetDownloadPath);
} catch (e) { /* ignore */ }
throw e;
}
verbose(`Unzipping to ${opts.destDir}`);
try {
const destinationPath = await unzipRipgrep(assetDownloadPath, opts.destDir);
if (!isWindows) {
await util.promisify(fs.chmod)(destinationPath, '755');
}
return destinationPath;
} catch (e) {
verbose('Deleting invalid download');
try {
await fsUnlink(assetDownloadPath);
} catch (e) { /* ignore */ }
throw e;
}
}
const VERSION = 'v13.0.0-10';
const ARM32_LINUX_VERSION = 'v13.0.0-4';// use this for arm-unknown-linux-gnueabihf until we can fix https://github.com/microsoft/ripgrep-prebuilt/issues/24
process.on('unhandledRejection', (reason, promise) => {
verbose('Unhandled rejection: ', promise, 'reason:', reason);
});
async function getTarget() {
const arch = process.env.npm_config_arch || os.arch();
switch (os.platform()) {
case 'darwin':
return arch === 'arm64' ? 'aarch64-apple-darwin' :
'x86_64-apple-darwin';
case 'win32':
return arch === 'x64' ? 'x86_64-pc-windows-msvc' :
arch === 'arm' ? 'aarch64-pc-windows-msvc' :
'i686-pc-windows-msvc';
case 'linux':
return arch === 'x64' ? 'x86_64-unknown-linux-musl' :
arch === 'arm' ? 'arm-unknown-linux-gnueabihf' :
arch === 'armv7l' ? 'arm-unknown-linux-gnueabihf' :
arch === 'arm64' ? 'aarch64-unknown-linux-musl' :
arch === 'ppc64' ? 'powerpc64le-unknown-linux-gnu' :
arch === 's390x' ? 's390x-unknown-linux-gnu' :
'i686-unknown-linux-musl';
default: throw new Error('Unknown platform: ' + os.platform());
}
}
export async function downloadRipgrep() {
const BIN_PATH = await mkdir(path.join(__dirname, '../../../../bin'));
const targetPath = resolve(BIN_PATH, isWindows ? 'rg.exe' : 'rg');
if (await filepath.isExecutable(targetPath)) {
return targetPath;
}
const target = await getTarget();
const opts = {
version: target === "arm-unknown-linux-gnueabihf" ? ARM32_LINUX_VERSION : VERSION,
token: process.env['GITHUB_TOKEN'],
target,
destDir: BIN_PATH,
force: false
};
try {
return await dl(opts);
} catch (err) {
if (err instanceof Error) {
console.error(`Downloading ripgrep failed: ${err.stack}`);
}
throw err;
}
return targetPath;
}
+6 -27
View File
@@ -5,10 +5,10 @@
import { basename, delimiter, extname, join, normalize as norm, resolve } from 'path';
import { fail, ok } from 'assert';
import { fail } from 'assert';
import { randomBytes } from 'crypto';
import { Stats, constants } from 'fs';
import { mkdir as md, stat } from 'fs/promises';
import { constants, Stats } from 'fs';
import { stat } from 'fs/promises';
import { tmpdir } from 'os';
import { isWindows } from '../../constants';
import { returns } from '../Async/returns';
@@ -45,7 +45,6 @@ export interface File extends Entry {
isFile: true;
isExecutable: boolean;
size: number;
timestamp: number;
}
export interface Folder extends Entry {
@@ -56,10 +55,6 @@ export interface Folder extends Entry {
// eslint-disable-next-line @typescript-eslint/naming-convention
export class filepath {
static normalize(path: string) {
return normalize(path);
}
static async stats(name: string | undefined | Promise<string | undefined>, baseFolder?: string): Promise<[string, Stats | undefined] | [undefined, undefined]> {
if (is.promise(name)) {
name = await name;
@@ -71,12 +66,12 @@ export class filepath {
}
// if we've been given a baseFolder, expand that, otherwise just normalize the value.
name = baseFolder ? resolve(baseFolder, name) : filepath.normalize(name);
name = baseFolder ? resolve(baseFolder, name) : normalize(name);
return [name, await stat(name).catch(returns.undefined)];
}
static async info(name: string | undefined | Promise<string | undefined>, baseFolder?: string, executableExtensions: Set<string> = process.platform === 'win32' ? new Set(['.exe']) : new Set()): Promise<undefined | File | Folder> {
static async info(name: string | undefined | Promise<string | undefined>, baseFolder?: string, executableExtensions: Set<string> = process.platform === 'win32' ? new Set(['.exe'/* ,'.cmd','.bat' */]) : new Set()): Promise<undefined | File | Folder> {
const [fullPath, stats] = await filepath.stats(name, baseFolder);
if (!stats) {
return undefined;
@@ -92,7 +87,6 @@ export class filepath {
if (entry.isFile) {
entry.size = stats.size;
entry.timestamp = stats.mtimeMs;
if (isWindows) {
const fp = fullPath.toLowerCase();
@@ -137,25 +131,10 @@ export class filepath {
static parent(name: Promise<string | undefined>): Promise<string | undefined>;
static parent(name: string | undefined): string | undefined;
static parent(name: string | undefined | Promise<string | undefined>): string | undefined | Promise<string | undefined> {
return is.promise(name) ? name.then(filepath.parent) : name ? filepath.normalize(resolve(name, '..')) : undefined;
return is.promise(name) ? name.then(filepath.parent) : name ? normalize(resolve(name, '..')) : undefined;
}
}
export function tmpFile(prefix = 'tmp.', suffix = '.tmp', folder = tmpdir()) {
return join(folder, prefix + randomBytes(32).toString('hex') + suffix);
}
/** Asnyc recursively create dir if it isn't there, no error if it is there already. */
export async function mkdir(filePath: string) {
const [fullPath, info] = await filepath.stats(filePath);
if (info) {
if (info.isDirectory()) {
return fullPath;
}
throw new Error(`Cannot create directory '${filePath}' because there is a file there.`);
}
ok(fullPath, `Cannot create directory ${filePath} because the path is invalid.`);
await md(fullPath, { recursive: true });
return fullPath;
}
+3 -3
View File
@@ -14,7 +14,7 @@ import { accumulator, foreach } from '../Async/iterators';
import { ManualPromise } from '../Async/manualPromise';
import { returns } from '../Async/returns';
import { is } from '../System/guards';
import { File, Folder, filepath } from './filepath';
import { File, Folder, normalize } from './filepath';
interface FolderWithChildren extends Folder {
children?: Map<string, File | FolderWithChildren>;
@@ -31,7 +31,7 @@ const cache = new Map<string, File | FolderWithChildren | Promise<FolderWithChil
* @param executableExtensions a set of file extensions that are considered executable on Windows
* @returns a map of the files and folders in the directory, or undefined if the directory doesn't exist or is inaccessible.
*/
async function readDirectory(fullPath: string, executableExtensions: Set<string> = process.platform === 'win32' ? new Set(['.exe']) : new Set()): Promise<Map<string, File | FolderWithChildren> | undefined> {
async function readDirectory(fullPath: string, executableExtensions: Set<string> = process.platform === 'win32' ? new Set(['.exe'/* ,'.cmd','.bat' */]) : new Set()): Promise<Map<string, File | FolderWithChildren> | undefined> {
// have we already read this directory?
let folder = cache.get(fullPath) as FolderWithChildren | undefined;
let promise: ManualPromise<FolderWithChildren | undefined> | undefined;
@@ -137,7 +137,7 @@ export async function scanFolder(folder: string, scanDepth: number, filePredicat
}
// normalize the folder
folder = filepath.normalize(folder);
folder = normalize(folder);
// if we have already visited this folder, return
await foreach(readDirectory(folder), async ([_name, entry]) => {
+22 -90
View File
@@ -3,86 +3,24 @@
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { strict } from 'assert';
import { existsSync } from 'fs';
import { resolve } from 'path';
import { isWindows } from '../../constants';
import { accumulator } from '../Async/iterators';
import { logAndReturn } from '../Async/returns';
import { Process } from '../Process/process';
import { ProcessFunction, Program } from '../Process/program';
import { is } from '../System/guards';
import { Instance } from '../System/types';
import { verbose } from '../Text/streams';
import { downloadRipgrep } from './downloadRipgrep';
import { filepath } from './filepath';
let ripgrep: Instance<ProcessFunction>;
async function setRipgrepBinaryLocation(filename: string) {
let ripgrep: Instance<ProcessFunction> | undefined;
export async function initRipGrep(filename: string) {
if (!ripgrep) {
const rg = await filepath.isExecutable(filename);
strict(rg, `File ${filename} is not executable`);
verbose(`Using ripgrep at'${filename}'`);
ripgrep = await new Program(filename);
}
return ripgrep;
}
export async function autoInitializeRipGrep() {
if (!ripgrep) {
try {
// if we're in vscode and it thas a copy of ripgrep, let's use that
const p = process as any;
if (p.resourcesPath) {
// if we're running in vscode, this will be there. If it isn't it's not likely that vscode itself is working correctly.
return setRipgrepBinaryLocation(resolve((process as any).resourcesPath, `app/node_modules.asar.unpacked/@vscode/ripgrep/bin/rg${isWindows ? '.exe' : ''}`));
}
} catch {
// ignore, move on.
}
// if we get here it might be because we're in a WSL or Remote vscode session,
// and the remote host is a bit different than a local instance of vscode.
// The vscode/ripgrep package should be in the node_modules folder, which means we can use it to find the ripgrep binary that it has.
// let's see if @vscode/ripgrep is installed
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const vs_rg = require('@vscode/ripgrep');
if (vs_rg) {
return setRipgrepBinaryLocation(vs_rg);
}
} catch {
// ignore, move on
}
// if we get here, ripgrep isn't installed for us and we don't appear to have the @vscode/ripgrep package
// we can call the downloadRipgrep function (which was borrowed from the @vscode/ripgrep package)
// this is a last resort, and should never happen in production, but could be necessary in development or CI or unit testing.
return setRipgrepBinaryLocation(await downloadRipgrep());
}
}
const initialization = autoInitializeRipGrep();
export async function fastFind(fileGlobs: string | string[], locations: string | string[], depth = 20): Promise<string[]> {
depth++;
fileGlobs = is.array(fileGlobs) ? fileGlobs : [fileGlobs];
locations = is.array(locations) ? locations : [locations];
fileGlobs.map(glob => glob.includes('**') ? glob : `**/${glob}`);
locations = locations.filter(each => existsSync(each.toString()));
const results = new Set<string>();
if (fileGlobs.length && locations.length) {
try {
const proc = await ripgrep(...fileGlobs.map(each => ['--glob', each]).flat(), '--max-depth', depth, '--null-data', '--no-messages', '-L', '--files', ...locations.map(each => each.toString()));
for await (const line of proc.stdio) {
results.add(line);
}
} catch {
// ignore
}
}
return [...results];
}
export class FastFinder implements AsyncIterable<string> {
@@ -105,6 +43,8 @@ export class FastFinder implements AsyncIterable<string> {
}
constructor(private fileGlobs: string[], options?: { executable?: boolean; executableExtensions?: string[] }) {
strict(ripgrep, 'initRipGrep must be called before using FastFinder');
this.keepOnlyExecutables = options?.executable ?? false;
if (this.keepOnlyExecutables && process.platform === 'win32') {
this.executableExtensions = options?.executableExtensions ?? ['.exe', '.bat', '.cmd', '.ps1'];
@@ -131,30 +71,23 @@ export class FastFinder implements AsyncIterable<string> {
// only search if there are globs and locations to search
if (globs.length && location.length) {
this.pending++;
void initialization.then(async () => {
try {
const proc = await ripgrep(...globs.map(each => ['--glob', each]).flat(), '--max-depth', depth, '--null-data', '--no-messages', '-L', '--files', ...location.map(each => each.toString()));
const process = proc as unknown as Instance<Process>;
this.processes.push(process);
for await (const line of process.stdio) {
if (this.distinct.has(line)) {
continue;
}
this.distinct.add(line);
if (!this.keepOnlyExecutables || await filepath.isExecutable(line)) {
this.#files.add(line);
}
void ripgrep!(...globs.map(each => ['--glob', each]).flat(), '--max-depth', depth, '--null-data', '--no-messages', '-L', '--files', ...location.map(each => each.toString())).then(async proc => {
const process = proc as unknown as Instance<Process>;
this.processes.push(process);
for await (const line of process.stdio) {
if (this.distinct.has(line)) {
continue;
}
} catch (e) {
console.log(e);
} finally {
this.pending--;
if (this.readyToComplete && this.pending === 0) {
this.#files.complete();
this.distinct.add(line);
if (!this.keepOnlyExecutables || await filepath.isExecutable(line)) {
this.#files.add(line);
}
}
}).catch(logAndReturn.undefined).finally(() => {
this.pending--;
if (this.readyToComplete && this.pending === 0) {
this.#files.complete();
}
});
}
return this;
@@ -184,7 +117,7 @@ function isMatch(obj: Record<string, any>): obj is RipGrepMatch {
/** Calls RipGrep looking for strings */
export async function* ripGrep(target: string, regex: string, options?: { glob?: string; binary?: boolean; encoding?: 'utf-16' | 'utf-8'; ignoreCase?: boolean }): AsyncGenerator<MatchData> {
await initialization;
strict(ripgrep, 'initRipGrep must be called before using ripGrep');
const optionalArguments = new Array<string>();
if (options?.binary) {
@@ -201,7 +134,6 @@ export async function* ripGrep(target: string, regex: string, options?: { glob?:
}
regex = regex.replace(/\?\</g, '\?P<');
const proc = await ripgrep(regex, '--null-data', '--json', '--no-messages', ...optionalArguments, target);
for await (const line of proc.stdio) {
try {
const obj = JSON.parse(line);
-1
View File
@@ -435,7 +435,6 @@ export class ReadWriteLineStream extends ReadableLineStream {
this.writeable.on('error', (_error) => {
/*
uncomment if testing :
this is handy for debugging to see if errors are happening.
if ((global as any).DEVMODE && error) {
-186
View File
@@ -1,186 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { is } from './guards';
export function isExpired(timeoutValue: number) {
return timeoutValue && timeoutValue < Date.now();
}
export function isLater(a: number, b: number) {
return a ? b ? a > b : true : false;
}
export class Cache<T = any> implements Iterable<[string, T]> {
static OneMinute = 60 * 1000;
static OneHour = 60 * Cache.OneMinute;
static OneDay = 24 * Cache.OneHour;
static OneWeek = 7 * Cache.OneDay;
static OneMonth = 30 * Cache.OneDay;
static OneYear = 365 * Cache.OneDay;
private map = new Map<string, [number, T]>();
private defaultTimeout = 0;
*[Symbol.iterator](): Iterator<[string, T]> {
for (const [key, [timeout, value]] of this.map.entries()) {
if (!isExpired(timeout)) {
yield [key, value];
}
}
}
constructor(defaultTimeout?: number);
constructor(entries?: readonly (readonly [string, [number, T]])[], defaultTimeout?: number);
constructor(arg1?: number | readonly (readonly [string, [number, T]])[], defaultTimeout?: number) {
if (arg1 === undefined) {
// overload #0 : no arguments
return;
}
if (is.numeric(arg1)) {
// overload #1 : default timeout
this.defaultTimeout = arg1 ?? 0;
return;
}
// overload #2 : entries and default timeout
this.defaultTimeout = defaultTimeout ?? 0;
this.loadValues(arg1);
}
/** Loads the values into the cache, overwriting any existing values that are older. */
loadValues(values: Iterable<readonly [string, [number, T]]>) {
for (const [key, newValue] of values) {
if (isExpired(newValue[0])) {
// if the current value is expired, skip it
continue;
}
const existing = this.map.get(key);
// if there is an existing value, and the new value is older, skip it
if (existing) {
if (isLater(existing[0], newValue[0])) {
continue;
}
}
// the new value is either not present, or is newer than the existing value
this.map.set(key, newValue);
}
}
/** Returns a value for a given key if it exists in the cache (and is not expired) otherwise, returns undefined */
get(key: string, timeout?: number): T | undefined {
const existing = this.map.get(key);
if (!existing) {
// no data for this key
return undefined;
}
if (existing[0] && existing[0] < Date.now()) {
// data in this key has expired
this.map.delete(key);
return undefined;
}
// update the timeout for this key
existing[0] = Date.now() + (timeout ?? this.defaultTimeout);
return existing[1];
}
getCacheEntry(key: string) {
return this.map.get(key);
}
/**
* Returns a value for a given key if it exists in the cache (and is not expired)
* or runs the action to get the value, and adds it to the cache, and then returns the value.
*/
getOrAdd(key: string, action: () => T | undefined, timeout?: number): T | undefined;
getOrAdd(key: string, action: () => Promise<T | undefined>, timeout?: number): Promise<T | undefined>;
getOrAdd(key: string, action: () => T | undefined | Promise<T | undefined>, timeout?: number): T | undefined | Promise<T | undefined>{
const result = this.get(key);
if (result !== undefined) {
return result;
}
const v = action();
if (is.promise(v)) {
return v.then(v => this.set(key, v, timeout));
}
return this.set(key, v, timeout);
}
/**
* Sets the value in the cache to the given value, with an optional timeout
*
* If the value is undefined, the key is removed from the cache.
*
*/
set(key: string, value: T | undefined, timeout?: number): T | undefined{
timeout = timeout ?? this.defaultTimeout;
if (timeout && timeout < Cache.OneMonth) {
// the timeout value is the number of milliseconds to keep the value in the cache
timeout += Date.now();
}
// temporary: sanity check
if (timeout > Date.now() + Cache.OneYear) {
// this date is clearly wrong, and too far in the future.
throw new Error('Timeout should not be that far in the future');
}
if (timeout && timeout < Date.now()) {
// this is already expired.
throw new Error('Timeout should not be in the past');
}
if (value === undefined) {
// auto delete undefined values
this.map.delete(key);
return undefined;
}
// insert the item with the timeout (or 0 for no timeout)
this.map.set(key, [timeout, value]);
return value;
}
/** Clears out the cache of entries that are expired. */
clean() {
for (const [key, [timeout]] of this.map) {
if (isExpired(timeout)) {
this.map.delete(key);
}
}
}
/** Clear out the Cache of all entries */
clear() {
this.map.clear();
}
/** Returns the entries in the cache that are not expired as an array of [key,T] */
entries() {
// filter out entries that are expired before returning
return [...this.map.entries()].filter(([,[timeout]]) => !isExpired(timeout)).map(([key, [,value]]) => [key, value] as const);
}
/** Returns the entries in the cache that are not expired as an array of [key, [timeout,T]] */
cacheEntries() {
// filter out entries that are expired before returning
return [...this.map.entries()].filter(([,[timeout]]) => !isExpired(timeout));
}
/** returns the values in the cache as an array of T */
values() {
// filter out entries that are expired before returning
return [...this.map.values()].filter(([timeout]) => !isExpired(timeout)).map(([,value]) => value);
}
/** returns the number of entries in the cache */
get size() {
return this.map.size;
}
}
@@ -1,11 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
// enable typescript disposable types/interfaces
/// <reference lib="esnext.disposable" />
export function dispose(onDispose: () => void): Disposable {
return { [Symbol.dispose] : onDispose };
}
-63
View File
@@ -1,63 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { is } from './guards';
export function equal(a: any, b: any) {
return is.array(a) && is.array(b) ? sequenceEqual(a, b) : deepEqual(a, b);
}
/** determines if two collections are equal */
export function sequenceEqual(a: any[], b: any[]) {
if (a.length !== b.length) {
return false;
}
for (let i: number = 0; i < a.length; ++i) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
/** determines if two arbitrary things are equvalent (deep) */
export function deepEqual(a: any, b: any) {
if (a === b) {
return true; // identical primitives, undefined, null, or same object
}
if (a !== Object(a) || b !== Object(b) || (Object.keys(a).length !== Object.keys(b).length)) {
return false; // not object, or objects that have different number of keys
}
// compare objects
for (const key in a) {
// if the key isn't in both, or the values aren't equal, return false
if (!(key in b || deepEqual(a[key], b[key]))) {
return false;
}
}
return true;
}
/** Extending this class makes an easy way for ensuring that a comparison for changed state is cached, and then
* when the state is changed, the new state is cached. This is useful for determining
*/
export class LastKnownState {
changed<T extends keyof this, V extends this[T] >(k: T, content: V): boolean {
if ((k in this) && equal(content, this[k])) {
return false;
}
this[k] = content;
return true;
}
unchanged<T extends keyof this, V extends this[T] >(k: T, content: V): boolean {
return !this.changed(k, content);
}
}
-3
View File
@@ -54,9 +54,6 @@ export function finalize(...items: any[]): void {
ignore(() => item.dispose?.());
});
ActiveFinalizers = Promise.all([fin, ActiveFinalizers, DispatcherBusy]).then(() => item.removeAllListeners?.());
if (item[Symbol.dispose]) {
using _ = item; // make it call the [Symbol.dispose()] method
}
return;
}
} catch {
-22
View File
@@ -21,10 +21,6 @@ export class is {
return false;
}
static numeric(node: any): node is number {
return typeof node === 'number' && !isNaN(node) && isFinite(node);
}
static object(node: any): node is Record<string, any> {
return typeof node === 'object' && node !== null && !is.array(node);
}
@@ -57,10 +53,6 @@ export class is {
return Array.isArray(instance);
}
static set(instance: any): instance is Set<any> {
return instance instanceof Set;
}
static string(instance: any): instance is string {
return typeof instance === 'string';
}
@@ -86,18 +78,4 @@ export class is {
static error(instance: any): instance is Error {
return instance instanceof Error;
}
static empty(instance: any): boolean {
// if the thing is undefined or an empty array
if (instance === undefined || (is.array(instance) && instance.length === 0)) {
return true;
}
// if it's an object, check if it's empty
if (is.object(instance)) {
// objects can be 'empty' if all the children are empty
return !!Object.entries(instance).find(([,value]) => !is.empty(value));
}
return false;
}
}
-80
View File
@@ -1,80 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { MessagePort } from 'worker_threads';
import { collectGarbage } from './garbageCollector';
function showActiveHandles() {
const open = (process as any)._getActiveHandles().filter(
(each: any) =>
!each.destroyed && // discard handles that claim they are destroyed.
!(each.fd === 0) && // ignore stdin/stdout/stderr
!(each.fd === 1) && // ignore stdin/stdout/stderr
!(each.fd === 2) && // ignore stdin/stdout/stderr
!(each instanceof MessagePort) && // ignore worker thread message ports
each.listening // keep servers that are still listening.
);
if (open.length) {
console.log('################');
console.log('Active Handles: ');
console.log('################');
console.log(open);
}
}
let misbehavingPromises: Set<Promise<any>>;
export function addMisbehavingPromise(promise: Promise<any>) {
misbehavingPromises?.add(promise);
return promise;
}
(global as any).addMisbehavingPromise = addMisbehavingPromise;
let MAX = 20;
export function initDevModeChecks() {
misbehavingPromises = new Set<Promise<any>>();
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('mocha').afterAll?.(() => {
collectGarbage();
console.log("showing!");
showActiveHandles();
});
} catch {
// ignore
console.log('oops');
}
process.on('unhandledRejection', (reason: any, p) => {
console.log(`Unhandled Rejection at: Promise ${p} - reason:, ${(reason as any)?.stack ?? reason}`);
});
process.on('multipleResolves', (type, promise, reason) => {
if (misbehavingPromises.has(promise)) {
return;
}
if (reason && (reason as any).stack) {
console.error((reason as any).stack);
return;
}
if (!MAX--) {
throw new Error('MAX MULTIPLE RESOLVED REACHED');
}
console.error({text: 'Multiple Resolves', type, promise, reason});
});
process.on("beforeExit", () => {
console.log("EXITING!!!");
});
process.on("exit", () => {
console.log("EXITING!!!");
});
process.on('exit', showActiveHandles);
}
-13
View File
@@ -33,16 +33,3 @@ export function getOrAdd<TKey, TValue>(map: Map<TKey, TValue> | WeakMap<any, TVa
return initializer;
}
}
export function entries<TKey, TValue>(map: Map<TKey, TValue>): [TKey, TValue][];
export function entries<TKey, TValue>(map: Promise<Map<TKey, TValue>>): Promise<[TKey, TValue][]>;
export function entries<TKey, TValue, TKeyOut, TValueOut>(map: Map<TKey, TValue>, selector?: (key: TKey, value: TValue) => [TKeyOut, TValueOut]): [TKeyOut, TValueOut][];
export function entries<TKey, TValue, TKeyOut, TValueOut>(map: Promise<Map<TKey, TValue>>, selector?: (key: TKey, value: TValue) => [TKeyOut, TValueOut]): Promise<[TKeyOut, TValueOut][]>;
export function entries<TKey, TValue, TKeyOut = TKey, TValueOut = TValue>(map: Promise<Map<TKey, TValue>> | Map<TKey, TValue>, selector?: (key: TKey, value: TValue) => [TKeyOut, TValueOut]): [TKeyOut, TValueOut][] | [TKey, TValue][] | Promise<[TKeyOut, TValueOut][] | [TKey, TValue][]> {
return is.promise(map) ?
map.then(m => entries(m, selector)) : // async version
selector ?
[...map.entries()].map(([key, value]) => selector(key, value)) : // map the values with a selector
[...map.entries()]; // return the values
}
@@ -1,19 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
const startTime = Date.now();
export function elapsed() {
return `[${Date.now() - startTime}msec] `;
}
export function time(fn: (msec: number, elapsed: number) => void) {
const now = Date.now();
return {
[Symbol.dispose]() {
fn(Date.now() - now, Date.now() - startTime);
}
};
}
-112
View File
@@ -1,112 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { normalize } from 'path';
import { isWindows } from '../../constants';
import { Command, CommandFunction } from '../Process/program';
import { Cache } from './cache';
let powerShell: CommandFunction;
async function init() {
powerShell = await new Command("powershell", '-NoProfile', '-NonInteractive', '-Command');
}
const initialized = init();
type QueryResults = { subKeys: string[]; values: Record<string, { data: any; type: string } >};
type RegistryProperties = Record<string, string | number | string[] | Buffer >;
interface RegKey {
properties: RegistryProperties;
children: string[];
}
const regKeyCache = new Cache<RegKey>(5 * Cache.OneMinute);
/**
* Returns a RegKey containing the registry data from the given hive and path
*
* @param hive the hive to read from (HKLM, HKCU, etc)
* @param path the path to read from (ie, 'SOFTWARE\Microsoft\Windows Kits\Installed Roots')
* @returns a RegKey containing the registry data from the given hive and path, or undefined if the key does not exist or the user does not have access
*
* @remarks if anything goes wrong, this will return undefined
*/
export async function readKey(hive: string, path: string): Promise<RegKey | undefined> {
if (!isWindows) {
// registry is only available on windows
return undefined;
}
const cacheKey = `${hive}:${path}`;
const result = regKeyCache.get(cacheKey);
if (result) {
return result;
}
try {
// normalize the hive name
switch (hive.toUpperCase()) {
case 'HKLM':
case 'HKEY_LOCAL_MACHINE':
hive = 'HKLM';
break;
case 'HKCU':
case 'HKEY_CURRENT_USER':
hive = 'HKCU';
break;
default:
// invalid hive, PS only has HKLM and HKCU PSDrives.
return undefined;
}
// remove all unprintable characters and normalize the path (backslashes as separators, no leading/trailing slashes)
// eslint-disable-next-line no-control-regex
path = normalize(path.replace(/[\x00-\x1F]/gm, '')).replace(/(^\\+|\\$)/gm, '');
// ensure that the command is initialized
await initialized;
// shell out to powershell to get the registry data
const data = await powerShell(`
$item = (Get-Item -Path "${hive}:${path}" -ea 0)
if( $item ) {
$result = @{
subKeys = $item.getSubKeyNames()
values = @{}
}
$item.GetValueNames() |% {
$result.values[$_] = @{
data = $item.GetValue($_)
type = $item.GetValueKind($_).toString().toLower()
}
}
$result | convertto-json -depth 4
} else {
exit -1
}
`);
// if the command failed, return undefined
if (data.code) {
return undefined;
}
// parse the json output
const queried = JSON.parse(data.stdio.all().join('')) as QueryResults;
// return the data in a more usable format
return regKeyCache.set(cacheKey, {
children: queried.subKeys,
properties: Object.entries(queried.values).reduce((result, [key, value]) => {
result[key] = value.type === 'binary' ? Buffer.from(value.data) : value.data;
return result;
}, {} as RegistryProperties)
});
} catch {
// failures will always return undefined
return undefined;
}
}
-52
View File
@@ -1,52 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { normalize } from 'path';
import { Uri } from 'vscode';
import { is } from './guards';
/**
* Adds one or more values to a set (filters out falsy values)
* @param set the set to add values to
* @param values the one or more values to add
* @returns the set
*/
export function add<T>(set: Set<T>, values: Iterable<T> | T | undefined): Set<T> {
if (!is.iterable(values)) {
return values ? set.add(values) : set;
}
for (const value of values) {
if (value) {
set.add(value);
}
}
return set;
}
function normalizePath(path: string) {
return Uri.file(normalize(path)).fsPath;
}
/**
* Adds one or more file path values to a set (filters out falsy values)
* @param set the set to add values to
* @param values the one or more values to add
* @returns the set
*/
export function addNormalizedPath(set: Set<string>, values: Iterable<string> | string | undefined): Set<string> {
if (!is.iterable(values)) {
return values ? set.add(normalizePath(values)) : set;
}
for (const value of values) {
if (value) {
set.add(normalizePath(value));
}
}
return set;
}
-234
View File
@@ -1,234 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { fail } from 'assert';
import { MessagePort, Worker, isMainThread } from 'worker_threads';
import { ManualPromise } from '../Async/manualPromise';
import { finalize } from './finalize';
import { is } from './guards';
// enable typescript disposable types/interfaces
/// <reference lib="esnext.disposable" />
// polyfill Symbol.dispose
(Symbol as any).dispose ??= Symbol("Symbol.dispose");
(Symbol as any).asyncDispose ??= Symbol("Symbol.asyncDispose");
/*
* SNARE: Simple Nodejs Asynchronous Remoting Engine
*
* SNARE is an extremely lightweight remoting engine that
* allows you to call functions in a nodejs worker thread
*
* It supports:
* - notifications (no response)
* - requests (async calls with a response)
* - error handling
* - some simple byref object management
*
* As long as the values you pass and return are supported by the Structured Clone Algorithm,
* (see https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)
* you can pass them by value to the remote thread.
*
* If you need to pass an object by reference, you can manually craft a remote object extending
* the MarshalByReference; see the Toolset class for an example.
*/
const results = new Map<number, ManualPromise<any>>();
let next = 0;
interface EventData {
id: string;
sequence: number;
parameters?: any[];
result?: any;
error?: any;
}
export interface RemoteConnection {
connection: Worker | MessagePort;
terminate(): void;
request(id: string, ...parameters: any[]): Promise<any>;
notify(id: string, ...parameters: any[]): void;
marshall<T extends MarshalByReference>(ctor: new (remote: RemoteConnection, instance: number) => T, instance: Promise<number>): Promise<T | undefined>;
marshall<T extends MarshalByReference>(ctor: new (remote: RemoteConnection, instance: number) => T, instance: number): T | undefined;
}
/** SNARE: Simple Nodejs Asynchronous Remoting Engine */
export function startRemoting(connection: Worker | MessagePort, endpoint: Record<string, (...args: any[]) => any>): RemoteConnection {
const ready = new ManualPromise<void>();
connection.on('online', () => ready.resolve());
// the worker threads don't have an 'online' event (the port is already connected)
if (!isMainThread) {
ready.resolve();
}
connection.on('message', (eventData: EventData) => {
// if the event is an error, reject the promise
if (eventData.id === '$error') {
results.get(eventData.sequence)?.reject(eventData.error);
return results.delete(eventData.sequence);
}
// if the event is a result, resolve the promise
if (eventData.id === '$result') {
results.get(eventData.sequence)?.resolve(eventData.result);
return results.delete(eventData.sequence);
}
// otherwise, we're going to call a remote function
// get the sequence number for the result (0 indicates that it's a notification)
const sequence = eventData.sequence;
try {
// call the endpoint
const result = endpoint[eventData.id](...eventData.parameters ?? []);
// is it a request?
if (sequence) {
if (is.promise(result)) {
// wait for the async call to complete, then post the result back
void result.then(
(result) => connection.postMessage({ id:"$result", sequence, result }),
(error) => connection.postMessage({ id:"$error", sequence, error })); // call failed, threw
return;
}
// post the result back (call returned synchronously)
connection.postMessage({id:"$result", sequence, result }); // call succeeded
}
} catch (error) {
if (sequence) {
connection.postMessage({id:"$error", sequence, error }); // call failed, threw
}
}
});
connection.on('messageerror', (error) => {
console.log(error);
});
connection.on('error', (error) => {
console.log(error);
});
const remote = {
connection,
request: async (id: string, ...parameters: any[]) => {
if (isMainThread) {
await ready;
}
const result = new ManualPromise<any>();
const eventData: EventData = { id, parameters, sequence: ++next };
results.set(eventData.sequence, result);
// watch call times:
// const now = Date.now();
// result.then(() => console.log(`REMOTE RESULT ${JSON.stringify(eventData)} ${Date.now() - now}`), () => console.log(`REMOTE ERROR ${JSON.stringify(eventData)} ${Date.now() - now}`));
connection.postMessage(eventData);
return result;
},
notify: (id: string, ...parameters: any[]) => isMainThread ? void ready.then(() => connection.postMessage({ id, parameters, sequence: 0 })) : connection.postMessage({ id, parameters, sequence: 0 }),
marshall: <T extends MarshalByReference>(ctor: new (remote: RemoteConnection, instance: number) => T, instance: number | Promise<number>) => instance ? is.promise(instance) ? instance.then(i => new ctor(remote, i)) : new ctor(remote, instance) : undefined,
terminate: () => { if (isMainThread) { void (connection as Worker).terminate(); } }
};
connection.on('close', () => {
// disable the remote connection interface so that it can't be used anymore
const r = remote as any;
r.request = r.marshal = async () => {};
r.notify = r.terminate = () => {};
r.connection = undefined;
// the connection is closed, so reject all pending requests
for (const result of results.values()) {
try {
result.reject('Connection closed');
} catch {
// ignore
}
}
// and clear the results map
results.clear();
// clear out any byref objects
identityIndex.length = 0;
instanceIndex.clear();
});
return remote;
}
const identityIndex = new Array<any>();
const instanceIndex = new Map<any, [number, number]>();
export function getByRef<T = any>(identity: number): T {
return identityIndex[identity] ?? fail(`Invalid ${identity} for ByRef object`);
}
export function ref(instance: Promise<any>): Promise<number | undefined>;
export function ref(instance: any): number | undefined;
export function ref(instance: any | Promise<any>): number | undefined | Promise<number | undefined> {
if (is.promise(instance)) {
return instance.then(ref);
}
if (is.object(instance)) {
// lookup the instance in the index
const [identity, refcount] = instanceIndex.get(instance) ?? [++next, 0];
// if refcount is zero, then we need to add it to the index
if (!refcount) {
identityIndex[identity] = instance;
}
// and increment the refcount
instanceIndex.set(instance, [identity, refcount + 1]);
// and return the identity
return identity;
}
// if it's not an object, we can't ref it.
return undefined;
}
export function unref(identity: number) {
// lookup the instance
const instance = getByRef(identity);
if (instance) {
// decrement the refcount
const [identity, refcount] = instanceIndex.get(instance) ?? [0, 0];
if (refcount > 1) {
// reduce the refcount by one
return instanceIndex.set(instance, [identity, refcount - 1]);
}
// it's the last reference, so remove it from the index
identityIndex[identity] = undefined;
instanceIndex.delete(instance);
finalize(instance);
}
}
/**
* A base class for objects that are passed by reference to a remote thread.
*
* All MarshalByReference wrappers, are references to an object that lives in the remote thread.
* It is important to call .dispose() when you are done with it, as this enables the remote
* thread to release the object and free up resources.
*/
export class MarshalByReference implements Disposable {
constructor(protected remote: RemoteConnection, protected instance: number) {
}
/**
* This disposes the ByRef object, and notifies the remote thread to reduce the refcount,
* which would dispose the remote object if it was the last reference.
*/
[Symbol.dispose]() {
void this.remote.notify('unref', this.instance);
this.instance = 0;
}
}
@@ -1,22 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
/**
* This performs a structured clone using the built-in serialization and deserialization
* v8 APIs. This is the fastest thing available.
*
* This can be replaced with the built-in structuredClone() when it is available in
* node.js and electron (it's in node V17+)
*
* for more information, see the Structured Clone Algorithm for JavaScript:
* https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
*
* @param instance any value to be cloned
* @returns a copy of the instance
*/
export function structuredClone<T>(instance: T): T {
/// return instance ? deserialize(serialize(instance)) : instance;
return instance ? JSON.parse(JSON.stringify(instance)) : instance;
}
+1 -1
View File
@@ -6,5 +6,5 @@
import { verboseEnabled } from '../../constants';
export function verbose(...args: any[]): void {
return verboseEnabled || process.argv.includes('--verbose') ? console.log(`${args.join(' ')}`) : undefined;
return verboseEnabled || process.argv.includes('--verbose') ? console.log(...args) : undefined;
}
+21 -45
View File
@@ -66,7 +66,7 @@ function parseTaggedLiteral(templateString: string) {
result.state = 'text';
continue;
// ...case ' ':
case ' ':
case '\t':
case '\r':
case '\n':
@@ -78,12 +78,12 @@ function parseTaggedLiteral(templateString: string) {
continue;
}
if (expression) {
if (isIdentifierPart(char.codePointAt(0)!) || char === '-' || char === '/' || char === ';' || char === ' ') {
if (isIdentifierPart(char.codePointAt(0)!) || char === '-' || char === '/') {
expression += char;
continue;
}
// error, fall through
} else if (isIdentifierStart(char.codePointAt(0)!) || char === '-' || char === '/' || char === ';' || char === ' ') {
} else if (isIdentifierStart(char.codePointAt(0)!) || char === '-' || char === '/') {
expression += char;
continue;
}
@@ -117,11 +117,7 @@ function split(expression: string) {
return (expression.match(/(.*?):(.*)/) || ['', '', expression]).slice(1);
}
async function resolveValue(expression: string, context: Record<string, any>, customResolver: CustomResolver = async (_prefix: string, _expression: string) => ''): Promise<string> {
if (!expression) {
return '';
}
function resolveValue(expression: string, context: Record<string, any>, customResolver = (_prefix: string, _expression: string) => ''): string {
const [prefix, suffix] = split(expression);
function joinIfArray(value: any, separator = '\u0007') {
@@ -133,16 +129,16 @@ async function resolveValue(expression: string, context: Record<string, any>, cu
if (variable !== undefined && variable !== null) { // did we get back an actual value
// it's a child of a variable
return joinIfArray(suffix.includes(':') ? // is the suffix another expression?
await resolveValue(suffix, variable) : // Yeah, resolve it
variable[suffix] ?? await customResolver(prefix, suffix) ?? ''); // No, return the member of the variable, or dynamic, or empty string
resolveValue(suffix, variable) : // Yeah, resolve it
variable[suffix] ?? customResolver(prefix, suffix) ?? ''); // No, return the member of the variable, or dynamic, or empty string
}
// no variable by that name, so return the dynamic value, or an empty string
return joinIfArray(await customResolver(prefix, suffix) ?? '');
return joinIfArray(customResolver(prefix, suffix) ?? '');
}
// look up the value in the variables, or ask the dynamic function to resolve it, failing that, an empty string
return joinIfArray(context[suffix] ?? await customResolver(prefix, suffix) ?? '');
return joinIfArray(context[suffix] ?? customResolver(prefix, suffix) ?? '');
}
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -218,58 +214,38 @@ class as {
}
}
export type CustomResolver = (prefix: string, expression: string) => Promise<string>;
export async function render(templateStrings: string[], context: Record<string, any>, customResolver?: CustomResolver, ensureValuesAreValidJS?: boolean): Promise<string[]>;
export async function render(templateString: string, context: Record<string, any>, customResolver?: CustomResolver, ensureValuesAreValidJS?: boolean): Promise<string>;
export async function render(templateString: string | string[], context: Record<string, any>, customResolver: CustomResolver = async (_prefix: string, _expression: string) => '', asJs = false): Promise<string | string[]> {
export function render(templateStrings: string[], context: Record<string, any>, customResolver?: (prefix: string, expression: string) => string, ensureValuesAreValidJS?: boolean): string[];
export function render(templateString: string, context: Record<string, any>, customResolver?: (prefix: string, expression: string) => string, ensureValuesAreValidJS?: boolean): string;
export function render(templateString: string | string[], context: Record<string, any>, customResolver = (_prefix: string, _expression: string) => '', asJs = false): string | string[] {
if (Array.isArray(templateString)) {
return Promise.all(templateString.map(each => render(each, context, customResolver, asJs)));
return templateString.map(each => render(each, context, customResolver, asJs));
}
// quick exit if it's not a templated string
if (!templateString.includes('${')) {
return templateString;
}
const { template, expressions, state, message } = parseTaggedLiteral(templateString);
const stabilize = asJs ? as.js : (x: string) => as.string(x) ?? '';
if (state === 'error') {
console.error(`Error parsing tagged literal: ${message}`);
return message;
}
let result = '';
for (let index = 0; index < template.length; ++index) {
const each = template[index];
if (index) {
const v = await resolveValue(expressions[index - 1], context, customResolver);
result = `${result}${stabilize(v)}${each}`;
} else {
result = `${each}`;
}
}
// if the result isn't the same as the original, but still has template strings, resolve any additional ones we can.
return result !== templateString && result.includes('${') ? render(result, context, customResolver, asJs) : result;
return state === 'error' ?
message : // return the error message if the parse failed. (this is fatal anyways)
template.reduce((result, each, index) => `${result}${stabilize(resolveValue(expressions[index - 1], context, customResolver))}${each}`); // resolve the inline expressions and join the template
}
export async function evaluateExpression(expression: string, context: Record<string, any>, customResolver: CustomResolver = async (_prefix: string, _expression: string) => ''): Promise<Primitive | undefined> {
const result = expression.match(/\!|==|!=|>=|<=|>|<|\?|\|\||&&/) ? safeEval(await render(expression, context, customResolver, true)) as Primitive : await render(expression, context, customResolver);
export function evaluateExpression(expression: string, context: Record<string, any>, customResolver = (_prefix: string, _expression: string) => ''): Primitive | undefined {
const result = expression.match(/\!|==|!=|>=|<=|>|<|\?|\|\||&&/) ? safeEval(render(expression, context, customResolver, true)) as Primitive : render(expression, context, customResolver);
return result === '' || result === 'undefined' || result === 'null' || result === null ? undefined : result;
}
export async function recursiveRender<T extends Record<string, any>>(obj: T, context: Record<string, any>, customResolver = async (_prefix: string, _expression: string) => ''): Promise<T> {
export function recursiveRender<T extends Record<string, any>>(obj: T, context: Record<string, any>, customResolver = (_prefix: string, _expression: string) => ''): T {
const result = (is.array(obj) ? [] : {}) as Record<string, any>;
for (const [key, value] of Object.entries(obj)) {
const newKey = is.string(key) && key.includes('${') ? await render(key, context, customResolver) : key;
const newKey = is.string(key) && key.includes('${') ? render(key, context, customResolver) : key;
if (is.string(value)) {
result[newKey] = await evaluateExpression(value, context, customResolver);
result[newKey] = evaluateExpression(value, context, customResolver);
} else if (typeof value === 'object') {
result[newKey] = await recursiveRender(value, context, customResolver);
result[newKey] = recursiveRender(value, context, customResolver);
} else {
result[newKey] = value;
}
-3
View File
@@ -13,6 +13,3 @@ export const isLinux = OperatingSystem === 'linux';
// if you want to see the output of verbose logging, set this to true.
export const verboseEnabled = false;
export const headers = ["*.hpp", "*.hh", "*.hxx", "*.h++", "*.hp", "*.h", "*.ii", "*.inl", "*.ipp", "*.tcc", "*.idl"];
export const sources = ["*.c", "*.i", "*.cpp", "*.cc", "*.cxx", "*.c++", "*.cp", "*.ino", "*.cu", "*.cuh"];
+1 -1
View File
@@ -62,7 +62,7 @@ export async function expandString(input: string, options: ExpansionOptions): Pr
return replaceAll(result, '${dollar}', '$');
}
/** @returns a promise to [expandedString, didReplacement] */
/** Returns [expandedString, didReplacement] */
async function expandStringImpl(input: string, options: ExpansionOptions): Promise<[string, boolean]> {
if (!input) {
return [input, false];
+6 -31
View File
@@ -9,7 +9,6 @@ import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { CppSourceStr } from './LanguageServer/extension';
import { getLocalizedString, LocalizeStringParams } from './LanguageServer/localization';
import { is } from './Utility/System/guards';
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
@@ -131,37 +130,13 @@ export interface DebugProtocolParams {
params?: any;
}
export function logDebugProtocol(output: DebugProtocolParams | string): void {
try {
if (!output) {
return;
}
if (is.string(output)) {
if (output.startsWith("Content")) {
output = output.substring(output.indexOf("{"), output.length);
}
output = JSON.parse(output) as DebugProtocolParams;
}
if (!debugChannel) {
debugChannel = vscode.window.createOutputChannel(`${localize("c.cpp.debug.protocol", "C/C++ Debug Protocol")}`, 'javascript');
debugChannel.appendLine("const msgs = {};");
debugChannel.appendLine("const results = {};");
}
if ('result' in output) {
debugChannel.appendLine("");
debugChannel.appendLine("// ************************************************************************************************************************");
debugChannel.append(`results["${(output as any).id}"]= ${JSON.stringify((output as any).result, null, 2)};`);
return;
}
if (!["cpptools/debugProtocol", "cpptools/debugLog", "cpptools/onIntervalTimer", "cpptools/logTelemetry" ].includes(output.method)) {
debugChannel.appendLine("");
debugChannel.appendLine("// ************************************************************************************************************************");
debugChannel.append(`msgs["${output.method}"]= ${JSON.stringify(output.params, null, 2)};`);
}
} catch (e) {
console.log(e);
export function logDebugProtocol(output: DebugProtocolParams): void {
if (!debugChannel) {
debugChannel = vscode.window.createOutputChannel(`${localize("c.cpp.debug.protocol", "C/C++ Debug Protocol")}`);
}
debugChannel.appendLine("");
debugChannel.appendLine("************************************************************************************************************************");
debugChannel.append(`${output}`);
}
export interface ShowWarningParams {
+1 -5
View File
@@ -20,7 +20,6 @@ import { CppBuildTaskProvider, cppBuildTaskProvider } from './LanguageServer/cpp
import { getLocaleId, getLocalizedHtmlPath } from './LanguageServer/localization';
import { PersistentState } from './LanguageServer/persistentState';
import { CppSettings } from './LanguageServer/settings';
import { initialize } from './ToolsetDetection/detection';
import { logAndReturn, returns } from './Utility/Async/returns';
import { CppTools1 } from './cppTools1';
import { logMachineIdMappings } from './id';
@@ -57,10 +56,6 @@ export async function activate(context: vscode.ExtensionContext): Promise<CppToo
vscode.workspace.registerTextDocumentContentProvider('cpptools-schema', new SchemaProvider());
// initialize the toolset detection
void initialize([util.getExtensionFilePath("bin/definitions")], {storagePath: context.globalStorageUri.fsPath});
// Initialize the DebuggerExtension and register the related commands and providers.
await DebuggerExtension.initialize(context);
@@ -151,6 +146,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<CppToo
// the message on old Macs that we've already displayed a warning for.
log(localize("intellisense.disabled", "intelliSenseEngine is disabled"));
}
return cppTools;
}
+23 -1
View File
@@ -451,5 +451,27 @@
"refactor_copy_declaration_definition_failed": {
"text": "Copying Declaration / Definition to clipboard failed: %s",
"hint": "The operation 'Copy Declaration / Definition' on a function was not successful. %s is the error info that has a period at the end of the string."
}
},
"refactor_extract_to_function": "Extract to function",
"refactor_extract_to_free_function": "Extract to free function",
"refactor_extract_to_member_function_in": {
"text": "Extract to member function in '{0}'",
"hint": "{0} is the name of the struct or class the member function belongs to, e.g. 'class Foo'."
},
"refactor_extract_outsidefunc": "The selected text is not inside a function.",
"refactor_extract_multifunc": "The selected text cannot span different functions.",
"refactor_extract_variable": "Variable '%s' is declared in the selected region and then used below it.",
"refactor_extract_macro": "Preprocessor macro '%s' is used below the selected region.",
"refactor_extract_inactive": "The selected region spans an inactive preprocessor block.",
"refactor_extract_no_il": "The selected region does not contain any code that can be extracted.",
"refactor_extract_entirefunc": "The selected region is not entirely within the function's body.",
"refactor_extract_errors_selection": "The selection contains IntelliSense errors.",
"refactor_extract_reference_c_code": {
"text": "'%s' is not declared within the selected code, but is being modified. C code cannot pass arguments by reference.",
"hint": "%s is the name of a variable."
},
"refactor_extract_reference_return_c_code": "The function would have to return a value by reference. C code cannot return references.",
"refactor_extract_xborder_jump": "Jumps between the selected code and the surrounding code are present.",
"refactor_extract_missing_return": "In the selected code, some control paths exit without setting the return value. This is supported only for scalar, numeric, and pointer return types.",
"expand_selection": "Expand selection (to enable 'Extract to function')"
}
+1
View File
@@ -79,6 +79,7 @@ export function getExperimentationService(): Promise<IExperimentationService> |
return initializationPromise;
}
// @ts-expect-error The function isExperimentEnabled will be used for future experiments.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function isExperimentEnabled(experimentName: string): Promise<boolean> {
if (new CppSettings().experimentalFeatures) {
+1
View File
@@ -10,6 +10,7 @@
"sourceMap": true,
"rootDir": ".",
"removeComments": true,
"noUnusedLocals": true,
"strictNullChecks": true,
"strict": true,
"forceConsistentCasingInFileNames": true
-8
View File
@@ -83,14 +83,6 @@ export function initDevModeChecks() {
console.error({text: 'Multiple Resolves', type, promise, reason});
});
process.on("beforeExit", () => {
console.log("EXITING!!!");
});
process.on("exit", () => {
console.log("EXITING!!!");
});
process.on('exit', showActiveHandles);
}
@@ -1 +0,0 @@
{}
@@ -1,23 +0,0 @@
{
"configurations": [
{
"enableNewIntellisense": true,
"compiler": "Microsoft Visual C++/19.*/x64/x64",
// "compiler": "C:\\program files\\microsoft visual studio\\2022\\enterprise\\vc\\tools\\msvc\\14.36.32532\\bin\\hostx64\\x64\\cl.exe",
// "compiler": "cl.exe",
// "compiler": "C:\\program files\\microsoft visual studio\\2022\\enterprise\\vc\\tools\\msvc\\14.36.32532\\bin\\hostx64\\x64\\cl.exe",
"name": "newstyle",
// "intelliSenseMode": "clang-x64",
"compilerArgs": [
"-Dfoo=bar",
"-I${workspaceFolder}/include",
"/MD"
]
// "intelliSenseMode": "windows-msvc-x64",
// "compilerPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC\\14.36.32532\\bin\\Hostx64\\x64\\cl.exe",
// "cStandard": "c17",
// "cppStandard": "c++17"
}
],
"version": 4
}
@@ -1,3 +0,0 @@
{
"C_Cpp.loggingLevel": "Debug"
}
@@ -1 +0,0 @@
#define APP_NAME "Sample"
@@ -1,7 +0,0 @@
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
@@ -1,8 +0,0 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved.
* See 'LICENSE' in the project root for license information.
* ------------------------------------------------------------------------------------------ */
export function foo() {
}

Some files were not shown because too many files have changed in this diff Show More