Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3364cab2af | ||
|
|
ae3f635cb6 | ||
|
|
d8557a59cf | ||
|
|
92595d8b04 | ||
|
|
8483206530 | ||
|
|
28b25b63cf | ||
|
|
1566f46b94 | ||
|
|
497c3bac14 | ||
|
|
2c14fa5c79 |
@@ -21,7 +21,8 @@ module.exports = {
|
||||
"eslint-plugin-jsdoc",
|
||||
"@typescript-eslint/eslint-plugin",
|
||||
"eslint-plugin-import",
|
||||
"eslint-plugin-header"
|
||||
"eslint-plugin-header",
|
||||
"etc"
|
||||
],
|
||||
"rules": {
|
||||
"indent": [
|
||||
@@ -83,6 +84,7 @@ 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",
|
||||
@@ -90,7 +92,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": "^_" }],
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
|
||||
"@typescript-eslint/no-unnecessary-boolean-literal-compare": "error",
|
||||
"arrow-body-style": "error",
|
||||
"comma-dangle": "error",
|
||||
|
||||
@@ -13,6 +13,7 @@ bin/cpptools*
|
||||
bin/*.dll
|
||||
bin/.vs
|
||||
bin/LICENSE.txt
|
||||
bin/rg*
|
||||
|
||||
# ignore lock files
|
||||
install.lock
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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]')}`)}`);
|
||||
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { $args, $root, $scenario, assertAnyFile, brightGreen, gray, green, pwd }
|
||||
|
||||
import { resolve } from 'path';
|
||||
import { getTestInfo } from '../test/common/selectTests';
|
||||
import { install, options } from "./vscode";
|
||||
import { environment, 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: { ...process.env, DONT_PROMPT_WSL_INSTALL:"1" } });
|
||||
spawnSync(cli, ARGS, { encoding: 'utf-8', stdio: 'ignore', env:environment()});
|
||||
}
|
||||
|
||||
@@ -119,13 +119,17 @@ export async function write(filePath: string, data: Buffer | string) {
|
||||
await writeFile(filePath, data);
|
||||
}
|
||||
|
||||
export async function updateFiles(files: string[], dest: string | Promise<string>) {
|
||||
export async function updateFiles(files: string[], dest: string | Promise<string>, prefix?: 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 = resolve(target, each);
|
||||
await write(targetFile, await readFile(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} `);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -135,7 +139,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 (!each.startsWith('--') && require.main.exports[each]) {
|
||||
if (require.main.exports[each]) {
|
||||
$cmd = each;
|
||||
$args.splice(i, 1);
|
||||
break;
|
||||
@@ -341,3 +345,55 @@ 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();
|
||||
|
||||
@@ -19,63 +19,11 @@ 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;
|
||||
}
|
||||
@@ -90,7 +38,8 @@ 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
|
||||
SCENARIO: assets,
|
||||
DONT_PROMPT_WSL_INSTALL:"1"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "node16",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "node16",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
|
||||
@@ -3,12 +3,16 @@
|
||||
* 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 { mkdir, readJson, rimraf, write } from './common';
|
||||
import { $switches, error, 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');
|
||||
@@ -52,7 +56,57 @@ 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() {
|
||||
|
||||
Vendored
+4
@@ -13,8 +13,12 @@
|
||||
"--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/**"
|
||||
|
||||
Vendored
+18
@@ -1,11 +1,16 @@
|
||||
// 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,
|
||||
@@ -64,4 +69,17 @@
|
||||
"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",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -22,7 +22,7 @@
|
||||
"isDefault": true
|
||||
},
|
||||
"isBackground": true,
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"problemMatcher": "$tsc-watch"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"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" },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"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",
|
||||
],
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"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", }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"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}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"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": [],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"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}" ]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
|
||||
# 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)
|
||||
|
||||
@@ -6253,8 +6253,8 @@
|
||||
"@types/tmp": "^0.1.0",
|
||||
"@types/which": "^1.3.2",
|
||||
"@types/yauzl": "^2.9.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||
"@typescript-eslint/parser": "^6.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.5.0",
|
||||
"@typescript-eslint/parser": "^6.5.0",
|
||||
"eslint-plugin-header": "^3.1.1",
|
||||
"@vscode/test-electron": "^2.3.3",
|
||||
"@vscode/dts": "^0.4.0",
|
||||
@@ -6263,6 +6263,7 @@
|
||||
"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",
|
||||
@@ -6277,7 +6278,7 @@
|
||||
"parse5-traverse": "^1.0.3",
|
||||
"ts-loader": "^8.1.0",
|
||||
"ts-node": "10.9.1",
|
||||
"typescript": "^5.1.3",
|
||||
"typescript": "^5.2.2",
|
||||
"@vscode/debugadapter": "^1.61.0",
|
||||
"@vscode/debugprotocol": "^1.61.0",
|
||||
"vscode-nls-dev": "^4.0.0-next.1",
|
||||
@@ -6305,7 +6306,9 @@
|
||||
"vscode-languageclient": "^8.1.0-next.4",
|
||||
"vscode-nls": "^5.0.0",
|
||||
"vscode-tas-client": "^0.1.27",
|
||||
"which": "^2.0.2"
|
||||
"which": "^2.0.2",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"chokidar": "^3.5.3",
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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]
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -56,12 +56,11 @@ 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 defaultClient.languageClient.sendRequest(GetDocumentSymbolRequest, params, token);
|
||||
const response: GetDocumentSymbolResult = await client.languageClient.sendRequest(GetDocumentSymbolRequest, params, token);
|
||||
if (token.isCancellationRequested || response.symbols === undefined) {
|
||||
throw new vscode.CancellationError();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
@@ -27,22 +28,31 @@ 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 { CloseAction, DidOpenTextDocumentParams, ErrorAction, LanguageClientOptions, NotificationType, Position, Range, RequestType, TextDocumentIdentifier } from 'vscode-languageclient';
|
||||
import { CancellationTokenSource, 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, logDebugProtocol, logLocalized, showWarning } from '../logger';
|
||||
import { DebugProtocolParams, Logger, ShowWarningParams, getDiagnosticsChannel, getOutputChannelLogger, log, 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,
|
||||
@@ -64,9 +74,6 @@ 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();
|
||||
|
||||
@@ -246,8 +253,11 @@ interface InternalSourceFileConfiguration extends SourceFileConfiguration {
|
||||
compilerArgsLegacy?: string[];
|
||||
}
|
||||
|
||||
interface InternalWorkspaceBrowseConfiguration extends WorkspaceBrowseConfiguration {
|
||||
export 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.
|
||||
@@ -738,7 +748,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): Promise<void>;
|
||||
provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean, provider?: CustomConfigurationProvider1): Promise<void>;
|
||||
logDiagnostics(): Promise<void>;
|
||||
rescanFolder(): Promise<void>;
|
||||
toggleReferenceResultsView(): void;
|
||||
@@ -925,13 +935,12 @@ 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;
|
||||
}
|
||||
defaultClient.configuration.CompilerDefaults = compilerDefaults;
|
||||
defaultClient.configuration.handleConfigurationChange();
|
||||
client.configuration.CompilerDefaults = compilerDefaults;
|
||||
client.configuration.handleConfigurationChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1232,8 +1241,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.SelectionChanged((e) => this.onSelectedConfigurationChanged(e));
|
||||
this.innerConfiguration.CompileCommandsChanged((e) => this.onCompileCommandsChanged(e));
|
||||
this.innerConfiguration.SelectionChanged((e) => this.onSelectedConfigurationChanged(e));
|
||||
this.disposables.push(this.innerConfiguration);
|
||||
|
||||
this.innerLanguageClient = languageClient;
|
||||
@@ -1288,10 +1297,16 @@ export class DefaultClient implements Client {
|
||||
}, 15000);
|
||||
}
|
||||
});
|
||||
// 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();
|
||||
|
||||
// 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();
|
||||
}
|
||||
DefaultClient.updateClientConfigurations();
|
||||
|
||||
clients.forEach(client => {
|
||||
if (client instanceof DefaultClient) {
|
||||
client.configStateReceived.compilers = true;
|
||||
@@ -1680,7 +1695,10 @@ export class DefaultClient implements Client {
|
||||
}
|
||||
}
|
||||
|
||||
public onDidOpenTextDocument(document: vscode.TextDocument): void {
|
||||
public async onDidOpenTextDocument(document: vscode.TextDocument) {
|
||||
if (this.isNewIntellisense) {
|
||||
await this.updatingNewIntellisense;
|
||||
}
|
||||
if (document.uri.scheme === "file") {
|
||||
const uri: string = document.uri.toString();
|
||||
openFileVersions.set(uri, document.version);
|
||||
@@ -1757,6 +1775,8 @@ export class DefaultClient implements Client {
|
||||
}
|
||||
|
||||
public async updateCustomConfigurations(requestingProvider?: CustomConfigurationProvider1): Promise<void> {
|
||||
this.ensureNotNewIntellisense();
|
||||
log("updateCustomConfigurations: Legacy Mode");
|
||||
await this.ready;
|
||||
|
||||
if (!this.configurationProvider) {
|
||||
@@ -1785,10 +1805,14 @@ 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;
|
||||
@@ -1901,23 +1925,34 @@ export class DefaultClient implements Client {
|
||||
return this.languageClient.sendNotification(RescanFolderNotification);
|
||||
}
|
||||
|
||||
public async provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean): Promise<void> {
|
||||
const onFinished: () => void = () => {
|
||||
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 = () => {
|
||||
if (requestFile) {
|
||||
void this.languageClient.sendNotification(FinishedRequestCustomConfig, { uri: requestFile });
|
||||
}
|
||||
};
|
||||
const providerId: string | undefined = this.configurationProvider;
|
||||
if (!providerId) {
|
||||
onFinished();
|
||||
return;
|
||||
if (!provider) {
|
||||
const providerId: string | undefined = this.configurationProvider;
|
||||
if (!providerId) {
|
||||
onFinished();
|
||||
return;
|
||||
}
|
||||
provider = getCustomConfigProviders().get(providerId);
|
||||
telemetry.logLanguageServerEvent('provideCustomConfiguration', { providerId });
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1968,38 +2003,45 @@ 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (fileConfiguration.defines) {
|
||||
fileConfiguration.defines.forEach(d => {
|
||||
if (!config.configuration.defines.includes(d)) {
|
||||
config.configuration.defines.push(d);
|
||||
}
|
||||
});
|
||||
}
|
||||
// do we have an actual configuration?
|
||||
if (fileConfiguration) {
|
||||
|
||||
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 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 (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[];
|
||||
}
|
||||
@@ -2007,9 +2049,11 @@ export class DefaultClient implements Client {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const configs: SourceFileConfigurationItem[] | null | undefined = await this.callTaskWithTimeout(provideConfigurationAsync, configProviderTimeout, tokenSource);
|
||||
if (configs && configs.length > 0) {
|
||||
|
||||
if (configs?.length) {
|
||||
this.sendCustomConfigurations(configs, provider.version);
|
||||
}
|
||||
onFinished();
|
||||
@@ -2247,8 +2291,7 @@ export class DefaultClient implements Client {
|
||||
this.languageClient.onNotification(RequestCustomConfig, (requestFile: string) => {
|
||||
const client: Client = clients.getClientFor(vscode.Uri.file(requestFile));
|
||||
if (client instanceof DefaultClient) {
|
||||
const defaultClient: DefaultClient = client as DefaultClient;
|
||||
void defaultClient.handleRequestCustomConfig(requestFile);
|
||||
void client.handleRequestCustomConfig(requestFile);
|
||||
}
|
||||
});
|
||||
this.languageClient.onNotification(PublishIntelliSenseDiagnosticsNotification, publishIntelliSenseDiagnostics);
|
||||
@@ -2759,10 +2802,231 @@ 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: [],
|
||||
@@ -2773,8 +3037,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.
|
||||
configurations.forEach((c) => {
|
||||
const modifiedConfig: configs.Configuration = deepCopy(c);
|
||||
params.configurations = await Promise.all(configurations.map(async (c) => {
|
||||
const modifiedConfig: configs.Configuration = structuredClone(c);
|
||||
// Separate compiler path and args before sending to language client
|
||||
const compilerPathAndArgs: util.CompilerPathAndArgs =
|
||||
util.extractCompilerPathAndArgs(!!settings.legacyCompilerArgsBehavior, c.compilerPath, c.compilerArgs);
|
||||
@@ -2786,10 +3050,11 @@ export class DefaultClient implements Client {
|
||||
modifiedConfig.compilerArgs = compilerPathAndArgs.allCompilerArgs;
|
||||
}
|
||||
|
||||
params.configurations.push(modifiedConfig);
|
||||
});
|
||||
return 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.
|
||||
@@ -2804,6 +3069,7 @@ 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) {
|
||||
@@ -2832,10 +3098,19 @@ 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);
|
||||
}
|
||||
@@ -2858,6 +3133,8 @@ 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);
|
||||
@@ -2872,6 +3149,13 @@ 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.
|
||||
@@ -2880,15 +3164,16 @@ 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,
|
||||
@@ -2905,6 +3190,7 @@ export class DefaultClient implements Client {
|
||||
itemConfig.compilerArgs = compilerPathAndArgs.allCompilerArgs;
|
||||
}
|
||||
}
|
||||
|
||||
sanitized.push({
|
||||
uri,
|
||||
configuration: itemConfig
|
||||
@@ -2917,11 +3203,8 @@ export class DefaultClient implements Client {
|
||||
if (sanitized.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const params: CustomConfigurationParams = {
|
||||
configurationItems: sanitized,
|
||||
workspaceFolderUri: this.RootUri?.toString()
|
||||
};
|
||||
const params = { configurationItems: sanitized, workspaceFolderUri: this.RootUri?.toString() };
|
||||
out.appendLine(`${CustomConfigurationNotification}:\n${JSON.stringify(params, null, 2)}`);
|
||||
|
||||
void this.languageClient.sendNotification(CustomConfigurationNotification, params).catch(logAndReturn.undefined);
|
||||
}
|
||||
@@ -2938,6 +3221,9 @@ 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
|
||||
@@ -2971,7 +3257,7 @@ export class DefaultClient implements Client {
|
||||
}
|
||||
|
||||
const browseConfig: InternalWorkspaceBrowseConfiguration = config as InternalWorkspaceBrowseConfiguration;
|
||||
sanitized = deepCopy(browseConfig);
|
||||
sanitized = structuredClone(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;
|
||||
@@ -3562,7 +3848,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): Promise<void> { return Promise.resolve(); }
|
||||
provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean, provider?: CustomConfigurationProvider1): Promise<void> { return Promise.resolve(); }
|
||||
logDiagnostics(): Promise<void> { return Promise.resolve(); }
|
||||
rescanFolder(): Promise<void> { return Promise.resolve(); }
|
||||
toggleReferenceResultsView(): void { }
|
||||
|
||||
@@ -13,6 +13,7 @@ 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';
|
||||
@@ -60,7 +61,16 @@ export interface ConfigurationJson {
|
||||
enableConfigurationSquiggles?: boolean;
|
||||
}
|
||||
|
||||
export interface Configuration {
|
||||
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{
|
||||
name: string;
|
||||
compilerPathInCppPropertiesJson?: string;
|
||||
compilerPath?: string;
|
||||
@@ -904,6 +914,12 @@ 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;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
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';
|
||||
|
||||
@@ -18,6 +19,10 @@ 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";
|
||||
|
||||
|
||||
@@ -10,8 +10,10 @@ import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
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';
|
||||
@@ -342,18 +344,27 @@ 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");
|
||||
client.sendDidChangeSettings();
|
||||
void client.sendDidChangeSettings();
|
||||
document = await vscode.languages.setTextDocumentLanguage(document, "cpp");
|
||||
}
|
||||
await client.provideCustomConfiguration(document.uri, undefined);
|
||||
|
||||
// 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);
|
||||
}
|
||||
// client.takeOwnership() will call client.TrackedDocuments.add() again, but that's ok. It's a Set.
|
||||
client.onDidOpenTextDocument(document);
|
||||
void client.onDidOpenTextDocument(document);
|
||||
await client.takeOwnership(document);
|
||||
return true;
|
||||
}
|
||||
@@ -1074,6 +1085,9 @@ 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,6 +8,7 @@ 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';
|
||||
@@ -16,10 +17,6 @@ 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
|
||||
@@ -228,7 +225,7 @@ export class SettingsPanel {
|
||||
}
|
||||
|
||||
private updateWebview(configSelection: string[], configuration: config.Configuration, errors: config.ConfigurationErrors | null): void {
|
||||
this.configValues = deepCopy(configuration); // Copy configuration values
|
||||
this.configValues = structuredClone(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 });
|
||||
|
||||
@@ -32,7 +32,7 @@ export class TargetLeafNode extends LabelLeafNode {
|
||||
super(name);
|
||||
}
|
||||
|
||||
async getTreeItem(): Promise<TreeItem> {
|
||||
override async getTreeItem(): Promise<TreeItem> {
|
||||
const item: TreeItem = await super.getTreeItem();
|
||||
const removable: boolean = await isWritable(this.sshConfigHostInfo.file);
|
||||
if (_activeTarget === this.name) {
|
||||
|
||||
@@ -10,9 +10,10 @@ import * as path from 'path';
|
||||
import {
|
||||
Configuration, ConfigurationDirective,
|
||||
ConfigurationEntry,
|
||||
HostConfigurationDirective, parse,
|
||||
Type as ConfigurationEntryType,
|
||||
HostConfigurationDirective,
|
||||
ResolvedConfiguration,
|
||||
Type as ConfigurationEntryType
|
||||
parse
|
||||
} from 'ssh-config';
|
||||
import { promisify } from 'util';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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 '';
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,621 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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'));
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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>;
|
||||
@@ -0,0 +1,122 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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 }[];
|
||||
}
|
||||
|
||||
@@ -42,8 +42,9 @@ export class ManualPromise<T = void> implements Promise<T> {
|
||||
/**
|
||||
* A method to manually resolve the Promise.
|
||||
*/
|
||||
public resolve: (value?: T | PromiseLike<T> | undefined) => void = (v) => {
|
||||
public resolve: (value?: T | PromiseLike<T> | undefined) => T = (v) => {
|
||||
void v; /* */
|
||||
return v as T;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -89,6 +90,7 @@ 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') {
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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,7 +3,6 @@
|
||||
* 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';
|
||||
@@ -81,7 +80,7 @@ async function dispatch<TResult>(event: Event<any, TResult>): Promise<void> {
|
||||
resultValue = r as TResult | EventStatus;
|
||||
|
||||
if (is.cancelled(resultValue)) {
|
||||
return event.completed.resolve(resultValue); // the event has been cancelled
|
||||
event.completed.resolve(resultValue); // the event has been cancelled
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
@@ -135,7 +134,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'
|
||||
return event.completed.resolve((await Promise.all(results)).find((each: any) => each !== Continue));
|
||||
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[]]> {
|
||||
|
||||
@@ -10,7 +10,7 @@ export class requests {
|
||||
static readonly get = 'get';
|
||||
}
|
||||
|
||||
// [noun]-[verb]
|
||||
// event names should be like [noun]-[verb]
|
||||
export class events {
|
||||
static readonly writing = 'writing';
|
||||
static readonly reading = 'reading';
|
||||
@@ -27,8 +27,7 @@ export class channels {
|
||||
}
|
||||
|
||||
/** Notifications */
|
||||
// [state]
|
||||
// [pastTenseVerb]-[noun]
|
||||
// should be like [state] or [pastTenseVerb]-[noun]
|
||||
export class notifications {
|
||||
static readonly ready = 'ready';
|
||||
static readonly exited = 'exited';
|
||||
|
||||
@@ -0,0 +1,398 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
import { basename, delimiter, extname, join, normalize as norm, resolve } from 'path';
|
||||
|
||||
import { fail } from 'assert';
|
||||
import { fail, ok } from 'assert';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { constants, Stats } from 'fs';
|
||||
import { stat } from 'fs/promises';
|
||||
import { Stats, constants } from 'fs';
|
||||
import { mkdir as md, stat } from 'fs/promises';
|
||||
import { tmpdir } from 'os';
|
||||
import { isWindows } from '../../constants';
|
||||
import { returns } from '../Async/returns';
|
||||
@@ -45,6 +45,7 @@ export interface File extends Entry {
|
||||
isFile: true;
|
||||
isExecutable: boolean;
|
||||
size: number;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface Folder extends Entry {
|
||||
@@ -55,6 +56,10 @@ 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;
|
||||
@@ -66,12 +71,12 @@ export class filepath {
|
||||
}
|
||||
|
||||
// if we've been given a baseFolder, expand that, otherwise just normalize the value.
|
||||
name = baseFolder ? resolve(baseFolder, name) : normalize(name);
|
||||
name = baseFolder ? resolve(baseFolder, name) : filepath.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'/* ,'.cmd','.bat' */]) : 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']) : new Set()): Promise<undefined | File | Folder> {
|
||||
const [fullPath, stats] = await filepath.stats(name, baseFolder);
|
||||
if (!stats) {
|
||||
return undefined;
|
||||
@@ -87,6 +92,7 @@ export class filepath {
|
||||
|
||||
if (entry.isFile) {
|
||||
entry.size = stats.size;
|
||||
entry.timestamp = stats.mtimeMs;
|
||||
|
||||
if (isWindows) {
|
||||
const fp = fullPath.toLowerCase();
|
||||
@@ -131,10 +137,25 @@ 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 ? normalize(resolve(name, '..')) : undefined;
|
||||
return is.promise(name) ? name.then(filepath.parent) : name ? filepath.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;
|
||||
}
|
||||
|
||||
@@ -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, normalize } from './filepath';
|
||||
import { File, Folder, filepath } 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'/* ,'.cmd','.bat' */]) : new Set()): Promise<Map<string, File | FolderWithChildren> | undefined> {
|
||||
async function readDirectory(fullPath: string, executableExtensions: Set<string> = process.platform === 'win32' ? new Set(['.exe']) : 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 = normalize(folder);
|
||||
folder = filepath.normalize(folder);
|
||||
|
||||
// if we have already visited this folder, return
|
||||
await foreach(readDirectory(folder), async ([_name, entry]) => {
|
||||
|
||||
@@ -3,24 +3,86 @@
|
||||
* 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> | undefined;
|
||||
export async function initRipGrep(filename: string) {
|
||||
let ripgrep: Instance<ProcessFunction>;
|
||||
async function setRipgrepBinaryLocation(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> {
|
||||
@@ -43,8 +105,6 @@ 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'];
|
||||
@@ -71,23 +131,30 @@ export class FastFinder implements AsyncIterable<string> {
|
||||
// only search if there are globs and locations to search
|
||||
if (globs.length && location.length) {
|
||||
this.pending++;
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
this.distinct.add(line);
|
||||
if (!this.keepOnlyExecutables || await filepath.isExecutable(line)) {
|
||||
this.#files.add(line);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
this.pending--;
|
||||
if (this.readyToComplete && this.pending === 0) {
|
||||
this.#files.complete();
|
||||
}
|
||||
}
|
||||
}).catch(logAndReturn.undefined).finally(() => {
|
||||
this.pending--;
|
||||
if (this.readyToComplete && this.pending === 0) {
|
||||
this.#files.complete();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
return this;
|
||||
@@ -117,7 +184,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> {
|
||||
strict(ripgrep, 'initRipGrep must be called before using ripGrep');
|
||||
await initialization;
|
||||
|
||||
const optionalArguments = new Array<string>();
|
||||
if (options?.binary) {
|
||||
@@ -134,6 +201,7 @@ 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);
|
||||
|
||||
@@ -435,6 +435,7 @@ 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) {
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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 };
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,9 @@ 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 {
|
||||
|
||||
@@ -21,6 +21,10 @@ 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);
|
||||
}
|
||||
@@ -53,6 +57,10 @@ 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';
|
||||
}
|
||||
@@ -78,4 +86,18 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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);
|
||||
}
|
||||
@@ -33,3 +33,16 @@ 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
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
}
|
||||
@@ -6,5 +6,5 @@
|
||||
import { verboseEnabled } from '../../constants';
|
||||
|
||||
export function verbose(...args: any[]): void {
|
||||
return verboseEnabled || process.argv.includes('--verbose') ? console.log(...args) : undefined;
|
||||
return verboseEnabled || process.argv.includes('--verbose') ? console.log(`${args.join(' ')}`) : undefined;
|
||||
}
|
||||
|
||||
@@ -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 === '/') {
|
||||
if (isIdentifierPart(char.codePointAt(0)!) || char === '-' || char === '/' || char === ';' || char === ' ') {
|
||||
expression += char;
|
||||
continue;
|
||||
}
|
||||
// error, fall through
|
||||
} else if (isIdentifierStart(char.codePointAt(0)!) || char === '-' || char === '/') {
|
||||
} else if (isIdentifierStart(char.codePointAt(0)!) || char === '-' || char === '/' || char === ';' || char === ' ') {
|
||||
expression += char;
|
||||
continue;
|
||||
}
|
||||
@@ -117,7 +117,11 @@ function split(expression: string) {
|
||||
return (expression.match(/(.*?):(.*)/) || ['', '', expression]).slice(1);
|
||||
}
|
||||
|
||||
function resolveValue(expression: string, context: Record<string, any>, customResolver = (_prefix: string, _expression: string) => ''): string {
|
||||
async function resolveValue(expression: string, context: Record<string, any>, customResolver: CustomResolver = async (_prefix: string, _expression: string) => ''): Promise<string> {
|
||||
if (!expression) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const [prefix, suffix] = split(expression);
|
||||
|
||||
function joinIfArray(value: any, separator = '\u0007') {
|
||||
@@ -129,16 +133,16 @@ function resolveValue(expression: string, context: Record<string, any>, customRe
|
||||
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?
|
||||
resolveValue(suffix, variable) : // Yeah, resolve it
|
||||
variable[suffix] ?? customResolver(prefix, suffix) ?? ''); // No, return the member of the variable, or dynamic, or empty string
|
||||
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
|
||||
}
|
||||
|
||||
// no variable by that name, so return the dynamic value, or an empty string
|
||||
return joinIfArray(customResolver(prefix, suffix) ?? '');
|
||||
return joinIfArray(await 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] ?? customResolver(prefix, suffix) ?? '');
|
||||
return joinIfArray(context[suffix] ?? await customResolver(prefix, suffix) ?? '');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
@@ -214,38 +218,58 @@ class as {
|
||||
}
|
||||
}
|
||||
|
||||
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[] {
|
||||
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[]> {
|
||||
if (Array.isArray(templateString)) {
|
||||
return templateString.map(each => render(each, context, customResolver, asJs));
|
||||
return Promise.all(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) ?? '';
|
||||
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
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
return result === '' || result === 'undefined' || result === 'null' || result === null ? undefined : result;
|
||||
}
|
||||
|
||||
export function recursiveRender<T extends Record<string, any>>(obj: T, context: Record<string, any>, customResolver = (_prefix: string, _expression: string) => ''): T {
|
||||
export async function recursiveRender<T extends Record<string, any>>(obj: T, context: Record<string, any>, customResolver = async (_prefix: string, _expression: string) => ''): Promise<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('${') ? render(key, context, customResolver) : key;
|
||||
const newKey = is.string(key) && key.includes('${') ? await render(key, context, customResolver) : key;
|
||||
|
||||
if (is.string(value)) {
|
||||
result[newKey] = evaluateExpression(value, context, customResolver);
|
||||
result[newKey] = await evaluateExpression(value, context, customResolver);
|
||||
} else if (typeof value === 'object') {
|
||||
result[newKey] = recursiveRender(value, context, customResolver);
|
||||
result[newKey] = await recursiveRender(value, context, customResolver);
|
||||
} else {
|
||||
result[newKey] = value;
|
||||
}
|
||||
|
||||
@@ -13,3 +13,6 @@ 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"];
|
||||
|
||||
@@ -62,7 +62,7 @@ export async function expandString(input: string, options: ExpansionOptions): Pr
|
||||
return replaceAll(result, '${dollar}', '$');
|
||||
}
|
||||
|
||||
/** Returns [expandedString, didReplacement] */
|
||||
/** @returns a promise to [expandedString, didReplacement] */
|
||||
async function expandStringImpl(input: string, options: ExpansionOptions): Promise<[string, boolean]> {
|
||||
if (!input) {
|
||||
return [input, false];
|
||||
|
||||
+31
-6
@@ -9,6 +9,7 @@ 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();
|
||||
@@ -130,13 +131,37 @@ export interface DebugProtocolParams {
|
||||
params?: any;
|
||||
}
|
||||
|
||||
export function logDebugProtocol(output: DebugProtocolParams): void {
|
||||
if (!debugChannel) {
|
||||
debugChannel = vscode.window.createOutputChannel(`${localize("c.cpp.debug.protocol", "C/C++ Debug Protocol")}`);
|
||||
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);
|
||||
}
|
||||
debugChannel.appendLine("");
|
||||
debugChannel.appendLine("************************************************************************************************************************");
|
||||
debugChannel.append(`${output}`);
|
||||
}
|
||||
|
||||
export interface ShowWarningParams {
|
||||
|
||||
@@ -20,6 +20,7 @@ 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';
|
||||
@@ -56,6 +57,10 @@ 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);
|
||||
|
||||
@@ -146,7 +151,6 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,6 @@ 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) {
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
"sourceMap": true,
|
||||
"rootDir": ".",
|
||||
"removeComments": true,
|
||||
"noUnusedLocals": true,
|
||||
"strictNullChecks": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
|
||||
@@ -83,6 +83,14 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"C_Cpp.loggingLevel": "Debug"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
#define APP_NAME "Sample"
|
||||
@@ -0,0 +1,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("Hello World!\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
export function foo() {
|
||||
|
||||
}
|
||||
@@ -70,12 +70,13 @@ suite(`[Reference test]`, function(): void {
|
||||
// assert.equal(beforeEditResult.length, 3);
|
||||
// assertTextInLocation(document, expectedText, beforeEditResult);
|
||||
|
||||
//*
|
||||
// // Add another reference to "func1()"
|
||||
// let workspaceEdit: vscode.WorkspaceEdit = new vscode.WorkspaceEdit();
|
||||
// workspaceEdit.insert(fileUri, new vscode.Position(34, 5), "int y = func1();");
|
||||
// await vscode.workspace.applyEdit(workspaceEdit);
|
||||
// await getIntelliSenseStatus;
|
||||
|
||||
//
|
||||
// let afterEditResult: vscode.Location[] = <vscode.Location[]>(await vscode.commands.executeCommand("vscode.executeReferenceProvider", fileUri, new vscode.Position(17, 7)));
|
||||
// assert.equal(afterEditResult.length, 4);
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { describe, it } from 'mocha';
|
||||
import { fail, ok, strictEqual } from 'node:assert';
|
||||
import { accumulator } from '../../src/Utility/Async/iterators';
|
||||
|
||||
@@ -104,7 +104,7 @@ const success: [string | undefined, string, string[], string?][] = [
|
||||
[ undefined, "$((1 + 1))", [ "2" ], marker() ],
|
||||
[ undefined, "$((2-3))", [ "-1" ], marker() ],
|
||||
[ undefined, "$((-1))", [ "-1" ], marker() ],
|
||||
// [ undefined, "$[50+20]", [ "70" ], marker() ],
|
||||
// ignore: [ undefined, "$[50+20]", [ "70" ], marker() ],
|
||||
[ undefined, "$(((2+3)*(4+5)))", [ "45" ], marker() ],
|
||||
[ undefined, "$((010))", [ "8" ], marker() ],
|
||||
[ undefined, "$((0x10))", [ "16" ], marker() ],
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
|
||||
import { ok } from 'assert';
|
||||
import { existsSync } from 'fs';
|
||||
import { describe, it } from 'mocha';
|
||||
import { homedir } from 'os';
|
||||
import { resolve } from 'path';
|
||||
import { getToolsets, identifyToolset, initialize } from '../../src/ToolsetDetection/Service/discovery';
|
||||
import { verbose } from '../../src/Utility/Text/streams';
|
||||
import { isWindows } from '../../src/constants';
|
||||
import { when } from '../common/internal';
|
||||
|
||||
// location of definitions folder.
|
||||
const root = resolve(__dirname, '..', '..', '..', 'bin', 'definitions');
|
||||
|
||||
describe('Detect Compilers', async () => {
|
||||
it('can find some compilers', async () => {
|
||||
const started = Date.now();
|
||||
await initialize([root], {quick: true}); // quick init - we'll call getToolsets next
|
||||
console.debug(`Initialized in ${Date.now() - started}ms`);
|
||||
|
||||
const sets = await getToolsets();
|
||||
console.debug(`Completed detection of ${sets.size} in ${Date.now() - started}ms`);
|
||||
|
||||
for (const [id, toolset] of sets) {
|
||||
console.debug(`Detected Compiler [${id}]: ${toolset}`);
|
||||
}
|
||||
|
||||
// make sure it doesn't take long if we ask again.
|
||||
{
|
||||
const now = Date.now();
|
||||
const sets = await getToolsets();
|
||||
const elapsed = Date.now() - now;
|
||||
console.debug(`Second detection of ${sets.size} in ${elapsed}ms`);
|
||||
ok(elapsed < 100, "should be fast for second detection");
|
||||
}
|
||||
});
|
||||
|
||||
when(isWindows && existsSync('C:\\Program Files\\IAR Systems\\Embedded Workbench 9.3\\arm\\bin\\iccarm.exe')).it('Get Toolset for IAR', async () => {
|
||||
const started = Date.now();
|
||||
|
||||
await initialize([root], {quick: true});
|
||||
console.debug(`Initialized in ${Date.now() - started}ms`);
|
||||
|
||||
const toolset = await identifyToolset('C:\\Program Files\\IAR Systems\\Embedded Workbench 9.3\\arm\\bin\\iccarm.exe');
|
||||
console.debug(`Identify ran in ${Date.now() - started}ms`);
|
||||
|
||||
if (toolset) {
|
||||
console.debug(`Detected Compiler [${toolset.name}]: ${toolset.compilerPath}`);
|
||||
const isense = await toolset.getIntellisenseConfiguration([]);
|
||||
console.debug(`Generated intellisense config in ${Date.now() - started}ms`);
|
||||
verbose(JSON.stringify(isense, null, 2));
|
||||
}
|
||||
});
|
||||
|
||||
when(isWindows && existsSync(`${homedir()}\\AppData\\Local\\Arduino15\\packages\\arduino\\tools\\avr-gcc\\7.3.0-atmel3.6.1-arduino7\\bin\\avr-g++.exe`)).it('Get Toolset for GCC', async () => {
|
||||
const started = Date.now();
|
||||
|
||||
await initialize([root], {quick: true});
|
||||
console.debug(`Initialized in ${Date.now() - started}ms`);
|
||||
|
||||
const toolset = await identifyToolset(`${homedir()}\\AppData\\Local\\Arduino15\\packages\\arduino\\tools\\avr-gcc\\7.3.0-atmel3.6.1-arduino7\\bin\\avr-g++.exe`);
|
||||
console.debug(`Identify ran in ${Date.now() - started}ms`);
|
||||
|
||||
if (toolset) {
|
||||
console.debug(`Detected Compiler ${toolset.definition.name}/${toolset.default.version}/TARGET:${toolset.default.architecture}/HOST:${toolset.default.host}/BITS:${toolset.default.bit}/${toolset.compilerPath}`);
|
||||
const isense = await toolset.getIntellisenseConfiguration([]);
|
||||
console.debug(`Generated intellisense config in ${Date.now() - started}ms`);
|
||||
verbose(JSON.stringify(isense, null, 2));
|
||||
}
|
||||
});
|
||||
|
||||
when(isWindows && existsSync('C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC\\14.36.32532\\bin\\Hostx86\\x64\\cl.exe')).it('Find Toolset by identity', async () => {
|
||||
await initialize([root], {quick: true}); // quick init - we'll call getToolsets next
|
||||
await getToolsets();
|
||||
const toolset = await identifyToolset("Microsoft Visual C++/19.*/x64/x64");
|
||||
ok(toolset, "should have found a toolset");
|
||||
});
|
||||
|
||||
when(isWindows && existsSync('C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC\\14.36.32532\\bin\\Hostx86\\x64\\cl.exe')).it('Get Toolset for MSVC', async () => {
|
||||
const started = Date.now();
|
||||
|
||||
await initialize([root], {quick: true});
|
||||
console.debug(`Initialized in ${Date.now() - started}ms`);
|
||||
|
||||
const toolset = await identifyToolset('C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC\\14.36.32532\\bin\\Hostx86\\x64\\cl.exe');
|
||||
console.debug(`Identify ran in ${Date.now() - started}ms`);
|
||||
|
||||
if (toolset) {
|
||||
console.debug(`Detected Compiler ${toolset.name}/${toolset.default.version}\n kits:${toolset.default.k10}`);
|
||||
const isense = await toolset.getIntellisenseConfiguration([]);
|
||||
console.debug(`Generated intellisense config in ${Date.now() - started}ms`);
|
||||
console.debug(JSON.stringify(isense, null, 2));
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@@ -28,7 +28,7 @@ describe('Manual Promise', () => {
|
||||
// verify that the promise is resolved
|
||||
ok(promise.isResolved, "promise should be in the resolved state.");
|
||||
|
||||
// await it
|
||||
// and await it
|
||||
strictEqual(await Promise.race([promise, setTimeout(1, "timed-out")]), "promise-resolved", "promise should have resolved.");
|
||||
|
||||
// can't resolve it twice!
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
import { ok, strictEqual } from 'assert';
|
||||
import { describe } from 'mocha';
|
||||
import { is } from '../../src/Utility/System/guards';
|
||||
import { readKey } from '../../src/Utility/System/registry';
|
||||
import { isWindows } from '../../src/constants';
|
||||
import { when } from '../common/internal';
|
||||
|
||||
describe('Verify that registry access works', () => {
|
||||
when(isWindows).it('can read from the registry', async () => {
|
||||
const currentVersion = await readKey("HKLM", "SOFTWARE/Microsoft/Windows NT/CurrentVersion");
|
||||
ok(currentVersion, "Should return an object!");
|
||||
ok(is.string(currentVersion.properties.SystemRoot), "Should return a string for SystemRoot");
|
||||
strictEqual(currentVersion.properties.SystemRoot.toUpperCase(), "C:\\WINDOWS", "Should return the correct value for SystemRoot");
|
||||
});
|
||||
});
|
||||
@@ -10,10 +10,10 @@
|
||||
"inlineSourceMap": true,
|
||||
"rootDir": ".",
|
||||
"removeComments": true,
|
||||
"noUnusedLocals": true,
|
||||
"strictNullChecks": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitOverride": true
|
||||
},
|
||||
"include": [
|
||||
"test/**/*.ts",
|
||||
|
||||
@@ -13,10 +13,23 @@ const path = require('path');
|
||||
const config = {
|
||||
target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
|
||||
|
||||
entry: './src/main.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
|
||||
|
||||
// we now have two entries - one for the main entrypoint and one for the worker thread.
|
||||
// we can webpack each one and they won't interfere with each other.
|
||||
entry: {
|
||||
main: {
|
||||
import: './src/main.ts',
|
||||
filename: 'main.js'
|
||||
},
|
||||
worker: {
|
||||
import: './src/ToolsetDetection/Service/worker.ts',
|
||||
filename: 'ToolsetDetection/Service/worker.js'
|
||||
},
|
||||
},
|
||||
|
||||
// the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
|
||||
output: { // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
|
||||
path: path.resolve(__dirname, 'dist', 'src'),
|
||||
filename: 'main.js',
|
||||
libraryTarget: "commonjs2",
|
||||
devtoolModuleFilenameTemplate: "../[resource-path]",
|
||||
},
|
||||
|
||||
+189
-65
@@ -314,6 +314,13 @@
|
||||
dependencies:
|
||||
"@octokit/openapi-types" "^12.11.0"
|
||||
|
||||
"@phenomnomnominal/tsquery@^5.0.0":
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@phenomnomnominal/tsquery/-/tsquery-5.0.1.tgz#a2a5abc89f92c01562a32806655817516653a388"
|
||||
integrity sha512-3nVv+e2FQwsW8Aw6qTU6f+1rfcJ3hrcnvH/mu9i8YhxO+9sqbOfpL8m6PbET5+xKOlz/VSbp0RoYWYCtIsnmuA==
|
||||
dependencies:
|
||||
esquery "^1.4.0"
|
||||
|
||||
"@tootallnate/once@1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
|
||||
@@ -368,7 +375,7 @@
|
||||
"@types/minimatch" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.8":
|
||||
"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
|
||||
version "7.0.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
|
||||
integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
|
||||
@@ -418,7 +425,7 @@
|
||||
"@types/node" "*"
|
||||
xmlbuilder ">=11.0.1"
|
||||
|
||||
"@types/semver@^7.1.0", "@types/semver@^7.5.0":
|
||||
"@types/semver@^7.1.0", "@types/semver@^7.3.12", "@types/semver@^7.5.0":
|
||||
version "7.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a"
|
||||
integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==
|
||||
@@ -438,6 +445,18 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/which/-/which-1.3.2.tgz#9c246fc0c93ded311c8512df2891fb41f6227fdf"
|
||||
integrity sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==
|
||||
|
||||
"@types/yargs-parser@*":
|
||||
version "21.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
|
||||
integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==
|
||||
|
||||
"@types/yargs@^17.0.0":
|
||||
version "17.0.24"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902"
|
||||
integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@types/yauzl@^2.9.1":
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599"
|
||||
@@ -445,90 +464,144 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.1.0.tgz#96f3ca6615717659d06c9f7161a1d14ab0c49c66"
|
||||
integrity sha512-qg7Bm5TyP/I7iilGyp6DRqqkt8na00lI6HbjWZObgk3FFSzH5ypRwAHXJhJkwiRtTcfn+xYQIMOR5kJgpo6upw==
|
||||
"@typescript-eslint/eslint-plugin@^6.5.0":
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.5.0.tgz#5cee33edf0d45d5ec773e3b3111206b098ac8599"
|
||||
integrity sha512-2pktILyjvMaScU6iK3925uvGU87E+N9rh372uGZgiMYwafaw9SXq86U04XPq3UH6tzRvNgBsub6x2DacHc33lw==
|
||||
dependencies:
|
||||
"@eslint-community/regexpp" "^4.5.1"
|
||||
"@typescript-eslint/scope-manager" "6.1.0"
|
||||
"@typescript-eslint/type-utils" "6.1.0"
|
||||
"@typescript-eslint/utils" "6.1.0"
|
||||
"@typescript-eslint/visitor-keys" "6.1.0"
|
||||
"@typescript-eslint/scope-manager" "6.5.0"
|
||||
"@typescript-eslint/type-utils" "6.5.0"
|
||||
"@typescript-eslint/utils" "6.5.0"
|
||||
"@typescript-eslint/visitor-keys" "6.5.0"
|
||||
debug "^4.3.4"
|
||||
graphemer "^1.4.0"
|
||||
ignore "^5.2.4"
|
||||
natural-compare "^1.4.0"
|
||||
natural-compare-lite "^1.4.0"
|
||||
semver "^7.5.4"
|
||||
ts-api-utils "^1.0.1"
|
||||
|
||||
"@typescript-eslint/parser@^6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.1.0.tgz#3135bf65dca5340d8650703eb8cb83113e156ee5"
|
||||
integrity sha512-hIzCPvX4vDs4qL07SYzyomamcs2/tQYXg5DtdAfj35AyJ5PIUqhsLf4YrEIFzZcND7R2E8tpQIZKayxg8/6Wbw==
|
||||
"@typescript-eslint/experimental-utils@^5.0.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz#14559bf73383a308026b427a4a6129bae2146741"
|
||||
integrity sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "6.1.0"
|
||||
"@typescript-eslint/types" "6.1.0"
|
||||
"@typescript-eslint/typescript-estree" "6.1.0"
|
||||
"@typescript-eslint/visitor-keys" "6.1.0"
|
||||
"@typescript-eslint/utils" "5.62.0"
|
||||
|
||||
"@typescript-eslint/parser@^6.5.0":
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.5.0.tgz#3d6ed231c5e307c5f5f4a0d86893ec01e92b8c77"
|
||||
integrity sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "6.5.0"
|
||||
"@typescript-eslint/types" "6.5.0"
|
||||
"@typescript-eslint/typescript-estree" "6.5.0"
|
||||
"@typescript-eslint/visitor-keys" "6.5.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/scope-manager@6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.1.0.tgz#a6cdbe11630614f8c04867858a42dd56590796ed"
|
||||
integrity sha512-AxjgxDn27hgPpe2rQe19k0tXw84YCOsjDJ2r61cIebq1t+AIxbgiXKvD4999Wk49GVaAcdJ/d49FYel+Pp3jjw==
|
||||
"@typescript-eslint/scope-manager@5.62.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c"
|
||||
integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "6.1.0"
|
||||
"@typescript-eslint/visitor-keys" "6.1.0"
|
||||
"@typescript-eslint/types" "5.62.0"
|
||||
"@typescript-eslint/visitor-keys" "5.62.0"
|
||||
|
||||
"@typescript-eslint/type-utils@6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.1.0.tgz#21cc6c3bc1980b03f9eb4e64580d0c5be6f08215"
|
||||
integrity sha512-kFXBx6QWS1ZZ5Ni89TyT1X9Ag6RXVIVhqDs0vZE/jUeWlBv/ixq2diua6G7ece6+fXw3TvNRxP77/5mOMusx2w==
|
||||
"@typescript-eslint/scope-manager@6.5.0":
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.5.0.tgz#f2cb20895aaad41b3ad27cc3a338ce8598f261c5"
|
||||
integrity sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==
|
||||
dependencies:
|
||||
"@typescript-eslint/typescript-estree" "6.1.0"
|
||||
"@typescript-eslint/utils" "6.1.0"
|
||||
"@typescript-eslint/types" "6.5.0"
|
||||
"@typescript-eslint/visitor-keys" "6.5.0"
|
||||
|
||||
"@typescript-eslint/[email protected]":
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.5.0.tgz#6d246c93739282bc0d2e623f28d0dec6cfcc38d7"
|
||||
integrity sha512-f7OcZOkRivtujIBQ4yrJNIuwyCQO1OjocVqntl9dgSIZAdKqicj3xFDqDOzHDlGCZX990LqhLQXWRnQvsapq8A==
|
||||
dependencies:
|
||||
"@typescript-eslint/typescript-estree" "6.5.0"
|
||||
"@typescript-eslint/utils" "6.5.0"
|
||||
debug "^4.3.4"
|
||||
ts-api-utils "^1.0.1"
|
||||
|
||||
"@typescript-eslint/types@6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.1.0.tgz#2d607c62827bb416ada5c96ebfa2ef84e45a8dfa"
|
||||
integrity sha512-+Gfd5NHCpDoHDOaU/yIF3WWRI2PcBRKKpP91ZcVbL0t5tQpqYWBs3z/GGhvU+EV1D0262g9XCnyqQh19prU0JQ==
|
||||
"@typescript-eslint/types@5.62.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f"
|
||||
integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==
|
||||
|
||||
"@typescript-eslint/typescript-estree@6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.1.0.tgz#ea382f6482ba698d7e993a88ce5391ea7a66c33d"
|
||||
integrity sha512-nUKAPWOaP/tQjU1IQw9sOPCDavs/iU5iYLiY/6u7gxS7oKQoi4aUxXS1nrrVGTyBBaGesjkcwwHkbkiD5eBvcg==
|
||||
"@typescript-eslint/types@6.5.0":
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.5.0.tgz#f4e55cfd99ac5346ea772770bf212a3e689a8f04"
|
||||
integrity sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==
|
||||
|
||||
"@typescript-eslint/[email protected]":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b"
|
||||
integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "6.1.0"
|
||||
"@typescript-eslint/visitor-keys" "6.1.0"
|
||||
"@typescript-eslint/types" "5.62.0"
|
||||
"@typescript-eslint/visitor-keys" "5.62.0"
|
||||
debug "^4.3.4"
|
||||
globby "^11.1.0"
|
||||
is-glob "^4.0.3"
|
||||
semver "^7.3.7"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/[email protected]":
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.5.0.tgz#1cef6bc822585e9ef89d88834bc902d911d747ed"
|
||||
integrity sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "6.5.0"
|
||||
"@typescript-eslint/visitor-keys" "6.5.0"
|
||||
debug "^4.3.4"
|
||||
globby "^11.1.0"
|
||||
is-glob "^4.0.3"
|
||||
semver "^7.5.4"
|
||||
ts-api-utils "^1.0.1"
|
||||
|
||||
"@typescript-eslint/utils@6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.1.0.tgz#1641843792b4e3451cc692e2c73055df8b26f453"
|
||||
integrity sha512-wp652EogZlKmQoMS5hAvWqRKplXvkuOnNzZSE0PVvsKjpexd/XznRVHAtrfHFYmqaJz0DFkjlDsGYC9OXw+OhQ==
|
||||
"@typescript-eslint/utils@5.62.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86"
|
||||
integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.2.0"
|
||||
"@types/json-schema" "^7.0.9"
|
||||
"@types/semver" "^7.3.12"
|
||||
"@typescript-eslint/scope-manager" "5.62.0"
|
||||
"@typescript-eslint/types" "5.62.0"
|
||||
"@typescript-eslint/typescript-estree" "5.62.0"
|
||||
eslint-scope "^5.1.1"
|
||||
semver "^7.3.7"
|
||||
|
||||
"@typescript-eslint/[email protected]":
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.5.0.tgz#6668bee4f7f24978b11df8a2ea42d56eebc4662c"
|
||||
integrity sha512-9nqtjkNykFzeVtt9Pj6lyR9WEdd8npPhhIPM992FWVkZuS6tmxHfGVnlUcjpUP2hv8r4w35nT33mlxd+Be1ACQ==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.4.0"
|
||||
"@types/json-schema" "^7.0.12"
|
||||
"@types/semver" "^7.5.0"
|
||||
"@typescript-eslint/scope-manager" "6.1.0"
|
||||
"@typescript-eslint/types" "6.1.0"
|
||||
"@typescript-eslint/typescript-estree" "6.1.0"
|
||||
"@typescript-eslint/scope-manager" "6.5.0"
|
||||
"@typescript-eslint/types" "6.5.0"
|
||||
"@typescript-eslint/typescript-estree" "6.5.0"
|
||||
semver "^7.5.4"
|
||||
|
||||
"@typescript-eslint/visitor-keys@6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.1.0.tgz#d2b84dff6b58944d3257ea03687e269a788c73be"
|
||||
integrity sha512-yQeh+EXhquh119Eis4k0kYhj9vmFzNpbhM3LftWQVwqVjipCkwHBQOZutcYW+JVkjtTG9k8nrZU1UoNedPDd1A==
|
||||
"@typescript-eslint/visitor-keys@5.62.0":
|
||||
version "5.62.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e"
|
||||
integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "6.1.0"
|
||||
"@typescript-eslint/types" "5.62.0"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
"@typescript-eslint/[email protected]":
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.5.0.tgz#1a6f474a0170a447b76f0699ce6700110fd11436"
|
||||
integrity sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "6.5.0"
|
||||
eslint-visitor-keys "^3.4.1"
|
||||
|
||||
"@vscode/debugadapter@^1.61.0":
|
||||
@@ -1817,6 +1890,15 @@ escape-string-regexp@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
|
||||
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
|
||||
|
||||
eslint-etc@^5.1.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-etc/-/eslint-etc-5.2.1.tgz#43e2554a347677ebb6386c915f374918f2efcb87"
|
||||
integrity sha512-lFJBSiIURdqQKq9xJhvSJFyPA+VeTh5xvk24e8pxVL7bwLBtGF60C/KRkLTMrvCZ6DA3kbPuYhLWY0TZMlqTsg==
|
||||
dependencies:
|
||||
"@typescript-eslint/experimental-utils" "^5.0.0"
|
||||
tsutils "^3.17.1"
|
||||
tsutils-etc "^1.4.1"
|
||||
|
||||
eslint-import-resolver-node@^0.3.7:
|
||||
version "0.3.7"
|
||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7"
|
||||
@@ -1833,6 +1915,18 @@ eslint-module-utils@^2.7.4:
|
||||
dependencies:
|
||||
debug "^3.2.7"
|
||||
|
||||
eslint-plugin-etc@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-etc/-/eslint-plugin-etc-2.0.3.tgz#befab319413701dfc4afd2bfefa14362f1db74a5"
|
||||
integrity sha512-o5RS/0YwtjlGKWjhKojgmm82gV1b4NQUuwk9zqjy9/EjxNFKKYCaF+0M7DkYBn44mJ6JYFZw3Ft249dkKuR1ew==
|
||||
dependencies:
|
||||
"@phenomnomnominal/tsquery" "^5.0.0"
|
||||
"@typescript-eslint/experimental-utils" "^5.0.0"
|
||||
eslint-etc "^5.1.0"
|
||||
requireindex "~1.2.0"
|
||||
tslib "^2.0.0"
|
||||
tsutils "^3.0.0"
|
||||
|
||||
eslint-plugin-header@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz#6ce512432d57675265fac47292b50d1eff11acd6"
|
||||
@@ -1874,7 +1968,7 @@ eslint-plugin-jsdoc@^46.4.4:
|
||||
semver "^7.5.1"
|
||||
spdx-expression-parse "^3.0.1"
|
||||
|
||||
[email protected]:
|
||||
[email protected], eslint-scope@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
||||
integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
|
||||
@@ -1952,7 +2046,7 @@ esprima@^4.0.1:
|
||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
||||
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
||||
|
||||
esquery@^1.4.2, esquery@^1.5.0:
|
||||
esquery@^1.4.0, esquery@^1.4.2, esquery@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b"
|
||||
integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==
|
||||
@@ -3593,11 +3687,6 @@ nanomatch@^1.2.9:
|
||||
snapdragon "^0.8.1"
|
||||
to-regex "^3.0.1"
|
||||
|
||||
natural-compare-lite@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4"
|
||||
integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==
|
||||
|
||||
natural-compare@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
@@ -4027,6 +4116,11 @@ prompts@^2.4.2:
|
||||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.5"
|
||||
|
||||
proxy-from-env@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
|
||||
prr@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
|
||||
@@ -4209,6 +4303,11 @@ require-main-filename@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
|
||||
integrity sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==
|
||||
|
||||
requireindex@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef"
|
||||
integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==
|
||||
|
||||
resolve-cwd@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
|
||||
@@ -4898,6 +4997,31 @@ tsconfig-paths@^3.14.1:
|
||||
minimist "^1.2.6"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
tslib@^1.8.1:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.0:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
|
||||
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
||||
|
||||
tsutils-etc@^1.4.1:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/tsutils-etc/-/tsutils-etc-1.4.2.tgz#6d6a9f33aa61867d832e4a455b2cebb6b104ebfa"
|
||||
integrity sha512-2Dn5SxTDOu6YWDNKcx1xu2YUy6PUeKrWZB/x2cQ8vY2+iz3JRembKn/iZ0JLT1ZudGNwQQvtFX9AwvRHbXuPUg==
|
||||
dependencies:
|
||||
"@types/yargs" "^17.0.0"
|
||||
yargs "^17.0.0"
|
||||
|
||||
tsutils@^3.0.0, tsutils@^3.17.1, tsutils@^3.21.0:
|
||||
version "3.21.0"
|
||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
|
||||
integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
|
||||
dependencies:
|
||||
tslib "^1.8.1"
|
||||
|
||||
type-check@^0.4.0, type-check@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
||||
@@ -4939,10 +5063,10 @@ typescript@^4.5.4:
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
|
||||
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
|
||||
|
||||
typescript@^5.1.3:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274"
|
||||
integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==
|
||||
typescript@^5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78"
|
||||
integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==
|
||||
|
||||
unbox-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
@@ -5427,7 +5551,7 @@ [email protected]:
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^20.2.2"
|
||||
|
||||
yargs@^17.3.0:
|
||||
yargs@^17.0.0, yargs@^17.3.0:
|
||||
version "17.7.2"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
|
||||
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
|
||||
|
||||
Reference in New Issue
Block a user