Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62614d8fa8 | ||
|
|
fecfc08451 | ||
|
|
62a03c2b21 | ||
|
|
7273d6112b | ||
|
|
74bddb82e5 | ||
|
|
4af6ca9db0 | ||
|
|
7e4ee25c48 | ||
|
|
6da9a49652 | ||
|
|
51d88dbb24 | ||
|
|
cd8ecf9060 |
@@ -7,44 +7,8 @@ on:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js 16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Install Dependencies
|
||||
run: yarn install
|
||||
working-directory: Extension
|
||||
|
||||
- name: Compile Sources
|
||||
run: yarn run compile
|
||||
working-directory: Extension
|
||||
|
||||
- name: Run Linter
|
||||
run: yarn run lint
|
||||
working-directory: Extension
|
||||
|
||||
- name: Run unit tests
|
||||
run: yarn test
|
||||
working-directory: Extension
|
||||
|
||||
# # NOTE : We can't run the test that require the native binary files
|
||||
# # yet -- there will be an update soon that allows the tester to
|
||||
# # acquire them on-the-fly
|
||||
# - name: Run simple vscode unit tests
|
||||
# uses: GabrielBB/[email protected]
|
||||
# with:
|
||||
# run: yarn test --scenario=SingleRootProject
|
||||
# working-directory: Extension
|
||||
|
||||
# - name: Run languageServer integration tests
|
||||
# uses: GabrielBB/[email protected]
|
||||
# with:
|
||||
# run: yarn run integrationTests
|
||||
# working-directory: Extension
|
||||
job:
|
||||
uses: ./.github/workflows/job-compile-and-test.yml
|
||||
with:
|
||||
runner-env: ubuntu-22.04
|
||||
platform: linux
|
||||
@@ -7,44 +7,9 @@ on:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-12
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js 16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Install Dependencies
|
||||
run: yarn install --network-timeout 100000
|
||||
working-directory: Extension
|
||||
|
||||
- name: Compile Sources
|
||||
run: yarn run compile
|
||||
working-directory: Extension
|
||||
|
||||
- name: Run Linter
|
||||
run: yarn run lint
|
||||
working-directory: Extension
|
||||
|
||||
- name: Run unit tests
|
||||
run: yarn test
|
||||
working-directory: Extension
|
||||
|
||||
# # NOTE : We can't run the test that require the native binary files
|
||||
# # yet -- there will be an update soon that allows the tester to
|
||||
# # acquire them on-the-fly
|
||||
# - name: Run simple vscode unit tests
|
||||
# uses: GabrielBB/[email protected]
|
||||
# with:
|
||||
# run: yarn test --scenario=SingleRootProject
|
||||
# working-directory: Extension
|
||||
|
||||
# - name: Run languageServer integration tests
|
||||
# uses: GabrielBB/[email protected]
|
||||
# with:
|
||||
# run: yarn run integrationTests
|
||||
# working-directory: Extension
|
||||
job:
|
||||
uses: ./.github/workflows/job-compile-and-test.yml
|
||||
with:
|
||||
runner-env: macos-12
|
||||
platform: mac
|
||||
yarn-args: --network-timeout 100000
|
||||
@@ -7,40 +7,8 @@ on:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js 16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Install Dependencies
|
||||
run: yarn install
|
||||
working-directory: Extension
|
||||
|
||||
- name: Compile Sources
|
||||
run: yarn run compile
|
||||
working-directory: Extension
|
||||
|
||||
- name: Run Linter
|
||||
run: yarn run lint
|
||||
working-directory: Extension
|
||||
|
||||
- name: Run unit tests
|
||||
run: yarn test
|
||||
working-directory: Extension
|
||||
|
||||
# # NOTE : We can't run the test that require the native binary files
|
||||
# # yet -- there will be an update soon that allows the tester to
|
||||
# # acquire them on-the-fly
|
||||
# - name: Run simple vscode unit tests
|
||||
# run: yarn test --scenario=SingleRootProject
|
||||
# working-directory: Extension
|
||||
|
||||
# - name: Run languageServer integration tests
|
||||
# run: yarn run integrationTests
|
||||
# working-directory: Extension
|
||||
job:
|
||||
uses: ./.github/workflows/job-compile-and-test.yml
|
||||
with:
|
||||
runner-env: windows-2022
|
||||
platform: windows
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# Reuable workflow for compiling and testing extension.
|
||||
name: Compile and test extension
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
runner-env:
|
||||
required: true
|
||||
type: string
|
||||
platform:
|
||||
# Expects 'mac', 'linux', or 'windows'
|
||||
required: true
|
||||
type: string
|
||||
yarn-args:
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ inputs.runner-env }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js 16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Install Dependencies
|
||||
run: yarn install ${{ inputs.yarn-args }}
|
||||
working-directory: Extension
|
||||
|
||||
- name: Compile Sources
|
||||
run: yarn run compile
|
||||
working-directory: Extension
|
||||
|
||||
- name: Run Linter
|
||||
run: yarn run lint
|
||||
working-directory: Extension
|
||||
|
||||
- name: Run unit tests
|
||||
run: yarn test
|
||||
working-directory: Extension
|
||||
|
||||
# NOTE : We can't run the test that require the native binary files
|
||||
# yet -- there will be an update soon that allows the tester to
|
||||
# acquire them on-the-fly
|
||||
# - name: Run languageServer integration tests
|
||||
# if: ${{ inputs.platform == 'windows' }}
|
||||
# run: yarn test --scenario=SingleRootProject
|
||||
# working-directory: Extension
|
||||
|
||||
# - name: Run E2E IntelliSense features tests
|
||||
# if: ${{ inputs.platform == 'windows' }}
|
||||
# run: yarn test --scenario=MultirootDeadlockTest
|
||||
# working-directory: Extension
|
||||
|
||||
# NOTE: For mac/linux run the tests with xvfb-action for UI support.
|
||||
# Another way to start xvfb https://github.com/microsoft/vscode-test/blob/master/sample/azure-pipelines.yml
|
||||
|
||||
# - name: Run languageServer integration tests (xvfb)
|
||||
# if: ${{ inputs.platform == 'mac' || inputs.platform == 'linux' }}
|
||||
# uses: coactions/setup-xvfb@v1
|
||||
# with:
|
||||
# run: yarn test --scenario=SingleRootProject
|
||||
# working-directory: Extension
|
||||
|
||||
# - name: Run E2E IntelliSense features tests (xvfb)
|
||||
# if: ${{ inputs.platform == 'mac' || inputs.platform == 'linux' }}
|
||||
# uses: coactions/setup-xvfb@v1
|
||||
# with:
|
||||
# run: yarn test --scenario=MultirootDeadlockTest
|
||||
# working-directory: Extension
|
||||
@@ -19,7 +19,7 @@ parameters:
|
||||
|
||||
# Note: Make sure lldb_mi_commit is the same as the one in Extension/cgmanifest.json
|
||||
# 'CommitHash' for lldb-mi.
|
||||
lldb_mi_commit: 2388bd74133bc21eac59b2e2bf97f2a30770a315
|
||||
lldb_mi_commit: 4fe9c663edce2447e114c71851694d8c529b982d
|
||||
|
||||
lldb_mi_additional_parameters: "-DUSE_LLDB_FRAMEWORK=1"
|
||||
|
||||
@@ -28,9 +28,9 @@ jobs:
|
||||
timeoutInMinutes: 360
|
||||
pool:
|
||||
${{if eq(parameters['llvm_arch'], 'arm64')}}:
|
||||
name: cpptoolsMacM1pool
|
||||
vmImage: macos-13-arm64
|
||||
${{ else }}:
|
||||
vmImage: macOS-latest
|
||||
vmImage: macOS-13
|
||||
steps:
|
||||
- task: CmdLine@2
|
||||
displayName: 'Install Dependencies'
|
||||
|
||||
@@ -21,8 +21,7 @@ module.exports = {
|
||||
"eslint-plugin-jsdoc",
|
||||
"@typescript-eslint/eslint-plugin",
|
||||
"eslint-plugin-import",
|
||||
"eslint-plugin-header",
|
||||
"etc"
|
||||
"eslint-plugin-header"
|
||||
],
|
||||
"rules": {
|
||||
"indent": [
|
||||
@@ -84,7 +83,6 @@ module.exports = {
|
||||
"@typescript-eslint/prefer-function-type": "error",
|
||||
"@typescript-eslint/prefer-namespace-keyword": "error",
|
||||
"@typescript-eslint/semi": "error",
|
||||
"etc/no-commented-out-code": "error",
|
||||
"@typescript-eslint/triple-slash-reference": "error",
|
||||
"@typescript-eslint/type-annotation-spacing": "error",
|
||||
"@typescript-eslint/unified-signatures": "error",
|
||||
@@ -92,7 +90,7 @@ module.exports = {
|
||||
"@typescript-eslint/method-signature-style": ["error", "method"],
|
||||
"@typescript-eslint/space-infix-ops": "error",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
||||
"@typescript-eslint/no-unnecessary-boolean-literal-compare": "error",
|
||||
"arrow-body-style": "error",
|
||||
"comma-dangle": "error",
|
||||
|
||||
@@ -13,7 +13,6 @@ bin/cpptools*
|
||||
bin/*.dll
|
||||
bin/.vs
|
||||
bin/LICENSE.txt
|
||||
bin/rg*
|
||||
|
||||
# ignore lock files
|
||||
install.lock
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import { parse } from 'semver';
|
||||
import { $root, $switches, cyan, glob, green, note, updateFiles } from './common';
|
||||
import { extensionsDir, installExtension, uninstallExtension } from './vscode';
|
||||
|
||||
export async function install(version: string) {
|
||||
if (!version || parse(version)) {
|
||||
note(`Attempting to install binaries from published vsix (${version ? version : $switches.includes('--pre-release') ? 'latest pre-release' : 'latest'})`);
|
||||
}
|
||||
// install it in the isolated vscodearea
|
||||
note("Temporarily installing vscode-cpptools extension");
|
||||
const { id, ver } = await installExtension('ms-vscode.cpptools', version);
|
||||
|
||||
// grab the binaries out
|
||||
let files = [] as string[];
|
||||
|
||||
files.push(...await glob(`${extensionsDir}/${id}-${ver}*/bin/cpptools*`));
|
||||
files.push(...await glob(`${extensionsDir}/${id}-${ver}*/bin/*.dll`));
|
||||
files.push(...await glob(`${extensionsDir}/${id}-${ver}*/bin/*.exe`));
|
||||
|
||||
files.push(...await glob(`${extensionsDir}/${id}-${ver}*/LLVM/**`));
|
||||
files.push(...await glob(`${extensionsDir}/${id}-${ver}*/debugAdapters/**`));
|
||||
files = [...new Set(files)];
|
||||
const extensionFolder = files[0].replace(/.bin.cpptools.*$/g, '');
|
||||
note(`Copying files from vscode-cpptools extension ${ver} in '${extensionFolder}'`);
|
||||
await updateFiles(files, $root, extensionFolder);
|
||||
|
||||
// remove the extension fromthe isolated vscode
|
||||
note("Removing temporary vscode-cpptools extension");
|
||||
await uninstallExtension('ms-vscode.cpptools');
|
||||
|
||||
}
|
||||
|
||||
export async function installbuild() {
|
||||
//*
|
||||
// cpptools-srv.exe
|
||||
// cpptools-vcpkgsrv.exe
|
||||
// cpptools.exe
|
||||
// vcpkgsrvtest.exe
|
||||
// xobjgen.exe
|
||||
|
||||
// search relative for the vcpkgsrv
|
||||
//
|
||||
// (grep --iglob **/vcpkgsrv/cpptools-vcpkgsrv.exe --max-depth 5 --ignore-case --files ../../ | Resolve-Path).Path
|
||||
|
||||
// base folder:
|
||||
// ((grep --iglob **/vcpkgsrv/cpptools-vcpkgsrv.exe --max-depth 7 --ignore-case --files ../../) -replace "designtime.*",'' | Resolve-Path).Path
|
||||
// typical locations for binaries
|
||||
// ../../build/debug
|
||||
//
|
||||
}
|
||||
|
||||
export async function main() {
|
||||
console.log(`use ${green(`yarn binary install ${cyan('[version] [--pre-release]')}`)}`);
|
||||
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { $args, $root, $scenario, assertAnyFile, brightGreen, gray, green, pwd }
|
||||
|
||||
import { resolve } from 'path';
|
||||
import { getTestInfo } from '../test/common/selectTests';
|
||||
import { environment, install, options } from "./vscode";
|
||||
import { install, options } from "./vscode";
|
||||
|
||||
export { install, reset } from './vscode';
|
||||
|
||||
@@ -36,5 +36,5 @@ export async function main() {
|
||||
const ARGS = [...args, ...options.launchArgs.filter(each => !each.startsWith('--extensions-dir=') && !each.startsWith('--user-data-dir=')), `--extensionDevelopmentPath=${$root}`, ...$args ];
|
||||
verbose(gray(`${cli}\n ${ [...ARGS ].join('\n ')}`));
|
||||
|
||||
spawnSync(cli, ARGS, { encoding: 'utf-8', stdio: 'ignore', env:environment()});
|
||||
spawnSync(cli, ARGS, { encoding: 'utf-8', stdio: 'ignore', env: { ...process.env, DONT_PROMPT_WSL_INSTALL:"1" } });
|
||||
}
|
||||
|
||||
@@ -119,17 +119,13 @@ export async function write(filePath: string, data: Buffer | string) {
|
||||
await writeFile(filePath, data);
|
||||
}
|
||||
|
||||
export async function updateFiles(files: string[], dest: string | Promise<string>, prefix?: string) {
|
||||
export async function updateFiles(files: string[], dest: string | Promise<string>) {
|
||||
const target = is.promise(dest) ? await dest : dest;
|
||||
await Promise.all(files.map(async (each) => {
|
||||
const sourceFile = await filepath.isFile(each, $root);
|
||||
if (sourceFile) {
|
||||
const targetFile = prefix ? resolve(target, each.replace(prefix, '.')) : resolve(target, each);
|
||||
try {
|
||||
await write(targetFile, await readFile(sourceFile));
|
||||
} catch (e) {
|
||||
verbose(`Error during update of '${targetFile}' ${e} `);
|
||||
}
|
||||
const targetFile = resolve(target, each);
|
||||
await write(targetFile, await readFile(sourceFile));
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -139,7 +135,7 @@ export async function go() {
|
||||
// loop through the args and pick out the first non --arg and remove it from the $args and set $cmd
|
||||
for (let i = 0; i < $args.length; i++) {
|
||||
const each = $args[i];
|
||||
if (require.main.exports[each]) {
|
||||
if (!each.startsWith('--') && require.main.exports[each]) {
|
||||
$cmd = each;
|
||||
$args.splice(i, 1);
|
||||
break;
|
||||
@@ -345,55 +341,3 @@ export async function checkBinaries() {
|
||||
}
|
||||
return failing;
|
||||
}
|
||||
|
||||
const sowrite = process.stdout.write.bind(process.stdout) as (...args: unknown[]) => boolean;
|
||||
const sewrite = process.stderr.write.bind(process.stderr) as (...args: unknown[]) => boolean;
|
||||
|
||||
const filters = [
|
||||
/^\[(.*)\].*/,
|
||||
/^Unexpected token A/,
|
||||
/Cannot register 'cmake.cmakePath'/,
|
||||
/\[DEP0005\] DeprecationWarning/,
|
||||
/--trace-deprecation/,
|
||||
/Iconv-lite warning/,
|
||||
/^Found existing install/
|
||||
];
|
||||
|
||||
// remove unwanted messages from stdio
|
||||
function filterStdio() {
|
||||
process.stdout.write = function (...args: unknown[]) {
|
||||
if (typeof args[0] === 'string') {
|
||||
const text = args[0];
|
||||
|
||||
if (filters.some(each => text.match(each))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (args[0] instanceof Buffer) {
|
||||
const text = args[0].toString();
|
||||
if (filters.some(each => text.match(each))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return sowrite(...args);
|
||||
};
|
||||
|
||||
process.stderr.write = function (...args: unknown[]) {
|
||||
if (typeof args[0] === 'string') {
|
||||
const text = args[0];
|
||||
|
||||
if (filters.some(each => text.match(each))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (args[0] instanceof Buffer) {
|
||||
const text = args[0].toString();
|
||||
if (filters.some(each => text.match(each))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return sewrite(...args);
|
||||
};
|
||||
}
|
||||
|
||||
filterStdio();
|
||||
|
||||
@@ -19,11 +19,63 @@ import { install, isolated, options } from './vscode';
|
||||
|
||||
export { install, reset } from './vscode';
|
||||
|
||||
const sowrite = process.stdout.write.bind(process.stdout) as (...args: unknown[]) => boolean;
|
||||
const sewrite = process.stderr.write.bind(process.stderr) as (...args: unknown[]) => boolean;
|
||||
|
||||
const filters = [
|
||||
/^\[(.*)\].*/,
|
||||
/^Unexpected token A/,
|
||||
/Cannot register 'cmake.cmakePath'/,
|
||||
/\[DEP0005\] DeprecationWarning/,
|
||||
/--trace-deprecation/,
|
||||
/Iconv-lite warning/,
|
||||
/^Extension '/,
|
||||
/^Found existing install/
|
||||
];
|
||||
|
||||
// remove unwanted messages from stdio
|
||||
function filterStdio() {
|
||||
process.stdout.write = function (...args: unknown[]) {
|
||||
if (typeof args[0] === 'string') {
|
||||
const text = args[0];
|
||||
|
||||
if (filters.some(each => text.match(each))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (args[0] instanceof Buffer) {
|
||||
const text = args[0].toString();
|
||||
if (filters.some(each => text.match(each))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return sowrite(...args);
|
||||
};
|
||||
|
||||
process.stderr.write = function (...args: unknown[]) {
|
||||
if (typeof args[0] === 'string') {
|
||||
const text = args[0];
|
||||
|
||||
if (filters.some(each => text.match(each))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (args[0] instanceof Buffer) {
|
||||
const text = args[0].toString();
|
||||
if (filters.some(each => text.match(each))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return sewrite(...args);
|
||||
};
|
||||
}
|
||||
|
||||
filterStdio();
|
||||
|
||||
async function unitTests() {
|
||||
await assertAnyFolder('dist/test/unit', `The folder '${$root}/dist/test/unit is missing. You should run ${brightGreen("yarn compile")}\n\n`);
|
||||
const mocha = await assertAnyFile(["node_modules/.bin/mocha.cmd", "node_modules/.bin/mocha"], `Can't find the mocha testrunner. You might need to run ${brightGreen("yarn install")}\n\n`);
|
||||
const result = spawnSync(mocha, [`${$root}/dist/test/unit/**/*.test.js`, '--timeout', '30000'], { stdio:'inherit'});
|
||||
|
||||
verbose(`\n${green("NOTE:")} If you want to run a scenario test (end-to-end) use ${cmdSwitch('scenario=<NAME>')} \n\n`);
|
||||
return result.status;
|
||||
}
|
||||
@@ -38,8 +90,7 @@ async function scenarioTests(assets: string, name: string, workspace: string) {
|
||||
extensionTestsPath: resolve($root, 'dist/test/common/selectTests'),
|
||||
launchArgs: workspace ? [...options.launchArgs, workspace] : options.launchArgs,
|
||||
extensionTestsEnv: {
|
||||
SCENARIO: assets,
|
||||
DONT_PROMPT_WSL_INSTALL:"1"
|
||||
SCENARIO: assets
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "Node16",
|
||||
"module": "node16",
|
||||
"moduleResolution": "node16",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
|
||||
@@ -3,16 +3,12 @@
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-dynamic-delete */
|
||||
|
||||
import { downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath } from '@vscode/test-electron';
|
||||
import { fail } from 'assert';
|
||||
import { spawnSync } from 'child_process';
|
||||
import { createHash } from 'crypto';
|
||||
import { tmpdir } from 'os';
|
||||
import { resolve } from 'path';
|
||||
import { verbose } from '../src/Utility/Text/streams';
|
||||
import { $switches, error, mkdir, readJson, rimraf, write } from './common';
|
||||
import { mkdir, readJson, rimraf, write } from './common';
|
||||
|
||||
export const isolated = resolve(tmpdir(), '.vscode-test', createHash('sha256').update(__dirname).digest('hex').substring(0, 6));
|
||||
export const extensionsDir = resolve(isolated, 'extensions');
|
||||
@@ -56,57 +52,7 @@ export async function install() {
|
||||
} catch (err: unknown) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
export async function installExtension(name: string, version?: string) {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let {cli, args} = await install();
|
||||
args = [...args, '--install-extension', version ? `${name}@${version}` : name ];
|
||||
if ($switches.includes('--pre-release')) {
|
||||
args.push('--pre-release');
|
||||
}
|
||||
verbose({cli, args});
|
||||
const result = spawnSync(cli, args, { encoding: 'utf-8', stdio: 'pipe', env:environment()});
|
||||
verbose(result.stdout);
|
||||
if (!result.status){
|
||||
for (const line of result.output){
|
||||
const [,id, ver] = /Extension '(.*)' v(.*?)\s/g.exec(line) ?? [];
|
||||
if (id) {
|
||||
return {id, ver};
|
||||
}
|
||||
}
|
||||
}
|
||||
error(result.stderr);
|
||||
fail('Failed to install extension');
|
||||
}
|
||||
|
||||
export async function uninstallExtension(name: string) {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let {cli, args} = await install();
|
||||
args = [...args, '--uninstall-extension', name];
|
||||
|
||||
const result = spawnSync(cli, args, { encoding: 'utf-8', stdio: 'pipe', env:environment()});
|
||||
if (!result.status){
|
||||
for (const line of result.output){
|
||||
const [,id, ver] = /Extension '(.*)' v(.*?)\s/g.exec(line) ?? [];
|
||||
if (id) {
|
||||
return {id, ver};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function extensions() {
|
||||
// eslint-disable-next-line prefer-const, @typescript-eslint/no-unused-vars
|
||||
let {cli, args} = await install();
|
||||
}
|
||||
|
||||
/** returns a copy of the environment, with tweaks to ensure isolated instance works correctly in WSL and remote dev situations */
|
||||
export function environment() {
|
||||
const env = { ...process.env, DONT_PROMPT_WSL_INSTALL:"1" }; // this lets you run the native VSCODE instance, even if you're working in WSL
|
||||
Object.keys(env).map(each => each.includes('VSCODE') && delete env[each]); // prevent VSCode remoting from hijacking the launching of the isolated vscode
|
||||
return env;
|
||||
}
|
||||
|
||||
export async function reset() {
|
||||
|
||||
Vendored
-4
@@ -13,12 +13,8 @@
|
||||
"--skip-welcome",
|
||||
"--skip-release-notes",
|
||||
"--disable-workspace-trust",
|
||||
"--disable-extensions",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
],
|
||||
"env": {
|
||||
//"VSCODE_CPP_DEBUG_VSCODE": "c:\\work\\vs\\src\\vc\\.vscode\\launch.json"
|
||||
},
|
||||
"sourceMaps": true,
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/dist/**"
|
||||
|
||||
Vendored
-18
@@ -1,16 +1,11 @@
|
||||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"window.title": "CppTools <Typescript> ${separator}${activeEditorShort}${separator}${dirty}",
|
||||
"workbench.colorTheme": "Hacker",
|
||||
"files.exclude": {
|
||||
"out": false // set this to true to hide the "out" folder with the compiled JS files
|
||||
},
|
||||
"search.exclude": {
|
||||
"out": true // set this to false to include "out" folder in search results
|
||||
},
|
||||
"files.associations": {
|
||||
"**/bin/definitions/**/*.json": "jsonl",
|
||||
},
|
||||
"typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version
|
||||
// if you install the mocha test explorer extension, you can run the unit tests from the test explorer UI
|
||||
"testExplorer.useNativeTesting": true,
|
||||
@@ -69,17 +64,4 @@
|
||||
"directory": "./.scripts",
|
||||
}
|
||||
],
|
||||
"[jsonl]": {
|
||||
"editor.tabSize": 2,
|
||||
},
|
||||
"workbench.colorCustomizations": {
|
||||
"[Hacker]": {
|
||||
"sideBar.background": "#050a0a",
|
||||
"menu.background": "#050a0a",
|
||||
"editorWidget.background": "#050a0a",
|
||||
"terminal.background": "#030303",
|
||||
"activityBar.background": "#050a0a",
|
||||
"activityBarBadge.foreground": "#eaeaea",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -22,7 +22,7 @@
|
||||
"isDefault": true
|
||||
},
|
||||
"isBackground": true,
|
||||
"problemMatcher": "$tsc-watch"
|
||||
"problemMatcher": "$tsc-watch",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# C/C++ for Visual Studio Code Changelog
|
||||
|
||||
## Version 1.18.0: October 12, 2023
|
||||
### New Features
|
||||
* Add an 'Extract to Function' (or Member Function) code action after selecting code. [#1162](https://github.com/microsoft/vscode-cpptools/issues/1162)
|
||||
* Currently, it's only enabled when `C_Cpp.experimentFeatures` is `true`. Also, 'Extract to Free Function' is disabled.
|
||||
* Compiler acquisition improvements. [#10525](https://github.com/microsoft/vscode-cpptools/issues/10525)
|
||||
|
||||
### Enhancements
|
||||
* Add setting `C_Cpp.refactoring.includeHeader` to customize whether or not to add an include header when doing a refactoring code action. [#11271](https://github.com/microsoft/vscode-cpptools/issues/11271)
|
||||
* Update clang-format and clang-tidy to 17.0.2. [PR #11491](https://github.com/microsoft/vscode-cpptools/pull/11491)
|
||||
|
||||
### Bug Fixes
|
||||
* Fix the debugger truncating long strings when inspecting values. [#1786](https://github.com/microsoft/vscode-cpptools/issues/1786)
|
||||
* Switch to using `XDG_CACHE_HOME` on Linux for the default database path. [#10191](https://github.com/microsoft/vscode-cpptools/issues/10191)
|
||||
* Fix incorrect status and commands with the tag parsing language status UI. [#10749](https://github.com/microsoft/vscode-cpptools/issues/10749)
|
||||
* Fix an empty (`""`) `compilerPath` in a base configuration overriding the compiler specified by a custom configuration provider or a `compile_commands.json`. [#11373](https://github.com/microsoft/vscode-cpptools/issues/11373)
|
||||
* Fix a startup crash when reading values from JSON (settings) that are not the type expected. [#11375](https://github.com/microsoft/vscode-cpptools/issues/11375)
|
||||
* Fix a crash detected by crash telemetry. [#11401](https://github.com/microsoft/vscode-cpptools/issues/11401)
|
||||
* Fix handling of an undefined `env` variable on Linux and macOS. [#11447](https://github.com/microsoft/vscode-cpptools/issues/11447)
|
||||
* Fix multiple issues with querying `nvcc` (CUDA) as a compiler. [#11454](https://github.com/microsoft/vscode-cpptools/issues/11454)
|
||||
* Fix an issue that could cause a C language standard to be applied to a C++ file, or vice versa.
|
||||
* Remove `cpp` and `clang-cpp` preprocessors from the list of detectable compilers.
|
||||
|
||||
## Version 1.17.5: August 28, 2023
|
||||
### Bug Fixes
|
||||
* Fix a language server crash for platforms that don't support the IntelliSense cache (AutoPCH). [#10789](https://github.com/microsoft/vscode-cpptools/issues/10789)
|
||||
|
||||
@@ -17,7 +17,7 @@ required to debug changes to any libraries licensed under the GNU Lesser General
|
||||
|
||||
---------------------------------------------------------
|
||||
|
||||
lldb-tools/lldb-mi 2388bd74133bc21eac59b2e2bf97f2a30770a315 - Apache-2.0 WITH LLVM-exception
|
||||
lldb-tools/lldb-mi 4fe9c663edce2447e114c71851694d8c529b982d - Apache-2.0 WITH LLVM-exception
|
||||
|
||||
|
||||
Copyright (c) 2010 Apple Inc.
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
// common query file for gcc-style compilers
|
||||
"analysis": {
|
||||
/*
|
||||
"query": {
|
||||
"-E -dM -v ${tmp:c}": {
|
||||
"macro": "^#define (\\S*) ?(.*)?$", // match defines in output
|
||||
"includePaths": "#include <...> search starts here:\n((?: [^\n]*\n)*)*", // match the lines for include folders
|
||||
"target": "^Target: (.*)", // get the target
|
||||
"cStandard": "^#define __STDC_VERSION__\\s*(.*)",
|
||||
"cppStandard": "^#define __cplusplus\\s*(.*)",
|
||||
},
|
||||
"--version": {
|
||||
"version": "^clang version (\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$"
|
||||
}
|
||||
}
|
||||
}*/
|
||||
// how do we specify args to trim off if the discovery is handled/initiated by someone else
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
{
|
||||
"intellisense": {
|
||||
"hostArchitecture": "${host:arch}",
|
||||
},
|
||||
|
||||
"conditions": {
|
||||
// example using a conditional to set the target platform based on the host platform
|
||||
"${host:os} === 'linux'": { "intellisense.platform": "linux" },
|
||||
"${host:os} === 'win32'": { "intellisense.platform": "windows" },
|
||||
"${host:os} === 'darwin'": { "intellisense.platform": "macos" }
|
||||
},
|
||||
"analysis": {
|
||||
// there are a lot of things that can be inferred from the macros that are defined by the compiler
|
||||
// if they are specified, let's use them.
|
||||
"expressions: PRIORITY=90 # things inferred from macros": {
|
||||
/* if this is truthy */ /* then apply this to the intellisense configuration */
|
||||
|
||||
"${macro:_X86_}": { "architecture": "x86", "bits" : 32 },
|
||||
"${macro:_x86_64_}": { "architecture": "x64", "bits" : 64 },
|
||||
"${macro:__aarch64__}": { "architecture": "arm64", "bits" : 64 },
|
||||
"${macro:__arm__}": { "architecture": "arm", "bits" : 32 },
|
||||
"${macro:__AVR__}": { "architecture": "avr", "bits": 16 },
|
||||
|
||||
"${macro:winnt}": { "platform": "windows" },
|
||||
"${macro:_WIN32}": { "platform": "windows" },
|
||||
"${macro:_WIN64}": { "platform": "windows", "bits": 64, "architecture": "x64" },
|
||||
"${macro:__linux__}": { "platform": "linux" },
|
||||
"${macro:__APPLE__}": { "platform": "macos" },
|
||||
|
||||
"${macro:__INTPTR_WIDTH__}": { "bits": "${macro:.__INTPTR_WIDTH__}" },
|
||||
|
||||
"!${sizes.char} && ${macro:__CHAR_SIZE__}" : { "sizes.char": "${macro:__CHAR_SIZE__}" },
|
||||
|
||||
"!${sizes.short} && ${macro:__SIZEOF_SHORT__}": { "sizes.short": "${macro:__SIZEOF_SHORT__}"},
|
||||
"!${sizes.short} && ${macro:__SHORT_SIZE__}": { "sizes.short": "${macro:__SHORT_SIZE__}"},
|
||||
|
||||
"!${sizes.int} && ${macro:__SIZEOF_INT__}": { "sizes.int": "${macro:__SIZEOF_INT__}"},
|
||||
"!${sizes.int} && ${macro:__INT_SIZE__}": { "sizes.int": "${macro:__INT_SIZE__}"},
|
||||
|
||||
"!${sizes.long} && ${macro:__SIZEOF_LONG__}" : { "sizes.long": "${macro:__SIZEOF_LONG__}" },
|
||||
"!${sizes.long} && ${macro:__LONG_SIZE__}" : { "sizes.long": "${macro:__LONG_SIZE__}" },
|
||||
|
||||
"!${sizes.longDouble} && ${macro:__SIZEOF_LONG_DOUBLE__}" : { "sizes.longDouble": "${macro:__SIZEOF_LONG_DOUBLE__}" },
|
||||
"!${sizes.longDouble} && ${macro:__LONG_DOUBLE_SIZE__}" : { "sizes.longDouble": "${macro:__LONG_DOUBLE_SIZE__}" },
|
||||
|
||||
"!${sizes.float} && ${macro:__SIZEOF_FLOAT__}": { "sizes.float": "${macro:__SIZEOF_FLOAT__}"},
|
||||
"!${sizes.float} && ${macro:__FLOAT_SIZE__}": { "sizes.float": "${macro:__FLOAT_SIZE__}"},
|
||||
|
||||
"!${sizes.double} && ${macro:__SIZEOF_DOUBLE__}": { "sizes.double": "${macro:__SIZEOF_DOUBLE__}"},
|
||||
"!${sizes.double} && ${macro:__DOUBLE_SIZE__}": { "sizes.double": "${macro:__DOUBLE_SIZE__}"},
|
||||
|
||||
"!${sizes.pointer} && ${macro:__SIZEOF_POINTER__}": { "sizes.pointer": "${macro:__SIZEOF_POINTER__}"},
|
||||
"!${sizes.pointer} && ${macro:__DEF_PTR_SIZE__}": { "sizes.pointer": "${macro:__DEF_PTR_SIZE__}"},
|
||||
|
||||
"!${sizes.digitsInLongMantissa} && ${macro:__LDBL_MANT_DIG__}": { "sizes.digitsInLongMantissa": "${macro:__LDBL_MANT_DIG__}"},
|
||||
|
||||
"!${types.wcharT} && ${macro:__WCHAR_TYPE__}": { "types.wcharT":"${macro:__WCHAR_TYPE__}"},
|
||||
"!${types.wcharT} && ${macro:__WCHAR_T_TYPE__}": { "types.wcharT":"${macro:__WCHAR_T_TYPE__}"},
|
||||
|
||||
"!${types.sizeT} && ${macro:__SIZE_TYPE__}": { "types.sizeT":"${macro:__SIZE_TYPE__}"},
|
||||
"!${types.sizeT} && ${macro:__SIZE_T_TYPE__}": { "types.sizeT":"${macro:__SIZE_T_TYPE__}"},
|
||||
|
||||
"!${types.ptrDiffT} && ${macro:__PTRDIFF_TYPE__}": { "types.ptrDiffT":"${macro:__PTRDIFF_TYPE__}"},
|
||||
"!${types.ptrDiffT} && ${macro:__PTRDIFF_T_TYPE__}": { "types.ptrDiffT":"${macro:__PTRDIFF_T_TYPE__}"},
|
||||
},
|
||||
|
||||
// when setting priority, it forces it to be higher or lower depending on the value.
|
||||
// higher numbers mean that the block is applied later, lower numbers mean that the block is applied earlier
|
||||
// the default number is the order in which it is loaded
|
||||
"expressions: PRIORITY=100": {
|
||||
"!${sizes.long}" : { "sizes.long": "${platform} === 'windows' ? 4 : ${bits} === 64 ? 8: 4" },
|
||||
"!${sizes.longDouble}": { "sizes.longDouble": "${platform} === 'windows' || ${architecture} === 'arm' || (${architecture} === 'arm64' && ${platform} === 'macos' ) ? 8 : 16" },
|
||||
"!${sizes.char}": { "sizes.char": 1},
|
||||
"!${sizes.short}": { "sizes.short": 2},
|
||||
"!${sizes.int}": { "sizes.int": 4},
|
||||
"!${sizes.float}": { "sizes.float": 4},
|
||||
"!${sizes.double}": { "sizes.double": 8},
|
||||
"!${sizes.pointer}": { "sizes.pointer": "${bits} === 64 ? 8: 4" },
|
||||
"!${sizes.digitsInLongMantissa}": { "sizes.digitsInLongMantissa": "${platform} === 'windows' || ${architecture} === 'arm' || (${architecture} === 'arm64' && ${platform} === 'macos' ) ? 53 : 64" },
|
||||
"!${sizes.alignmentOfLongDouble}": { "sizes.alignmentOfLongDouble": "${platform} === 'windows' || ${architecture} === 'arm' || (${architecture} === 'arm64' && ${platform} === 'macos' ) ? 8 : 16" },
|
||||
"!${sizes.defaultNewAlignment}": { "sizes.defaultNewAlignment": "${bits} === 64 ? 16 : 8" },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
{
|
||||
"analysis": { // affects
|
||||
"queries: ONCE": { // we never need to do this more than once
|
||||
// gets the version of the compiler by executing it with --version
|
||||
"-dumpversion": {
|
||||
"(?<version>.*)" : {"version": "${version}"}, // parse the version from the output
|
||||
},
|
||||
},
|
||||
|
||||
"tasks: ": [
|
||||
// tasks are one-off, built-in functionality that can be executed during analysis
|
||||
],
|
||||
|
||||
"commandLineArguments: C #" :{
|
||||
"-std=(?<std>.+)" : { "standard":"${std}" },
|
||||
"-ansi" : { "standard":"c90" },
|
||||
},
|
||||
|
||||
"commandLineArguments: CPP # " :{
|
||||
"-std=(?<std>.+)" : { "standard":"${std}" },
|
||||
},
|
||||
|
||||
"commandLineArguments: PRIORITY=-10 # drop these (maybe we don't even care to do this?...)": {
|
||||
"-o;.+": { }, // output file
|
||||
"-c;.+": { }, // input file
|
||||
"-param;${keyEqualsValue}": { }, // compiler parameter
|
||||
"-aux-info;.+": { }, // aux info
|
||||
"-MF;.+": { }, // output file for dependencies
|
||||
"-MT;.+": { }, // target for dependencies
|
||||
"-MQ;.+": { }, // target for dependencies
|
||||
"-T;.+": { }, // link script
|
||||
"-Xlinker;.+": { }, // pass to linker
|
||||
"-Wl,.+": { }, // pass to linker
|
||||
"-z;.+": { }, // pass to linker
|
||||
"-Xpreprocessor;.+": { }, // pass to preprocessor
|
||||
"-Xassembler;.+": { }, // pass to assembler
|
||||
"-Wa,.+": { }, // pass to assembler,
|
||||
"-fmacro-prefix-map=.*": { }, // macro prefix map
|
||||
"-fdebug-prefix-map=.*": { }, // debug prefix map
|
||||
},
|
||||
|
||||
"commandLineArguments: # " :{
|
||||
"-B(?<prefix>.+)": { "queryArgument": "-B${prefix}" }, // binary prefix
|
||||
|
||||
"-fms-extensions" : { "parserArgument": "--ms_extensions" },
|
||||
"-fno-ms-extensions" : { "remove:parserArgument": "--ms_extensions" },
|
||||
|
||||
"-m(?<arch>.+)": { "queryArgument" : "-m${arch}" },
|
||||
|
||||
"-I-": { "splitDir": true},
|
||||
|
||||
"-I(?<path>.+)": { "path.include": "${path}", "queryArgument": "-I${path}" },
|
||||
"-I;(?<path>.+)": { "path.include": "${path}", "queryArgument": "-I${path}"},
|
||||
"-isystem;(?<path>.+)": { "path.systemInclude": "${path}", "queryArgument": ["-isystem","${path}"] },
|
||||
"-idirafter;(?<path>.+)": { "path.afterInclude": "${path}", "queryArgument": ["-idirafter","${path}"] },
|
||||
"-iprefix;(?<path>.+)": { "prefixPath": "${path}", "queryArgument": ["-iprefix","${path}"] },
|
||||
"-iwithprefix;(?<path>.+)": { "path.afterInclude": "${prefixPath}${path}", "queryArgument": ["-iwithprefix","${path}"] },
|
||||
"-iwithprefixbefore;(?<path>.+)": { "path.include": "${prefixPath}${path}", "queryArgument": ["-iwithprefixbefore","${path}"] },
|
||||
"-iquote;(?<path>.+)": { "path.quoteInclude": "${path}", "queryArgument": ["-iquote","${path}"] },
|
||||
|
||||
"-isysroot;(?<path>.+)": { "sysroot": "${path}" },
|
||||
"-imultilib;(?<path>.+)": { "path.multilibpaths": "${path}" },
|
||||
"-F(?<path>.+)": { "path.framework": "${path}" },
|
||||
|
||||
"-D${keyEqualsValue}" : { "macro": { "${key}": "${value}" } , "queryArgument": ["-D", "${key}=${value}"]},
|
||||
"-D${keyNoValue}" : { "macro": { "${key}": 1 }, "queryArgument": ["-D", "${key}"]},
|
||||
"-D;${keyEqualsValue}" : { "macro": { "${key}": "${value}" }, "queryArgument": ["-D", "${key}=${value}"]},
|
||||
"-D;${keyNoValue}" : { "macro": { "${key}": 1 }, "queryArgument": ["-D", "${key}"]},
|
||||
"-U${keyNoValue}" : { "macro": { "${key}": null }, "queryArgument": ["-U", "${key}"]},
|
||||
"-U$;{keyNoValue}" : { "macro": { "${key}": null}, "queryArgument": ["-U", "${key}"]},
|
||||
|
||||
"-include;(?<path>.+)": { "path.forcedIncludeFile": "${path}" }, // forced include
|
||||
"-fexperimental-library": { "macro": {"__has_feature(experimental_library)": 1 }},
|
||||
"-undef": { "queryArgument": "-undef" },
|
||||
|
||||
"-x;(?<language>.+)": { "language": "${language}" }
|
||||
|
||||
},
|
||||
|
||||
"expressions: # environment variables" : {
|
||||
"${env:CPATH}": { "path.include": "${env:CPATH}" },
|
||||
"${language}=== 'c' && ${env:C_INCLUDE_PATH}": { "path.systemInclude": "${env:C_INCLUDE_PATH}" },
|
||||
"${language}=== 'cpp' && ${env:CPLUS_INCLUDE_PATH}":{ "path.systemInclude": "${env:CPLUS_INCLUDE_PATH}" },
|
||||
"${language}=== 'objc' && ${env:OBJC_INCLUDE_PATH}":{ "path.systemInclude": "${env:OBJC_INCLUDE_PATH}" },
|
||||
},
|
||||
|
||||
// before the sizes are set, we need to query the compiler with the right args to get the macros again
|
||||
// (so we can let things like -m32 or -m64 or -mthumb or other arch changes affect the macros )
|
||||
"queries:":{
|
||||
// ==== COMMAND LINE IS THE KEY ====
|
||||
"-E -dM -v ${queryArgument} ${tmp:c}": {
|
||||
|
||||
/* regex on output */ /* apply this to the intellisense config*/
|
||||
"^#define (?<key>\\S*) ?(?<value>.*)?$": { "macro":{ "${key}": "${value}" } },
|
||||
"#include <...> search starts here:\n(?<path>(?: [^\n]*\n)*)*": { "path.builtInInclude": "${path}" },
|
||||
"#include \"...\" search starts here:\n(?<path>(?: [^\n]*\n)*)*": { "path.quoteInclude": "${path}" },
|
||||
"^COLLECT_GCC_OPTIONS=(?<opts>.*)": { "gcc.options" : "${opts}" },
|
||||
"^Target: (?<target>.*)": { "gcc.target": "${target}" },
|
||||
"^COMPILER_PATH=(?<cp>.*)": { "gcc.compiler.paths": "${cp}" },
|
||||
"^LIBRARY_PATH=(?<lp>.*)": { "gcc.library.paths": "${lp}" },
|
||||
"^#define __STDC_VERSION__\\s*(?<ver>.*)": { "cStandard": "${ver}" },
|
||||
"^#define __cplusplus\\s*(?<ver>.*)": { "cppStandard": "${ver}" },
|
||||
}
|
||||
},
|
||||
|
||||
"expressions: ONCE # get parts of gcc version" : {
|
||||
"${macro:__GNUC__}" : {
|
||||
"MAJOR": "${macro:__GNUC__} >= 10 ? ${macro:__GNUC__} : '0'+${macro:__GNUC__}",
|
||||
"MINOR": "${macro:__GNUC_MINOR__} >= 10 ? ${macro:__GNUC_MINOR__} : '0'+${macro:__GNUC_MINOR__}",
|
||||
"PATCH": "${macro:__GNUC_PATCHLEVEL__} >= 10 ? ${macro:__GNUC_PATCHLEVEL__} : '0'+${macro:__GNUC_PATCHLEVEL__}"
|
||||
}
|
||||
},
|
||||
|
||||
"expressions: ONCE # gnu_version" : {
|
||||
"${macro:__GNUC__}" : {
|
||||
"parserArgument": [ "--gcc", "--gnu_version=${MAJOR}${MINOR}${PATCH}" ]
|
||||
}
|
||||
},
|
||||
}
|
||||
// how do we specify args to trim off if the discovery is handled/initiated by someone else
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
{
|
||||
"analysis": {
|
||||
// analysis steps are applied in order
|
||||
|
||||
"tasks: # handle environment variables" : [
|
||||
"inline-environment-variables", // takes the $env:CL and $env:_CL_ variables and adds them to the command line
|
||||
"inline-response-file", // takes the @file response file and adds it to the command line
|
||||
"consume-lib-path", // adds $env:LIB to the lib paths
|
||||
"remove-linker-arguments", // removes linker arguments from the command line
|
||||
],
|
||||
|
||||
"expressions: #environment variables": {
|
||||
"${env:INCLUDE}": { "path.environmentInclude": "${env:INCLUDE}" },
|
||||
},
|
||||
|
||||
"commandLineArguments: # detect target language": { // looks thru args to find things that set certain states (ie, /Tc, /Tp for language selection )
|
||||
"${-/}T[cC]" : { "language": "c",},
|
||||
"${-/}T[pP]" : { "language": "c++" },
|
||||
},
|
||||
|
||||
"commandLineArguments: NO_CONSUME # telemetry check for CLR usage": {
|
||||
// don"t consume the parameter
|
||||
"${-/}clr.*" : {"telemetry:cliDetected": {"cliDetection": true} },
|
||||
"${-/}ZW.*" : {"telemetry:cxDetected": {"cxDetection": true} }
|
||||
},
|
||||
|
||||
"commandLineArguments: # process most args": {
|
||||
"${-/}I(?<path>.+)" : { "path.include": "${path}" },
|
||||
"${-/}I;(?<path>.+)" : { "path.include": "${path}" },
|
||||
"${-/}FI(?<path>.+)" : { "path.forcedIncludeFile": "${path}" }, // should we be validating the path exists against system and user include folders? -- cl_compiler_info.cpp@191
|
||||
"${-/}FI;(?<path>.+)" : { "path.forcedIncludeFile": "${path}" },
|
||||
"-include;(?<path>.+)" : { "path.forcedIncludeFile": "${path}" }, // This support for passing "-include" to cl.exe can be removed in the future, when Unreal Engine is updated to no longer pass it to cl.exe.
|
||||
"${-/}external:I(?<path>.+)" : { "path.externalInclude": "${path}" },
|
||||
"${-/}external:I;(?<path>.+)" : { "path.externalInclude": "${path}" },
|
||||
|
||||
"${-/}D;${keyEqualsValue}" : { "macro": { "${key}": "${value}" } },
|
||||
"${-/}D;${keyNoValue}" : { "macro": { "${key}": 1 } },
|
||||
"${-/}D${keyEqualsValue}" : { "macro": { "${key}": "${value}" } },
|
||||
"${-/}D${keyNoValue}" : { "macro": { "${key}": 1 } },
|
||||
|
||||
"${-/}Zl" : { "macro": { "_VC_NODEFAULTLIB": 1 } },
|
||||
"${-/}Zp(?<alignment>1|2|4|8|16)" : { "parserArgument": [ "--pack_alignment", "${alignment}" ] },
|
||||
"${-/}Zp(?<alignment>.+)": { "warning": "unknown pack alignment ${alignment}", "parserArgument": [ "--pack_alignment", 1 ] },
|
||||
|
||||
"${-/}fp:except": { "macro": { "_M_FP_EXCEPT": 1} },
|
||||
"${-/}fp:except-": { "macro": { "_M_FP_EXCEPT": null} },
|
||||
"${-/}fp:fast": { "macro": { "_M_FP_FAST": 1} },
|
||||
"${-/}fp:precise": { "macro": { "_M_FP_PRECISE": 1} },
|
||||
"${-/}fp:strict": { "macro": { "_M_FP_STRICT": 1, "_M_FP_EXCEPT":1} },
|
||||
"${-/}GR": { "parserArgument": "--rtti" },
|
||||
"${-/}GR-": { "parserArgument": "--no_rtti" },
|
||||
"${-/}headerUnit;(?<hu>.+)": { "parserArgument": ["--ms_header_unit","${hu}"] },
|
||||
"${-/}headerUnit(?<hu>.+)": { "parserArgument": ["--ms_header_unit","${hu}"] },
|
||||
"${-/}ifcSearchDir;(?<md>.+)": { "parserArgument": ["--modules_directory","${md}"] },
|
||||
"${-/}ifcSearchDir(?<md>.+)": { "parserArgument": ["--modules_directory","${md}"] },
|
||||
"${-/}J": { "parserArgument": "--unsiged_chars" , "macro": { "_CHAR_UNSIGNED": 1 } },
|
||||
"${-/}LD": { "macro": { "_MT":1 } },
|
||||
"${-/}MT": { "macro": { "_MT":1 } },
|
||||
"${-/}LDd": { "macro": { "_MT":1, "_DEBUG":1 } },
|
||||
"${-/}MTd": { "macro": { "_MT":1, "_DEBUG":1 } },
|
||||
"${-/}MD": { "macro": { "_MT":1, "_DLL":1 } },
|
||||
"${-/}MDd": { "macro": { "_MT":1, "_DLL":1, "_DEBUG":1 } },
|
||||
"${-/}openmp": { "macro": { "_OPENMP":"200203" } },
|
||||
"${-/}openmp:experimental": { "macro": { "_OPENMP":"2019" } },
|
||||
"${-/}openmp:llvm": { "macro": { "_OPENMP":"200203", "_OPENMP_LLVM_RUNTIME":1 } },
|
||||
"${-/}permissive": { "parserArgument": "--ms_permissive" },
|
||||
"${-/}permissive-": { "parserArgument": "--no_ms_permissive" },
|
||||
"${-/}reference;(?<path>.+)": { "parserArgument": ["--ms_mod_file_map","${path}"] },
|
||||
"${-/}reference(?<path>.+)": { "parserArgument": ["--ms_mod_file_map","${path}"] },
|
||||
"${-/}showIncludes": { "parserArgument": "--trace_includes" },
|
||||
"${-/}stdIfcDir;(?<path>.+)": { "parserArgument": ["--std_modules_directory","${path}"] },
|
||||
"${-/}stdIfcDir(?<path>.+)": { "parserArgument": ["--std_modules_directory","${path}"] },
|
||||
"${-/}volatile:iso": { "macro": { "_VOLATILE_ISO":1 } },
|
||||
"${-/}Za": { "parserArgument": "--alternative_tokens" },
|
||||
"${-/}Zc:__cplusplus": { "parserArgument": "--ms_cplusplus_std_value" },
|
||||
"${-/}Zc:__cplusplus-": { "parserArgument": "--no_ms_cplusplus_std_value" },
|
||||
"${-/}Zc:char8_t": { "parserArgument": "--char8_t" },
|
||||
"${-/}Zc:char8_t-": { "parserArgument": "--no_char8_t" },
|
||||
"${-/}Zc:forScope": { "parserArgument": "--new_for_init" },
|
||||
"${-/}Zc:forScope-": { "parserArgument": "--old_for_init" },
|
||||
"${-/}Zc:wchar_t": { "parserArgument": "--wchar_t_keyword" },
|
||||
"${-/}Zc:wchar_t-": { "parserArgument": "--no_wchar_t_keyword" },
|
||||
"${-/}Zc:auto-": { "parserArgument": "--no_auto_type" },
|
||||
"${-/}Zc:trigraphs": { "parserArgument": "--trigraphs" },
|
||||
"${-/}Zc:trigraphs-": { "parserArgument": "--no_trigraphs" },
|
||||
"${-/}Zc:rvalueCast": { "parserArgument": "--ms_rvalue_cast" },
|
||||
"${-/}Zc:rvalueCast-": { "parserArgument": "--no_ms_rvalue_cast" },
|
||||
"${-/}Zc:strictStrings": { "parserArgument": "--no_deprecated_string_conv" },
|
||||
"${-/}Zc:strictStrings-": { "parserArgument": "--deprecated_string_conv" },
|
||||
"${-/}Zc:noexceptTypes": { "parserArgument": "--exc_spec_in_func_type" },
|
||||
"${-/}Zc:noexceptTypes-": { "parserArgument": "--no_exc_spec_in_func_type" },
|
||||
"${-/}Zc:alignedNew": { "parserArgument": "--overaligned_allocation" },
|
||||
"${-/}Zc:alignedNew-": { "parserArgument": "--no_overaligned_allocation" },
|
||||
"${-/}Zc:referenceBinding": { "parserArgument": "--no_nonconst_ref_anachronism" },
|
||||
"${-/}Zc:referenceBinding-": { "parserArgument": "--nonconst_ref_anachronism" },
|
||||
"${-/}Zc:ternary": { "parserArgument": "--ms_strict_ternary" },
|
||||
"${-/}Zc:ternary-": { "parserArgument": "--no_ms_strict_ternary", "remove:parserArgument": "--ms_strict_ternary" },
|
||||
"${-/}Zc:twoPhase": { "parserArgument": "--no_dep_name" },
|
||||
},
|
||||
|
||||
"commandLineArguments:CPP": {
|
||||
"${-/}await": { "parserArgument": "--ms_await" },
|
||||
"${-/}experimental:module" : { "parserArgument": "--modules" },
|
||||
"${-/}experimental:preprocessor" : { "parserArgument": "--ms_std_preprocessor" },
|
||||
"${-/}Zc:preprocessor" : { "parserArgument": "--ms_std_preprocessor" },
|
||||
"${-/}experimental:preprocessor-" : { "parserArgument": "--no_ms_std_preprocessor" },
|
||||
"${-/}Zc:preprocessor-" : { "parserArgument": "--no_ms_std_preprocessor" },
|
||||
"${-/}std:(?<std>.+)" : { "standard":"${std}" },
|
||||
"${-/}kernel": { "macro": { "_KERNEL_MODE": 1 }, "parserArgument": ["--no_exceptions","no_rtti"] },
|
||||
},
|
||||
|
||||
|
||||
"commandLineArguments: # CPP-CLI ??? todo - come back to this (cl_compiler_info.cpp@485 to )": {
|
||||
|
||||
// *** This section would not apply to ARM complier? <#if !VSCODE_ARM> -- see cl_compiler_info.cpp@485 --
|
||||
// *** which I think may be a mistake, since being applied to ARM64 as a host isn"t right, we"re talking about a target?
|
||||
// cpp/cli
|
||||
"${-/}clr:initialAppDomain": { "macro": { "_CPPUNWIND":1 }},
|
||||
"${-/}clr:netcore": { "parserArgument": ["--cppcli_netcore","--no_using_framework_directory", "--cppcli"],"macro":{"D_CLR_NETCORE":1, "_MANAGED":1, "_M_CEE_":"001"} },
|
||||
"${-/}clr:newSyntax": { "parserArgument": "--cppcli", "macro": {"_CPPUNWIND":1, "_M_CEE_PURE":"001", "_MANAGED":1, "_M_CEE_":"001" }},
|
||||
"${-/}clr:noAssembly": { "parserArgument": "--cppcli", "macro": { "_MANAGED":1, "_M_CEE_":"001" }},
|
||||
"${-/}clr:nostdlib": { "parserArgument": ["--no_using_framework_directory"] },
|
||||
"${-/}clr:nostdimport": { "parserArgument": ["--no_stdlib"] },
|
||||
"${-/}clr": { "parserArgument": "--cppcli", "macro": { "_MANAGED":1, "_M_CEE_":"001", "_CPPUNWIND":1, }},
|
||||
"${-/}clr:pure": { "parserArgument": "--cppcli", "macro": { "_MANAGED":1, "_M_CEE_":"001", "_CPPUNWIND":1, }},
|
||||
"${-/}clr:safe": { "parserArgument": "--cppcli", "macro": { "_MANAGED":1, "_M_CEE_":"001", "_CPPUNWIND":1, "_M_CEE_PURE":"001", "_M_CEE_SAFE":"001" }},
|
||||
|
||||
"${-/}zw": { "parserArgument": "--cppcx" },
|
||||
"${-/}zw:nostdlib": { "parserArgument": "--cppcx"},
|
||||
},
|
||||
|
||||
"commandLineArguments:C" : {
|
||||
"${-/}kernel": { "macro": { "_KERNEL_MODE": 1 } },
|
||||
"${-/}std:(?<std>.+)" : { "cStandard":"${std}" },
|
||||
},
|
||||
|
||||
"query" : {
|
||||
"-E /Zc:preprocessor ${queryArgument} /PD ${tmp.c}": {
|
||||
"^#define (?<key>\\S*) ?(?<value>.*)?$": { "macro":{ "${key}": "${value}" } },
|
||||
}
|
||||
},
|
||||
|
||||
"expressions: PRIORITY=85" : {
|
||||
"${bits} === 64" : {
|
||||
"parserArgument": [
|
||||
"-D_MSC_EXTENSIONS",
|
||||
"--microsoft",
|
||||
"--microsoft_bugs",
|
||||
"--microsoft_version", "1935",
|
||||
"--pack_alignment", "8",
|
||||
"-D_CPPUNWIND=1",
|
||||
"-D_MSC_VER=1935",
|
||||
"-D_MSC_FULL_VER=193532215",
|
||||
"-D_MSC_BUILD=0",
|
||||
"-D_M_X64=100",
|
||||
"-D_M_AMD64=100"
|
||||
]
|
||||
|
||||
}
|
||||
},
|
||||
"task:post-process": [
|
||||
// see cl_compiler_info.cpp@617 - process zw directories
|
||||
"zwCommandLineSwitch",
|
||||
"experimentalModuleNegative",
|
||||
],
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "GNU C++ [ARM]",
|
||||
"import": "common/gcc.json",
|
||||
"discover": {
|
||||
"binary": "arm-none-eabi-g++",
|
||||
"locations": [ "c:/nxp/", "c:/st/" ],
|
||||
"match": {
|
||||
"^(?<ver>\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$" : { "version": "${ver}" }
|
||||
}
|
||||
|
||||
},
|
||||
"intellisense": {
|
||||
"compiler": "gcc",
|
||||
"architecture": "arm",
|
||||
"bits": 32,
|
||||
"platform": "none"
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "GNU C [ARM]",
|
||||
"import": "common/gcc.json",
|
||||
"discover": {
|
||||
"binary": "arm-none-eabi-gcc",
|
||||
"locations": [ "c:/nxp/", "c:/st/" ],
|
||||
"match": {
|
||||
"^(?<ver>\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$" : { "version": "${ver}" }
|
||||
}
|
||||
},
|
||||
"intellisense": {
|
||||
"compiler": "gcc",
|
||||
"architecture": "arm",
|
||||
"bits": 32,
|
||||
"platform": "none"
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"name": "GNU C++ [AVR/Arduino]",
|
||||
"import": [
|
||||
"common/gcc.json",
|
||||
"common/defaults.json"
|
||||
],
|
||||
"discover": {
|
||||
"binary": "avr-g++",
|
||||
"locations": [ "${env:home}/appdata/local" ],
|
||||
"match": {
|
||||
"^(?<ver>\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$" : { "version": "${ver}" }
|
||||
}
|
||||
},
|
||||
|
||||
"intellisense": {
|
||||
"compiler": "gcc",
|
||||
"architecture": "avr",
|
||||
"platform": "none"
|
||||
},
|
||||
"conditions": {
|
||||
"${host:os} === 'win32'": { "discover.locations": "${env:home}/appdata/local", }
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "Clang C++",
|
||||
"import": [
|
||||
"common/defaults.json",
|
||||
"common/clang.json",
|
||||
],
|
||||
"discover": {
|
||||
"binary": "clang++",
|
||||
"match": {
|
||||
"^clang (?<ver>\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$": { "version": "${ver}" }
|
||||
}
|
||||
},
|
||||
"intellisense": {
|
||||
"compiler": "clang",
|
||||
"architecture": "x64",
|
||||
"bits": 64,
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "GNU C++ [x64]",
|
||||
"import": [
|
||||
"common/gcc.json",
|
||||
"common/defaults.json"
|
||||
],
|
||||
"discover": {
|
||||
"binary": "g++",
|
||||
"match": {
|
||||
"^(?<ver>\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$" : { "version": "${ver}" }
|
||||
}
|
||||
},
|
||||
"intellisense": {
|
||||
"compiler": "gcc",
|
||||
"architecture": "x64",
|
||||
"bits": 64,
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "GNU C [x64]",
|
||||
"import": [
|
||||
"common/gcc.json",
|
||||
"common/defaults.json"
|
||||
],
|
||||
"discover": {
|
||||
"binary": "gcc",
|
||||
"match": {
|
||||
"^(?<ver>\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$" : { "version": "${ver}" }
|
||||
}
|
||||
},
|
||||
"intellisense": {
|
||||
"compiler": "gcc",
|
||||
"architecture": "x64",
|
||||
"bits": 64,
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"name": "IAR C/C++ [ARM]",
|
||||
"import": [
|
||||
"common/defaults.json"
|
||||
],
|
||||
"discover": {
|
||||
"binary": "iccarm",
|
||||
"match": {
|
||||
"^(?<ver>\\d{1,2}[.]\\d+[.]\\d+[.]\\d+)(?:/\\S*)?$": { "version": "${ver}" }
|
||||
},
|
||||
"expression: FOLDER" : {
|
||||
"${binary}/../../" : { "IARPath" : "${binary}/../../" }
|
||||
}
|
||||
},
|
||||
"intellisense": {
|
||||
"compiler": "iar", // this just gets us the 'base' that EDG will think it is
|
||||
"architecture": "arm", // we can have a common file that remaps this to something else if needed
|
||||
"bits": 32, // another hint for later.
|
||||
"platform": "none", // bare-metal target. (can be remaped to windows/macos/linux if needed to make EDG happy)
|
||||
"path.systemInclude": [ // we can't get the include path from the compiler, so we had to explicity state it
|
||||
"${IARPath}inc/cpp" // include folder for c++ headers in IAR
|
||||
]
|
||||
},
|
||||
"analysis": {
|
||||
"queries": {
|
||||
"${tmp.c} --c++ --predef_macros ${tmp.stdout} --output ${tmp.o}": {
|
||||
"^#define (?<key>\\S*) ?(?<value>.*)?$" : { "macro": { "${key}": "${value}" } },
|
||||
"^#define __STDC_VERSION__\\s*(?<ver>.*)": { "cStandard": "${ver}" },
|
||||
"^#define __cplusplus\\s*(?<ver>.*)": { "cppStandard": "${ver}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
{
|
||||
"name": "Microsoft Visual C++",
|
||||
"import": [
|
||||
"common/msvc.json",
|
||||
"common/defaults.json"
|
||||
],
|
||||
"discover": {
|
||||
"binary": "cl",
|
||||
|
||||
// Add the default paths.include for the WindowsSDK
|
||||
"expression: FOLDER, OPTIONAL": {
|
||||
"${KitsRoot10}/Include/${SDKVer}/um" : { "path.builtInInclude": "${KitsRoot10}/Include/${SDKVer}/um" },
|
||||
"${KitsRoot10}/Include/${SDKVer}/ucrt" : { "path.builtInInclude": "${KitsRoot10}/Include/${SDKVer}/ucrt" },
|
||||
"${KitsRoot10}/Include/${SDKVer}/shared" : { "path.builtInInclude": "${KitsRoot10}/Include/${SDKVer}/shared" },
|
||||
"${KitsRoot10}/Include/${SDKVer}/winrt" : { "path.builtInInclude": "${KitsRoot10}/Include/${SDKVer}/winrt" },
|
||||
"${KitsRoot10}/Include/${SDKVer}/cppwinrt" : { "path.builtInInclude": "${KitsRoot10}/Include/${SDKVer}/cppwinrt" }
|
||||
},
|
||||
|
||||
// get the compiler version from the binary itself
|
||||
"match: # detect the compiler version from the binary": {
|
||||
"^(?<ver>\\d+[.]\\d+[.]\\d+)[.].*$" : { "version": "${ver}" }
|
||||
},
|
||||
|
||||
// get the host architecture based on the string we can find inside the binary
|
||||
"match: ONEOF # detect the host architecture from the binary" : {
|
||||
"\\\\amd64\\\\": { "hostArchitecture": "x64" ,"bits": 64, "architecture": "x64"},
|
||||
"\\\\i386\\\\" : { "hostArchitecture": "x86" , "bits": 32, "architecture": "x86" },
|
||||
"\\\\arm64\\\\": { "hostArchitecture": "arm64" , "bits": 64, "architecture": "arm64" },
|
||||
|
||||
"\\\\x86_amd64\\\\": { "hostArchitecture": "x86" ,"bits": 64, "architecture": "x64"},
|
||||
"\\\\x86_arm64\\\\": { "hostArchitecture": "x86", "bits": 64, "architecture": "arm64" },
|
||||
"\\\\x86_arm\\\\": { "hostArchitecture": "x86", "bits": 32, "architecture": "arm" },
|
||||
|
||||
"\\\\amd64_x86\\\\": { "hostArchitecture": "x64" , "bits": 32, "architecture": "x86" },
|
||||
"\\\\amd64_arm\\\\": { "hostArchitecture": "x64" , "bits": 32, "architecture": "arm"},
|
||||
"\\\\amd64_arm64\\\\": { "hostArchitecture": "x64", "bits": 64, "architecture": "arm64" },
|
||||
|
||||
"\\\\arm64_amd64\\\\": { "hostArchitecture": "arm64" ,"bits": 64, "architecture": "x64"},
|
||||
"\\\\arm64_x86\\\\": { "hostArchitecture": "arm64", "bits": 32, "architecture": "x86" },
|
||||
"\\\\arm64_arm\\\\": { "hostArchitecture": "arm64" , "bits": 32, "architecture": "arm"},
|
||||
},
|
||||
|
||||
// add the default paths.include for the Visual C++ compiler
|
||||
"expression: ONEOF,FOLDER # find the base include folder": {
|
||||
"${binary}/../include": { "path.builtInInclude": "${binary}/../include"},
|
||||
"${binary}/../../include": { "path.builtInInclude": "${binary}/../../include"},
|
||||
"${binary}/../../../include": { "path.builtInInclude": "${binary}/../../../include"},
|
||||
"${binary}/../../../../include": { "path.builtInInclude": "${binary}/../../../../include" }
|
||||
},
|
||||
|
||||
// add the atlmfc paths.include for the Visual C++ compiler
|
||||
"expression: ONEOF,OPTIONAL,FOLDER # find the optional atlmfc include folder": {
|
||||
"${binary}/../atlmfc/include": { "path.builtInInclude": "${binary}/../atlmfc/include" },
|
||||
"${binary}/../../atlmfc/include": { "path.builtInInclude": "${binary}/../../atlmfc/include" },
|
||||
"${binary}/../../../atlmfc/include": { "path.builtInInclude": "${binary}/../../../atlmfc/include" },
|
||||
"${binary}/../../../../atlmfc/include": { "path.builtInInclude": "${binary}/../../../../atlmfc/include" }
|
||||
}
|
||||
},
|
||||
|
||||
"intellisense": {
|
||||
// get the location of the SDK root from the registry
|
||||
"KitsRoot10": "${HKLM:SOFTWARE/WOW6432Node/Microsoft/Windows Kits/Installed Roots;KitsRoot10}",
|
||||
"KitsRoot81": "${HKLM:SOFTWARE/WOW6432Node/Microsoft/Windows Kits/Installed Roots;KitsRoot81}",
|
||||
|
||||
// set the default SDK version (todo: this should be able to be pulled in from the user's config)
|
||||
"SDKVer": "10.0.22000.0",
|
||||
|
||||
"parserArgument": [
|
||||
"--no_warnings",
|
||||
"--rtti",
|
||||
"--wchar_t_keyword",
|
||||
"--edge",
|
||||
"--exceptions",
|
||||
"--error_limit", "25000",
|
||||
"-D_EDG_COMPILER",
|
||||
"-D_USE_DECLSPECS_FOR_SAL=1"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"name": "GNU C++ [xtensa-esp32]",
|
||||
"import": [
|
||||
"common/gcc.json",
|
||||
"common/defaults.json"
|
||||
],
|
||||
"discover": {
|
||||
"binary": "xtensa-esp32-elf-g++",
|
||||
"locations": [ "${env:IDF_TOOLS_PATH}" ],
|
||||
"match": {
|
||||
"^(?<ver>\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$" : { "version": "${ver}" }
|
||||
}
|
||||
|
||||
},
|
||||
"intellisense": {
|
||||
"compiler": "gcc",
|
||||
"architecture": "xtensa",
|
||||
"bits": 32,
|
||||
"platform": "none",
|
||||
"parserArgument": [],
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"name": "GNU C [xtensa-esp32]",
|
||||
"import": [
|
||||
"common/gcc.json",
|
||||
"common/defaults.json"
|
||||
],
|
||||
"discover": {
|
||||
"binary": "xtensa-esp32-elf-gcc",
|
||||
"locations": [ "${env:IDF_TOOLS_PATH}" ],
|
||||
"match": {
|
||||
"^(?<ver>\\d+[.]\\d+[.]\\d+)(?:-\\S*)?$" : { "version": "${ver}" }
|
||||
}
|
||||
|
||||
},
|
||||
"intellisense": {
|
||||
"compiler": "gcc",
|
||||
"architecture": "xtensa",
|
||||
"bits": 32,
|
||||
"platform": "none",
|
||||
"parserArgument": ["--c", "--c${cStandard}" ]
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
"Type": "git",
|
||||
"Git": {
|
||||
"RepositoryUrl": "https://github.com/lldb-tools/lldb-mi",
|
||||
"CommitHash": "2388bd74133bc21eac59b2e2bf97f2a30770a315"
|
||||
"CommitHash": "4fe9c663edce2447e114c71851694d8c529b982d"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
|
||||
# Performance Notes
|
||||
I moved the entire discover/identify process to a nodejs `worker_thread` (a background thread).
|
||||
This is useful when the foreground app has cpu-intensive work or a lot of things in the event loop.
|
||||
|
||||
Technically, this wouldn't be super important to do if everything was *simply* async IO bound, but the startup
|
||||
of the extension is kinda CPU intensive (as ), and moving the logic to a worker thread completely frees up the
|
||||
foreground to do what it needs to.
|
||||
|
||||
|
||||
|
||||
# Results
|
||||
|
||||
| Initial State | selecting compiler | discovery in init? | time to identify | time to get intellisense |
|
||||
| ------------- | ------------------ | ------------------ | ---------------- | ------------------------ |
|
||||
| no cache | explicit path | no | 600-700 ms | 550-745 ms |
|
||||
| no cache | explicit path | yes | 1000-1350 ms | 1625-1675 ms |
|
||||
| no cache | by name/wildcard | no | 6900-7100 ms | 700-730 ms |
|
||||
| no cache | by name/wildcard | yes | 5900-6100 ms | 700-720 ms |
|
||||
| cached | explicit path | no | 6-7 ms | 6-15 ms |
|
||||
| cached | explicit path | yes | 6-8 ms | 6-19 ms |
|
||||
| cached | by name/wildcard | no | 6-7 ms | 6-15 ms |
|
||||
| cached | by name/wildcard | yes | 6-7 ms | 6-15 ms |
|
||||
|
||||
1. "cached" means that the extension has already performed some discovery
|
||||
or identification at some point, and it loaded the previously used data from
|
||||
the cache.
|
||||
|
||||
2. "no cache" means that the extension has not performed any discovery or
|
||||
identification at all, the extension has to identify the compiler from scratch.
|
||||
|
||||
3. "explicit path" means that in the `c_cpp_properties.json` file, the full path
|
||||
is specified in the `compiler` field.
|
||||
|
||||
The compiler can be identified soley by path, and doesn't have to wait
|
||||
for full discovery to complete. If the data is cached, it's a fast lookup.
|
||||
|
||||
4. "by name/wildcard" means that in the `c_cpp_properties.json` file, the compiler
|
||||
is specified by name (with or without a wildcard).
|
||||
|
||||
The compiler can be identified quickly if there are cached entries, but if there isn't
|
||||
we have to wait for full discovery to be done.
|
||||
|
||||
5. "discover in init = yes" means that the discovery process is initiated in the background
|
||||
during initialization. If there are any cached entries, then it waits 5 seconds before kicking
|
||||
it off in the background. If there are zero cached entries, then it kicks it off immediately.
|
||||
|
||||
6. "discover in init = no" means that the discovery process is not initiated in the background
|
||||
during initialization. It is only kicked off when the extension needs to identify a compiler
|
||||
and it misses the cache hit.
|
||||
|
||||
|
||||
Notes :
|
||||
- if the `compiler` field isn't in the intellisense, we're not activating any of this.
|
||||
|
||||
- when we enable the extension to prompt the user which compiler to use, we have to
|
||||
have discovery done (~6-12 seconds) before we can prompt the user.
|
||||
|
||||
This may be a good reason to have discovery done in the background during initialization
|
||||
every time.
|
||||
|
||||
- Cached data is stored in a single JSON file in the global storage path for the extension
|
||||
|
||||
- Cached data is loading on startup.
|
||||
|
||||
- Cached data is saved every time the extension does something that alters the data in the cache.
|
||||
- discovery finds a compiler
|
||||
- a (new/different) intellisense config is generated for a given command line
|
||||
- a (new/different) query of a compiler is done
|
||||
|
||||
|
||||
Questions:
|
||||
- The cached data is globally stored for all instances of vscode.
|
||||
- that means that currently, 'last-one-wins' is how it's being stored.
|
||||
- I'm leaning towards making it load/merge/store whenever any instance is about to modify the cache file.
|
||||
- this would make the cache more and more accurate over time, and effort by a single instance would benefit
|
||||
all instances
|
||||
- I *can* enable a staleness timeout on things in the cache (even partially, so like queries/analysis). Currently, this file would grow and grow over time
|
||||
and get somewhat large. A missed cache hit could incur a hit of 500 to 2000 ms depending on how much work it has
|
||||
to do.
|
||||
- if we stale out the whole toolset entry, then we'd have to run identify on that one again the next time (more expensive, and could trigger a full discovery)
|
||||
- if we stale out just the queries/analysis, then just those would be regenerated/rerun when the intellisense config (not as expensive, and doesn't require us to do discovery again)
|
||||
|
||||
+71
-9
@@ -2,7 +2,7 @@
|
||||
"name": "cpptools",
|
||||
"displayName": "C/C++",
|
||||
"description": "C/C++ IntelliSense, debugging, and code browsing.",
|
||||
"version": "1.17.5-main",
|
||||
"version": "1.18.0-main",
|
||||
"publisher": "ms-vscode",
|
||||
"icon": "LanguageCCPP_color_128x.png",
|
||||
"readme": "README.md",
|
||||
@@ -1789,6 +1789,7 @@
|
||||
"bugprone-dangling-handle",
|
||||
"bugprone-dynamic-static-initializers",
|
||||
"bugprone-easily-swappable-parameters",
|
||||
"bugprone-empty-catch",
|
||||
"bugprone-exception-escape",
|
||||
"bugprone-fold-init-type",
|
||||
"bugprone-forward-declaration-namespace",
|
||||
@@ -1806,9 +1807,12 @@
|
||||
"bugprone-misplaced-pointer-arithmetic-in-alloc",
|
||||
"bugprone-misplaced-widening-cast",
|
||||
"bugprone-move-forwarding-reference",
|
||||
"bugprone-multiple-*",
|
||||
"bugprone-multiple-new-in-one-expression",
|
||||
"bugprone-multiple-statement-macro",
|
||||
"bugprone-narrowing-conversions",
|
||||
"bugprone-no-escape",
|
||||
"bugprone-non-zero-enum-to-bool-conversion",
|
||||
"bugprone-not-null-terminated-result",
|
||||
"bugprone-parent-virtual-call",
|
||||
"bugprone-posix-return",
|
||||
@@ -1837,6 +1841,7 @@
|
||||
"bugprone-suspicious-semicolon",
|
||||
"bugprone-suspicious-string-compare",
|
||||
"bugprone-swapped-arguments",
|
||||
"bugprone-switch-missing-default-case",
|
||||
"bugprone-terminating-continue",
|
||||
"bugprone-throw-keyword-missing",
|
||||
"bugprone-too-small-loop-variable",
|
||||
@@ -1846,6 +1851,7 @@
|
||||
"bugprone-unhandled-*",
|
||||
"bugprone-unhandled-exception-at-new",
|
||||
"bugprone-unhandled-self-assignment",
|
||||
"bugprone-unique-ptr-array-mismatch",
|
||||
"bugprone-unused-raii",
|
||||
"bugprone-unused-return-value",
|
||||
"bugprone-use-after-move",
|
||||
@@ -2005,6 +2011,7 @@
|
||||
"cppcoreguidelines-*",
|
||||
"cppcoreguidelines-avoid-*",
|
||||
"cppcoreguidelines-avoid-c-arrays",
|
||||
"cppcoreguidelines-avoid-capturing-lambda-coroutines",
|
||||
"cppcoreguidelines-avoid-const-or-ref-data-members",
|
||||
"cppcoreguidelines-avoid-do-while",
|
||||
"cppcoreguidelines-avoid-goto",
|
||||
@@ -2017,6 +2024,8 @@
|
||||
"cppcoreguidelines-interfaces-global-init",
|
||||
"cppcoreguidelines-macro-to-enum",
|
||||
"cppcoreguidelines-macro-usage",
|
||||
"cppcoreguidelines-misleading-capture-default-by-value",
|
||||
"cppcoreguidelines-missing-std-forward",
|
||||
"cppcoreguidelines-narrowing-conversions",
|
||||
"cppcoreguidelines-no-malloc",
|
||||
"cppcoreguidelines-non-private-member-variables-in-classes",
|
||||
@@ -2034,6 +2043,7 @@
|
||||
"cppcoreguidelines-pro-type-static-cast-downcast",
|
||||
"cppcoreguidelines-pro-type-union-access",
|
||||
"cppcoreguidelines-pro-type-vararg",
|
||||
"cppcoreguidelines-rvalue-reference-param-not-moved",
|
||||
"cppcoreguidelines-slicing",
|
||||
"cppcoreguidelines-special-member-functions",
|
||||
"cppcoreguidelines-virtual-class-destructor",
|
||||
@@ -2124,11 +2134,14 @@
|
||||
"llvmlibc-*",
|
||||
"llvmlibc-callee-namespace",
|
||||
"llvmlibc-implementation-in-namespace",
|
||||
"llvmlibc-inline-function-decl",
|
||||
"llvmlibc-restrict-system-libc-headers",
|
||||
"misc-*",
|
||||
"misc-confusable-identifiers",
|
||||
"misc-const-correctness",
|
||||
"misc-definitions-in-headers",
|
||||
"misc-header-include-cycle",
|
||||
"misc-include-cleaner",
|
||||
"misc-misleading-*",
|
||||
"misc-misleading-bidirectional",
|
||||
"misc-misleading-identifier",
|
||||
@@ -2170,6 +2183,7 @@
|
||||
"modernize-replace-random-shuffle",
|
||||
"modernize-return-braced-init-list",
|
||||
"modernize-shrink-to-fit",
|
||||
"modernize-type-traits",
|
||||
"modernize-unary-static-assert",
|
||||
"modernize-use-*",
|
||||
"modernize-use-auto",
|
||||
@@ -2184,6 +2198,7 @@
|
||||
"modernize-use-noexcept",
|
||||
"modernize-use-nullptr",
|
||||
"modernize-use-override",
|
||||
"modernize-use-std-print",
|
||||
"modernize-use-trailing-return-type",
|
||||
"modernize-use-transparent-functors",
|
||||
"modernize-use-uncaught-exceptions",
|
||||
@@ -2205,6 +2220,7 @@
|
||||
"openmp-exception-escape",
|
||||
"openmp-use-default-none",
|
||||
"performance-*",
|
||||
"performance-avoid-endl",
|
||||
"performance-faster-string-find",
|
||||
"performance-for-range-copy",
|
||||
"performance-implicit-conversion-in-loop",
|
||||
@@ -2218,7 +2234,10 @@
|
||||
"performance-no-*",
|
||||
"performance-no-automatic-move",
|
||||
"performance-no-int-to-ptr",
|
||||
"performance-noexcept-*",
|
||||
"performance-noexcept-destructor",
|
||||
"performance-noexcept-move-constructor",
|
||||
"performance-noexcept-swap",
|
||||
"performance-trivially-destructible",
|
||||
"performance-type-promotion-in-math-fn",
|
||||
"performance-unnecessary-*",
|
||||
@@ -2229,7 +2248,9 @@
|
||||
"portability-simd-intrinsics",
|
||||
"portability-std-allocator-const",
|
||||
"readability-*",
|
||||
"readability-avoid-*",
|
||||
"readability-avoid-const-params-in-decls",
|
||||
"readability-avoid-unconditional-preprocessor-if",
|
||||
"readability-braces-around-statements",
|
||||
"readability-const-return-type",
|
||||
"readability-container-*",
|
||||
@@ -2255,6 +2276,7 @@
|
||||
"readability-misplaced-array-index",
|
||||
"readability-named-parameter",
|
||||
"readability-non-const-parameter",
|
||||
"readability-operators-representation",
|
||||
"readability-qualified-auto",
|
||||
"readability-redundant-*",
|
||||
"readability-redundant-access-specifiers",
|
||||
@@ -2353,6 +2375,7 @@
|
||||
"bugprone-dangling-handle",
|
||||
"bugprone-dynamic-static-initializers",
|
||||
"bugprone-easily-swappable-parameters",
|
||||
"bugprone-empty-catch",
|
||||
"bugprone-exception-escape",
|
||||
"bugprone-fold-init-type",
|
||||
"bugprone-forward-declaration-namespace",
|
||||
@@ -2370,9 +2393,12 @@
|
||||
"bugprone-misplaced-pointer-arithmetic-in-alloc",
|
||||
"bugprone-misplaced-widening-cast",
|
||||
"bugprone-move-forwarding-reference",
|
||||
"bugprone-multiple-*",
|
||||
"bugprone-multiple-new-in-one-expression",
|
||||
"bugprone-multiple-statement-macro",
|
||||
"bugprone-narrowing-conversions",
|
||||
"bugprone-no-escape",
|
||||
"bugprone-non-zero-enum-to-bool-conversion",
|
||||
"bugprone-not-null-terminated-result",
|
||||
"bugprone-parent-virtual-call",
|
||||
"bugprone-posix-return",
|
||||
@@ -2401,6 +2427,7 @@
|
||||
"bugprone-suspicious-semicolon",
|
||||
"bugprone-suspicious-string-compare",
|
||||
"bugprone-swapped-arguments",
|
||||
"bugprone-switch-missing-default-case",
|
||||
"bugprone-terminating-continue",
|
||||
"bugprone-throw-keyword-missing",
|
||||
"bugprone-too-small-loop-variable",
|
||||
@@ -2410,6 +2437,7 @@
|
||||
"bugprone-unhandled-*",
|
||||
"bugprone-unhandled-exception-at-new",
|
||||
"bugprone-unhandled-self-assignment",
|
||||
"bugprone-unique-ptr-array-mismatch",
|
||||
"bugprone-unused-raii",
|
||||
"bugprone-unused-return-value",
|
||||
"bugprone-use-after-move",
|
||||
@@ -2569,6 +2597,7 @@
|
||||
"cppcoreguidelines-*",
|
||||
"cppcoreguidelines-avoid-*",
|
||||
"cppcoreguidelines-avoid-c-arrays",
|
||||
"cppcoreguidelines-avoid-capturing-lambda-coroutines",
|
||||
"cppcoreguidelines-avoid-const-or-ref-data-members",
|
||||
"cppcoreguidelines-avoid-do-while",
|
||||
"cppcoreguidelines-avoid-goto",
|
||||
@@ -2581,6 +2610,8 @@
|
||||
"cppcoreguidelines-interfaces-global-init",
|
||||
"cppcoreguidelines-macro-to-enum",
|
||||
"cppcoreguidelines-macro-usage",
|
||||
"cppcoreguidelines-misleading-capture-default-by-value",
|
||||
"cppcoreguidelines-missing-std-forward",
|
||||
"cppcoreguidelines-narrowing-conversions",
|
||||
"cppcoreguidelines-no-malloc",
|
||||
"cppcoreguidelines-non-private-member-variables-in-classes",
|
||||
@@ -2598,6 +2629,7 @@
|
||||
"cppcoreguidelines-pro-type-static-cast-downcast",
|
||||
"cppcoreguidelines-pro-type-union-access",
|
||||
"cppcoreguidelines-pro-type-vararg",
|
||||
"cppcoreguidelines-rvalue-reference-param-not-moved",
|
||||
"cppcoreguidelines-slicing",
|
||||
"cppcoreguidelines-special-member-functions",
|
||||
"cppcoreguidelines-virtual-class-destructor",
|
||||
@@ -2688,11 +2720,14 @@
|
||||
"llvmlibc-*",
|
||||
"llvmlibc-callee-namespace",
|
||||
"llvmlibc-implementation-in-namespace",
|
||||
"llvmlibc-inline-function-decl",
|
||||
"llvmlibc-restrict-system-libc-headers",
|
||||
"misc-*",
|
||||
"misc-confusable-identifiers",
|
||||
"misc-const-correctness",
|
||||
"misc-definitions-in-headers",
|
||||
"misc-header-include-cycle",
|
||||
"misc-include-cleaner",
|
||||
"misc-misleading-*",
|
||||
"misc-misleading-bidirectional",
|
||||
"misc-misleading-identifier",
|
||||
@@ -2734,6 +2769,7 @@
|
||||
"modernize-replace-random-shuffle",
|
||||
"modernize-return-braced-init-list",
|
||||
"modernize-shrink-to-fit",
|
||||
"modernize-type-traits",
|
||||
"modernize-unary-static-assert",
|
||||
"modernize-use-*",
|
||||
"modernize-use-auto",
|
||||
@@ -2748,6 +2784,7 @@
|
||||
"modernize-use-noexcept",
|
||||
"modernize-use-nullptr",
|
||||
"modernize-use-override",
|
||||
"modernize-use-std-print",
|
||||
"modernize-use-trailing-return-type",
|
||||
"modernize-use-transparent-functors",
|
||||
"modernize-use-uncaught-exceptions",
|
||||
@@ -2769,6 +2806,7 @@
|
||||
"openmp-exception-escape",
|
||||
"openmp-use-default-none",
|
||||
"performance-*",
|
||||
"performance-avoid-endl",
|
||||
"performance-faster-string-find",
|
||||
"performance-for-range-copy",
|
||||
"performance-implicit-conversion-in-loop",
|
||||
@@ -2782,7 +2820,10 @@
|
||||
"performance-no-*",
|
||||
"performance-no-automatic-move",
|
||||
"performance-no-int-to-ptr",
|
||||
"performance-noexcept-*",
|
||||
"performance-noexcept-destructor",
|
||||
"performance-noexcept-move-constructor",
|
||||
"performance-noexcept-swap",
|
||||
"performance-trivially-destructible",
|
||||
"performance-type-promotion-in-math-fn",
|
||||
"performance-unnecessary-*",
|
||||
@@ -2793,7 +2834,9 @@
|
||||
"portability-simd-intrinsics",
|
||||
"portability-std-allocator-const",
|
||||
"readability-*",
|
||||
"readability-avoid-*",
|
||||
"readability-avoid-const-params-in-decls",
|
||||
"readability-avoid-unconditional-preprocessor-if",
|
||||
"readability-braces-around-statements",
|
||||
"readability-const-return-type",
|
||||
"readability-container-*",
|
||||
@@ -2819,6 +2862,7 @@
|
||||
"readability-misplaced-array-index",
|
||||
"readability-named-parameter",
|
||||
"readability-non-const-parameter",
|
||||
"readability-operators-representation",
|
||||
"readability-qualified-auto",
|
||||
"readability-redundant-*",
|
||||
"readability-redundant-access-specifiers",
|
||||
@@ -3307,6 +3351,15 @@
|
||||
"apply": "first"
|
||||
},
|
||||
"when": "editorLangId =~ /^(c|(cuda-)?cpp)$/ && editorTextFocus && !(config.C_Cpp.intelliSenseEngine =~ /^[dD]isabled$/)"
|
||||
},
|
||||
{
|
||||
"command": "editor.action.codeAction",
|
||||
"key": "ctrl+shift+r ctrl+e",
|
||||
"args": {
|
||||
"kind": "refactor.extract.function",
|
||||
"apply": "first"
|
||||
},
|
||||
"when": "editorLangId =~ /^(c|(cuda-)?cpp)$/ && editorTextFocus && !(config.C_Cpp.intelliSenseEngine =~ /^[dD]isabled$/)"
|
||||
}
|
||||
],
|
||||
"debuggers": [
|
||||
@@ -6206,13 +6259,25 @@
|
||||
"languages": [
|
||||
"c",
|
||||
"cpp",
|
||||
"cude-cpp"
|
||||
"cuda-cpp"
|
||||
],
|
||||
"actions": {
|
||||
"kind": "refactor.inline.macro",
|
||||
"title": "%c_cpp.codeActions.refactor.inline.macro.title%",
|
||||
"description": "%c_cpp.codeActions.refactor.inline.macro.description%"
|
||||
}
|
||||
},
|
||||
{
|
||||
"languages": [
|
||||
"c",
|
||||
"cpp",
|
||||
"cuda-cpp"
|
||||
],
|
||||
"actions": {
|
||||
"kind": "refactor.extract.function",
|
||||
"title": "%c_cpp.codeActions.refactor.extract.function.title%",
|
||||
"description": "%c_cpp.codeActions.refactor.extract.function.description%"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -6253,8 +6318,8 @@
|
||||
"@types/tmp": "^0.1.0",
|
||||
"@types/which": "^1.3.2",
|
||||
"@types/yauzl": "^2.9.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.5.0",
|
||||
"@typescript-eslint/parser": "^6.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||
"@typescript-eslint/parser": "^6.1.0",
|
||||
"eslint-plugin-header": "^3.1.1",
|
||||
"@vscode/test-electron": "^2.3.3",
|
||||
"@vscode/dts": "^0.4.0",
|
||||
@@ -6263,7 +6328,6 @@
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jsdoc": "^46.4.4",
|
||||
"eslint-plugin-etc": "^2.0.3",
|
||||
"event-stream": "^4.0.1",
|
||||
"fs-extra": "^8.1.0",
|
||||
"gulp": "^4.0.2",
|
||||
@@ -6278,7 +6342,7 @@
|
||||
"parse5-traverse": "^1.0.3",
|
||||
"ts-loader": "^8.1.0",
|
||||
"ts-node": "10.9.1",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript": "^5.1.3",
|
||||
"@vscode/debugadapter": "^1.61.0",
|
||||
"@vscode/debugprotocol": "^1.61.0",
|
||||
"vscode-nls-dev": "^4.0.0-next.1",
|
||||
@@ -6306,9 +6370,7 @@
|
||||
"vscode-languageclient": "^8.1.0-next.4",
|
||||
"vscode-nls": "^5.0.0",
|
||||
"vscode-tas-client": "^0.1.27",
|
||||
"which": "^2.0.2",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
"which": "^2.0.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"chokidar": "^3.5.3",
|
||||
|
||||
@@ -997,6 +997,8 @@
|
||||
"c_cpp.walkthrough.customize.debugging.altText": "Image that shows Add Debug Configuration in the drop-down",
|
||||
"c_cpp.codeActions.refactor.inline.macro.title": "Inline macro",
|
||||
"c_cpp.codeActions.refactor.inline.macro.description": "Replace the macro invocation with the expanded code.",
|
||||
"c_cpp.codeActions.refactor.extract.function.title": "Extract to function",
|
||||
"c_cpp.codeActions.refactor.extract.function.description": "Extract the selected code to a free or member function.",
|
||||
"c_cpp.configuration.refactoring.includeHeader.markdownDescription": "Controls whether to include the header file of a refactored function/symbol to its corresponding source file when doing a refactoring action, such as create declaration/definition.",
|
||||
"c_cpp.configuration.refactoring.includeHeader.always.description": "Always include the header file if it is not included explicitly in its source file.",
|
||||
"c_cpp.configuration.refactoring.includeHeader.ifNeeded.description": "Only include the header file if it is not included explicitly in its source file or through implicit inclusion.",
|
||||
|
||||
@@ -1,268 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { CommentArray, CommentObject, parse } from 'comment-json';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { dirname } from 'path';
|
||||
|
||||
import { CancellationToken, Uri } from 'vscode';
|
||||
import { SourceFileConfiguration, SourceFileConfigurationItem, Version, WorkspaceBrowseConfiguration } from 'vscode-cpptools';
|
||||
import { CancellationTokenSource } from 'vscode-languageclient';
|
||||
import { identifyToolset } from '../../ToolsetDetection/detection';
|
||||
import { IntelliSenseConfiguration } from '../../ToolsetDetection/interfaces';
|
||||
import { PriorityQueue } from '../../Utility/Async/priorityQueue';
|
||||
import { returns } from '../../Utility/Async/returns';
|
||||
import { filepath } from '../../Utility/Filesystem/filepath';
|
||||
import { extractArgs } from '../../Utility/Process/commandLine';
|
||||
import { is } from '../../Utility/System/guards';
|
||||
import { getOrAdd } from '../../Utility/System/map';
|
||||
import { elapsed } from '../../Utility/System/performance';
|
||||
import { addNormalizedPath } from '../../Utility/System/set';
|
||||
import { log } from '../../logger';
|
||||
import { DefaultClient } from '../client';
|
||||
import { Configuration } from '../configurations';
|
||||
import { ConfigurationAdapter } from './configurationAdapter';
|
||||
import { ExtendedBrowseInformation, IntellisenseConfigurationAdapter } from './interfaces';
|
||||
|
||||
type CompileCommand = CommentObject & {
|
||||
directory: string;
|
||||
file: string;
|
||||
command?: string;
|
||||
arguments?: string[];
|
||||
output?: string;
|
||||
};
|
||||
|
||||
// create a static cancelled token
|
||||
const cts = new CancellationTokenSource();
|
||||
cts.cancel();
|
||||
const cancelled = cts.token;
|
||||
|
||||
export class CompileCommandsConfigurationAdapter extends ConfigurationAdapter implements IntellisenseConfigurationAdapter {
|
||||
private intellisenseConfigurations = new PriorityQueue<IntelliSenseConfiguration>();
|
||||
|
||||
private sourceFiles: Uri[] = [];
|
||||
|
||||
isReady = true;
|
||||
isValid = true;
|
||||
|
||||
get version() { return Version.latest; }
|
||||
get name() { return "CompileCommandsProvider"; }
|
||||
get extensionId() { return "built-in.compile-commands"; }
|
||||
|
||||
private parsing: Promise<void> | undefined;
|
||||
|
||||
private constructor(client: DefaultClient, private path: string, configuration: Configuration, private token?: CancellationToken) {
|
||||
super(client, configuration);
|
||||
log('CREATING CompileCommandsConfiguration');
|
||||
}
|
||||
|
||||
async getSourceFiles(): Promise<Uri[]> {
|
||||
await this.parsing;
|
||||
return [...this.sourceFiles];
|
||||
}
|
||||
|
||||
async getHeaderFiles(): Promise<Uri[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
// we store a static map of instances that are tied to the filename+timestamp
|
||||
static instances = new Map<string, CompileCommandsConfigurationAdapter>();
|
||||
|
||||
static async getProvider(client: DefaultClient, path: string, configuration: Configuration, token: CancellationToken): Promise<IntellisenseConfigurationAdapter | undefined> {
|
||||
const [jsonfile, stats] = await filepath.stats(path);
|
||||
if (stats) {
|
||||
const key = jsonfile + stats.mtime;
|
||||
const result = getOrAdd(this.instances, key, () => new CompileCommandsConfigurationAdapter(client, jsonfile, configuration, token));
|
||||
|
||||
// delete old instances that have the same path
|
||||
for (const [k, v] of this.instances) {
|
||||
if (v.path === jsonfile && k !== key) {
|
||||
v.token = cancelled;
|
||||
this.instances.delete(k);
|
||||
}
|
||||
}
|
||||
|
||||
// start it updating...
|
||||
void result.update();
|
||||
|
||||
// return the instance
|
||||
return result;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
update() {
|
||||
return this.parsing ?? (this.parsing = this.updateAsync());
|
||||
}
|
||||
|
||||
private async updateAsync() {
|
||||
// reload the file
|
||||
const content = await readFile(this.path, 'utf8');
|
||||
const data = parse(content);
|
||||
if (!is.array(data)) {
|
||||
this.isValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const userIntellisenseConfiguration = this.client.configuration?.CurrentConfiguration?.intellisense;
|
||||
|
||||
console.log(`${elapsed()} =>> Begin parsing compile_commands.json...`);
|
||||
|
||||
this.intellisenseConfigurations.once('item', (key, value) => {
|
||||
// if we don't have a toolset selected in the configuration, let's
|
||||
// steal the one from the first file we find.
|
||||
if (!this.compiler) {
|
||||
this.compiler = value.compilerPath;
|
||||
if (!this.compilerArgs) {
|
||||
this.compilerArgs = value.compilerArgs;
|
||||
}
|
||||
}
|
||||
this.initialized.resolve();
|
||||
});
|
||||
|
||||
this.intellisenseConfigurations.on('item', (key, value) => {
|
||||
// propogate the event
|
||||
this.emit('configuration', key, value);
|
||||
});
|
||||
|
||||
this.intellisenseConfigurations.on('empty', () => {
|
||||
this.emit('done');
|
||||
this.intellisenseConfigurations.removeAllListeners();
|
||||
});
|
||||
|
||||
for (const cmd of data as CommentArray<CompileCommand>) {
|
||||
if (this.token?.isCancellationRequested) {
|
||||
break;
|
||||
}
|
||||
|
||||
// we need to make sure that all files are passed thru Uri because the Uri class can modify things like the
|
||||
// drive letter on Windows (it lowercases it) -- so we normalize the path first.
|
||||
const uri = Uri.file(cmd.file);
|
||||
cmd.file = uri.fsPath;
|
||||
|
||||
const args = is.array(cmd.arguments) ? cmd.arguments : is.string(cmd.command) ? extractArgs(cmd.command) : [];
|
||||
if (!args) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// grab the compiler from the arguments
|
||||
const tool = args.shift();
|
||||
if (!tool) {
|
||||
continue;
|
||||
}
|
||||
// we have a tool and some args,
|
||||
this.sourceFiles.push(uri);
|
||||
|
||||
// add the source directory to the browse path
|
||||
addNormalizedPath(this.browseInfo.browsePaths, dirname(cmd.file));
|
||||
|
||||
// start it processing
|
||||
void this.intellisenseConfigurations.enqueue(cmd.file, async () => {
|
||||
const toolset = await identifyToolset(tool);
|
||||
if (!toolset) {
|
||||
throw new Error("Unable to identify toolset");
|
||||
}
|
||||
|
||||
const isense = await toolset.getIntellisenseConfiguration(args, {baseDirectory: cmd.directory, sourceFile : cmd.file, userIntellisenseConfiguration });
|
||||
|
||||
if ((isense.parserArgument?.length || 0) < 10) {
|
||||
console.log("ouch");
|
||||
}
|
||||
|
||||
// update the browse paths with info from the intellisense
|
||||
this.mergeBrowseInfo(isense);
|
||||
|
||||
return isense;
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`${elapsed()} =>> Completed parsing compile_commands.json...`);
|
||||
}
|
||||
|
||||
async canProvideConfiguration(uri: Uri, token?: CancellationToken | undefined): Promise<boolean> {
|
||||
await this.parsing;
|
||||
|
||||
if (this.token?.isCancellationRequested || token?.isCancellationRequested) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.intellisenseConfigurations.has(uri.fsPath);
|
||||
}
|
||||
|
||||
async provideConfigurations(uris: Uri[], token?: CancellationToken | undefined): Promise<SourceFileConfigurationItem[]> {
|
||||
await this.parsing;
|
||||
|
||||
if (this.token?.isCancellationRequested || token?.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// if they pass in a zero length array, assume they mean 'all that are ready'
|
||||
if (uris.length === 0) {
|
||||
uris = this.intellisenseConfigurations.completedKeys.map(each => Uri.file(each));
|
||||
}
|
||||
|
||||
const result = [] as SourceFileConfigurationItem[];
|
||||
for (const uri of uris) {
|
||||
const intellisense = await this.intellisenseConfigurations.get(uri.fsPath).catch(returns.undefined);
|
||||
if (intellisense) {
|
||||
// trim stuff not needed.
|
||||
//* delete intellisense.path;
|
||||
delete intellisense.macro;
|
||||
result.push({
|
||||
uri,
|
||||
configuration: {
|
||||
includePath: [],
|
||||
defines: [],
|
||||
intellisense,
|
||||
enableNewIntellisense: true
|
||||
} as SourceFileConfiguration
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async canProvideBrowseConfiguration(token?: CancellationToken | undefined): Promise<boolean> {
|
||||
await this.parsing;
|
||||
|
||||
if (this.token?.isCancellationRequested || token?.isCancellationRequested) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async provideBrowseConfiguration(token?: CancellationToken | undefined): Promise<WorkspaceBrowseConfiguration | null> {
|
||||
await this.parsing;
|
||||
|
||||
if (this.token?.isCancellationRequested || token?.isCancellationRequested) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { browsePath: [...this.browseInfo.browsePaths] };
|
||||
}
|
||||
|
||||
async canProvideBrowseConfigurationsPerFolder(token?: CancellationToken | undefined): Promise<boolean> {
|
||||
// todo: support multi-root workspaces
|
||||
return false;
|
||||
}
|
||||
|
||||
async provideFolderBrowseConfiguration(uri: Uri, token?: CancellationToken | undefined): Promise<WorkspaceBrowseConfiguration | null> {
|
||||
// todo: support multi-root workspaces
|
||||
return null;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.token = cancelled;
|
||||
CompileCommandsConfigurationAdapter.instances.delete(this.path);
|
||||
}
|
||||
|
||||
override async getExtendedBrowseInformation(token: CancellationToken): Promise<ExtendedBrowseInformation> {
|
||||
await this.parsing;
|
||||
return super.getExtendedBrowseInformation(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import EventEmitter = require('events');
|
||||
import { CancellationToken, Uri } from 'vscode';
|
||||
import { SourceFileConfigurationItem, Version, WorkspaceBrowseConfiguration } from 'vscode-cpptools';
|
||||
import { identifyToolset } from '../../ToolsetDetection/detection';
|
||||
import { IntelliSenseConfiguration } from '../../ToolsetDetection/interfaces';
|
||||
import { ManualPromise } from '../../Utility/Async/manualPromise';
|
||||
import { is } from '../../Utility/System/guards';
|
||||
import { addNormalizedPath } from '../../Utility/System/set';
|
||||
import { structuredClone } from '../../Utility/System/structuredClone';
|
||||
import { DefaultClient } from '../client';
|
||||
import { Configuration } from '../configurations';
|
||||
import { ExtendedBrowseInformation, IntellisenseConfigurationAdapter } from './interfaces';
|
||||
|
||||
export abstract class ConfigurationAdapter extends EventEmitter implements IntellisenseConfigurationAdapter {
|
||||
|
||||
browseInfo = {
|
||||
browsePaths: new Set<string>(),
|
||||
systemPaths: new Set<string>(),
|
||||
userFrameworks: new Set<string>(),
|
||||
systemFrameworks: new Set<string>()
|
||||
};
|
||||
|
||||
constructor(protected client: DefaultClient, protected configuration: Configuration) {
|
||||
super();
|
||||
}
|
||||
abstract isReady: boolean;
|
||||
abstract isValid: boolean;
|
||||
abstract version: Version;
|
||||
abstract name: string;
|
||||
abstract extensionId: string;
|
||||
abstract canProvideConfiguration(uri: Uri, token?: CancellationToken | undefined): Thenable<boolean>;
|
||||
|
||||
abstract provideConfigurations(uris: Uri[], token?: CancellationToken | undefined): Thenable<SourceFileConfigurationItem[]>;
|
||||
|
||||
abstract canProvideBrowseConfiguration(token?: CancellationToken | undefined): Thenable<boolean>;
|
||||
|
||||
abstract provideBrowseConfiguration(token?: CancellationToken | undefined): Thenable<WorkspaceBrowseConfiguration | null>;
|
||||
|
||||
abstract canProvideBrowseConfigurationsPerFolder(token?: CancellationToken | undefined): Thenable<boolean>;
|
||||
|
||||
abstract provideFolderBrowseConfiguration(uri: Uri, token?: CancellationToken | undefined): Thenable<WorkspaceBrowseConfiguration | null> ;
|
||||
abstract dispose(): void ;
|
||||
abstract getSourceFiles(): Promise<Uri[]> ;
|
||||
abstract getHeaderFiles(): Promise<Uri[]> ;
|
||||
|
||||
#compilerArgs: string[] | undefined;
|
||||
get compilerArgs(): string[] | undefined {
|
||||
return this.#compilerArgs ?? (this.#compilerArgs = this.configuration.compilerArgs);
|
||||
}
|
||||
set compilerArgs(compilerArgs: string[] | undefined) {
|
||||
this.#compilerArgs = compilerArgs;
|
||||
}
|
||||
|
||||
initialized = new ManualPromise<void>();
|
||||
|
||||
#compiler: string | undefined;
|
||||
get compiler(): string | undefined {
|
||||
return this.#compiler ?? (this.#compiler = this.configuration.compiler ?? this.configuration.compilerPath);
|
||||
}
|
||||
set compiler(compiler: string | undefined) {
|
||||
this.#compiler = compiler;
|
||||
}
|
||||
|
||||
async getBaseConfiguration() {
|
||||
await this.initialized;
|
||||
|
||||
// first we have to send the base config
|
||||
const baseConfiguration = {
|
||||
enableNewIntellisense: true,
|
||||
...structuredClone(this.configuration)
|
||||
// we don't want the server to handle any configuration provider or compile commands.
|
||||
} as Configuration;
|
||||
|
||||
delete baseConfiguration.configurationProvider;
|
||||
delete baseConfiguration.compileCommands;
|
||||
delete baseConfiguration.compileCommandsInCppPropertiesJson;
|
||||
|
||||
baseConfiguration.browse = baseConfiguration.browse ?? {};
|
||||
addNormalizedPath(this.browseInfo.browsePaths, baseConfiguration.browse.path);
|
||||
|
||||
await this.probeForBrowseInfo(this.compiler, this.compilerArgs);
|
||||
|
||||
baseConfiguration.browse.path = [...this.browseInfo.browsePaths];
|
||||
|
||||
// if we don't have a toolset, let's see if we can pick one;
|
||||
/*!
|
||||
if (!this.compiler) {
|
||||
log(`No compiler specified for new Intellisense?`);
|
||||
// can we get provided one?
|
||||
|
||||
} else {
|
||||
// the user has specifically set the compiler name/path
|
||||
const toolset = await identifyToolset(this.compiler);
|
||||
if (toolset) {
|
||||
baseConfiguration.intellisense = await toolset.getIntellisenseConfiguration(this.compilerArgs || [], { userIntellisenseConfiguration: baseConfiguration.intellisense});
|
||||
}
|
||||
}
|
||||
*/
|
||||
return baseConfiguration;
|
||||
}
|
||||
|
||||
mergeBrowseInfo(intellisense: IntelliSenseConfiguration) {
|
||||
// add include paths to the browse paths
|
||||
addNormalizedPath(this.browseInfo.browsePaths, intellisense.path?.quoteInclude);
|
||||
addNormalizedPath(this.browseInfo.browsePaths, intellisense.path?.include);
|
||||
addNormalizedPath(this.browseInfo.browsePaths, intellisense.path?.afterInclude);
|
||||
addNormalizedPath(this.browseInfo.browsePaths, intellisense.path?.externalInclude);
|
||||
addNormalizedPath(this.browseInfo.browsePaths, intellisense.path?.environmentInclude);
|
||||
|
||||
// add system include and built-in paths to the system paths
|
||||
addNormalizedPath(this.browseInfo.systemPaths, intellisense.path?.systemInclude);
|
||||
addNormalizedPath(this.browseInfo.systemPaths, intellisense.path?.builtInInclude);
|
||||
|
||||
// add frameworks to the user frameworks.
|
||||
addNormalizedPath(this.browseInfo.userFrameworks, intellisense.path?.framework);
|
||||
}
|
||||
|
||||
async probeForBrowseInfo(compilerPath: string | undefined, compilerArgs: string[] | undefined): Promise<void> {
|
||||
if (compilerPath) {
|
||||
using toolset = await identifyToolset(compilerPath);
|
||||
if (toolset) {
|
||||
// todo: support compilerFragments args too
|
||||
let intellisense = await toolset.getIntellisenseConfiguration(compilerArgs ?? [], { userIntellisenseConfiguration: is.object(this.configuration.intellisense) ? this.configuration.intellisense : undefined});
|
||||
intellisense = toolset.harvestFromConfiguration(this.configuration, intellisense);
|
||||
this.mergeBrowseInfo(intellisense);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override on(event: 'configuration', listener: (key: string, value: IntelliSenseConfiguration) => void): this;
|
||||
override on(event: 'done', listener: () => void): this;
|
||||
override on(eventName: string | symbol, listener: (...args: any[]) => void): this {
|
||||
return super.on(eventName, listener);
|
||||
}
|
||||
|
||||
override once(event: 'configuration', listener: (key: string, value: IntelliSenseConfiguration) => void): this;
|
||||
override once(event: 'done', listener: () => void): this;
|
||||
override once(eventName: string | symbol, listener: (...args: any[]) => void): this {
|
||||
return super.once(eventName, listener);
|
||||
}
|
||||
|
||||
async getExtendedBrowseInformation(_token: CancellationToken): Promise<ExtendedBrowseInformation> {
|
||||
return {
|
||||
browsePath: [...this.browseInfo.browsePaths],
|
||||
systemPath: [...this.browseInfo.systemPaths],
|
||||
userFrameworks: [...this.browseInfo.userFrameworks],
|
||||
systemFrameworks: [...this.browseInfo.systemFrameworks]
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
import { CancellationToken, Uri } from 'vscode';
|
||||
import { IntelliSenseConfiguration } from '../../ToolsetDetection/interfaces';
|
||||
import { Configuration } from '../configurations';
|
||||
import { CustomConfigurationProvider1 } from '../customProviders';
|
||||
import EventEmitter = require('events');
|
||||
|
||||
export interface ExtendedBrowseInformation {
|
||||
browsePath: string[];
|
||||
systemPath: string[];
|
||||
userFrameworks: string[];
|
||||
systemFrameworks: string[];
|
||||
}
|
||||
|
||||
export interface IntellisenseConfigurationAdapter extends EventEmitter, CustomConfigurationProvider1 {
|
||||
readonly initialized: Promise<void>;
|
||||
|
||||
// source files, aka 'translation units'
|
||||
getSourceFiles(): Promise<Uri[]>;
|
||||
|
||||
// header files, aka 'include'd files'
|
||||
getHeaderFiles(): Promise<Uri[]>;
|
||||
|
||||
getExtendedBrowseInformation(token: CancellationToken): Promise<ExtendedBrowseInformation>;
|
||||
getBaseConfiguration(token: CancellationToken): Promise<Configuration>;
|
||||
|
||||
// events
|
||||
on(event: 'configuration', listener: (key: string, value: IntelliSenseConfiguration) => void): this;
|
||||
on(event: 'done', listener: () => void): this;
|
||||
|
||||
once(event: 'configuration', listener: (key: string, value: IntelliSenseConfiguration) => void): this;
|
||||
once(event: 'done', listener: () => void): this;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
import { dirname } from 'path';
|
||||
|
||||
import { CancellationToken, Uri } from 'vscode';
|
||||
import { SourceFileConfigurationItem, WorkspaceBrowseConfiguration } from 'vscode-cpptools';
|
||||
import { getOrAdd } from '../../Utility/System/map';
|
||||
import { addNormalizedPath } from '../../Utility/System/set';
|
||||
import { DefaultClient, InternalWorkspaceBrowseConfiguration } from '../client';
|
||||
import { Configuration } from '../configurations';
|
||||
import { CustomConfigurationProvider1 } from '../customProviders';
|
||||
import { ConfigurationAdapter } from './configurationAdapter';
|
||||
import { ExtendedBrowseInformation, IntellisenseConfigurationAdapter } from './interfaces';
|
||||
|
||||
export class ProviderConfigurationAdapter extends ConfigurationAdapter implements IntellisenseConfigurationAdapter {
|
||||
private constructor(client: DefaultClient, private provider: CustomConfigurationProvider1, configuration: Configuration) {
|
||||
super(client, configuration);
|
||||
}
|
||||
static instances = new Map<CustomConfigurationProvider1, ProviderConfigurationAdapter>();
|
||||
|
||||
static async getProvider(client: DefaultClient, provider: CustomConfigurationProvider1 | undefined, configuration: Configuration): Promise<IntellisenseConfigurationAdapter | undefined> {
|
||||
if (!provider) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getOrAdd(this.instances, provider, () => new ProviderConfigurationAdapter(client, provider, configuration));
|
||||
}
|
||||
|
||||
async getSourceFiles(): Promise<Uri[]> {
|
||||
if ('getSourceFiles' in this.provider) {
|
||||
return (this.provider as any).getSourceFiles();
|
||||
}
|
||||
// if we don't have a provider that has 'getSourceFiles', we have to find the files ourselves.
|
||||
|
||||
return [];
|
||||
}
|
||||
async getHeaderFiles(): Promise<Uri[]> {
|
||||
if ('getHeaderFiles' in this.provider) {
|
||||
return (this.provider as any).getHeaderFiles();
|
||||
}
|
||||
|
||||
// if we don't have a provider that has 'getSourceFiles', we have to find the files ourselves.
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
get isReady() { return this.provider.isReady; }
|
||||
get isValid() { return this.provider.isValid; }
|
||||
get version() { return this.provider.version; }
|
||||
get name() { return this.provider.name; }
|
||||
get extensionId() { return this.provider.extensionId; }
|
||||
|
||||
canProvideConfiguration(uri: Uri, token?: CancellationToken | undefined): Thenable<boolean> {
|
||||
return this.provider.canProvideConfiguration(uri, token);
|
||||
}
|
||||
provideConfigurations(uris: Uri[], token?: CancellationToken | undefined): Thenable<SourceFileConfigurationItem[]> {
|
||||
return this.provider.provideConfigurations(uris, token);
|
||||
}
|
||||
canProvideBrowseConfiguration(token?: CancellationToken | undefined): Thenable<boolean> {
|
||||
return this.provider.canProvideBrowseConfiguration(token);
|
||||
}
|
||||
provideBrowseConfiguration(token?: CancellationToken | undefined): Thenable<WorkspaceBrowseConfiguration | null> {
|
||||
return this.provider.provideBrowseConfiguration(token);
|
||||
}
|
||||
canProvideBrowseConfigurationsPerFolder(token?: CancellationToken | undefined): Thenable<boolean> {
|
||||
return this.provider.canProvideBrowseConfigurationsPerFolder(token);
|
||||
}
|
||||
provideFolderBrowseConfiguration(uri: Uri, token?: CancellationToken | undefined): Thenable<WorkspaceBrowseConfiguration | null> {
|
||||
return this.provider.provideFolderBrowseConfiguration(uri, token);
|
||||
}
|
||||
dispose() {
|
||||
this.provider.dispose();
|
||||
}
|
||||
override async getExtendedBrowseInformation(token: CancellationToken): Promise<ExtendedBrowseInformation> {
|
||||
const browseConfig = await this.provideBrowseConfiguration();
|
||||
|
||||
if (browseConfig) {
|
||||
// got a browse config
|
||||
|
||||
// we expanded the kinds of paths to include the system, user frameworks, etc.
|
||||
// so a very enlightened provider give us all the information we need
|
||||
addNormalizedPath(this.browseInfo.browsePaths, browseConfig.browsePath);
|
||||
addNormalizedPath(this.browseInfo.systemPaths, (browseConfig as InternalWorkspaceBrowseConfiguration).systemPath);
|
||||
addNormalizedPath(this.browseInfo.userFrameworks, (browseConfig as InternalWorkspaceBrowseConfiguration).userFrameworks);
|
||||
addNormalizedPath(this.browseInfo.systemFrameworks, (browseConfig as InternalWorkspaceBrowseConfiguration).systemFrameworks);
|
||||
|
||||
// if we have a compilerPath, we can use that to find the system paths.
|
||||
await this.probeForBrowseInfo(browseConfig.compilerPath, browseConfig.compilerArgs);
|
||||
}
|
||||
|
||||
// if we are not given any browsePaths, we have to find the browse paths ourselves.
|
||||
if (this.browseInfo.browsePaths.size === 0) {
|
||||
// we assume that any folders that source files are in are part of the browse path.
|
||||
const sourceFiles = await this.getSourceFiles();
|
||||
sourceFiles.map(each => addNormalizedPath(this.browseInfo.browsePaths, dirname(each.fsPath)));
|
||||
|
||||
// if we can get a compiler for the source files, we can use that to query for system paths.
|
||||
const configurations = await this.provideConfigurations(sourceFiles, token);
|
||||
|
||||
// todo: I don't like this. we could have hundreds or even thousands of source files.
|
||||
// todo: and add in cancellation support for this too.
|
||||
await Promise.all(configurations.map(config => this.probeForBrowseInfo(config.configuration.compilerPath, config.configuration.compilerArgs)));
|
||||
}
|
||||
|
||||
// we have to find the system/frameworks/etc paths ourselves.
|
||||
|
||||
return super.getExtendedBrowseInformation(token);
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
import { dirname } from "path";
|
||||
import { CancellationToken, Uri } from 'vscode';
|
||||
import { SourceFileConfiguration, SourceFileConfigurationItem, WorkspaceBrowseConfiguration } from "vscode-cpptools";
|
||||
import { identifyToolset } from '../../ToolsetDetection/detection';
|
||||
import { fastFind } from "../../Utility/Filesystem/ripgrep";
|
||||
import { is } from '../../Utility/System/guards';
|
||||
import { getOrAdd } from "../../Utility/System/map";
|
||||
import { sources } from '../../constants';
|
||||
import { DefaultClient } from "../client";
|
||||
import { Configuration } from '../configurations';
|
||||
import { ConfigurationAdapter } from './configurationAdapter';
|
||||
import { ExtendedBrowseInformation, IntellisenseConfigurationAdapter } from './interfaces';
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
export class WorkspaceCofigurationAdapter extends ConfigurationAdapter implements IntellisenseConfigurationAdapter {
|
||||
private sourceFiles!: Uri[];
|
||||
private browsePath!: string[];
|
||||
|
||||
private ready: Promise<void>;
|
||||
|
||||
private constructor(client: DefaultClient, configuration: Configuration) {
|
||||
super(client, configuration);
|
||||
this.ready = this.init();
|
||||
}
|
||||
static instances = new Map<DefaultClient, WorkspaceCofigurationAdapter>();
|
||||
|
||||
static async getProvider(client: DefaultClient, configuration: Configuration): Promise<IntellisenseConfigurationAdapter> {
|
||||
const provider = getOrAdd(this.instances, client, () => new WorkspaceCofigurationAdapter(client, configuration));
|
||||
// ensure that if we've changed the configuration that we update this.
|
||||
provider.configuration = configuration;
|
||||
return provider;
|
||||
}
|
||||
|
||||
async init() {
|
||||
// scan for files in the workspace folder
|
||||
await Promise.all([
|
||||
// get all the source files (and the )
|
||||
fastFind(sources, this.client.RootPath).then(results => {
|
||||
this.sourceFiles = results.map(each => Uri.file(each));
|
||||
this.browsePath = [...new Set(results.map(each => dirname(each)))];
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
async getSourceFiles(): Promise<Uri[]> {
|
||||
// scan for source (things that can be TUs) files in the workspace folder
|
||||
await this.ready;
|
||||
return this.sourceFiles;
|
||||
}
|
||||
|
||||
async getHeaderFiles(): Promise<Uri[]> {
|
||||
// scan for header files in the workspace folder
|
||||
await this.ready;
|
||||
return [];
|
||||
}
|
||||
|
||||
get isReady() { return true; }
|
||||
get isValid() { return true; }
|
||||
get version() { return 7; }
|
||||
get name() { return "WorkspaceConfigurationProvider"; }
|
||||
get extensionId() { return "built-in.workspaace"; }
|
||||
|
||||
async canProvideConfiguration(uri: Uri, token?: CancellationToken | undefined): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
async provideConfigurations(uris: Uri[], token?: CancellationToken | undefined): Promise<SourceFileConfigurationItem[]> {
|
||||
const compiler = this.configuration.compiler || this.configuration.compilerPath;
|
||||
if (compiler) {
|
||||
using toolset = await identifyToolset(compiler);
|
||||
|
||||
if (toolset) {
|
||||
const intellisense = await toolset.getIntellisenseConfiguration(this.configuration.compilerArgs ?? [], { userIntellisenseConfiguration: is.object(this.configuration.intellisense) ? this.configuration.intellisense : undefined});
|
||||
//* cfg.intellisense = toolset.harvestFromConfiguration(cfg, intellisense);
|
||||
return uris.map(uri => ({
|
||||
uri: uri,
|
||||
configuration: { intellisense } as unknown as SourceFileConfiguration
|
||||
} as SourceFileConfigurationItem));
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
async canProvideBrowseConfiguration(token?: CancellationToken | undefined): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
async provideBrowseConfiguration(token?: CancellationToken | undefined): Promise<WorkspaceBrowseConfiguration | null> {
|
||||
return { browsePath: this.browsePath };
|
||||
}
|
||||
|
||||
async canProvideBrowseConfigurationsPerFolder(token?: CancellationToken | undefined): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
async provideFolderBrowseConfiguration(uri: Uri, token?: CancellationToken | undefined): Promise<WorkspaceBrowseConfiguration | null> {
|
||||
return { browsePath: [] };
|
||||
}
|
||||
|
||||
dispose() {
|
||||
}
|
||||
override async getExtendedBrowseInformation(token: CancellationToken): Promise<ExtendedBrowseInformation> {
|
||||
return super.getExtendedBrowseInformation(token);
|
||||
}
|
||||
}
|
||||
@@ -28,17 +28,13 @@ interface CodeActionCommand {
|
||||
edit?: TextEdit;
|
||||
uri?: string;
|
||||
range?: Range;
|
||||
disabledReason?: string;
|
||||
}
|
||||
|
||||
interface GetCodeActionsResult {
|
||||
commands: CodeActionCommand[];
|
||||
}
|
||||
|
||||
export interface CreateDeclDefnCommandArguments {
|
||||
sender: string;
|
||||
range: Range;
|
||||
}
|
||||
|
||||
export const GetCodeActionsRequest: RequestType<GetCodeActionsRequestParams, GetCodeActionsResult, void> =
|
||||
new RequestType<GetCodeActionsRequestParams, GetCodeActionsResult, void>('cpptools/getCodeActions');
|
||||
|
||||
@@ -49,6 +45,8 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
|
||||
}
|
||||
|
||||
private static inlineMacroKind: vscode.CodeActionKind = vscode.CodeActionKind.RefactorInline.append("macro");
|
||||
private static extractToFunctionKind: vscode.CodeActionKind = vscode.CodeActionKind.RefactorExtract.append("function");
|
||||
private static expandSelectionKind: vscode.CodeActionKind = CodeActionProvider.extractToFunctionKind.append("expandSelection");
|
||||
|
||||
public async provideCodeActions(document: vscode.TextDocument, range: vscode.Range | vscode.Selection,
|
||||
context: vscode.CodeActionContext, token: vscode.CancellationToken): Promise<(vscode.Command | vscode.CodeAction)[]> {
|
||||
@@ -90,6 +88,7 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
|
||||
this.client.configuration.CurrentConfiguration?.compilerPathInCppPropertiesJson !== undefined ||
|
||||
!!this.client.configuration.CurrentConfiguration?.compileCommandsInCppPropertiesJson ||
|
||||
!!this.client.configuration.CurrentConfiguration?.configurationProviderInCppPropertiesJson;
|
||||
const hasExperimentalFeatures: boolean = new CppSettings().experimentalFeatures ?? false;
|
||||
|
||||
// Convert to vscode.CodeAction array
|
||||
let hasInlineMacro: boolean = false;
|
||||
@@ -196,12 +195,8 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
|
||||
return;
|
||||
} else if ((command.command === 'C_Cpp.CreateDeclarationOrDefinition' || command.command === 'C_Cpp.CopyDeclarationOrDefinition')
|
||||
&& (command.arguments ?? []).length === 0 && command.range !== undefined) {
|
||||
const args: CreateDeclDefnCommandArguments = {
|
||||
sender: 'codeAction',
|
||||
range: command.range
|
||||
};
|
||||
command.arguments = [];
|
||||
command.arguments.push(args);
|
||||
command.arguments.push({ sender: 'codeAction', range: command.range });
|
||||
} else if (command.command === "C_Cpp.SelectIntelliSenseConfiguration") {
|
||||
command.arguments = ['codeAction'];
|
||||
hasSelectIntelliSenseConfiguration = true;
|
||||
@@ -214,6 +209,20 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (command.command === "C_Cpp.ExtractToFunction" ||
|
||||
command.command === "C_Cpp.ExtractToMemberFunction") {
|
||||
if (!hasExperimentalFeatures) {
|
||||
return;
|
||||
}
|
||||
codeActionKind = CodeActionProvider.extractToFunctionKind;
|
||||
} else if (command.command === "C_Cpp.ExtractToFreeFunction") {
|
||||
// TODO: https://github.com/microsoft/vscode-cpptools/issues/11473 needs to be fixed.
|
||||
return;
|
||||
} else if (command.command === "C_Cpp.ExpandSelection") {
|
||||
if (!hasExperimentalFeatures) {
|
||||
return;
|
||||
}
|
||||
codeActionKind = CodeActionProvider.expandSelectionKind;
|
||||
}
|
||||
const vscodeCodeAction: vscode.CodeAction = {
|
||||
title: title,
|
||||
@@ -223,7 +232,8 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
|
||||
arguments: command.arguments
|
||||
},
|
||||
edit: wsEdit,
|
||||
kind: codeActionKind
|
||||
kind: codeActionKind,
|
||||
disabled: command.disabledReason ? { reason: command.disabledReason } : undefined
|
||||
};
|
||||
resultCodeActions.push(vscodeCodeAction);
|
||||
};
|
||||
@@ -238,11 +248,11 @@ export class CodeActionProvider implements vscode.CodeActionProvider {
|
||||
if (!editor) {
|
||||
return false;
|
||||
}
|
||||
const result: vscode.Hover[] = <vscode.Hover[]>(await vscode.commands.executeCommand('vscode.executeHoverProvider', document.uri, range.start));
|
||||
const result: vscode.Hover[] = (await vscode.commands.executeCommand('vscode.executeHoverProvider', document.uri, range.start)) as vscode.Hover[];
|
||||
if (result.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const hoverResult: vscode.MarkdownString = <vscode.MarkdownString>result[0].contents[0];
|
||||
const hoverResult: vscode.MarkdownString = result[0].contents[0] as vscode.MarkdownString;
|
||||
if (!hoverResult.value.includes(localize("expands.to", "Expands to:"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ export class DocumentRangeFormattingEditProvider implements vscode.DocumentRange
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public async provideDocumentRangeFormattingEdits(document: vscode.TextDocument, range: vscode.Range, options: vscode.FormattingOptions, token: vscode.CancellationToken): Promise<vscode.TextEdit[]> {
|
||||
public async provideDocumentRangeFormattingEdits(document: vscode.TextDocument, range: vscode.Range,
|
||||
options: vscode.FormattingOptions, token: vscode.CancellationToken): Promise<vscode.TextEdit[]> {
|
||||
const settings: CppSettings = new CppSettings(vscode.workspace.getWorkspaceFolder(document.uri)?.uri);
|
||||
if (settings.formattingEngine === "disabled") {
|
||||
return [];
|
||||
@@ -58,4 +59,12 @@ export class DocumentRangeFormattingEditProvider implements vscode.DocumentRange
|
||||
return configCallBack(editorConfigSettings);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This is needed for correct Extract to function formatting.
|
||||
/*
|
||||
public async provideDocumentRangesFormattingEdits(_document: vscode.TextDocument, _ranges: vscode.Range[],
|
||||
_options: vscode.FormattingOptions, _token: vscode.CancellationToken): Promise<vscode.TextEdit[]> {
|
||||
return [];
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -56,11 +56,12 @@ export class DocumentSymbolProvider implements vscode.DocumentSymbolProvider {
|
||||
public async provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.SymbolInformation[] | vscode.DocumentSymbol[]> {
|
||||
const client: Client = clients.getClientFor(document.uri);
|
||||
if (client instanceof DefaultClient) {
|
||||
const defaultClient: DefaultClient = <DefaultClient>client;
|
||||
await client.enqueue(() => processDelayedDidOpen(document));
|
||||
const params: GetDocumentSymbolRequestParams = {
|
||||
uri: document.uri.toString()
|
||||
};
|
||||
const response: GetDocumentSymbolResult = await client.languageClient.sendRequest(GetDocumentSymbolRequest, params, token);
|
||||
const response: GetDocumentSymbolResult = await defaultClient.languageClient.sendRequest(GetDocumentSymbolRequest, params, token);
|
||||
if (token.isCancellationRequested || response.symbols === undefined) {
|
||||
throw new vscode.CancellationError();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,16 @@ export class RenameProvider implements vscode.RenameProvider {
|
||||
}
|
||||
|
||||
public async provideRenameEdits(document: vscode.TextDocument, position: vscode.Position, newName: string, _token: vscode.CancellationToken): Promise<vscode.WorkspaceEdit | undefined> {
|
||||
// Bypass the normal rename processing during Extract to function,
|
||||
// since we already know the locations of the required edits.
|
||||
if (this.client.renameDataForExtractToFunction.length > 0) {
|
||||
const workspaceEditResult: vscode.WorkspaceEdit = new vscode.WorkspaceEdit();
|
||||
for (const renameData of this.client.renameDataForExtractToFunction) {
|
||||
workspaceEditResult.replace(renameData.uri, renameData.range, newName);
|
||||
}
|
||||
this.client.renameDataForExtractToFunction = [];
|
||||
return workspaceEditResult;
|
||||
}
|
||||
await this.client.ready;
|
||||
workspaceReferences.cancelCurrentReferenceRequest(CancellationSender.NewRequest);
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
@@ -28,31 +27,22 @@ import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import { SourceFileConfiguration, SourceFileConfigurationItem, Version, WorkspaceBrowseConfiguration } from 'vscode-cpptools';
|
||||
import { IntelliSenseStatus, Status } from 'vscode-cpptools/out/testApi';
|
||||
import { CancellationTokenSource, CloseAction, DidOpenTextDocumentParams, ErrorAction, LanguageClientOptions, NotificationType, Position, Range, RequestType, TextDocumentIdentifier } from 'vscode-languageclient';
|
||||
import { CloseAction, DidOpenTextDocumentParams, ErrorAction, LanguageClientOptions, NotificationType, Position, Range, RequestType, TextDocumentIdentifier } from 'vscode-languageclient';
|
||||
import { LanguageClient, ServerOptions } from 'vscode-languageclient/node';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DebugConfigurationProvider } from '../Debugger/configurationProvider';
|
||||
import { CustomConfigurationProvider1, getCustomConfigProviders, isSameProviderExtensionId } from '../LanguageServer/customProviders';
|
||||
import { identifyToolset } from '../ToolsetDetection/detection';
|
||||
import { ManualPromise } from '../Utility/Async/manualPromise';
|
||||
import { ManualSignal } from '../Utility/Async/manualSignal';
|
||||
import { logAndReturn } from '../Utility/Async/returns';
|
||||
import { LastKnownState } from '../Utility/System/equality';
|
||||
|
||||
import { is } from '../Utility/System/guards';
|
||||
import { elapsed } from '../Utility/System/performance';
|
||||
import { structuredClone } from '../Utility/System/structuredClone';
|
||||
import * as util from '../common';
|
||||
import { isWindows } from '../constants';
|
||||
import { DebugProtocolParams, Logger, ShowWarningParams, getDiagnosticsChannel, getOutputChannelLogger, log, logDebugProtocol, logLocalized, showWarning } from '../logger';
|
||||
import { DebugProtocolParams, Logger, ShowWarningParams, getDiagnosticsChannel, getOutputChannelLogger, logDebugProtocol, logLocalized, showWarning } from '../logger';
|
||||
import { localizedStringCount, lookupString } from '../nativeStrings';
|
||||
import { SessionState } from '../sessionState';
|
||||
import * as telemetry from '../telemetry';
|
||||
import { TestHook, getTestHook } from '../testHook';
|
||||
import { CompileCommandsConfigurationAdapter } from './Intellisense/compileCommandsConfigurationAdapter';
|
||||
import { ExtendedBrowseInformation, IntellisenseConfigurationAdapter } from './Intellisense/interfaces';
|
||||
import { ProviderConfigurationAdapter } from './Intellisense/providerConfigurationAdapter';
|
||||
import { WorkspaceCofigurationAdapter } from './Intellisense/workspaceConfigurationAdapter';
|
||||
import {
|
||||
CodeAnalysisDiagnosticIdentifiersAndUri,
|
||||
RegisterCodeAnalysisNotifications,
|
||||
@@ -74,6 +64,9 @@ import { ConfigurationType, LanguageStatusUI, getUI } from './ui';
|
||||
import { handleChangedFromCppToC, makeVscodeLocation, makeVscodeRange } from './utils';
|
||||
import minimatch = require("minimatch");
|
||||
|
||||
function deepCopy(obj: any) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
|
||||
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
|
||||
|
||||
@@ -201,6 +194,21 @@ interface WorkspaceFolderParams {
|
||||
workspaceFolderUri?: string;
|
||||
}
|
||||
|
||||
interface SelectionParams {
|
||||
uri: string;
|
||||
range: Range;
|
||||
}
|
||||
|
||||
export interface VsCodeUriAndRange {
|
||||
uri: vscode.Uri;
|
||||
range: vscode.Range;
|
||||
}
|
||||
|
||||
interface WorkspaceEditResult {
|
||||
workspaceEdits: WorkspaceEdit[];
|
||||
errorText?: string;
|
||||
}
|
||||
|
||||
interface TelemetryPayload {
|
||||
event: string;
|
||||
properties?: Record<string, string>;
|
||||
@@ -253,11 +261,8 @@ interface InternalSourceFileConfiguration extends SourceFileConfiguration {
|
||||
compilerArgsLegacy?: string[];
|
||||
}
|
||||
|
||||
export interface InternalWorkspaceBrowseConfiguration extends WorkspaceBrowseConfiguration {
|
||||
interface InternalWorkspaceBrowseConfiguration extends WorkspaceBrowseConfiguration {
|
||||
compilerArgsLegacy?: string[];
|
||||
systemPath?: string[];
|
||||
userFrameworks?: string[];
|
||||
systemFrameworks?: string[];
|
||||
}
|
||||
|
||||
// Need to convert vscode.Uri to a string before sending it to the language server.
|
||||
@@ -327,16 +332,16 @@ interface PublishRefactorDiagnosticsParams {
|
||||
diagnostics: RefactorDiagnostic[];
|
||||
}
|
||||
|
||||
export interface CreateDeclarationOrDefinitionParams {
|
||||
uri: string;
|
||||
range: Range;
|
||||
export interface CreateDeclarationOrDefinitionParams extends SelectionParams {
|
||||
copyToClipboard: boolean;
|
||||
}
|
||||
|
||||
export interface CreateDeclarationOrDefinitionResult {
|
||||
edit: WorkspaceEdit;
|
||||
export interface CreateDeclarationOrDefinitionResult extends WorkspaceEditResult {
|
||||
clipboardText?: string;
|
||||
errorText?: string;
|
||||
}
|
||||
|
||||
export interface ExtractToFunctionParams extends SelectionParams {
|
||||
extractAsGlobal: boolean;
|
||||
}
|
||||
|
||||
interface ShowMessageWindowParams {
|
||||
@@ -381,9 +386,7 @@ export interface LocalizeSymbolInformation {
|
||||
suffix: LocalizeStringParams;
|
||||
}
|
||||
|
||||
export interface FormatParams {
|
||||
uri: string;
|
||||
range: Range;
|
||||
export interface FormatParams extends SelectionParams {
|
||||
character: string;
|
||||
insertSpaces: boolean;
|
||||
tabSize: number;
|
||||
@@ -576,6 +579,7 @@ export const FormatDocumentRequest: RequestType<FormatParams, FormatResult, void
|
||||
export const FormatRangeRequest: RequestType<FormatParams, FormatResult, void> = new RequestType<FormatParams, FormatResult, void>('cpptools/formatRange');
|
||||
export const FormatOnTypeRequest: RequestType<FormatParams, FormatResult, void> = new RequestType<FormatParams, FormatResult, void>('cpptools/formatOnType');
|
||||
const CreateDeclarationOrDefinitionRequest: RequestType<CreateDeclarationOrDefinitionParams, CreateDeclarationOrDefinitionResult, void> = new RequestType<CreateDeclarationOrDefinitionParams, CreateDeclarationOrDefinitionResult, void>('cpptools/createDeclDef');
|
||||
const ExtractToFunctionRequest: RequestType<ExtractToFunctionParams, WorkspaceEditResult, void> = new RequestType<ExtractToFunctionParams, WorkspaceEditResult, void>('cpptools/extractToFunction');
|
||||
const GoToDirectiveInGroupRequest: RequestType<GoToDirectiveInGroupParams, Position | undefined, void> = new RequestType<GoToDirectiveInGroupParams, Position | undefined, void>('cpptools/goToDirectiveInGroup');
|
||||
const GenerateDoxygenCommentRequest: RequestType<GenerateDoxygenCommentParams, GenerateDoxygenCommentResult | undefined, void> = new RequestType<GenerateDoxygenCommentParams, GenerateDoxygenCommentResult, void>('cpptools/generateDoxygenComment');
|
||||
const ChangeCppPropertiesRequest: RequestType<CppPropertiesParams, void, void> = new RequestType<CppPropertiesParams, void, void>('cpptools/didChangeCppProperties');
|
||||
@@ -748,7 +752,7 @@ export interface Client {
|
||||
onRegisterCustomConfigurationProvider(provider: CustomConfigurationProvider1): Thenable<void>;
|
||||
updateCustomConfigurations(requestingProvider?: CustomConfigurationProvider1): Thenable<void>;
|
||||
updateCustomBrowseConfiguration(requestingProvider?: CustomConfigurationProvider1): Thenable<void>;
|
||||
provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean, provider?: CustomConfigurationProvider1): Promise<void>;
|
||||
provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean): Promise<void>;
|
||||
logDiagnostics(): Promise<void>;
|
||||
rescanFolder(): Promise<void>;
|
||||
toggleReferenceResultsView(): void;
|
||||
@@ -794,6 +798,7 @@ export interface Client {
|
||||
handleFixCodeAnalysisProblems(workspaceEdit: vscode.WorkspaceEdit, refreshSquigglesOnSave: boolean, identifiersAndUris: CodeAnalysisDiagnosticIdentifiersAndUri[]): Promise<void>;
|
||||
handleDisableAllTypeCodeAnalysisProblems(code: string, identifiersAndUris: CodeAnalysisDiagnosticIdentifiersAndUri[]): Promise<void>;
|
||||
handleCreateDeclarationOrDefinition(isCopyToClipboard: boolean, codeActionRange?: Range): Promise<void>;
|
||||
handleExtractToFunction(extractAsGlobal: boolean): Promise<void>;
|
||||
onInterval(): void;
|
||||
dispose(): void;
|
||||
addFileAssociations(fileAssociations: string, languageId: string): void;
|
||||
@@ -935,12 +940,13 @@ export class DefaultClient implements Client {
|
||||
public static updateClientConfigurations(): void {
|
||||
clients.forEach(client => {
|
||||
if (client instanceof DefaultClient) {
|
||||
const defaultClient: DefaultClient = client as DefaultClient;
|
||||
if (!client.isInitialized() || !compilerDefaults) {
|
||||
// This can randomly get hit when adding/removing workspace folders.
|
||||
return;
|
||||
}
|
||||
client.configuration.CompilerDefaults = compilerDefaults;
|
||||
client.configuration.handleConfigurationChange();
|
||||
defaultClient.configuration.CompilerDefaults = compilerDefaults;
|
||||
defaultClient.configuration.handleConfigurationChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1241,8 +1247,8 @@ export class DefaultClient implements Client {
|
||||
const workspaceFolder: vscode.WorkspaceFolder | undefined = this.rootFolder;
|
||||
this.innerConfiguration = new configs.CppProperties(this, rootUri, workspaceFolder);
|
||||
this.innerConfiguration.ConfigurationsChanged((e) => this.onConfigurationsChanged(e));
|
||||
this.innerConfiguration.CompileCommandsChanged((e) => this.onCompileCommandsChanged(e));
|
||||
this.innerConfiguration.SelectionChanged((e) => this.onSelectedConfigurationChanged(e));
|
||||
this.innerConfiguration.CompileCommandsChanged((e) => this.onCompileCommandsChanged(e));
|
||||
this.disposables.push(this.innerConfiguration);
|
||||
|
||||
this.innerLanguageClient = languageClient;
|
||||
@@ -1297,16 +1303,10 @@ export class DefaultClient implements Client {
|
||||
}, 15000);
|
||||
}
|
||||
});
|
||||
|
||||
// if we're in newIntellisenseMode, we don't want to ask the service for the compiler
|
||||
if (!this.isNewIntellisense) {
|
||||
log("init: calling requestCompiler for legacy mode");
|
||||
// The configurations will not be sent to the language server until the default include paths and frameworks have been set.
|
||||
// The event handlers must be set before this happens.
|
||||
compilerDefaults = await this.requestCompiler();
|
||||
}
|
||||
// The configurations will not be sent to the language server until the default include paths and frameworks have been set.
|
||||
// The event handlers must be set before this happens.
|
||||
compilerDefaults = await this.requestCompiler();
|
||||
DefaultClient.updateClientConfigurations();
|
||||
|
||||
clients.forEach(client => {
|
||||
if (client instanceof DefaultClient) {
|
||||
client.configStateReceived.compilers = true;
|
||||
@@ -1695,10 +1695,7 @@ export class DefaultClient implements Client {
|
||||
}
|
||||
}
|
||||
|
||||
public async onDidOpenTextDocument(document: vscode.TextDocument) {
|
||||
if (this.isNewIntellisense) {
|
||||
await this.updatingNewIntellisense;
|
||||
}
|
||||
public onDidOpenTextDocument(document: vscode.TextDocument): void {
|
||||
if (document.uri.scheme === "file") {
|
||||
const uri: string = document.uri.toString();
|
||||
openFileVersions.set(uri, document.version);
|
||||
@@ -1775,8 +1772,6 @@ export class DefaultClient implements Client {
|
||||
}
|
||||
|
||||
public async updateCustomConfigurations(requestingProvider?: CustomConfigurationProvider1): Promise<void> {
|
||||
this.ensureNotNewIntellisense();
|
||||
log("updateCustomConfigurations: Legacy Mode");
|
||||
await this.ready;
|
||||
|
||||
if (!this.configurationProvider) {
|
||||
@@ -1805,14 +1800,10 @@ export class DefaultClient implements Client {
|
||||
public async updateCustomBrowseConfiguration(requestingProvider?: CustomConfigurationProvider1): Promise<void> {
|
||||
await this.ready;
|
||||
|
||||
this.ensureNotNewIntellisense();
|
||||
|
||||
console.log("updateCustomBrowseConfiguration: Legacy Mode");
|
||||
|
||||
if (!this.configurationProvider) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("updateCustomBrowseConfiguration");
|
||||
const currentProvider: CustomConfigurationProvider1 | undefined = getCustomConfigProviders().get(this.configurationProvider);
|
||||
if (!currentProvider || !currentProvider.isReady || (requestingProvider && requestingProvider.extensionId !== currentProvider.extensionId)) {
|
||||
return;
|
||||
@@ -1925,34 +1916,23 @@ export class DefaultClient implements Client {
|
||||
return this.languageClient.sendNotification(RescanFolderNotification);
|
||||
}
|
||||
|
||||
public async provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean, provider?: CustomConfigurationProvider1): Promise<void> {
|
||||
if (this.isNewIntellisense) {
|
||||
await this.sendNewIntellisenseConfigurationForFile(docUri);
|
||||
void this.languageClient.sendNotification(FinishedRequestCustomConfig, { uri: requestFile });
|
||||
return;
|
||||
}
|
||||
// in newIntellisenseMode, this should not be getting called (the server shouldn't ask, and we're handling the files ourselves elsewhere.)
|
||||
this.ensureNotNewIntellisense();
|
||||
log("provideCustomConfiguration: Legacy Mode");
|
||||
|
||||
const onFinished = () => {
|
||||
public async provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean): Promise<void> {
|
||||
const onFinished: () => void = () => {
|
||||
if (requestFile) {
|
||||
void this.languageClient.sendNotification(FinishedRequestCustomConfig, { uri: requestFile });
|
||||
}
|
||||
};
|
||||
if (!provider) {
|
||||
const providerId: string | undefined = this.configurationProvider;
|
||||
if (!providerId) {
|
||||
onFinished();
|
||||
return;
|
||||
}
|
||||
provider = getCustomConfigProviders().get(providerId);
|
||||
telemetry.logLanguageServerEvent('provideCustomConfiguration', { providerId });
|
||||
const providerId: string | undefined = this.configurationProvider;
|
||||
if (!providerId) {
|
||||
onFinished();
|
||||
return;
|
||||
}
|
||||
const provider: CustomConfigurationProvider1 | undefined = getCustomConfigProviders().get(providerId);
|
||||
if (!provider || !provider.isReady) {
|
||||
onFinished();
|
||||
return;
|
||||
}
|
||||
telemetry.logLanguageServerEvent('provideCustomConfiguration', { providerId });
|
||||
void this.provideCustomConfigurationAsync(docUri, requestFile, replaceExisting, onFinished, provider);
|
||||
}
|
||||
|
||||
@@ -2003,45 +1983,38 @@ export class DefaultClient implements Client {
|
||||
|
||||
if (configs && configs.length > 0 && configs[0]) {
|
||||
const fileConfiguration: configs.Configuration | undefined = this.configuration.CurrentConfiguration;
|
||||
if (fileConfiguration?.mergeConfigurations) {
|
||||
configs.forEach(config => {
|
||||
if (fileConfiguration.includePath) {
|
||||
fileConfiguration.includePath.forEach(p => {
|
||||
if (!config.configuration.includePath.includes(p)) {
|
||||
config.configuration.includePath.push(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// do we have an actual configuration?
|
||||
if (fileConfiguration) {
|
||||
if (fileConfiguration.defines) {
|
||||
fileConfiguration.defines.forEach(d => {
|
||||
if (!config.configuration.defines.includes(d)) {
|
||||
config.configuration.defines.push(d);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// if the user has additional (legacy) configuration they want merged, do it here.
|
||||
if (fileConfiguration.mergeConfigurations) {
|
||||
configs.forEach(config => {
|
||||
if (fileConfiguration.includePath) {
|
||||
fileConfiguration.includePath.forEach(p => {
|
||||
if (!config.configuration.includePath.includes(p)) {
|
||||
config.configuration.includePath.push(p);
|
||||
if (!config.configuration.forcedInclude) {
|
||||
config.configuration.forcedInclude = [];
|
||||
}
|
||||
|
||||
if (fileConfiguration.forcedInclude) {
|
||||
fileConfiguration.forcedInclude.forEach(i => {
|
||||
if (config.configuration.forcedInclude) {
|
||||
if (!config.configuration.forcedInclude.includes(i)) {
|
||||
config.configuration.forcedInclude.push(i);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (fileConfiguration.defines) {
|
||||
fileConfiguration.defines.forEach(d => {
|
||||
if (!config.configuration.defines.includes(d)) {
|
||||
config.configuration.defines.push(d);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!config.configuration.forcedInclude) {
|
||||
config.configuration.forcedInclude = [];
|
||||
}
|
||||
|
||||
if (fileConfiguration.forcedInclude) {
|
||||
fileConfiguration.forcedInclude.forEach(i => {
|
||||
if (config.configuration.forcedInclude) {
|
||||
if (!config.configuration.forcedInclude.includes(i)) {
|
||||
config.configuration.forcedInclude.push(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return configs as SourceFileConfigurationItem[];
|
||||
}
|
||||
@@ -2049,11 +2022,9 @@ export class DefaultClient implements Client {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const configs: SourceFileConfigurationItem[] | null | undefined = await this.callTaskWithTimeout(provideConfigurationAsync, configProviderTimeout, tokenSource);
|
||||
|
||||
if (configs?.length) {
|
||||
if (configs && configs.length > 0) {
|
||||
this.sendCustomConfigurations(configs, provider.version);
|
||||
}
|
||||
onFinished();
|
||||
@@ -2291,7 +2262,8 @@ export class DefaultClient implements Client {
|
||||
this.languageClient.onNotification(RequestCustomConfig, (requestFile: string) => {
|
||||
const client: Client = clients.getClientFor(vscode.Uri.file(requestFile));
|
||||
if (client instanceof DefaultClient) {
|
||||
void client.handleRequestCustomConfig(requestFile);
|
||||
const defaultClient: DefaultClient = client as DefaultClient;
|
||||
void defaultClient.handleRequestCustomConfig(requestFile);
|
||||
}
|
||||
});
|
||||
this.languageClient.onNotification(PublishIntelliSenseDiagnosticsNotification, publishIntelliSenseDiagnostics);
|
||||
@@ -2802,231 +2774,10 @@ export class DefaultClient implements Client {
|
||||
|
||||
private doneInitialCustomBrowseConfigurationCheck: boolean = false;
|
||||
|
||||
private async patchConfigurationForNewIntellisense(cfg: configs.Configuration | (util.Mutable<SourceFileConfiguration> & configs.NewIntelliSense), compiler?: string): Promise<void> {
|
||||
// currently, the new IntelliSense requires a compiler for the configuration
|
||||
// we're not going to guess it here.
|
||||
// so either they sent us one, or we look in the configuration for 'compiler' (best), or fallback to 'compilerPath' (ok)
|
||||
compiler = compiler || cfg.compiler || cfg.compilerPath;
|
||||
|
||||
if (cfg.enableNewIntellisense) {
|
||||
(cfg as any).compileCommands = undefined;
|
||||
if (compiler) {
|
||||
try {
|
||||
using toolset = await identifyToolset(compiler);
|
||||
|
||||
if (toolset) {
|
||||
const intellisense = await toolset.getIntellisenseConfiguration(cfg.compilerArgs ?? [], { userIntellisenseConfiguration: is.object(cfg.intellisense) ? cfg.intellisense : undefined});
|
||||
cfg.intellisense = toolset.harvestFromConfiguration(cfg, intellisense);
|
||||
}
|
||||
log(JSON.stringify(cfg, null, 2));
|
||||
return;
|
||||
} catch (e) {
|
||||
log(`Unable to identify toolset from compilerPath: ${cfg.compiler} - ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// not using the new IntelliSense
|
||||
// make sure that the configuration is not marked as newIntellisense
|
||||
cfg.enableNewIntellisense = false;
|
||||
cfg.compiler = undefined;
|
||||
cfg.intellisense = undefined;
|
||||
}
|
||||
|
||||
// we need to store the last relevant version of these we used so we can tell if they've changed
|
||||
private lastKnownState = new class extends LastKnownState {
|
||||
browseInfo: ExtendedBrowseInformation | null = null;
|
||||
configuration = {} as configs.Configuration;
|
||||
sourceFiles = [] as string[];
|
||||
headerFiles = [] as string[];
|
||||
}();
|
||||
|
||||
private async sendBrowsePath(configuration: configs.Configuration) {
|
||||
const provider = await this.getAdapter(configuration);
|
||||
|
||||
const cancelToken = this.updateIntellisenseCancelToken;
|
||||
if (cancelToken.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
const browseInfo = await provider.getExtendedBrowseInformation(cancelToken);
|
||||
if (this.lastKnownState.unchanged('browseInfo', browseInfo)) {
|
||||
return;
|
||||
}
|
||||
log('Sending Browse Path Information');
|
||||
// send the custom browse config to the server
|
||||
return this.languageClient.sendNotification(CustomBrowseConfigurationNotification, {
|
||||
workspaceFolderUri: this.RootUri?.toString(),
|
||||
browseConfiguration: browseInfo // we only want to pass in the folders - which ensures the service doesn't 'think' about anything
|
||||
}).catch(logAndReturn.undefined);
|
||||
}
|
||||
|
||||
public get isNewIntellisense() {
|
||||
return !!this.configuration.CurrentConfiguration?.enableNewIntellisense;
|
||||
}
|
||||
|
||||
private ensureNotNewIntellisense() {
|
||||
if (this.isNewIntellisense) {
|
||||
// eslint-disable-next-line no-debugger
|
||||
debugger;
|
||||
throw new Error("This method should not be called when using the new IntelliSense.");
|
||||
}
|
||||
}
|
||||
|
||||
async sendNewIntellisenseConfigurationForFile(fileUri: vscode.Uri) {
|
||||
await this.updatingNewIntellisense;
|
||||
// this is kind of like provideCustomConfiguration
|
||||
// except that we're going to generate the configuration for the file
|
||||
// and send it (if it hasn't changed from whence last it was sent)
|
||||
|
||||
// ask the provider for the configuration
|
||||
const base = this.configuration.CurrentConfiguration;
|
||||
if (!base) {
|
||||
// this should not happen.
|
||||
// eslint-disable-next-line no-debugger
|
||||
debugger;
|
||||
log("ERROR: ============== No base configuration for new intellisense ====================");
|
||||
return;
|
||||
}
|
||||
const adapter = await this.getAdapter(base);
|
||||
|
||||
if (adapter) {
|
||||
await adapter.initialized;
|
||||
return this.sendFileConfigurations(adapter, [fileUri]);
|
||||
}
|
||||
}
|
||||
|
||||
async getAdapter(configuration: configs.Configuration): Promise<IntellisenseConfigurationAdapter> {
|
||||
let adapter: IntellisenseConfigurationAdapter | undefined;
|
||||
if (configuration.compileCommands) {
|
||||
// if they are using compile commands, use the provider for that.
|
||||
adapter = await CompileCommandsConfigurationAdapter.getProvider(this, configuration.compileCommands, configuration, this.updateIntellisenseCancelToken);
|
||||
}
|
||||
|
||||
if (!adapter && configuration.configurationProvider) {
|
||||
// can we get a provider from the configuration provider?
|
||||
adapter = await ProviderConfigurationAdapter.getProvider(this, getCustomConfigProviders().get(configuration.configurationProvider), configuration);
|
||||
}
|
||||
|
||||
if (!adapter) {
|
||||
// no provider, use the workspace
|
||||
adapter = await WorkspaceCofigurationAdapter.getProvider(this, configuration);
|
||||
}
|
||||
return adapter;
|
||||
}
|
||||
|
||||
#updateIntellisenseCancelTokenSource: CancellationTokenSource | undefined;
|
||||
|
||||
private get updateIntellisenseCancelToken() {
|
||||
return (this.#updateIntellisenseCancelTokenSource ?? (this.#updateIntellisenseCancelTokenSource = new CancellationTokenSource())).token;
|
||||
}
|
||||
|
||||
private cancelUpdateIntellisense() {
|
||||
if (this.#updateIntellisenseCancelTokenSource) {
|
||||
this.#updateIntellisenseCancelTokenSource.cancel();
|
||||
this.#updateIntellisenseCancelTokenSource = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private filterUnchanged(configurationItems: SourceFileConfigurationItem[], filter = true) {
|
||||
return filter ?
|
||||
configurationItems.
|
||||
map(each => ({ uri: each.uri.toString(), configuration: each.configuration })).
|
||||
filter(({uri, configuration}) => this.lastKnownState.changed(uri as any, configuration)) :
|
||||
configurationItems.
|
||||
map(each => ({ uri: each.uri.toString(), configuration: each.configuration }));
|
||||
}
|
||||
|
||||
private async sendFileConfigurations(adapter: IntellisenseConfigurationAdapter, uris: vscode.Uri[] = []) {
|
||||
const configurations = await adapter.provideConfigurations(uris);
|
||||
if (uris.length && configurations.length !== uris.length) {
|
||||
// we didn't get a configuration for everything requested.
|
||||
for (const each of uris) {
|
||||
if (!configurations.find(c => c.uri.toString() === each.toString())) {
|
||||
log(`Unable to provide configuration for ${each.fsPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we're only going to send one configuration (ie, because we're opening a document) - don't filter out previously sent configurations
|
||||
// because if a configuration was sent before it could be used, the server will have forgotten it.
|
||||
const configurationItems = this.filterUnchanged(configurations, uris.length !== 1);
|
||||
if (configurationItems.length > 0) {
|
||||
log(`Sending file configuration for ${configurationItems.length} files: ${configurationItems[0].uri.toString()}...`);
|
||||
return this.languageClient.sendNotification(CustomConfigurationNotification, { configurationItems, workspaceFolderUri: this.RootUri?.toString() }).catch(logAndReturn.undefined);
|
||||
}
|
||||
}
|
||||
|
||||
/** This sends the base configuration to the language client */
|
||||
private async sendBaseConfiguration(adapter: IntellisenseConfigurationAdapter) {
|
||||
return this.languageClient.sendRequest(ChangeCppPropertiesRequest, {
|
||||
configurations: [await adapter.getBaseConfiguration(this.updateIntellisenseCancelToken)],
|
||||
currentConfiguration: 0,
|
||||
workspaceFolderUri: this.RootUri?.toString(),
|
||||
isReady: true
|
||||
});
|
||||
}
|
||||
|
||||
// temporary - to see if we can stop it from doing things before we're ready.
|
||||
updatingNewIntellisense = new ManualSignal<void>();
|
||||
|
||||
/**
|
||||
* This gets called when the configuration changes
|
||||
*/
|
||||
private async onIntellisenseConfigurationChanged(configuration: configs.Configuration, force = false): Promise<void> {
|
||||
log(`${elapsed()} updating (NEW) intellisense for ${configuration.name}`);
|
||||
|
||||
if (this.lastKnownState.unchanged('configuration', configuration) || force) {
|
||||
// if the configuration hasn't changed, we don't need to do anything
|
||||
// todo: if something dependent has changed (like the compile commands, how do we know?)
|
||||
log(`${elapsed()} ========= SKIPPING UPDATE INTELLISENS (config hasn't changed) =========`);
|
||||
return;
|
||||
}
|
||||
this.updatingNewIntellisense.reset();
|
||||
|
||||
// if we had a previous update in progress, cancel it.
|
||||
this.cancelUpdateIntellisense();
|
||||
|
||||
// get the provider right away so that if it has to do any work, it can get started
|
||||
const adapter = await this.getAdapter(configuration);
|
||||
|
||||
log(`${elapsed()} ========= SENDING BASE CONFIG (includes some browse path) =========`);
|
||||
await this.sendBaseConfiguration(adapter);
|
||||
void this.sendBrowsePath(configuration);
|
||||
|
||||
// first, let's send the configuration for files that are open in the editor (how?)
|
||||
|
||||
// then, we send the configuration for the known files that are ready
|
||||
// then as the rest is done, send those too.
|
||||
|
||||
log(`${elapsed()} ========= SENDING configurations for files that are ready right now =========`);
|
||||
void this.sendFileConfigurations(adapter, []);
|
||||
|
||||
// when it's done, stop listening.
|
||||
adapter.once('done', () => {
|
||||
log(`${elapsed()} ========= SENDING the remaining configurations for the rest of the files =========`);
|
||||
adapter.removeAllListeners();
|
||||
void this.sendFileConfigurations(adapter, []);
|
||||
|
||||
// send a flag with this to say "yeah, delete what you want" - since we're giveing the whole browse path
|
||||
// see browse_engine.cpp:1512
|
||||
void this.sendBrowsePath(configuration);
|
||||
});
|
||||
|
||||
log(`${elapsed()} ========= DONE UPDATE INTELLISENSE =========`);
|
||||
this.updatingNewIntellisense.resolve();
|
||||
this.#updateIntellisenseCancelTokenSource = undefined;
|
||||
}
|
||||
|
||||
private async onConfigurationsChanged(cppProperties: configs.CppProperties): Promise<void> {
|
||||
if (!cppProperties.Configurations) {
|
||||
return;
|
||||
}
|
||||
// redirct to use new intellisense if that's what's selected
|
||||
const current = this.configuration.CurrentConfiguration ?? cppProperties.Configurations[0];
|
||||
if (current?.enableNewIntellisense) {
|
||||
return this.onIntellisenseConfigurationChanged(current);
|
||||
}
|
||||
|
||||
const configurations: configs.Configuration[] = cppProperties.Configurations;
|
||||
const params: CppPropertiesParams = {
|
||||
configurations: [],
|
||||
@@ -3037,8 +2788,8 @@ export class DefaultClient implements Client {
|
||||
const settings: CppSettings = new CppSettings(this.RootUri);
|
||||
// Clone each entry, as we make modifications before sending it, and don't
|
||||
// want to add those modifications to the original objects.
|
||||
params.configurations = await Promise.all(configurations.map(async (c) => {
|
||||
const modifiedConfig: configs.Configuration = structuredClone(c);
|
||||
configurations.forEach((c) => {
|
||||
const modifiedConfig: configs.Configuration = deepCopy(c);
|
||||
// Separate compiler path and args before sending to language client
|
||||
const compilerPathAndArgs: util.CompilerPathAndArgs =
|
||||
util.extractCompilerPathAndArgs(!!settings.legacyCompilerArgsBehavior, c.compilerPath, c.compilerArgs);
|
||||
@@ -3050,11 +2801,10 @@ export class DefaultClient implements Client {
|
||||
modifiedConfig.compilerArgs = compilerPathAndArgs.allCompilerArgs;
|
||||
}
|
||||
|
||||
return modifiedConfig;
|
||||
}));
|
||||
params.configurations.push(modifiedConfig);
|
||||
});
|
||||
|
||||
await this.languageClient.sendRequest(ChangeCppPropertiesRequest, params);
|
||||
|
||||
if (!!this.lastCustomBrowseConfigurationProviderId && !!this.lastCustomBrowseConfiguration && !!this.lastCustomBrowseConfigurationProviderVersion) {
|
||||
if (!this.doneInitialCustomBrowseConfigurationCheck) {
|
||||
// Send the last custom browse configuration we received from this provider.
|
||||
@@ -3069,7 +2819,6 @@ export class DefaultClient implements Client {
|
||||
}
|
||||
const configName: string | undefined = configurations[params.currentConfiguration].name ?? "";
|
||||
this.model.activeConfigName.setValueIfActive(configName);
|
||||
|
||||
const newProvider: string | undefined = this.configuration.CurrentConfigurationProvider;
|
||||
if (!isSameProviderExtensionId(newProvider, this.configurationProvider)) {
|
||||
if (this.configurationProvider) {
|
||||
@@ -3098,19 +2847,10 @@ export class DefaultClient implements Client {
|
||||
}
|
||||
|
||||
private async onCompileCommandsChanged(path: string): Promise<void> {
|
||||
// if we're using new Intellisense, we don't want to send this.
|
||||
// instead, we'd like to let the updateIntellisense method handle it.
|
||||
if (this.configuration.CurrentConfiguration?.enableNewIntellisense) {
|
||||
log('OnCompileCommands: Using New intellisense');
|
||||
return this.onIntellisenseConfigurationChanged(this.configuration.CurrentConfiguration);
|
||||
}
|
||||
|
||||
log('OnCompileCommands: Using Legacy intellisense');
|
||||
const params: FileChangedParams = {
|
||||
uri: vscode.Uri.file(path).toString(),
|
||||
workspaceFolderUri: this.RootUri?.toString()
|
||||
};
|
||||
|
||||
await this.ready;
|
||||
return this.languageClient.sendNotification(ChangeCompileCommandsNotification, params);
|
||||
}
|
||||
@@ -3133,8 +2873,6 @@ export class DefaultClient implements Client {
|
||||
}
|
||||
|
||||
private sendCustomConfigurations(configs: any, providerVersion: Version): void {
|
||||
this.ensureNotNewIntellisense();
|
||||
log('sendCustomConfigurations: Using Legacy intellisense');
|
||||
// configs is marked as 'any' because it is untrusted data coming from a 3rd-party. We need to sanitize it before sending it to the language server.
|
||||
if (!configs || !(configs instanceof Array)) {
|
||||
console.warn("discarding invalid SourceFileConfigurationItems[]: " + configs);
|
||||
@@ -3149,13 +2887,6 @@ export class DefaultClient implements Client {
|
||||
const sanitized: SourceFileConfigurationItemAdapter[] = [];
|
||||
configs.forEach(item => {
|
||||
if (this.isSourceFileConfigurationItem(item, providerVersion)) {
|
||||
const itemConfig: util.Mutable<InternalSourceFileConfiguration & configs.NewIntelliSense> = structuredClone(item.configuration);
|
||||
|
||||
// In legacy mode, make sure new Intellisense content isn't sent
|
||||
itemConfig.intelliSenseMode = undefined;
|
||||
itemConfig.enableNewIntellisense = undefined;
|
||||
itemConfig.compiler = undefined;
|
||||
|
||||
let uri: string;
|
||||
if (util.isString(item.uri) && !item.uri.startsWith("file://")) {
|
||||
// If the uri field is a string, it may actually contain an fsPath.
|
||||
@@ -3164,16 +2895,15 @@ export class DefaultClient implements Client {
|
||||
uri = item.uri.toString();
|
||||
}
|
||||
this.configurationLogging.set(uri, JSON.stringify(item.configuration, null, 4));
|
||||
|
||||
/// if (settings.loggingLevel === "Debug") {
|
||||
/// out.appendLine(` uri: ${uri}`);
|
||||
/// out.appendLine(` config: ${JSON.stringify(item.configuration, null, 2)}`);
|
||||
/// }
|
||||
if (settings.loggingLevel === "Debug") {
|
||||
out.appendLine(` uri: ${uri}`);
|
||||
out.appendLine(` config: ${JSON.stringify(item.configuration, null, 2)}`);
|
||||
}
|
||||
if (item.configuration.includePath.some(path => path.endsWith('**'))) {
|
||||
console.warn("custom include paths should not use recursive includes ('**')");
|
||||
}
|
||||
// Separate compiler path and args before sending to language client
|
||||
|
||||
const itemConfig: util.Mutable<InternalSourceFileConfiguration> = deepCopy(item.configuration);
|
||||
if (util.isString(itemConfig.compilerPath)) {
|
||||
const compilerPathAndArgs: util.CompilerPathAndArgs = util.extractCompilerPathAndArgs(
|
||||
providerVersion < Version.v6,
|
||||
@@ -3190,7 +2920,6 @@ export class DefaultClient implements Client {
|
||||
itemConfig.compilerArgs = compilerPathAndArgs.allCompilerArgs;
|
||||
}
|
||||
}
|
||||
|
||||
sanitized.push({
|
||||
uri,
|
||||
configuration: itemConfig
|
||||
@@ -3203,8 +2932,11 @@ export class DefaultClient implements Client {
|
||||
if (sanitized.length === 0) {
|
||||
return;
|
||||
}
|
||||
const params = { configurationItems: sanitized, workspaceFolderUri: this.RootUri?.toString() };
|
||||
out.appendLine(`${CustomConfigurationNotification}:\n${JSON.stringify(params, null, 2)}`);
|
||||
|
||||
const params: CustomConfigurationParams = {
|
||||
configurationItems: sanitized,
|
||||
workspaceFolderUri: this.RootUri?.toString()
|
||||
};
|
||||
|
||||
void this.languageClient.sendNotification(CustomConfigurationNotification, params).catch(logAndReturn.undefined);
|
||||
}
|
||||
@@ -3221,9 +2953,6 @@ export class DefaultClient implements Client {
|
||||
}
|
||||
|
||||
private sendCustomBrowseConfiguration(config: any, providerId: string | undefined, providerVersion: Version, timeoutOccured?: boolean): void {
|
||||
this.ensureNotNewIntellisense();
|
||||
log('sendCustomBrowseConfiguration: Using Legacy intellisense');
|
||||
|
||||
const rootFolder: vscode.WorkspaceFolder | undefined = this.RootFolder;
|
||||
if (!rootFolder
|
||||
|| !this.lastCustomBrowseConfiguration
|
||||
@@ -3257,7 +2986,7 @@ export class DefaultClient implements Client {
|
||||
}
|
||||
|
||||
const browseConfig: InternalWorkspaceBrowseConfiguration = config as InternalWorkspaceBrowseConfiguration;
|
||||
sanitized = structuredClone(browseConfig);
|
||||
sanitized = deepCopy(browseConfig);
|
||||
if (!this.isWorkspaceBrowseConfiguration(sanitized) || sanitized.browsePath.length === 0) {
|
||||
console.log("Received an invalid browse configuration from configuration provider: " + JSON.stringify(sanitized));
|
||||
const configValue: WorkspaceBrowseConfiguration | undefined = this.lastCustomBrowseConfiguration.Value;
|
||||
@@ -3611,7 +3340,7 @@ export class DefaultClient implements Client {
|
||||
|
||||
const result: CreateDeclarationOrDefinitionResult = await this.languageClient.sendRequest(CreateDeclarationOrDefinitionRequest, params);
|
||||
// Create/Copy returned no result.
|
||||
if (result.edit === undefined) {
|
||||
if (result.workspaceEdits === undefined) {
|
||||
// The only condition in which result.edit would be undefined is a
|
||||
// server-initiated cancellation, in which case the object is actually
|
||||
// a ResponseError. https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#responseMessage
|
||||
@@ -3634,26 +3363,32 @@ export class DefaultClient implements Client {
|
||||
return vscode.env.clipboard.writeText(result.clipboardText);
|
||||
}
|
||||
|
||||
const workspaceEdits: vscode.WorkspaceEdit = new vscode.WorkspaceEdit();
|
||||
let workspaceEdits: vscode.WorkspaceEdit = new vscode.WorkspaceEdit();
|
||||
let modifiedDocument: vscode.Uri | undefined;
|
||||
let lastEdit: vscode.TextEdit | undefined;
|
||||
let selectionPositionAdjustment: number = 0;
|
||||
for (const file in result.edit.changes) {
|
||||
const uri: vscode.Uri = vscode.Uri.file(file);
|
||||
for (const workspaceEdit of result.workspaceEdits) {
|
||||
const uri: vscode.Uri = vscode.Uri.file(workspaceEdit.file);
|
||||
// At most, there will only be two text edits:
|
||||
// 1.) an edit for: #include header file
|
||||
// 2.) an edit for: definition or declaration
|
||||
for (const edit of result.edit.changes[file]) {
|
||||
const range: vscode.Range = makeVscodeRange(edit.range);
|
||||
for (const edit of workspaceEdit.edits) {
|
||||
let range: vscode.Range = makeVscodeRange(edit.range);
|
||||
// Get new lines from an edit for: #include header file.
|
||||
if (lastEdit && lastEdit.newText.includes("#include") && lastEdit.range.isEqual(range)) {
|
||||
if (lastEdit && lastEdit.newText.length < 300 && lastEdit.newText.includes("#include") && lastEdit.range.isEqual(range)) {
|
||||
// Destination file is empty.
|
||||
// The edit positions for #include header file and definition or declaration are the same.
|
||||
selectionPositionAdjustment = (lastEdit.newText.match(/\n/g) ?? []).length;
|
||||
const selectionPositionAdjustment = (lastEdit.newText.match(/\n/g) ?? []).length;
|
||||
range = new vscode.Range(new vscode.Position(range.start.line + selectionPositionAdjustment, range.start.character),
|
||||
new vscode.Position(range.end.line + selectionPositionAdjustment, range.end.character));
|
||||
}
|
||||
lastEdit = new vscode.TextEdit(range, edit.newText);
|
||||
const position: vscode.Position = new vscode.Position(edit.range.start.line, edit.range.start.character);
|
||||
workspaceEdits.insert(uri, position, edit.newText);
|
||||
workspaceEdits.insert(uri, range.start, edit.newText);
|
||||
if (edit.newText.length < 300 && edit.newText.includes("#pragma once")) {
|
||||
// Commit this so that it can be undone separately, to avoid leaving an empty file,
|
||||
// which causes the next refactor to not add the #pragma once.
|
||||
await vscode.workspace.applyEdit(workspaceEdits);
|
||||
workspaceEdits = new vscode.WorkspaceEdit();
|
||||
}
|
||||
}
|
||||
modifiedDocument = uri;
|
||||
}
|
||||
@@ -3679,7 +3414,7 @@ export class DefaultClient implements Client {
|
||||
numNewlines++; // Increase the format range.
|
||||
}
|
||||
|
||||
const selectionPosition: vscode.Position = new vscode.Position(startLine + selectionPositionAdjustment, 0);
|
||||
const selectionPosition: vscode.Position = new vscode.Position(startLine, 0);
|
||||
const selectionRange: vscode.Range = new vscode.Range(selectionPosition, selectionPosition);
|
||||
await vscode.window.showTextDocument(modifiedDocument, { selection: selectionRange });
|
||||
|
||||
@@ -3711,6 +3446,207 @@ export class DefaultClient implements Client {
|
||||
await vscode.workspace.applyEdit(formatEdits);
|
||||
}
|
||||
|
||||
public async handleExtractToFunction(extractAsGlobal: boolean): Promise<void> {
|
||||
const editor: vscode.TextEditor | undefined = vscode.window.activeTextEditor;
|
||||
if (!editor || editor.selection.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Show a quick pick to get the name before generating the code.
|
||||
// That would allow the formatting to be done without waiting for the rename.
|
||||
// Also, it's less error prone and eliminates a class of bugs in which the
|
||||
// rename position can be incorrect.
|
||||
|
||||
const params: ExtractToFunctionParams = {
|
||||
uri: editor.document.uri.toString(),
|
||||
range: {
|
||||
start: {
|
||||
character: editor.selection.start.character,
|
||||
line: editor.selection.start.line
|
||||
},
|
||||
end: {
|
||||
character: editor.selection.end.character,
|
||||
line: editor.selection.end.line
|
||||
}
|
||||
},
|
||||
extractAsGlobal
|
||||
};
|
||||
|
||||
const result: WorkspaceEditResult = await this.languageClient.sendRequest(ExtractToFunctionRequest, params);
|
||||
if (result.workspaceEdits === undefined) {
|
||||
// The only condition in which result.edit would be undefined is a
|
||||
// server-initiated cancellation, in which case the object is actually
|
||||
// a ResponseError. https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#responseMessage
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle error messaging
|
||||
if (result.errorText) {
|
||||
void vscode.window.showErrorMessage(result.errorText);
|
||||
return;
|
||||
}
|
||||
|
||||
let workspaceEdits: vscode.WorkspaceEdit = new vscode.WorkspaceEdit();
|
||||
let replaceEditRange: vscode.Range | undefined;
|
||||
let hasProcessedReplace: boolean = false;
|
||||
const formatUriAndRanges: VsCodeUriAndRange[] = [];
|
||||
this.renameDataForExtractToFunction = [];
|
||||
let lineOffset: number = 0;
|
||||
let headerFileLineOffset: number = 0;
|
||||
let isSourceFile: boolean = true;
|
||||
// There will be 4-5 text edits:
|
||||
// - A #pragma once added to a new header file (optional)
|
||||
// - Add #include for header file (optional)
|
||||
// - Add the new function declaration (in the source or header file)
|
||||
// - Replace the selected code with the new function call,
|
||||
// plus possibly extra declarations beforehand,
|
||||
// plus possibly extra return value handling afterwards.
|
||||
// - Add the new function definition (below the selection)
|
||||
for (const workspaceEdit of result.workspaceEdits) {
|
||||
if (hasProcessedReplace) {
|
||||
isSourceFile = false;
|
||||
lineOffset = 0;
|
||||
}
|
||||
const uri: vscode.Uri = vscode.Uri.file(workspaceEdit.file);
|
||||
let nextLineOffset: number = 0;
|
||||
for (const edit of workspaceEdit.edits) {
|
||||
let range: vscode.Range = makeVscodeRange(edit.range);
|
||||
if (!isSourceFile && headerFileLineOffset) {
|
||||
range = new vscode.Range(new vscode.Position(range.start.line + headerFileLineOffset, range.start.character),
|
||||
new vscode.Position(range.end.line + headerFileLineOffset, range.end.character));
|
||||
}
|
||||
const isReplace: boolean = !range.isEmpty;
|
||||
lineOffset += nextLineOffset;
|
||||
nextLineOffset = (edit.newText.match(/\n/g) ?? []).length;
|
||||
let rangeStartLine: number = range.start.line + lineOffset;
|
||||
|
||||
// Find the editType.
|
||||
if (isReplace) {
|
||||
hasProcessedReplace = true;
|
||||
workspaceEdits.replace(uri, range, edit.newText);
|
||||
} else {
|
||||
workspaceEdits.insert(uri, range.start, edit.newText);
|
||||
if (edit.newText.length < 300) { // Avoid searching large code edits
|
||||
if (isSourceFile && !hasProcessedReplace && edit.newText.includes("#include")) {
|
||||
continue;
|
||||
}
|
||||
if (edit.newText.includes("#pragma once")) {
|
||||
// Commit this so that it can be undone separately, to avoid leaving an empty file,
|
||||
// which causes the next refactor to not add the #pragma once.
|
||||
await vscode.workspace.applyEdit(workspaceEdits);
|
||||
headerFileLineOffset = nextLineOffset;
|
||||
workspaceEdits = new vscode.WorkspaceEdit();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
let rangeStartCharacter: number = 0;
|
||||
if (edit.newText.startsWith("\r\n\r\n")) {
|
||||
rangeStartCharacter = 4;
|
||||
rangeStartLine += 2;
|
||||
} else if (edit.newText.startsWith("\n\n")) {
|
||||
rangeStartCharacter = 2;
|
||||
rangeStartLine += 2;
|
||||
} else if (edit.newText.startsWith("\r\n")) {
|
||||
rangeStartCharacter = 2;
|
||||
rangeStartLine += 1;
|
||||
} else if (edit.newText.startsWith("\n")) {
|
||||
rangeStartCharacter = 1;
|
||||
rangeStartLine += 1;
|
||||
}
|
||||
formatUriAndRanges.push({uri, range: new vscode.Range(
|
||||
new vscode.Position(rangeStartLine + (nextLineOffset < 0 ? nextLineOffset : 0), range.start.character),
|
||||
new vscode.Position(rangeStartLine + (nextLineOffset < 0 ? 0 : nextLineOffset),
|
||||
isReplace ? range.end.character :
|
||||
range.end.character + edit.newText.length - rangeStartCharacter))});
|
||||
const newFunctionString: string = "NewFunction";
|
||||
|
||||
// Handle additional declaration lines added before the new function call.
|
||||
let currentText: string = edit.newText.substring(rangeStartCharacter);
|
||||
let currentTextNextLineStart: number = currentText.indexOf("\n");
|
||||
let currentTextNewFunctionStart: number = currentText.indexOf(newFunctionString);
|
||||
let currentTextNextLineStartUpdated: boolean = false;
|
||||
while (currentTextNextLineStart !== -1 && currentTextNextLineStart < currentTextNewFunctionStart) {
|
||||
++rangeStartLine;
|
||||
currentText = currentText.substring(currentTextNextLineStart + 1);
|
||||
currentTextNextLineStart = currentText.indexOf("\n");
|
||||
currentTextNewFunctionStart = currentText.indexOf(newFunctionString);
|
||||
currentTextNextLineStartUpdated = true;
|
||||
}
|
||||
rangeStartCharacter = (rangeStartCharacter === 0 && !currentTextNextLineStartUpdated ? range.start.character : 0) +
|
||||
currentTextNewFunctionStart;
|
||||
if (rangeStartCharacter < 0) {
|
||||
// newFunctionString is missing -- unexpected error.
|
||||
void vscode.window.showErrorMessage(`${localize("invalid.edit",
|
||||
"Extract to function failed. An invalid edit was generated: '{0}'", edit.newText)}`);
|
||||
continue;
|
||||
}
|
||||
const currentEditRange: vscode.Range = new vscode.Range(
|
||||
new vscode.Position(rangeStartLine, rangeStartCharacter),
|
||||
new vscode.Position(rangeStartLine, rangeStartCharacter + newFunctionString.length));
|
||||
if (isReplace) {
|
||||
replaceEditRange = currentEditRange;
|
||||
nextLineOffset -= range.end.line - range.start.line;
|
||||
}
|
||||
this.renameDataForExtractToFunction.push({ uri, range: currentEditRange });
|
||||
}
|
||||
}
|
||||
|
||||
if (replaceEditRange === undefined || formatUriAndRanges.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply the extract to function text edits.
|
||||
await vscode.workspace.applyEdit(workspaceEdits);
|
||||
|
||||
const firstUri: vscode.Uri = formatUriAndRanges[0].uri;
|
||||
await vscode.window.showTextDocument(firstUri, { selection: replaceEditRange });
|
||||
await vscode.commands.executeCommand("editor.action.rename", firstUri, replaceEditRange.start);
|
||||
|
||||
// Format the new text edits.
|
||||
const formatEdits: vscode.WorkspaceEdit = new vscode.WorkspaceEdit();
|
||||
for (const formatUriAndRange of formatUriAndRanges) {
|
||||
const settings: OtherSettings = new OtherSettings(vscode.workspace.getWorkspaceFolder(formatUriAndRange.uri)?.uri);
|
||||
const formatOptions: vscode.FormattingOptions = {
|
||||
insertSpaces: settings.editorInsertSpaces ?? true,
|
||||
tabSize: settings.editorTabSize ?? 4
|
||||
};
|
||||
|
||||
const doFormat = async () => {
|
||||
const versionBeforeFormatting: number | undefined = openFileVersions.get(formatUriAndRange.uri.toString());
|
||||
if (versionBeforeFormatting === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Somehow invoke multiple range formatting (see https://github.com/microsoft/vscode/issues/193836).
|
||||
// Maybe call DocumentRangeFormattingEditProvider.provideDocumentRangesFormattingEdits directly.
|
||||
const formatTextEdits: vscode.TextEdit[] | undefined = await vscode.commands.executeCommand<vscode.TextEdit[] | undefined>(
|
||||
"vscode.executeFormatRangeProvider", formatUriAndRange.uri, formatUriAndRange.range, formatOptions);
|
||||
if (!formatTextEdits || formatTextEdits.length === 0 || versionBeforeFormatting === undefined) {
|
||||
return true;
|
||||
}
|
||||
// Only apply formatting if the document version hasn't changed to prevent
|
||||
// stale formatting results from being applied.
|
||||
const versionAfterFormatting: number | undefined = openFileVersions.get(formatUriAndRange.uri.toString());
|
||||
if (versionAfterFormatting === undefined || versionAfterFormatting > versionBeforeFormatting) {
|
||||
return false;
|
||||
}
|
||||
formatEdits.set(formatUriAndRange.uri, formatTextEdits);
|
||||
return true;
|
||||
};
|
||||
if (!await doFormat())
|
||||
{
|
||||
await doFormat(); // Try again;
|
||||
}
|
||||
}
|
||||
|
||||
if (formatEdits.size > 0) {
|
||||
await vscode.workspace.applyEdit(formatEdits);
|
||||
}
|
||||
}
|
||||
|
||||
public renameDataForExtractToFunction: VsCodeUriAndRange[] = [];
|
||||
|
||||
public onInterval(): void {
|
||||
// These events can be discarded until the language client is ready.
|
||||
// Don't queue them up with this.notifyWhenLanguageClientReady calls.
|
||||
@@ -3848,7 +3784,7 @@ class NullClient implements Client {
|
||||
onRegisterCustomConfigurationProvider(provider: CustomConfigurationProvider1): Thenable<void> { return Promise.resolve(); }
|
||||
updateCustomConfigurations(requestingProvider?: CustomConfigurationProvider1): Thenable<void> { return Promise.resolve(); }
|
||||
updateCustomBrowseConfiguration(requestingProvider?: CustomConfigurationProvider1): Thenable<void> { return Promise.resolve(); }
|
||||
provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean, provider?: CustomConfigurationProvider1): Promise<void> { return Promise.resolve(); }
|
||||
provideCustomConfiguration(docUri: vscode.Uri, requestFile?: string, replaceExisting?: boolean): Promise<void> { return Promise.resolve(); }
|
||||
logDiagnostics(): Promise<void> { return Promise.resolve(); }
|
||||
rescanFolder(): Promise<void> { return Promise.resolve(); }
|
||||
toggleReferenceResultsView(): void { }
|
||||
@@ -3894,6 +3830,7 @@ class NullClient implements Client {
|
||||
handleFixCodeAnalysisProblems(workspaceEdit: vscode.WorkspaceEdit, refreshSquigglesOnSave: boolean, identifiersAndUris: CodeAnalysisDiagnosticIdentifiersAndUri[]): Promise<void> { return Promise.resolve(); }
|
||||
handleDisableAllTypeCodeAnalysisProblems(code: string, identifiersAndUris: CodeAnalysisDiagnosticIdentifiersAndUri[]): Promise<void> { return Promise.resolve(); }
|
||||
handleCreateDeclarationOrDefinition(isCopyToClipboard: boolean, codeActionRange?: Range): Promise<void> { return Promise.resolve(); }
|
||||
handleExtractToFunction(extractAsGlobal: boolean): Promise<void> { return Promise.resolve(); }
|
||||
onInterval(): void { }
|
||||
dispose(): void {
|
||||
this.booleanEvent.dispose();
|
||||
|
||||
@@ -74,7 +74,7 @@ interface CodeActionAllInfo {
|
||||
interface CodeAnalysisDiagnosticRelatedInformation {
|
||||
location: Location;
|
||||
message: string;
|
||||
workspaceEdit?: WorkspaceEdit;
|
||||
workspaceEdits?: WorkspaceEdit[];
|
||||
}
|
||||
|
||||
interface CodeAnalysisDiagnostic {
|
||||
@@ -83,7 +83,7 @@ interface CodeAnalysisDiagnostic {
|
||||
severity: vscode.DiagnosticSeverity;
|
||||
localizeStringParams: LocalizeStringParams;
|
||||
relatedInformation?: CodeAnalysisDiagnosticRelatedInformation[];
|
||||
workspaceEdit?: WorkspaceEdit;
|
||||
workspaceEdits?: WorkspaceEdit[];
|
||||
}
|
||||
|
||||
interface CodeAnalysisDiagnosticIdentifier {
|
||||
@@ -274,18 +274,18 @@ export function publishCodeAnalysisDiagnostics(params: PublishCodeAnalysisDiagno
|
||||
kind: vscode.CodeActionKind.QuickFix
|
||||
}
|
||||
};
|
||||
const workspaceEdit: CodeActionWorkspaceEdit = {};
|
||||
if (d.workspaceEdit) {
|
||||
workspaceEdit.workspaceEdit = new vscode.WorkspaceEdit();
|
||||
for (const [uriStr, edits] of Object.entries(d.workspaceEdit.changes)) {
|
||||
workspaceEdit.workspaceEdit.set(vscode.Uri.parse(uriStr, true), makeVscodeTextEdits(edits));
|
||||
const codeActionWorkspaceEdit: CodeActionWorkspaceEdit = {};
|
||||
if (d.workspaceEdits) {
|
||||
codeActionWorkspaceEdit.workspaceEdit = new vscode.WorkspaceEdit();
|
||||
for (const workspaceEdit of d.workspaceEdits) {
|
||||
codeActionWorkspaceEdit.workspaceEdit.set(vscode.Uri.parse(workspaceEdit.file, true), makeVscodeTextEdits(workspaceEdit.edits));
|
||||
}
|
||||
const fixThisCodeAction: vscode.CodeAction = {
|
||||
title: localize("fix.this.problem", "Fix this {0} problem", d.code),
|
||||
command: {
|
||||
title: 'FixThisCodeAnalysisProblem',
|
||||
command: 'C_Cpp.FixThisCodeAnalysisProblem',
|
||||
arguments: [ nextVersion, workspaceEdit.workspaceEdit, true, [ identifiersAndUri ] ]
|
||||
arguments: [ nextVersion, codeActionWorkspaceEdit.workspaceEdit, true, [ identifiersAndUri ] ]
|
||||
},
|
||||
kind: vscode.CodeActionKind.QuickFix
|
||||
};
|
||||
@@ -297,19 +297,19 @@ export function publishCodeAnalysisDiagnostics(params: PublishCodeAnalysisDiagno
|
||||
const rootAndRelatedWorkspaceEdits: CodeActionWorkspaceEdit[] = [];
|
||||
const rootAndRelatedIdentifiersAndUris: CodeAnalysisDiagnosticIdentifiersAndUri[] = [];
|
||||
rootAndRelatedIdentifiersAndUris.push(identifiersAndUri);
|
||||
if (workspaceEdit.workspaceEdit !== undefined) {
|
||||
rootAndRelatedWorkspaceEdits.push(workspaceEdit);
|
||||
if (codeActionWorkspaceEdit.workspaceEdit !== undefined) {
|
||||
rootAndRelatedWorkspaceEdits.push(codeActionWorkspaceEdit);
|
||||
}
|
||||
if (d.relatedInformation) {
|
||||
diagnostic.relatedInformation = [];
|
||||
for (const info of d.relatedInformation) {
|
||||
diagnostic.relatedInformation.push(new vscode.DiagnosticRelatedInformation(makeVscodeLocation(info.location), info.message));
|
||||
if (info.workspaceEdit === undefined) {
|
||||
if (info.workspaceEdits === undefined) {
|
||||
continue;
|
||||
}
|
||||
const relatedWorkspaceEdit: vscode.WorkspaceEdit = new vscode.WorkspaceEdit();
|
||||
for (const [uriStr, edits] of Object.entries(info.workspaceEdit.changes)) {
|
||||
relatedWorkspaceEdit.set(vscode.Uri.parse(uriStr, true), makeVscodeTextEdits(edits));
|
||||
for (const workspaceEdit of info.workspaceEdits) {
|
||||
relatedWorkspaceEdit.set(vscode.Uri.parse(workspaceEdit.file, true), makeVscodeTextEdits(workspaceEdit.edits));
|
||||
}
|
||||
const relatedIdentifier: CodeAnalysisDiagnosticIdentifier = { range: info.location.range, code: d.code };
|
||||
const relatedIdentifiersAndUri: CodeAnalysisDiagnosticIdentifiersAndUri = {
|
||||
@@ -378,7 +378,7 @@ export function publishCodeAnalysisDiagnostics(params: PublishCodeAnalysisDiagno
|
||||
docPage = `checks${checksGroup}/${checksPage}.html`;
|
||||
}
|
||||
// TODO: This should be checking the clang-tidy version used to better support usage of older versions.
|
||||
const primaryDocUri: vscode.Uri = vscode.Uri.parse(`https://releases.llvm.org/16.0.0/tools/clang/tools/extra/docs/clang-tidy/${docPage}`);
|
||||
const primaryDocUri: vscode.Uri = vscode.Uri.parse(`https://releases.llvm.org/17.0.1/tools/clang/tools/extra/docs/clang-tidy/${docPage}`);
|
||||
diagnostic.code = { value: identifier.code, target: primaryDocUri };
|
||||
|
||||
if (new CppSettings().clangTidyCodeActionShowDocumentation) {
|
||||
|
||||
@@ -17,5 +17,6 @@ export interface TextEdit {
|
||||
}
|
||||
|
||||
export interface WorkspaceEdit {
|
||||
changes: { [uri: string]: TextEdit[] };
|
||||
file: string;
|
||||
edits: TextEdit[];
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import { setTimeout } from 'timers';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as which from 'which';
|
||||
import { IntelliSenseConfiguration } from '../ToolsetDetection/interfaces';
|
||||
import { logAndReturn, returns } from '../Utility/Async/returns';
|
||||
import * as util from '../common';
|
||||
import { isWindows } from '../constants';
|
||||
@@ -61,16 +60,7 @@ export interface ConfigurationJson {
|
||||
enableConfigurationSquiggles?: boolean;
|
||||
}
|
||||
|
||||
export interface NewIntelliSense {
|
||||
/** will trigger the use of the new intellisense when the compiler is set. */
|
||||
enableNewIntellisense?: boolean;
|
||||
/** select the compiler for new mode (full path, filename of binary in ${env:PATH} or compiler name (supports wildcards) ) */
|
||||
compiler?: string;
|
||||
/** new IntelliSense configuration */
|
||||
intellisense?: IntelliSenseConfiguration;
|
||||
}
|
||||
|
||||
export interface Configuration extends NewIntelliSense{
|
||||
export interface Configuration {
|
||||
name: string;
|
||||
compilerPathInCppPropertiesJson?: string;
|
||||
compilerPath?: string;
|
||||
@@ -772,8 +762,14 @@ export class CppProperties {
|
||||
}
|
||||
paths = this.resolveDefaults(paths, defaultValue);
|
||||
paths.forEach(entry => {
|
||||
const entries: string[] = util.resolveVariables(entry, env).split(path.delimiter).map(e => glob ? this.resolvePath(e, false) : e).filter(e => e);
|
||||
resolvedVariables.push(...entries);
|
||||
const resolvedVariable: string = util.resolveVariables(entry, env);
|
||||
if (resolvedVariable.includes("env:")) {
|
||||
// Do not futher try to resolve a "${env:VAR}"
|
||||
resolvedVariables.push(resolvedVariable);
|
||||
} else {
|
||||
const entries: string[] = resolvedVariable.split(path.delimiter).map(e => glob ? this.resolvePath(e, false) : e).filter(e => e);
|
||||
resolvedVariables.push(...entries);
|
||||
}
|
||||
});
|
||||
if (!glob) {
|
||||
return resolvedVariables;
|
||||
@@ -914,12 +910,6 @@ export class CppProperties {
|
||||
const env: Environment = this.ExtendedEnvironment;
|
||||
for (let i: number = 0; i < this.configurationJson.configurations.length; i++) {
|
||||
const configuration: Configuration = this.configurationJson.configurations[i];
|
||||
|
||||
if (configuration.compiler) {
|
||||
// we are going to skip all the magic when it's a new intellisense config
|
||||
continue;
|
||||
}
|
||||
|
||||
configuration.compilerPathInCppPropertiesJson = configuration.compilerPath;
|
||||
configuration.compileCommandsInCppPropertiesJson = configuration.compileCommands;
|
||||
configuration.configurationProviderInCppPropertiesJson = configuration.configurationProvider;
|
||||
@@ -1503,7 +1493,7 @@ export class CppProperties {
|
||||
return success;
|
||||
}
|
||||
|
||||
public resolvePath(input_path: string | undefined, replaceAsterisks: boolean = true): string {
|
||||
private resolvePath(input_path: string | undefined, replaceAsterisks: boolean = true): string {
|
||||
if (!input_path || input_path === "${default}") {
|
||||
return "";
|
||||
}
|
||||
@@ -1527,8 +1517,9 @@ export class CppProperties {
|
||||
result = result.replace(/\*/g, "");
|
||||
}
|
||||
|
||||
// Make sure all paths result to an absolute path
|
||||
if (!path.isAbsolute(result) && this.rootUri) {
|
||||
// Make sure all paths result to an absolute path.
|
||||
// Do not add the root path to an unresolved env variable.
|
||||
if (!result.includes("env:") && !path.isAbsolute(result) && this.rootUri) {
|
||||
result = path.join(this.rootUri.fsPath, result);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { CustomConfigurationProvider, SourceFileConfigurationItem, Version, WorkspaceBrowseConfiguration } from 'vscode-cpptools';
|
||||
import { InternalWorkspaceBrowseConfiguration } from './client';
|
||||
import * as ext from './extension';
|
||||
import { CppSettings } from './settings';
|
||||
|
||||
@@ -19,10 +18,6 @@ export interface CustomConfigurationProvider1 extends CustomConfigurationProvide
|
||||
readonly version: Version;
|
||||
}
|
||||
|
||||
export function isInternalWorkspaceBrowseConfiguration(config: WorkspaceBrowseConfiguration | null | undefined): config is InternalWorkspaceBrowseConfiguration {
|
||||
return !!config && ('systemPath' in config || 'userFrameworks' in config || 'systemFrameworks' in config);
|
||||
}
|
||||
|
||||
const oldCmakeToolsExtensionId: string = "vector-of-bool.cmake-tools";
|
||||
const newCmakeToolsExtensionId: string = "ms-vscode.cmake-tools";
|
||||
|
||||
|
||||
@@ -9,11 +9,10 @@ import * as StreamZip from 'node-stream-zip';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { Range } from 'vscode-languageclient';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { remote } from '../ToolsetDetection/detection';
|
||||
import { logAndReturn } from '../Utility/Async/returns';
|
||||
import * as util from '../common';
|
||||
import { log } from '../logger';
|
||||
import { PlatformInformation } from '../platform';
|
||||
import * as telemetry from '../telemetry';
|
||||
import { Client, DefaultClient, DoxygenCodeActionCommandArguments, openFileVersions } from './client';
|
||||
@@ -344,27 +343,18 @@ export async function processDelayedDidOpen(document: vscode.TextDocument): Prom
|
||||
if (!client.TrackedDocuments.has(document)) {
|
||||
// If not yet tracked, process as a newly opened file. (didOpen is sent to server in client.takeOwnership()).
|
||||
client.TrackedDocuments.add(document);
|
||||
|
||||
clients.timeTelemetryCollector.setDidOpenTime(document.uri);
|
||||
// Work around vscode treating ".C" or ".H" as c, by adding this file name to file associations as cpp
|
||||
if (document.languageId === "c" && shouldChangeFromCToCpp(document)) {
|
||||
const baseFileName: string = path.basename(document.fileName);
|
||||
const mappingString: string = baseFileName + "@" + document.fileName;
|
||||
client.addFileAssociations(mappingString, "cpp");
|
||||
void client.sendDidChangeSettings();
|
||||
client.sendDidChangeSettings();
|
||||
document = await vscode.languages.setTextDocumentLanguage(document, "cpp");
|
||||
}
|
||||
|
||||
// if we are in newIntellisense mode, we have to ensure that the configuration for the file is sent asap.
|
||||
if (client instanceof DefaultClient && client.isNewIntellisense) {
|
||||
log(`Processing delayed didOpen for newIntellisense mode: ${document.uri.toString()}`);
|
||||
await client.sendNewIntellisenseConfigurationForFile(document.uri);
|
||||
} else {
|
||||
log(`Processing delayed didOpen for Legacy Mode: ${document.uri.toString()}`);
|
||||
await client.provideCustomConfiguration(document.uri, undefined);
|
||||
}
|
||||
await client.provideCustomConfiguration(document.uri, undefined);
|
||||
// client.takeOwnership() will call client.TrackedDocuments.add() again, but that's ok. It's a Set.
|
||||
void client.onDidOpenTextDocument(document);
|
||||
client.onDidOpenTextDocument(document);
|
||||
await client.takeOwnership(document);
|
||||
return true;
|
||||
}
|
||||
@@ -447,6 +437,10 @@ export function registerCommands(enabled: boolean): void {
|
||||
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.CreateDeclarationOrDefinition', enabled ? onCreateDeclarationOrDefinition : onDisabledCommand));
|
||||
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.CopyDeclarationOrDefinition', enabled ? onCopyDeclarationOrDefinition : onDisabledCommand));
|
||||
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.RescanCompilers', enabled ? onRescanCompilers : onDisabledCommand));
|
||||
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ExtractToFunction', enabled ? () => onExtractToFunction(false, false) : onDisabledCommand));
|
||||
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ExtractToFreeFunction', enabled ? () => onExtractToFunction(true, false) : onDisabledCommand));
|
||||
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ExtractToMemberFunction', enabled ? () => onExtractToFunction(false, true) : onDisabledCommand));
|
||||
commandDisposables.push(vscode.commands.registerCommand('C_Cpp.ExpandSelection', enabled ? (r: Range) => onExpandSelection(r) : onDisabledCommand));
|
||||
}
|
||||
|
||||
function onDisabledCommand() {
|
||||
@@ -754,6 +748,25 @@ async function onCreateDeclarationOrDefinition(args?: any): Promise<void> {
|
||||
return getActiveClient().handleCreateDeclarationOrDefinition(false, args?.range);
|
||||
}
|
||||
|
||||
async function onExtractToFunction(extractAsGlobal: boolean, extractAsMemberFunction: boolean): Promise<void> {
|
||||
if (extractAsGlobal) {
|
||||
telemetry.logLanguageServerEvent('ExtractToFreeFunction');
|
||||
} else if (extractAsMemberFunction) {
|
||||
telemetry.logLanguageServerEvent('ExtractToMemberFunction');
|
||||
} else {
|
||||
telemetry.logLanguageServerEvent('ExtractToFunction');
|
||||
}
|
||||
return getActiveClient().handleExtractToFunction(extractAsGlobal);
|
||||
}
|
||||
|
||||
function onExpandSelection(r: Range) {
|
||||
const activeTextEditor: vscode.TextEditor | undefined = vscode.window.activeTextEditor;
|
||||
if (activeTextEditor) {
|
||||
activeTextEditor.selection = new vscode.Selection(new vscode.Position(r.start.line, r.start.character), new vscode.Position(r.end.line, r.end.character));
|
||||
telemetry.logLanguageServerEvent('ExpandSelection');
|
||||
}
|
||||
}
|
||||
|
||||
function onAddToIncludePath(path: string): void {
|
||||
if (isFolderOpen()) {
|
||||
// This only applies to the active client. It would not make sense to add the include path
|
||||
@@ -1085,9 +1098,6 @@ function handleMacCrashFileRead(err: NodeJS.ErrnoException | undefined | null, d
|
||||
}
|
||||
|
||||
export function deactivate(): Thenable<void> {
|
||||
// shutdown the worker thread if it's running
|
||||
remote.terminate();
|
||||
|
||||
clients.timeTelemetryCollector.clear();
|
||||
console.log("deactivating extension");
|
||||
telemetry.logLanguageServerEvent("LanguageServerShutdown");
|
||||
|
||||
@@ -8,7 +8,6 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { structuredClone } from '../Utility/System/structuredClone';
|
||||
import * as util from '../common';
|
||||
import * as telemetry from '../telemetry';
|
||||
import * as config from './configurations';
|
||||
@@ -17,6 +16,10 @@ import { getLocalizedHtmlPath } from './localization';
|
||||
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
|
||||
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
|
||||
|
||||
function deepCopy(obj: any) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
// TODO: share ElementId between SettingsPanel and SettingsApp. Investigate why SettingsApp cannot import/export
|
||||
const elementId: { [key: string]: string } = {
|
||||
// Basic settings
|
||||
@@ -225,7 +228,7 @@ export class SettingsPanel {
|
||||
}
|
||||
|
||||
private updateWebview(configSelection: string[], configuration: config.Configuration, errors: config.ConfigurationErrors | null): void {
|
||||
this.configValues = structuredClone(configuration); // Copy configuration values
|
||||
this.configValues = deepCopy(configuration); // Copy configuration values
|
||||
this.isIntelliSenseModeDefined = this.configValues.intelliSenseMode !== undefined;
|
||||
if (this.panel && this.initialized) {
|
||||
void this.panel.webview.postMessage({ command: 'setKnownCompilers', compilers: this.compilerPaths });
|
||||
|
||||
@@ -32,7 +32,7 @@ export class TargetLeafNode extends LabelLeafNode {
|
||||
super(name);
|
||||
}
|
||||
|
||||
override async getTreeItem(): Promise<TreeItem> {
|
||||
async getTreeItem(): Promise<TreeItem> {
|
||||
const item: TreeItem = await super.getTreeItem();
|
||||
const removable: boolean = await isWritable(this.sshConfigHostInfo.file);
|
||||
if (_activeTarget === this.name) {
|
||||
|
||||
@@ -10,10 +10,9 @@ import * as path from 'path';
|
||||
import {
|
||||
Configuration, ConfigurationDirective,
|
||||
ConfigurationEntry,
|
||||
Type as ConfigurationEntryType,
|
||||
HostConfigurationDirective,
|
||||
HostConfigurationDirective, parse,
|
||||
ResolvedConfiguration,
|
||||
parse
|
||||
Type as ConfigurationEntryType
|
||||
} from 'ssh-config';
|
||||
import { promisify } from 'util';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-dynamic-delete */
|
||||
|
||||
import { parse as parseJson } from 'comment-json';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { accumulator } from '../../Utility/Async/iterators';
|
||||
import { AsyncMap } from '../../Utility/Async/map';
|
||||
import { FastFinder } from '../../Utility/Filesystem/ripgrep';
|
||||
import { is } from '../../Utility/System/guards';
|
||||
import { CustomResolver, evaluateExpression } from '../../Utility/Text/taggedLiteral';
|
||||
import { DeepPartial, DefinitionFile, IntelliSense, IntelliSenseConfiguration, PartialDefinitionFile, PkgMgr } from '../interfaces';
|
||||
import { strings } from '../strings';
|
||||
import { mergeObjects } from './objectMerge';
|
||||
|
||||
// iterates recursively over the parsed data and transform all the keys that are just
|
||||
// identifiers and dots into nested objects
|
||||
function transform(obj: any): any {
|
||||
if (typeof obj === 'object' && obj !== null) {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (is.string(key) && /^[a-zA-Z0-9\$._]+$/.test(key)) {
|
||||
const parts = key.split('.');
|
||||
if (parts.length > 1) {
|
||||
const first = parts.shift()!;
|
||||
if (!obj[first]) {
|
||||
obj[first] = {};
|
||||
}
|
||||
obj[first][parts.join('.')] = value;
|
||||
transform(obj[first]);
|
||||
delete obj[key];
|
||||
}
|
||||
}
|
||||
transform(value);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
function parse(text: string) {
|
||||
try {
|
||||
return transform(parseJson(text));
|
||||
} catch (e: any) {
|
||||
if (e.message) {
|
||||
console.error(e.message);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isToolsetDefinition(definition: any): definition is DefinitionFile {
|
||||
// stub for now - we can add a schema validator once we're sure the schema is stable
|
||||
return true;
|
||||
}
|
||||
|
||||
function isPartialToolsetDefinition(definition: any): definition is DefinitionFile {
|
||||
// stub for now - we can add a schema validator once we're sure the schema is stable
|
||||
return true;
|
||||
}
|
||||
|
||||
const compilerDefintions = new AsyncMap<string, DefinitionFile>();
|
||||
const partialDefinitions = new AsyncMap<string, PartialDefinitionFile>();
|
||||
|
||||
export function formatIntelliSenseBlock<T extends DeepPartial<IntelliSenseConfiguration> | DeepPartial<IntelliSense>>(intellisense?: T): T {
|
||||
if (!intellisense) {
|
||||
return {} as T;
|
||||
}
|
||||
const p = intellisense.path = intellisense.path || {};
|
||||
|
||||
// expand out the include paths
|
||||
p.quoteInclude = strings(p.quoteInclude);
|
||||
p.include = strings(p.include);
|
||||
p.systemInclude = strings(p.systemInclude);
|
||||
p.builtInInclude = strings(p.builtInInclude);
|
||||
p.afterInclude = strings(p.afterInclude);
|
||||
p.externalInclude = strings(p.externalInclude);
|
||||
p.framework = strings(p.framework);
|
||||
p.environmentInclude = strings(p.environmentInclude);
|
||||
p.forcedIncludeFile = strings(p.forcedIncludeFile);
|
||||
|
||||
intellisense.parserArgument = strings(intellisense.parserArgument);
|
||||
intellisense.compilerArg = strings(intellisense.compilerArg);
|
||||
intellisense.macro = intellisense.macro || {};
|
||||
|
||||
for (const [key, value] of Object.entries(intellisense)) {
|
||||
if (key.startsWith('message') || key.startsWith('remove')) {
|
||||
// replace with strings array
|
||||
(intellisense as any)[key] = strings(value);
|
||||
}
|
||||
}
|
||||
|
||||
return intellisense;
|
||||
}
|
||||
|
||||
/** coerce the collections from OneOrMore<*> to Array<*> in the defintion */
|
||||
function formatDefinitionBlock(definition: DefinitionFile) {
|
||||
|
||||
// definition.intellisense.* members
|
||||
formatIntelliSenseBlock(definition.intellisense);
|
||||
|
||||
// definition.package.* = strings(definition.package.*);
|
||||
if (definition.package) {
|
||||
for (const key of Object.keys(definition.package)) {
|
||||
definition.package[key as PkgMgr] = strings(definition.package[key as PkgMgr]);
|
||||
}
|
||||
}
|
||||
|
||||
// definition.analysis.* members
|
||||
if (definition.analysis) {
|
||||
for (const [key, value] of Object.entries(definition.analysis)) {
|
||||
if (key.startsWith('task')) {
|
||||
// replace with strings array
|
||||
(definition.analysis as any)[key] = strings(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.discover) {
|
||||
definition.discover.binary = strings(definition.discover.binary);
|
||||
definition.discover.locations = strings(definition.discover.locations);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDefinition(definitionFile: string): Promise<DefinitionFile | undefined> {
|
||||
return compilerDefintions.getOrAdd(definitionFile, async () => {
|
||||
try {
|
||||
const definition = parse(await readFile(definitionFile, 'utf8'));
|
||||
if (!isToolsetDefinition(definition)) {
|
||||
console.error(`The definition file ${definitionFile} is not a valid toolset definition.`);
|
||||
return;
|
||||
}
|
||||
formatDefinitionBlock(definition);
|
||||
if (definition.import) {
|
||||
const files = strings(definition.import);
|
||||
for (const file of files) {
|
||||
// there should be a partial definition file that matches this expression
|
||||
const partialFile = resolve(dirname(definitionFile), file);
|
||||
await loadPartialDefinition(partialFile);
|
||||
|
||||
if (partialDefinitions.has(partialFile)) {
|
||||
const partial = partialDefinitions.get(partialFile)!;
|
||||
if (!isPartialToolsetDefinition(partial)) {
|
||||
continue;
|
||||
}
|
||||
mergeObjects(definition, partial);
|
||||
formatDefinitionBlock(definition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.conditions) {
|
||||
// eslint-disable-next-line prefer-const
|
||||
for (let [expression, part] of Object.entries(definition.conditions)) {
|
||||
if (is.string(part) || is.array(part)) {
|
||||
const files = strings(part);
|
||||
part = {};
|
||||
for (const file of files) {
|
||||
// there should be a partial definition file that matches this expression
|
||||
const partialFile = resolve(dirname(definitionFile), file);
|
||||
await loadPartialDefinition(partialFile);
|
||||
|
||||
if (partialDefinitions.has(partialFile)) {
|
||||
const partial = partialDefinitions.get(partialFile)!;
|
||||
if (!isPartialToolsetDefinition(partial)) {
|
||||
continue;
|
||||
}
|
||||
mergeObjects(part, partial);
|
||||
formatDefinitionBlock(definition);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isPartialToolsetDefinition(part)) {
|
||||
// replace the location with the contents
|
||||
definition.conditions[expression] = part;
|
||||
}
|
||||
}
|
||||
}
|
||||
compilerDefintions.set(definitionFile, definition);
|
||||
return definition;
|
||||
} catch (e: any) {
|
||||
if (e.message) {
|
||||
console.warn(`Error loading compiler definition file: ${definitionFile} - ${e.message}`);
|
||||
}
|
||||
}
|
||||
compilerDefintions.delete(definitionFile);
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
async function loadPartialDefinition(definitionFile: string) {
|
||||
if (!partialDefinitions.has(definitionFile)) {
|
||||
const definition = parse(await readFile(definitionFile, 'utf8'));
|
||||
if (!isPartialToolsetDefinition(definition)) {
|
||||
console.error(`Error loading partial compiler definition file: ${definitionFile} - Invalid definition file.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (definition.import) {
|
||||
const files = strings(definition.import);
|
||||
for (const file of files) {
|
||||
// there should be a partial definition file that matches this expression
|
||||
const partialFile = resolve(dirname(definitionFile), file);
|
||||
await loadPartialDefinition(partialFile);
|
||||
|
||||
if (partialDefinitions.has(partialFile)) {
|
||||
const partial = partialDefinitions.get(partialFile)!;
|
||||
if (!isPartialToolsetDefinition(partial)) {
|
||||
continue;
|
||||
}
|
||||
mergeObjects(definition, partial);
|
||||
formatDefinitionBlock(definition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partialDefinitions.set(definitionFile, definition);
|
||||
}
|
||||
}
|
||||
export function resetCompilerDefinitions() {
|
||||
compilerDefintions.clear();
|
||||
partialDefinitions.clear();
|
||||
}
|
||||
|
||||
export async function* loadCompilerDefinitions(configurationFolders: Set<string>): AsyncIterable<DefinitionFile> {
|
||||
// find all the definition files in the specified configuration folders.
|
||||
const result = accumulator<DefinitionFile>();
|
||||
const definitionFiles = new FastFinder(['toolset.*.json']).scan(...configurationFolders);
|
||||
const all = [];
|
||||
for await (const file of definitionFiles) {
|
||||
all.push(loadDefinition(file).then(each => result.add(each)));
|
||||
}
|
||||
void Promise.all(all).then(() => result.complete());
|
||||
|
||||
yield* result;
|
||||
}
|
||||
|
||||
export async function runConditions(definition: DefinitionFile, resolver: CustomResolver): Promise<boolean> {
|
||||
let conditionsRan = false;
|
||||
if (definition.conditions) {
|
||||
for (const [expression, part] of Object.entries(definition.conditions)) {
|
||||
if (await evaluateExpression(expression, definition, resolver)) {
|
||||
// the condition is true!
|
||||
// which means something changed...
|
||||
conditionsRan = true;
|
||||
|
||||
// remove the condition from the definition so we don't re-run it
|
||||
delete definition.conditions[expression];
|
||||
|
||||
// merge the part into the main document
|
||||
mergeObjects(definition, part as any);
|
||||
formatDefinitionBlock(definition);
|
||||
|
||||
// we should also run the conditions again, in case the new definition has more conditions
|
||||
await runConditions(definition, resolver);
|
||||
}
|
||||
}
|
||||
return conditionsRan;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1,450 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import * as os from 'os';
|
||||
import { basename, resolve } from 'path';
|
||||
import { rcompare } from 'semver';
|
||||
|
||||
import { accumulator } from '../../Utility/Async/iterators';
|
||||
import { ManualPromise } from '../../Utility/Async/manualPromise';
|
||||
import { sleep, then } from '../../Utility/Async/sleep';
|
||||
import { filepath, filterToFolders, pathsFromVariable } from '../../Utility/Filesystem/filepath';
|
||||
import { FastFinder, ripGrep } from '../../Utility/Filesystem/ripgrep';
|
||||
|
||||
import { Cache } from '../../Utility/System/cache';
|
||||
import { is } from '../../Utility/System/guards';
|
||||
import { structuredClone } from '../../Utility/System/structuredClone';
|
||||
import { verbose } from '../../Utility/Text/streams';
|
||||
import { render } from '../../Utility/Text/taggedLiteral';
|
||||
import { isWindows } from '../../constants';
|
||||
import { DefinitionFile, IntelliSense, IntelliSenseConfiguration } from '../interfaces';
|
||||
import { getActions, strings } from '../strings';
|
||||
import { loadCompilerDefinitions, resetCompilerDefinitions, runConditions } from './definition';
|
||||
import { createResolver } from './resolver';
|
||||
import { Toolset, loadToolsetData, persistToolsetData, settings } from './toolset';
|
||||
import escapeStringRegExp = require('escape-string-regexp');
|
||||
|
||||
let initialized: ManualPromise | undefined;
|
||||
|
||||
const discoveringInProgress = new Map<DefinitionFile, Promise<void>>();
|
||||
let discovering: Promise<any> | undefined;
|
||||
const configurationFolders = new Set<string>();
|
||||
|
||||
const cache = new Cache<Record<string, string>>();
|
||||
async function searchInsideBinary(compilerPath: string, rx: string | Promise<string>) {
|
||||
if (is.promise(rx)) {
|
||||
rx = await rx;
|
||||
}
|
||||
return cache.getOrAdd(compilerPath + rx, async () => {
|
||||
for await (const match of ripGrep(compilerPath, rx as string, { binary: true, ignoreCase: true })) {
|
||||
const rxResult = new RegExp(rx as string, 'i').exec(match.lines.text.replace(/\0/g, ''));
|
||||
if (rxResult) {
|
||||
return rxResult.groups || {};
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
async function discover(compilerPath: string, definition: DefinitionFile): Promise<Toolset | undefined> {
|
||||
// normalize the path separators to be forward slashes.
|
||||
compilerPath = resolve(compilerPath);
|
||||
|
||||
let toolset = settings.discoveredToolsets.get(compilerPath);
|
||||
if (toolset) {
|
||||
return toolset;
|
||||
}
|
||||
// toolset was not previously discovered for this binary, so, discover it now.
|
||||
|
||||
// clone the definition so it can be modified without affecting the original
|
||||
definition = structuredClone(definition);
|
||||
|
||||
// create toolset object for the result.
|
||||
toolset = new Toolset(compilerPath, definition);
|
||||
|
||||
const intellisense = definition.intellisense as IntelliSense;
|
||||
|
||||
const requirements = getActions<Record<string, IntelliSenseConfiguration>>(definition.discover as any, [
|
||||
['match', ['optional', 'priority', 'oneof']],
|
||||
['expression', ['oneof', 'optional', 'priority', 'folder', 'file']]
|
||||
]);
|
||||
nextBlock:
|
||||
for (const { action, block, flags } of requirements) {
|
||||
switch (action) {
|
||||
case 'match':
|
||||
// valid flags : 'oneof', 'optional'
|
||||
if (flags.has('oneof')) {
|
||||
// run them in parallel, but take the first winning result in order
|
||||
for (const [rawRx, isense] of Object.entries(block)) {
|
||||
const result = await searchInsideBinary(compilerPath, render(rawRx, {}, toolset.resolver));
|
||||
if (result) {
|
||||
await toolset.applyToConfiguration(toolset.default, isense, result);
|
||||
// first one wins, exit the block
|
||||
// await Promise.all(results); // wait for all the results to complete?
|
||||
continue nextBlock;
|
||||
}
|
||||
}
|
||||
// if this is optional, we can move to the next entry
|
||||
if (flags.has('optional')) {
|
||||
continue nextBlock;
|
||||
}
|
||||
// if we got here, none matched, so this whole toolset is not a match
|
||||
return;
|
||||
} else {
|
||||
for (const [rawRx, isense] of Object.entries(block)) {
|
||||
const r = await searchInsideBinary(compilerPath, render(rawRx, {}, toolset.resolver));
|
||||
if (r) {
|
||||
await toolset.applyToConfiguration(toolset.default, isense, r);
|
||||
continue;
|
||||
}
|
||||
// not found, but not a problem
|
||||
if (flags.has('optional')) {
|
||||
continue;
|
||||
}
|
||||
// not found, and not optional, so this whole toolset is not a match
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'expression':
|
||||
// verifies that the expression is true
|
||||
// valid flags : 'oneof', 'optional', 'priority', 'folder', 'file'
|
||||
for (const [expr, isense] of Object.entries(block)) {
|
||||
const value = await render(expr, {}, toolset.resolver);
|
||||
if (value) {
|
||||
if (flags.has('folder')) {
|
||||
if (await filepath.isFolder(value)) {
|
||||
await toolset.applyToConfiguration(intellisense, isense);
|
||||
if (flags.has('oneof')) {
|
||||
// first one wins, exit the block
|
||||
continue nextBlock;
|
||||
}
|
||||
// a success, move to the next entry
|
||||
continue;
|
||||
}
|
||||
// not a match
|
||||
if (flags.has('optional') || flags.has('oneof')) {
|
||||
// didn't find it, but it's optional (or we can still find a match later?), so we can move to the next entry
|
||||
continue;
|
||||
}
|
||||
|
||||
// should be a folder match, and not optional. this toolset is not a match
|
||||
return;
|
||||
}
|
||||
|
||||
if (flags.has('file')) {
|
||||
if (await filepath.isFile(value)) {
|
||||
await toolset.applyToConfiguration(intellisense, isense);
|
||||
if (flags.has('oneof')) {
|
||||
// first one wins, exit the block
|
||||
continue nextBlock;
|
||||
}
|
||||
// a success, move to the next entry
|
||||
continue;
|
||||
}
|
||||
|
||||
// not a match
|
||||
if (flags.has('optional') || flags.has('oneof')) {
|
||||
// didn't find it, but it's optional (or we can still find a match later?), so we can move to the next entry
|
||||
continue;
|
||||
}
|
||||
|
||||
// should be a file match, and not optional. this toolset is not a match
|
||||
return;
|
||||
}
|
||||
|
||||
// it's a truthy value, so it's a match
|
||||
await toolset.applyToConfiguration(intellisense, isense);
|
||||
if (flags.has('oneof')) {
|
||||
// first one wins, exit the block
|
||||
continue nextBlock;
|
||||
}
|
||||
// a success, move to the next entry
|
||||
continue;
|
||||
}
|
||||
// we didn't get a match
|
||||
if (flags.has('optional') || flags.has('oneof')) {
|
||||
// didn't find it, but it's optional (or we can still find a match later?), so we can move to the next entry
|
||||
continue;
|
||||
}
|
||||
|
||||
// no match, the whole toolset is not a match
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
settings.discoveredToolsets.set(compilerPath, toolset);
|
||||
void persistToolsetData();
|
||||
|
||||
return toolset;
|
||||
}
|
||||
|
||||
async function getWellKnownBinariesFromPath() {
|
||||
// create the finder
|
||||
const finder = new FastFinder(['cl'], { executable: true, executableExtensions: ['.exe'] });
|
||||
|
||||
// start scanning the folders in the $PATH
|
||||
finder.scan(...await filterToFolders(pathsFromVariable('PATH')));
|
||||
|
||||
for await (const compilerPath of finder) {
|
||||
await identify(compilerPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will search for toolsets based on the definitions
|
||||
* Calling this forces a reset of the compiler definitions and discovered toolsets -- ideally this shouldn't need to be called
|
||||
* more than the initial time
|
||||
*/
|
||||
export async function initialize(configFolders: string[], options?: { quick?: boolean; storagePath?: string }) {
|
||||
if (initialized) {
|
||||
// wait for an existing initialize to complete
|
||||
await initialized;
|
||||
}
|
||||
|
||||
initialized = new ManualPromise();
|
||||
|
||||
const forceReset = !options?.quick;
|
||||
|
||||
settings.globalStoragePath = options?.storagePath;
|
||||
|
||||
if (forceReset) {
|
||||
// if initialize is called more than once, we need to reset the compiler definitions and list of discovered toolsets
|
||||
// (options.quick=true should only be used with tests)
|
||||
resetCompilerDefinitions();
|
||||
settings.discoveredToolsets.clear();
|
||||
discoveringInProgress.clear();
|
||||
}
|
||||
|
||||
// add the configuration folders to the list of folders to scan
|
||||
configFolders.forEach(each => configurationFolders.add(each));
|
||||
|
||||
await loadToolsetData();
|
||||
// if we have zero entries, then we're going to prioritize finding well known compilers on the PATH.
|
||||
if (settings.discoveredToolsets.size === 0) {
|
||||
try {
|
||||
await getWellKnownBinariesFromPath();
|
||||
} catch {
|
||||
// ignore any failures during this process
|
||||
}
|
||||
}
|
||||
|
||||
initialized.resolve();
|
||||
|
||||
// find the well-known compilers on the path
|
||||
|
||||
if (forceReset) {
|
||||
// we kick off the discovery in the background but we wait
|
||||
// a few seconds to give the intelliSense engine an opportunity to start up
|
||||
// and perhaps get a few requests for previously discovered toolsets.
|
||||
// but we really do want to start the discovery so that it's in progress in the background
|
||||
// for the next time it's needed.
|
||||
void sleep(5000).then(() => getToolsets());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Async scan for all compilers using the definitions (toolset.*.json) in the given folders
|
||||
* (iterate over this with `for await`)
|
||||
*
|
||||
* UNUSED-- TARGET FOR DELETION
|
||||
* /
|
||||
export async function* detectToolsets(): AsyncIterable<Toolset> {
|
||||
const results = accumulator<Toolset>();
|
||||
for await (const definition of loadCompilerDefinitions(configurationFolders)) {
|
||||
results.add(searchForToolsets(definition));
|
||||
}
|
||||
results.complete();
|
||||
yield* results;
|
||||
}
|
||||
*/
|
||||
|
||||
/** Returns the discovered toolsets all at once
|
||||
*
|
||||
* If the discovery has been done before, it will just return the cached results.
|
||||
* If it hasn't, it will run the discovery process and then return all the results.
|
||||
*
|
||||
* To reset the cache, call initialize() before calling this.
|
||||
*/
|
||||
export async function getToolsets() {
|
||||
if (!initialized) {
|
||||
throw new Error('Compiler detection has not been initialized. Call initialize() before calling this.');
|
||||
}
|
||||
|
||||
// ensure that init is done
|
||||
await initialized;
|
||||
|
||||
// this exponentially/asychnronously searches for toolsets using the configuration folders
|
||||
for await (const definition of loadCompilerDefinitions(configurationFolders)) {
|
||||
// have we started searching with this definition yet?
|
||||
const searching = discoveringInProgress.get(definition);
|
||||
|
||||
// yeah, we're already searching, so skip this one
|
||||
if (is.promise(searching)) {
|
||||
continue;
|
||||
}
|
||||
// nope, we haven't started searching yet, so start it now
|
||||
discoveringInProgress.set(definition, then(async () => {
|
||||
for await (const toolset of searchForToolsets(definition)) {
|
||||
if (toolset) {
|
||||
verbose(`Detected Compiler ${toolset.name}`);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
// wait for the inProgress searches to complete
|
||||
discovering = Promise.all(discoveringInProgress.values());
|
||||
|
||||
await discovering;
|
||||
|
||||
// return the results
|
||||
return settings.discoveredToolsets;
|
||||
}
|
||||
|
||||
function lookupToolset(name: string) {
|
||||
// simple lookup first
|
||||
const result = settings.discoveredToolsets.get(name);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// if the name isn't wildcarded, and it's not a full path, then we just look at the filenames
|
||||
if (name.match(/[\\\/*?]/) === null) {
|
||||
for (const toolset of settings.discoveredToolsets.values()) {
|
||||
if (name === basename(toolset.compilerPath)) {
|
||||
return toolset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if the candidate is a name of a toolset (* AND ? are supported)
|
||||
const rx = new RegExp(escapeStringRegExp(name).replace(/\\\*/g, '.*'));
|
||||
|
||||
// iterate over the discovered toolsets starting with the highest versions
|
||||
for (const toolset of [...settings.discoveredToolsets.values()].sort((a, b) => rcompare(a.version ?? "0.0.0", b.version ?? "0.0.0"))) {
|
||||
// return the first match given the regex
|
||||
if (rx.exec(toolset.name)) {
|
||||
return toolset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const identifyInProgress = new Map<string, Promise<Toolset | undefined>>();
|
||||
|
||||
/**
|
||||
* Given a path to a binary, identify the compiler
|
||||
* @param candidate the path to the binary to identify
|
||||
* @returns a Toolset or undefined.
|
||||
*/
|
||||
export async function identifyToolset(candidate: string): Promise<Toolset | undefined> {
|
||||
if (!initialized) {
|
||||
throw new Error('Compiler detection has not been initialized. Call initialize() before calling this.');
|
||||
}
|
||||
await initialized;
|
||||
|
||||
// quick check if the given path is already in the discovered toolsets
|
||||
const toolset = lookupToolset(candidate);
|
||||
if (toolset) {
|
||||
return toolset;
|
||||
}
|
||||
|
||||
// check if we're already identifying this candidate
|
||||
if (identifyInProgress.get(candidate)) {
|
||||
return identifyInProgress.get(candidate);
|
||||
}
|
||||
|
||||
// set this candidate to in-progress.
|
||||
const promise = new ManualPromise<Toolset | undefined>();
|
||||
identifyInProgress.set(candidate, promise);
|
||||
|
||||
// get file info for the candidate (is it even a file?)
|
||||
const fileInfo = await filepath.info(candidate);
|
||||
|
||||
if (!fileInfo?.isFile) {
|
||||
// if it's not a file let's quickly check for a match in the discovered toolsets
|
||||
const toolset = lookupToolset(candidate);
|
||||
if (toolset) {
|
||||
return promise.resolve(toolset);
|
||||
}
|
||||
|
||||
// we didn't find it, but the discovery may not be done yet, (or hasn't been done).
|
||||
// make sure discovery is complete before doing another lookup.
|
||||
await (is.promise(discovering) ? discovering : getToolsets());
|
||||
|
||||
return promise.resolve(lookupToolset(candidate));
|
||||
}
|
||||
|
||||
if (fileInfo.isExecutable) {
|
||||
// otherwise, let's use the definitions to try to identify it.
|
||||
return identify(candidate).then((result) => promise.resolve(result));
|
||||
}
|
||||
// otherwise...
|
||||
return promise.resolve(undefined);
|
||||
}
|
||||
|
||||
async function identify(candidate: string, name?: string): Promise<Toolset | undefined> {
|
||||
const bn = basename(candidate);
|
||||
for await (const definition of loadCompilerDefinitions(configurationFolders)) {
|
||||
if (!name || definition.name === name) {
|
||||
const resolver = createResolver(definition);
|
||||
await runConditions(definition, resolver);
|
||||
|
||||
if (strings(definition.discover.binary).includes(basename(bn, isWindows ? '.exe' : undefined))) {
|
||||
const toolset = await discover(candidate, definition);
|
||||
if (toolset) {
|
||||
return toolset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Given a specific definition file, detect a compiler
|
||||
*
|
||||
* If a path to candidate is passed in then we will only check that path.
|
||||
*
|
||||
* Otherwise, it will scan the $PATH, $ProgramFiles* and locations specified in the definition file.
|
||||
*/
|
||||
async function* searchForToolsets(definition: DefinitionFile): AsyncIterable<Toolset | undefined> {
|
||||
// run the conditions once before we start.
|
||||
const resolver = createResolver(definition);
|
||||
await runConditions(definition, resolver);
|
||||
|
||||
// create the finder
|
||||
const finder = new FastFinder(strings(definition.discover.binary), { executable: true, executableExtensions: ['.exe'] });
|
||||
|
||||
// start scanning the folders in the $PATH
|
||||
finder.scan(...await filterToFolders(pathsFromVariable('PATH')));
|
||||
|
||||
// add any folders that the definition specifies (expand any variables)
|
||||
finder.scan(10, ...await render(strings(definition.discover.locations), {}, resolver));
|
||||
|
||||
// add any platform folders
|
||||
switch (os.platform()) {
|
||||
case 'win32':
|
||||
finder.scan(10, ...['ProgramFiles', 'ProgramW6432', 'ProgramFiles(x86)', 'ProgramFiles(Arm)'].map(each => process.env[each]).filter(each => each) as string[]);
|
||||
break;
|
||||
case 'linux':
|
||||
finder.scan(10, '/usr/lib/');
|
||||
break;
|
||||
case 'darwin':
|
||||
break;
|
||||
}
|
||||
|
||||
const results = accumulator<Toolset>();
|
||||
|
||||
// kick off each discovery asynchronously
|
||||
for await (const compilerPath of finder) {
|
||||
results.add(discover(compilerPath, definition));
|
||||
}
|
||||
results.complete();
|
||||
|
||||
// return them as they complete.
|
||||
yield* results;
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-dynamic-delete */
|
||||
|
||||
import { is } from '../../Utility/System/guards';
|
||||
import { structuredClone } from '../../Utility/System/structuredClone';
|
||||
import { strings } from '../strings';
|
||||
|
||||
function isMergeble(item: any): boolean {
|
||||
return item !== null && typeof item === 'object' && !is.array(item);
|
||||
}
|
||||
|
||||
export function replaceOrInsert(original: Record<string, any>, key: string, value: any): Record<string, any> {
|
||||
return Object.keys(original).reduce((result, existingKey, index) => {
|
||||
if (!index) {
|
||||
result[key] = value;
|
||||
}
|
||||
if (existingKey !== key) {
|
||||
result[existingKey] = original[existingKey];
|
||||
}
|
||||
return result;
|
||||
}, {} as Record<string, any>);
|
||||
}
|
||||
|
||||
function expandArray(value: any): any {
|
||||
return is.array(value) ? value.map(each => expandArray(each)).flat() : is.string(value) && value.includes('\u0007') ? value.split('\u0007') : value;
|
||||
}
|
||||
|
||||
export function mergeObjects<T extends Record<string, any>>(input: T, dataToMerge: Record<string, any>, options?: {uniqueArrays?: boolean}): T {
|
||||
const target: any = input;
|
||||
const uniqueArrays = options?.uniqueArrays ?? false;
|
||||
|
||||
if (is.promise(input) || is.promise(dataToMerge)) {
|
||||
throw new Error('Should not get promises here!');
|
||||
}
|
||||
|
||||
if (isMergeble(target) && isMergeble(dataToMerge)) {
|
||||
|
||||
for (let [key, value] of Object.entries(dataToMerge)) {
|
||||
if (key === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key.startsWith('remove:')) {
|
||||
key = key.substring(7);
|
||||
if (target[key]) {
|
||||
const v: string[] = strings(value);
|
||||
if (is.array(target[key])) {
|
||||
target.key[key] = target[key].filter((each: string) => !v.includes(each));
|
||||
} else if (is.string(target[key]) && v.includes(target[key])) {
|
||||
delete target[key];
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const prepend: boolean = key.startsWith('prepend:');
|
||||
if (prepend) {
|
||||
key = key.substring(8);
|
||||
}
|
||||
|
||||
// if this is supposed to be an array, lets expand it now.
|
||||
value = expandArray(value);
|
||||
|
||||
// if there isn't a target value, just assign a copy of the source value
|
||||
if (target[key] === undefined) {
|
||||
target[key] = structuredClone(value);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (target[key] === value) {
|
||||
//* console.log(`Same Same -> '${key}' => '${value}'`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// if the source value is null, we're going to delete the target value
|
||||
if (value === null) {
|
||||
delete target[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
// if the source value is empty/undefined, we're going to leave the target value as is
|
||||
if (value === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if the source value is an array, the target is going to be an array.
|
||||
if (is.array(value)) {
|
||||
// arrays are appended
|
||||
if (target[key] === undefined) {
|
||||
// no target value, just assign
|
||||
target[key] = [...value];
|
||||
} else if (is.array(target[key])) {
|
||||
// target value is an array, append or prepend
|
||||
if (prepend) {
|
||||
target[key].unshift(...value);
|
||||
} else {
|
||||
target[key].push(...value);
|
||||
}
|
||||
if (uniqueArrays) {
|
||||
target[key] = [...new Set(target[key])];
|
||||
}
|
||||
} else if (is.string(target[key])) {
|
||||
// strings are converted to arrays
|
||||
target[key] = [value, ...value];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// if the source value is an object, we're going to merge that with the target
|
||||
if (isMergeble(value)) {
|
||||
mergeObjects(target[key], value, { uniqueArrays : key === 'path' });
|
||||
continue;
|
||||
}
|
||||
|
||||
// otherwise,
|
||||
target[key] = value;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
import * as os from 'os';
|
||||
import { homedir } from 'os';
|
||||
import { basename, delimiter, sep } from 'path';
|
||||
import { Cache } from '../../Utility/System/cache';
|
||||
import { readKey } from '../../Utility/System/registry';
|
||||
import { CustomResolver } from '../../Utility/Text/taggedLiteral';
|
||||
import { DefinitionFile, IntelliSenseConfiguration } from '../interfaces';
|
||||
|
||||
export function createResolver(definition: DefinitionFile, compilerPath: string = ''): CustomResolver {
|
||||
// cache values/registry reads for the duration of the resolver.
|
||||
// (that is, scoped to the current definition)
|
||||
// this will drastically speed up resolution if an expensive variable is used repeatedly.
|
||||
const valueCache = new Cache<any>();
|
||||
|
||||
// the resolver function
|
||||
return async (prefix: string, expression: string): Promise<string> => {
|
||||
const cacheKey = `${prefix}:${expression}`;
|
||||
const cached = valueCache.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
function cache(value: any) {
|
||||
return valueCache.set(cacheKey, value);
|
||||
}
|
||||
|
||||
switch (prefix) {
|
||||
case 'env':
|
||||
// make sure ${env:HOME} is expanded to the user's home directory always
|
||||
if (expression.toLowerCase() === 'home') {
|
||||
return cache(homedir());
|
||||
}
|
||||
return cache(process.env[expression] || '');
|
||||
|
||||
case 'definition':
|
||||
return cache((definition as any)[expression] || '');
|
||||
|
||||
case 'HKLM':
|
||||
case 'HKCU':
|
||||
const [path, value] = expression.split(';');
|
||||
return cache((await readKey(prefix, path))?.properties[value]?.toString() || '');
|
||||
|
||||
case 'host':
|
||||
switch (expression) {
|
||||
case 'os':
|
||||
case 'platform':
|
||||
return cache(os.platform());
|
||||
|
||||
case 'arch':
|
||||
case 'architecture':
|
||||
return cache(os.arch());
|
||||
}
|
||||
break;
|
||||
|
||||
case 'compilerPath':
|
||||
switch (expression) {
|
||||
case 'basename':
|
||||
return cache(process.platform === 'win32' ? basename(compilerPath, '.exe') : basename(compilerPath));
|
||||
}
|
||||
break;
|
||||
|
||||
case '':
|
||||
switch (expression) {
|
||||
case 'cwd':
|
||||
return cache(process.cwd()); // fake, this should come from the host.
|
||||
|
||||
case 'pathSeparator':
|
||||
return sep;
|
||||
|
||||
case 'pathDelimiter':
|
||||
return delimiter;
|
||||
|
||||
case 'name':
|
||||
return definition.name;
|
||||
|
||||
case 'binary':
|
||||
case 'compilerPath':
|
||||
return compilerPath;
|
||||
|
||||
default:
|
||||
// if the request was looking for a value in the intellisense configuration, we'll try to resolve that
|
||||
if (definition.intellisense && expression in definition.intellisense) {
|
||||
return cache((definition.intellisense as any)[expression as keyof IntelliSenseConfiguration]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (definition.intellisense?.[expression]) {
|
||||
return definition.intellisense[expression];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
}
|
||||
@@ -1,621 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable prefer-const */
|
||||
|
||||
import { unlinkSync, writeFileSync } from 'fs';
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
import { delimiter, dirname, resolve } from 'path';
|
||||
import { filepath, mkdir, tmpFile } from '../../Utility/Filesystem/filepath';
|
||||
import { Command, CommandFunction, cmdlineToArray } from '../../Utility/Process/program';
|
||||
import { is } from '../../Utility/System/guards';
|
||||
import { CustomResolver, evaluateExpression, recursiveRender, render } from '../../Utility/Text/taggedLiteral';
|
||||
import { formatIntelliSenseBlock } from './definition';
|
||||
|
||||
import { parse } from 'comment-json';
|
||||
import { sleep } from '../../Utility/Async/sleep';
|
||||
import { Cache, isExpired, isLater } from '../../Utility/System/cache';
|
||||
import { structuredClone } from '../../Utility/System/structuredClone';
|
||||
import { CStandard, CppStandard, DeepPartial, DefinitionFile, IntelliSense, IntelliSenseConfiguration, Language, OneOrMore } from '../interfaces';
|
||||
import { getActions, strings } from '../strings';
|
||||
import { mergeObjects } from './objectMerge';
|
||||
import { createResolver } from './resolver';
|
||||
import { log } from './worker';
|
||||
|
||||
function isC(language?: string): boolean {
|
||||
return language === 'c';
|
||||
}
|
||||
|
||||
function isCpp(language?: string): boolean {
|
||||
return language === 'cpp' || language === 'c++';
|
||||
}
|
||||
|
||||
export let settings = {
|
||||
globalStoragePath: undefined as string | undefined,
|
||||
discoveredToolsets: new Cache<Toolset>(Cache.OneWeek),
|
||||
get toolsetFilePath() {
|
||||
return settings.globalStoragePath ? resolve(settings.globalStoragePath, 'detected-toolsets.json') : '';
|
||||
},
|
||||
timestamp: 0
|
||||
};
|
||||
|
||||
/** Trims out empty elements during serialization to JSON */
|
||||
function trim(key: string, value: any) {
|
||||
if (is.array(value) && value.length === 0) {
|
||||
// empty arrays
|
||||
return undefined;
|
||||
}
|
||||
if (is.object(value) && Object.keys(value).length === 0) {
|
||||
// empty objects
|
||||
return undefined;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export async function persistToolsetData() {
|
||||
// we can only store data if the globalStoragePath is set
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
if (1 !== 1 && settings.globalStoragePath) {
|
||||
|
||||
// ensure the folder is created
|
||||
await mkdir(settings.globalStoragePath);
|
||||
|
||||
// check to see if we have a file already
|
||||
const info = await filepath.info(settings.toolsetFilePath);
|
||||
if (info?.isFile && info.timestamp > settings.timestamp) {
|
||||
// we do have a file, and it's newer than the last time we wrote to it.
|
||||
// so we should merge that data before we write to it.
|
||||
await loadToolsetData();
|
||||
}
|
||||
|
||||
// ok, serialize out the data we have
|
||||
const contents = {} as Record<string, [number, Record<string, any>]>;
|
||||
for (const [path, [timeout, toolset]] of settings.discoveredToolsets.cacheEntries()) {
|
||||
contents[path] = [timeout, toolset.serialize()];
|
||||
}
|
||||
|
||||
log(`persisting ${Object.keys(contents).length} toolsets to ${settings.toolsetFilePath}`);
|
||||
|
||||
// write out the file
|
||||
await writeFile(settings.toolsetFilePath, JSON.stringify(contents, trim));
|
||||
|
||||
// set our timestamp to now
|
||||
settings.timestamp = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
function mergeToolsetData(newer: Toolset, older: Toolset) {
|
||||
for (const [key, [timeout, query]] of older.cachedQueries.cacheEntries()) {
|
||||
newer.cachedQueries.set(key, query, timeout);
|
||||
}
|
||||
for (const [key, [timeout, analysis]] of older.cachedAnalysis.cacheEntries()) {
|
||||
newer.cachedAnalysis.set(key, analysis, timeout);
|
||||
}
|
||||
return newer;
|
||||
}
|
||||
|
||||
export async function loadToolsetData() {
|
||||
if (!settings.globalStoragePath) {
|
||||
log(`GlobalStoragePath not set - can't load toolsets`);
|
||||
return false;
|
||||
}
|
||||
const location = resolve(settings.globalStoragePath, 'detected-toolsets.json');
|
||||
const cachePath = await filepath.isFile(location);
|
||||
if (!cachePath) {
|
||||
log(`No cached toolset file at ${location} - can't load toolsets`);
|
||||
return false;
|
||||
}
|
||||
|
||||
log(`loading toolsets from ${settings.toolsetFilePath}`);
|
||||
const entries = parse(await readFile(cachePath, 'utf8')) as Record<string, any>;
|
||||
if (!is.object(entries)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const [path, [timeout, serializedToolset]] of Object.entries(entries)) {
|
||||
// in the event that something throws, we'll skip it an move on.
|
||||
try {
|
||||
// if the entry is expired, skip it completely.
|
||||
if (isExpired(timeout)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if we have one, let's see if it's newer than the one we have.
|
||||
const current = settings.discoveredToolsets.getCacheEntry(path);
|
||||
if (!current) {
|
||||
// we don't have one currently, so let's just deserialize this one
|
||||
const toolset = settings.discoveredToolsets.set(path, await Toolset.deserialize(serializedToolset), timeout);
|
||||
log(`Loaded toolset: ${toolset?.name}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// there is a current one.
|
||||
|
||||
// ok, merge the current one and the deserialized one, and then set the cache to the merged one.
|
||||
let toolset = await Toolset.deserialize(serializedToolset);
|
||||
log(`Loaded toolset (merging): ${toolset?.name}`);
|
||||
if (toolset) {
|
||||
if (isLater(timeout, current[0])) {
|
||||
// merge the current one into the deserialized one
|
||||
settings.discoveredToolsets.set(path, mergeToolsetData(toolset, current[1]), timeout);
|
||||
} else {
|
||||
// merge the deserialized one into the current one
|
||||
settings.discoveredToolsets.set(path, mergeToolsetData(current[1], toolset), timeout);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore deserialization failures
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
type Entries = {
|
||||
action: string;
|
||||
block: Record<string, IntelliSenseConfiguration>;
|
||||
flags: Map<string, string | boolean>;
|
||||
priority: number;
|
||||
comment?: string | undefined;
|
||||
}[];
|
||||
|
||||
/**
|
||||
* The Toolset is the final results of the [discovery+query] process
|
||||
*
|
||||
* This is the contents that we're going to eventually pass to the back end.
|
||||
*/
|
||||
export class Toolset {
|
||||
cachedQueries = new Cache<string>(Cache.OneWeek);
|
||||
cachedAnalysis = new Cache<IntelliSenseConfiguration>(Cache.OneWeek);
|
||||
resolver: CustomResolver;
|
||||
cmd: Promise<CommandFunction>;
|
||||
rxResolver: (prefix: string, expression: string) => any;
|
||||
get default() {
|
||||
return this.definition.intellisense as IntelliSense;
|
||||
}
|
||||
|
||||
get version() {
|
||||
return this.definition.version || this.definition.intellisense?.version;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return `${this.definition.name}/${this.version}/${this.default.architecture}/${this.default.hostArchitecture || process.arch}`;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return {
|
||||
name: this.name,
|
||||
compilerPath: this.compilerPath,
|
||||
definition: this.definition,
|
||||
queries: this.cachedQueries.cacheEntries(),
|
||||
analysis: this.cachedAnalysis.cacheEntries()
|
||||
};
|
||||
}
|
||||
|
||||
static async deserialize(obj: Record<string, any>) {
|
||||
try {
|
||||
const { compilerPath, definition, queries, analysis } = obj;
|
||||
const result = new Toolset(compilerPath, definition);
|
||||
result.cachedQueries = new Cache(queries, Cache.OneWeek);
|
||||
result.cachedAnalysis = new Cache(analysis, Cache.OneWeek);
|
||||
return result;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
constructor(readonly compilerPath: string, readonly definition: DefinitionFile) {
|
||||
this.resolver = createResolver(definition, compilerPath);
|
||||
this.definition.intellisense = this.definition.intellisense || {};
|
||||
this.cmd = new Command(this.compilerPath, { env: { PATH: `${dirname(this.compilerPath)}${delimiter}${process.env.PATH}` } });
|
||||
|
||||
this.rxResolver = async (prefix: string, expression: string) => {
|
||||
if (!prefix) {
|
||||
switch (expression.toLowerCase()) {
|
||||
case '-/':
|
||||
case '/-':
|
||||
return '[\\-\\/]';
|
||||
|
||||
case 'key':
|
||||
case 'keynovalue':
|
||||
return '(?<key>[^=]+)';
|
||||
|
||||
case 'value':
|
||||
return '(?<value>.+)';
|
||||
|
||||
case 'keyequalsvalue':
|
||||
return '(?<key>[^=]+)=(?<value>.+)';
|
||||
}
|
||||
}
|
||||
|
||||
return this.resolver(prefix, expression);
|
||||
};
|
||||
}
|
||||
|
||||
async applyToConfiguration(intellisenseConfiguration: IntelliSenseConfiguration | IntelliSense, partial: DeepPartial<IntelliSenseConfiguration>, data: Record<string, any> = intellisenseConfiguration) {
|
||||
mergeObjects(intellisenseConfiguration, await recursiveRender(formatIntelliSenseBlock(partial), data, this.resolver));
|
||||
}
|
||||
|
||||
async query(command: string, queries: Record<string, DeepPartial<IntelliSenseConfiguration>>, intellisenseConfiguration: IntelliSenseConfiguration) {
|
||||
// check if we've handled this command before.
|
||||
const key = await render(command, {}, this.resolver);
|
||||
let text = this.cachedQueries.get(key);
|
||||
|
||||
if (!text) {
|
||||
// prepare the command to run
|
||||
const cmd = await this.cmd;
|
||||
const tmpFiles = new Array<string>();
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
const commandLine = await render(command, {}, async (prefix, expression) => {
|
||||
if (prefix === 'tmp') {
|
||||
// creating temp files
|
||||
const tmp = tmpFile('tmp.', `.${expression}`);
|
||||
writeFileSync(tmp, '');
|
||||
tmpFiles.push(tmp);
|
||||
switch (expression) {
|
||||
case 'stdout':
|
||||
stdout = tmp;
|
||||
break;
|
||||
case 'stderr':
|
||||
stderr = tmp;
|
||||
break;
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
return this.resolver(prefix, expression);
|
||||
});
|
||||
|
||||
// parse the arguments and replace any tmp files with actual files
|
||||
const args = cmdlineToArray(commandLine);
|
||||
// execute the command line now.
|
||||
const out = await cmd(...args);
|
||||
text = [...out.stdio.all(), ...out.error.all()].join('\n');
|
||||
|
||||
if (stdout) {
|
||||
text += await readFile(stdout, 'utf8');
|
||||
}
|
||||
if (stderr) {
|
||||
text += await readFile(stderr, 'utf8');
|
||||
}
|
||||
|
||||
for (const tmp of tmpFiles) {
|
||||
text = text!.replace(new RegExp(tmp, 'g'), '');
|
||||
unlinkSync(tmp);
|
||||
}
|
||||
|
||||
this.cachedQueries.set(key, text);
|
||||
void persistToolsetData();
|
||||
}
|
||||
|
||||
// now we can process the queries
|
||||
for (const [rxes, isense] of Object.entries(queries)) {
|
||||
for (const rx of strings(rxes)) {
|
||||
for (const match of [...text.matchAll(new RegExp(rx, 'gm'))]) {
|
||||
if (match?.groups) {
|
||||
// transform multi-line values into arrays
|
||||
const data = {} as Record<string, any>;
|
||||
|
||||
for (let [variable, value] of Object.entries(match.groups)) {
|
||||
value = value || '';
|
||||
data[variable] = value.includes('\n') ?
|
||||
value.split('\n').map(each => each.trim()).filter(each => each) :
|
||||
value;
|
||||
}
|
||||
|
||||
await this.applyToConfiguration(intellisenseConfiguration, isense, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async runTasks(block: OneOrMore<string>, commandLineArgs: string[]) {
|
||||
for (const task of strings(block)) {
|
||||
switch (task) {
|
||||
case 'inline-environment-variables':
|
||||
const CL = process.env.CL;
|
||||
const _CL_ = process.env['_CL_'];
|
||||
if (CL) {
|
||||
commandLineArgs.push(...cmdlineToArray(CL));
|
||||
}
|
||||
if (_CL_) {
|
||||
commandLineArgs.unshift(...cmdlineToArray(_CL_));
|
||||
}
|
||||
break;
|
||||
case 'inline-response-file':
|
||||
// scan thru the command line arguments and look for @file
|
||||
// and replace it with the contents of the file
|
||||
for (let i = 0; i < commandLineArgs.length; i++) {
|
||||
if (commandLineArgs[i].startsWith('@')) {
|
||||
const file = commandLineArgs[i].slice(1);
|
||||
const contents = await readFile(file, 'utf8');
|
||||
commandLineArgs.splice(i, 1, ...cmdlineToArray(contents));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'consume-lib-path':
|
||||
|
||||
break;
|
||||
|
||||
case 'remove-linker-arguments':
|
||||
const link = commandLineArgs.findIndex(each => /^[\/-]link$/i.exec(each));
|
||||
if (link !== -1) {
|
||||
commandLineArgs.length = link; // drop it and all that follow
|
||||
}
|
||||
break;
|
||||
|
||||
case 'zwCommandLineSwitch':
|
||||
break;
|
||||
|
||||
case 'experimentalModuleNegative':
|
||||
break;
|
||||
|
||||
case 'verifyIncludes':
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async processComamndLineArgs(block: Record<string, any>, commandLineArgs: string[], intellisenseConfiguration: IntelliSenseConfiguration, flags: Map<string, any>) {
|
||||
// get all the regular expressions and the results to apply
|
||||
let allEngineeredRegexes: [RegExp[], any][] = [];
|
||||
for (const [engineeredRx, result] of Object.entries(block)) {
|
||||
const rxes: RegExp[] = [];
|
||||
for (const rx of engineeredRx.split(';')) {
|
||||
rxes.push(new RegExp(await render(`^${rx}$`, {}, this.rxResolver)));
|
||||
|
||||
}
|
||||
allEngineeredRegexes.push([rxes, result]);
|
||||
}
|
||||
const keptArgs = new Array<string>();
|
||||
|
||||
nextArg:
|
||||
while (commandLineArgs.length) {
|
||||
nextRx:
|
||||
for (const [engineeredRegexSet, isense] of allEngineeredRegexes) {
|
||||
const capturedData = {};
|
||||
for (const result of engineeredRegexSet.map((rx, index) => rx.exec(commandLineArgs[index]))) {
|
||||
if (result === null) {
|
||||
continue nextRx; // something didn't match, we don't care.
|
||||
}
|
||||
if (result.groups) {
|
||||
mergeObjects(capturedData, result.groups);
|
||||
}
|
||||
}
|
||||
// now we can apply the results to the intellisenseConfiguration
|
||||
await this.applyToConfiguration(intellisenseConfiguration, isense, capturedData);
|
||||
|
||||
// remove the args used from the command line
|
||||
const usedArgs = commandLineArgs.splice(0, engineeredRegexSet.length);
|
||||
|
||||
// but if the no_consume flag set, we should keep the args in the KeptArgs list
|
||||
if (flags.get('no_consume')) {
|
||||
// remove the arguments from the command line
|
||||
keptArgs.push(...usedArgs);
|
||||
}
|
||||
continue nextArg;
|
||||
}
|
||||
|
||||
// if we got here after running the expressions, we did not have a match.
|
||||
// so we can just assume that something else will look at them later
|
||||
keptArgs.push(commandLineArgs.shift()!);
|
||||
}
|
||||
return keptArgs;
|
||||
}
|
||||
|
||||
async ensurePathsAreLegit(obj: Record<string, any>) {
|
||||
for (let [key, value] of Object.entries(obj)) {
|
||||
const k = key.toLowerCase();
|
||||
// if it's a *path(s), let's make sure they are real
|
||||
if (['path', 'paths', 'file', 'files'].find(each => k.endsWith(each))) {
|
||||
if (is.string(value)) {
|
||||
// if we started with a string, let's check if it's a concatenated path first.
|
||||
const values = value.split(delimiter);
|
||||
if (values.length <= 1) {
|
||||
obj[key] = await filepath.exists(render(value as string, {}, this.resolver)) || value;
|
||||
continue;
|
||||
}
|
||||
|
||||
// concatenated path (with delimiters)
|
||||
value = values;
|
||||
}
|
||||
|
||||
// if it's an array, let's check each value now.
|
||||
if (is.array(value)) {
|
||||
obj[key] = [...new Set(await Promise.all(value.map(each => each && filepath.exists(render(each as string, {}, this.resolver)))))].filter(each => each);
|
||||
}
|
||||
}
|
||||
|
||||
// if it's a nested object, let's recurse
|
||||
if (is.object(value)) {
|
||||
await this.ensurePathsAreLegit(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async process(entries: Entries, compilerArgs: string[], intellisenseConfiguration: IntelliSenseConfiguration) {
|
||||
for (const { action, block, flags } of entries) {
|
||||
// If the flags specifies 'C' and the language is not 'c', then we should skip this section.
|
||||
if (flags.get('c') && !isC(intellisenseConfiguration.lanugage)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the flags specifies 'c++' and the language is not 'c++', then we should skip this section.
|
||||
if (flags.get('cpp') || flags.get('c++') && !isCpp(intellisenseConfiguration.lanugage)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case 'task':
|
||||
await this.runTasks(block as unknown as OneOrMore<string>, compilerArgs /* , intellisenseConfiguration */);
|
||||
break;
|
||||
|
||||
case 'command':
|
||||
// commandLineArguments
|
||||
compilerArgs = await this.processComamndLineArgs(block, compilerArgs, intellisenseConfiguration, flags);
|
||||
break;
|
||||
|
||||
case 'quer':
|
||||
for (const [command, queries] of Object.entries(block as Record<string, Record<string, DeepPartial<IntelliSenseConfiguration>>>)) {
|
||||
await this.query(command, queries, intellisenseConfiguration);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'expression':
|
||||
for (const [expr, isense] of Object.entries(block as Record<string, DeepPartial<IntelliSenseConfiguration>>)) {
|
||||
if (await evaluateExpression(expr, intellisenseConfiguration, this.resolver)) {
|
||||
await this.applyToConfiguration(intellisenseConfiguration, isense);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return compilerArgs;
|
||||
}
|
||||
current: Promise<IntelliSenseConfiguration> | undefined;
|
||||
async _getIntellisenseConfiguration(compilerArgs: string[], options?: { baseDirectory?: string; sourceFile?: string; language?: Language; standard?: CppStandard | CStandard; userIntellisenseConfiguration?: IntelliSenseConfiguration }): Promise<IntelliSenseConfiguration> {
|
||||
while (this.current) {
|
||||
await this.current;
|
||||
await sleep(10);
|
||||
}
|
||||
|
||||
this.current = this._getIntellisenseConfiguration(compilerArgs, options);
|
||||
const result = await this.current;
|
||||
this.current = undefined;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the analysis section of the definition file given a command line to work with
|
||||
*/
|
||||
async getIntellisenseConfiguration(compilerArgs: string[], options?: { baseDirectory?: string; sourceFile?: string; language?: Language; standard?: CppStandard | CStandard; userIntellisenseConfiguration?: IntelliSenseConfiguration }): Promise<IntelliSenseConfiguration> {
|
||||
let entries: Entries = [];
|
||||
const userIntellisenseConfiguration = options?.userIntellisenseConfiguration ? structuredClone(options.userIntellisenseConfiguration) : {};
|
||||
|
||||
// if we have an analysis section, we're going to need to get it ready
|
||||
if (this.definition.analysis) {
|
||||
entries = getActions<Record<string, IntelliSenseConfiguration>>(this.definition.analysis as any, [
|
||||
['task', ['priority', 'c', 'cpp', 'c++', 'once']],
|
||||
['command', ['priority', 'c', 'cpp', 'c++', 'no_consume', 'once']],
|
||||
['quer', ['priority', 'c', 'cpp', 'c++', 'once']],
|
||||
['expression', ['priority', 'c', 'cpp', 'c++', 'once']]
|
||||
]);
|
||||
}
|
||||
|
||||
const early = entries.filter(each => each.priority < 0);
|
||||
let intellisenseConfiguration = {
|
||||
...structuredClone(this.definition.intellisense),
|
||||
language: options?.language,
|
||||
standard: options?.standard,
|
||||
compilerPath: this.compilerPath
|
||||
} as IntelliSenseConfiguration;
|
||||
|
||||
// process the 'early' steps before we generate the cache key so that we can filter out useless args
|
||||
if (early.length) {
|
||||
compilerArgs = await this.process(early, compilerArgs, intellisenseConfiguration);
|
||||
}
|
||||
|
||||
const late = entries.filter(each => each.priority >= 0);
|
||||
|
||||
const cacheKey = compilerArgs.join(' ');
|
||||
const i = this.cachedAnalysis.get(cacheKey);
|
||||
if (i) {
|
||||
intellisenseConfiguration = structuredClone(i);
|
||||
// after getting the cached results, merge in user settings (which are not cached here)
|
||||
if (options?.userIntellisenseConfiguration) {
|
||||
await this.applyToConfiguration(intellisenseConfiguration, userIntellisenseConfiguration);
|
||||
|
||||
// before we go, let's make sure that any *paths are unique, and that they are all absolute
|
||||
await this.ensurePathsAreLegit(intellisenseConfiguration);
|
||||
}
|
||||
|
||||
this.postProcessIntellisense(intellisenseConfiguration);
|
||||
|
||||
return intellisenseConfiguration;
|
||||
}
|
||||
|
||||
// (late) Analysis phase
|
||||
compilerArgs = await this.process(late, compilerArgs, intellisenseConfiguration);
|
||||
|
||||
// before we go, let's make sure that any *paths are unique, and that they are all absolute
|
||||
await this.ensurePathsAreLegit(intellisenseConfiguration);
|
||||
|
||||
// render any variables that are left (if therer are value that are specified explicity in definition that reference variables, this is when they get resolved)
|
||||
intellisenseConfiguration = await recursiveRender(intellisenseConfiguration, intellisenseConfiguration, this.resolver);
|
||||
|
||||
// cache the results
|
||||
this.cachedAnalysis.set(cacheKey, intellisenseConfiguration);
|
||||
void persistToolsetData();
|
||||
|
||||
intellisenseConfiguration = structuredClone(intellisenseConfiguration);
|
||||
|
||||
// after the cached results, merge in user settings (since the user can change those at any time)
|
||||
if (options?.userIntellisenseConfiguration) {
|
||||
await this.applyToConfiguration(intellisenseConfiguration, userIntellisenseConfiguration);
|
||||
|
||||
// before we go, let's make sure that any *paths are unique, and that they are all absolute
|
||||
await this.ensurePathsAreLegit(intellisenseConfiguration);
|
||||
}
|
||||
|
||||
this.postProcessIntellisense(intellisenseConfiguration);
|
||||
|
||||
/// this.postProcessIntellisense(intellisenseConfiguration);
|
||||
|
||||
return intellisenseConfiguration;
|
||||
}
|
||||
|
||||
/** the final steps to producing the parser args for EDG */
|
||||
postProcessIntellisense(intellisense: IntelliSense) {
|
||||
const args = [];
|
||||
// turn the macros into -D flags
|
||||
if (intellisense.macro) {
|
||||
for (const [name, value] of Object.entries(intellisense.macro)) {
|
||||
args.push(`-D${name}=${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
// generate the two sets of include paths that EDG supports:
|
||||
// --inlcude_directory and --sys_include
|
||||
for (const each of intellisense.path?.builtInInclude ?? []) {
|
||||
args.push('--sys_include', each);
|
||||
/// args.push(`-I${each}`);
|
||||
}
|
||||
|
||||
for (const each of intellisense.path?.systemInclude ?? []) {
|
||||
args.push('--sys_include', each);
|
||||
}
|
||||
for (const each of intellisense.path?.externalInclude ?? []) {
|
||||
args.push('--sys_include', each);
|
||||
}
|
||||
|
||||
for (const each of intellisense.path?.include ?? []) {
|
||||
args.push('--include_directory', each);
|
||||
}
|
||||
|
||||
for (const each of intellisense.path?.environmentInclude ?? []) {
|
||||
args.push('--include_directory', each);
|
||||
}
|
||||
|
||||
intellisense.parserArgument = strings(intellisense.parserArgument).concat(args);
|
||||
intellisense.queryArgument = undefined;
|
||||
intellisense.parserArgument.unshift('cpfe', "--no_warnings", "--edge", "--error_limit", "25000", "-D_EDG_COMPILER");
|
||||
|
||||
for (let i = 0; i < intellisense.parserArgument.length; i++) {
|
||||
const arg = intellisense.parserArgument[i];
|
||||
if (arg.startsWith('--c') && arg.length > 5) {
|
||||
intellisense.parserArgument[i] = '--c' + arg.substring(5, 7);
|
||||
}
|
||||
}
|
||||
|
||||
return intellisense;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import { parentPort } from 'worker_threads';
|
||||
import { getByRef, ref, startRemoting, unref } from '../../Utility/System/snare';
|
||||
import { getToolsets, identifyToolset, initialize } from './discovery';
|
||||
import { Toolset } from './toolset';
|
||||
|
||||
/** This is the SNARE remote call interface dispatcher that the worker thread supports */
|
||||
const remote = parentPort ? startRemoting(parentPort, {
|
||||
unref,
|
||||
initialize,
|
||||
getToolsets: () => getToolsets().then(toolsets => toolsets.entries()),
|
||||
identifyToolset: (candidate: string) => ref(identifyToolset(candidate)),
|
||||
"Toolset.getIntellisenseConfiguration": (identity: number, compilerArgs: string[], options: any) => getByRef<Toolset>(identity).getIntellisenseConfiguration(compilerArgs, options)
|
||||
}) : undefined; //: fail("Remoting: Failed to start remote thread - no parent port");
|
||||
|
||||
export function log(text: string) {
|
||||
if (!remote) {
|
||||
console.log(text);
|
||||
return;
|
||||
}
|
||||
remote.notify('console.log', text);
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import { Configuration, NewIntelliSense } from '../LanguageServer/configurations';
|
||||
import { MarshalByReference, startRemoting } from '../Utility/System/snare';
|
||||
import { CStandard, CppStandard, IntelliSenseConfiguration, Language, SizeOf } from './interfaces';
|
||||
import { appendUnique } from './strings';
|
||||
|
||||
import { resolve } from 'path';
|
||||
import { SHARE_ENV, Worker, isMainThread } from 'worker_threads';
|
||||
|
||||
import { SourceFileConfiguration } from 'vscode-cpptools';
|
||||
import { is } from '../Utility/System/guards';
|
||||
import { Mutable } from '../common';
|
||||
import { getOutputChannel } from '../logger';
|
||||
|
||||
// this code must only run in the main thread.
|
||||
if (!isMainThread) {
|
||||
throw new Error("Remoting: Failed to start host thread responder - not on main thread");
|
||||
}
|
||||
|
||||
// starts the worker thread and returns the RemoteConnection object
|
||||
export const remote = startRemoting(new Worker(resolve(__dirname.substring(0, __dirname.lastIndexOf('dist')), "dist", "src", "ToolsetDetection", "Service", 'worker.js'), {stderr:true, stdout: true, env: SHARE_ENV}), {
|
||||
// this is the functions we expose to the worker
|
||||
"console.log": (text: string) => {
|
||||
try {
|
||||
getOutputChannel().appendLine(text);
|
||||
} catch {
|
||||
// fall back to console?
|
||||
console.log(text);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function coerceIntellisense(intellisense: IntelliSenseConfiguration) {
|
||||
if (intellisense) {
|
||||
if (is.string(intellisense.bits)) {
|
||||
intellisense.bits = parseInt(intellisense.bits);
|
||||
}
|
||||
if (is.object(intellisense.sizes)) {
|
||||
for (const [key, value] of Object.entries(intellisense.sizes) as [keyof SizeOf, any][]) {
|
||||
intellisense.sizes[key] = parseInt(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return intellisense;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a byref proxy to the toolset.
|
||||
*
|
||||
* As with all byref proxies, it is a reference to an object that lives in the worker thread.
|
||||
* It is important to call .dispose() when you are done with it, as this enables the worker
|
||||
* thread to release the object and free up resources.
|
||||
*
|
||||
*/
|
||||
export class Toolset extends MarshalByReference {
|
||||
async getIntellisenseConfiguration(compilerArgs: string[], options?: { baseDirectory?: string; sourceFile?: string; language?: Language; standard?: CppStandard | CStandard; userIntellisenseConfiguration?: IntelliSenseConfiguration }): Promise<IntelliSenseConfiguration> {
|
||||
return coerceIntellisense(await this.remote.request('Toolset.getIntellisenseConfiguration', this.instance, compilerArgs, options));
|
||||
}
|
||||
harvestFromConfiguration(configuration: Configuration | (Mutable<SourceFileConfiguration> & NewIntelliSense), intellisense: IntelliSenseConfiguration) {
|
||||
// includePath
|
||||
intellisense.path!.include = appendUnique(intellisense.path!.include, configuration.includePath);
|
||||
|
||||
// macFrameworkPath
|
||||
intellisense.path!.framework = appendUnique(intellisense.path!.framework, (configuration as any).macFrameworkPath);
|
||||
|
||||
// cStandard
|
||||
// cppStandard
|
||||
|
||||
// defines
|
||||
for (const define of configuration.defines || []) {
|
||||
const [,key, value] = /^([^=]+)=*(.*)?$/.exec(define) ?? [];
|
||||
if (key && value) {
|
||||
intellisense.defines[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// forcedInclude
|
||||
intellisense.path!.forcedIncludeFile = appendUnique(intellisense.path!.forcedIncludeFile, configuration.forcedInclude);
|
||||
|
||||
return coerceIntellisense(intellisense);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a remote call to the identifyToolset function in the worker thread.
|
||||
*
|
||||
* @param candidate one of:
|
||||
* - the path to the compiler executable to identify
|
||||
* - a name of a binary on the PATH
|
||||
* - a name of a toolset definition (supports wildcards)
|
||||
* @returns a Promise to either a valid toolset or undefined if there was no match..
|
||||
*/
|
||||
export function identifyToolset(candidate: string): Promise<Toolset | undefined> {
|
||||
return remote.marshall(Toolset, remote.request('identifyToolset', candidate));
|
||||
}
|
||||
|
||||
/** Makes a remote call to initialize the toolset detection system */
|
||||
export async function initialize(configFolders: string[], options?: { quick?: boolean; storagePath?: string }): Promise<void> {
|
||||
return remote.request('initialize', configFolders, options);
|
||||
}
|
||||
|
||||
/** Makes a remote call to get the list of toolsets from the worker thread */
|
||||
export async function getToolsets(): Promise<Map<string, string>> {
|
||||
return new Map(await remote.request('getToolsets'));
|
||||
}
|
||||
@@ -1,309 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
|
||||
// Deep Partial implementation
|
||||
export type Primitive = string | number | boolean | bigint | symbol | undefined | null | Date | Function | RegExp;
|
||||
export type DeepPartial<T> =
|
||||
T extends Primitive | Function | Date ? T :
|
||||
{
|
||||
[P in keyof T]?:
|
||||
T[P] extends (infer U)[] ? DeepPartial<U>[] :
|
||||
T[P] extends readonly (infer V)[] ? readonly DeepPartial<V>[] :
|
||||
T[P] extends Primitive ? T[P] :
|
||||
DeepPartial<T[P]>
|
||||
} | T;
|
||||
|
||||
/** An Expression supports tempate variable substitution (ie `the workspace is $ {workspaceFolder}, the PATH is $ {env:PATH} `) */
|
||||
export type Expression = string;
|
||||
|
||||
/** A Conditional is an Expression that is used to conditially apply configuation based on a specific condition being met */
|
||||
export type Conditional = Expression;
|
||||
|
||||
/** One or more (as a type or an array of a type) */
|
||||
export type OneOrMore<T> = T | T[];
|
||||
|
||||
/** A regular expression in a string
|
||||
*
|
||||
* take care that the string is properly escaped (ie, backslashes)
|
||||
*/
|
||||
export type RegularExpression = string;
|
||||
|
||||
/** Discovery requirements operations */
|
||||
export type Operation = 'match' | 'folder' | 'file' | 'regex';
|
||||
|
||||
/** officially supported standards (c++) */
|
||||
export type CppStandard = 'c++98' | 'c++03' | 'c++11' | 'c++14' | 'c++17' | 'c++20' | 'c++23';
|
||||
|
||||
/** officially supported standards (c) */
|
||||
export type CStandard = 'c89' | 'c99' | 'c11' | 'c17' | 'c23';
|
||||
|
||||
/** Package manager names */
|
||||
export type PkgMgr = 'apt' | 'brew' | 'winget' | 'yum' | 'rpm' | 'dpkg';
|
||||
|
||||
/** Language selection */
|
||||
export type Language = 'c' | 'cpp' | 'c++' | 'cuda';
|
||||
|
||||
/** A package definition */
|
||||
export type Package = Partial<Record<PkgMgr, OneOrMore<string>>>;
|
||||
|
||||
/** A query definition - the 'active' requirements to get settings from a binary */
|
||||
export type Query = Record<Expression, Record<string, OneOrMore<Expression>>>;
|
||||
|
||||
/** #define macro definitions */
|
||||
export type Macros = Record<string, string | number | boolean | null>;
|
||||
|
||||
/** the target 'platform' (aka OS) */
|
||||
export type Platform =
|
||||
'windows' | // windows
|
||||
'linux' | // linux
|
||||
'macos' | // apple osx/darwin
|
||||
'ios' | // apple ios
|
||||
'none' | // bare metal
|
||||
'android' | // android
|
||||
'wasm' | // wasm
|
||||
'unknown'; // don't know what it is
|
||||
|
||||
/** The Target CPU/Processor architecture */
|
||||
export type Architecture =
|
||||
'arm' | // arm aka aarch32
|
||||
'arm64' | // 64bit arm, aka aarch64
|
||||
'avr' | // AVR (arduino)
|
||||
'x64' | // x86_64 aka amd64 aka x64
|
||||
'x86' | // x86 (32bit)
|
||||
'riscv' | // riscv
|
||||
'ia64' | // ia64
|
||||
'mips' | // mips
|
||||
'ppc' | // ppc
|
||||
'sparc' | // sparc
|
||||
'wasm' | // wasm
|
||||
'unknown'; // don't know what it is
|
||||
|
||||
/** The "well-known" compiler. At the moment, some back end parts make assumptions base on this */
|
||||
export type CompilerVariant = 'msvc' | 'clang' | 'gcc';
|
||||
|
||||
/** The (passive) requirements to discover a binary */
|
||||
export interface Discover {
|
||||
binary: OneOrMore<RegularExpression>;
|
||||
locations?: OneOrMore<string>;
|
||||
|
||||
/** 'match' examines the binary file itself to search for strings via regex */
|
||||
[key: `match:${string}`]: Record<RegularExpression, any>;
|
||||
[key: `match#${string}`]: Record<RegularExpression, any>;
|
||||
[key: `matches:${string}`]: Record<RegularExpression, any>;
|
||||
[key: `matches#${string}`]: Record<RegularExpression, any>;
|
||||
match?: Record<RegularExpression, any>;
|
||||
matches?: Record<RegularExpression, any>;
|
||||
|
||||
/** Expressions are evaluated, and if 'truthy' will apply the Installisense block */
|
||||
[key: `expressions:${string}`]: Record<Expression, any>;
|
||||
[key: `expressions#${string}`]: Record<Expression, any>;
|
||||
[key: `expression:${string}`]: Record<Expression, any>;
|
||||
[key: `expression#${string}`]: Record<Expression, any>;
|
||||
expressions?: Record<Expression, any>;
|
||||
expression?: Record<Expression, any>;
|
||||
}
|
||||
|
||||
export interface SizeOf {
|
||||
char?: number;
|
||||
short?: number;
|
||||
int?: number;
|
||||
long?: number;
|
||||
float?: number;
|
||||
double?: number;
|
||||
longDouble?: number;
|
||||
pointer?: number;
|
||||
|
||||
/** number of digits in long double manitissa (64/53) */
|
||||
digitsInLongDoubleMantissa?: number;
|
||||
|
||||
/** alignment of the long double */
|
||||
alignmentOfLongDouble?: number;
|
||||
|
||||
/** In C++17 mode, the alignment beyond which new and delete expressions will use the versions of
|
||||
allocation and deallocation functions with an alignment parameter */
|
||||
defaultNewAlignment?: number;
|
||||
}
|
||||
|
||||
export interface TypeAliases {
|
||||
wcharT?: string;
|
||||
sizeT?: string;
|
||||
ptrDiffT?: string;
|
||||
}
|
||||
|
||||
export interface Paths {
|
||||
/** specified `-iquote` paths - Used for ONLY #include "..." */
|
||||
quoteInclude?: OneOrMore<Expression>;
|
||||
|
||||
/** standard specified include paths (ie, `-I`) */
|
||||
include?: OneOrMore<Expression>;
|
||||
|
||||
/** specified `-isystem` paths */
|
||||
systemInclude?: OneOrMore<Expression>;
|
||||
|
||||
/** Directories specified that are built into the compiler (usually thru interrogation) */
|
||||
builtInInclude?: OneOrMore<Expression>;
|
||||
|
||||
/** specified `-idirafter` paths */
|
||||
afterInclude?: OneOrMore<Expression>;
|
||||
|
||||
/** specified `-external:I` paths (MSVC) */
|
||||
externalInclude?: OneOrMore<Expression>;
|
||||
|
||||
/** specified `-F` paths (MacOS) */
|
||||
framework?: OneOrMore<Expression>;
|
||||
|
||||
/** paths that are specified via environment variables (ie `INCLUDE`) */
|
||||
environmentInclude?: OneOrMore<Expression>;
|
||||
|
||||
/** paths that are forcibly #included */
|
||||
forcedIncludeFile?: OneOrMore<Expression>;
|
||||
}
|
||||
|
||||
/** the Intellisense interface represents the things that a given toolset supports/exposes */
|
||||
export interface IntelliSense {
|
||||
/** meta-property: telemetry entries to track */
|
||||
[key: `telemetry:${string}`]: Record<string, string | number | boolean>;
|
||||
|
||||
/** meta-property:error/warnings/info encountered */
|
||||
[key: `message:${string}`]: OneOrMore<string>;
|
||||
|
||||
/** any unstructured data that is added by definitions */
|
||||
[key: `${string}`]: any;
|
||||
|
||||
/** #define macros that are specified so that the backend understands how to handle the code */
|
||||
macro?: Macros;
|
||||
|
||||
/** Include folders */
|
||||
path?: Paths;
|
||||
|
||||
/** the C++ standard that this toolset supports */
|
||||
cppStandard?: CppStandard | number;
|
||||
|
||||
/** the C Standard that this toolset supports */
|
||||
cStandard?: CStandard | number;
|
||||
|
||||
/** refined arguments that are passed to the language parser (edg) */
|
||||
parserArgument?: OneOrMore<string>;
|
||||
|
||||
/** Well-known compiler variant (currently, just the three) */
|
||||
compiler?: CompilerVariant;
|
||||
|
||||
/** the target platform */
|
||||
platform?: Platform;
|
||||
|
||||
/** The target CPU/Processor architecture */
|
||||
architecture?: Architecture;
|
||||
|
||||
/** architecture bits? */
|
||||
bits?: number;
|
||||
|
||||
/** sizes of the various types */
|
||||
sizes?: SizeOf;
|
||||
|
||||
/** type aliases (what is the 'type' for a given type ) */
|
||||
types?: TypeAliases;
|
||||
|
||||
/** additional arguments that are being passed to the compiler (unprocessed) */
|
||||
compilerArg?: OneOrMore<string>; // arguments that are assumed to be passed to the compiler on the command line
|
||||
}
|
||||
|
||||
export interface IntelliSenseConfiguration extends IntelliSense {
|
||||
/** meta-property: things can be removed via remove:<setting> ... */
|
||||
[key: `remove:${string}`]: OneOrMore<string>;
|
||||
|
||||
/** meta-property: things can be prepended to a collection via prepend:<setting> ... */
|
||||
[key: `prepend:${string}`]: OneOrMore<string>;
|
||||
|
||||
/** the selected language for this configuration */
|
||||
lanugage?: Language;
|
||||
|
||||
/** the selected/overridden lanaguage standard version of this configuration */
|
||||
standard?: CppStandard | CStandard;
|
||||
|
||||
/** the full path to the compiler */
|
||||
compilerPath: string;
|
||||
}
|
||||
|
||||
/** The interface for the toolset.XXX.json file */
|
||||
export interface DefinitionFile {
|
||||
/** The cosmetic name for the toolkit */
|
||||
name: string;
|
||||
|
||||
/** The cosmetic version for the toolkit */
|
||||
version?: string;
|
||||
|
||||
/** files to automatically load and merge */
|
||||
import?: OneOrMore<string>;
|
||||
|
||||
/** Describes the steps to find this toolkit */
|
||||
discover: Discover;
|
||||
|
||||
/** Analysis steps to take the gathered data and transform it for the backend */
|
||||
analysis?: Analysis;
|
||||
|
||||
/** Explicitly declared settings about this toolset */
|
||||
intellisense?: DeepPartial<IntelliSense>;
|
||||
|
||||
/** The package identities if we are interested in bootstrapping it. */
|
||||
package?: Package;
|
||||
|
||||
/** Conditional events that allow us to overlay additional configuration when a condition is met */
|
||||
conditions?: Record<string, OneOrMore<string> | PartialDefinitionFile>;
|
||||
}
|
||||
|
||||
/** Analysis phase declarations */
|
||||
export interface Analysis {
|
||||
/** Custom steps to trigger (ie, specific built-in actions) */
|
||||
[key: `tasks:${string}`]: OneOrMore<string>;
|
||||
[key: `tasks#${string}`]: OneOrMore<string>;
|
||||
[key: `task:${string}`]: OneOrMore<string>;
|
||||
[key: `task#${string}`]: OneOrMore<string>;
|
||||
tasks?: Record<string, OneOrMore<string>>;
|
||||
task?: Record<string, OneOrMore<string>>;
|
||||
|
||||
/**
|
||||
* A map of <engineered regex sequences> to <what to apply when it matches>
|
||||
*
|
||||
* "engineered regex sequence" is a packed string that is semicolon separated regular expressions
|
||||
* each regular expression will have '^'' and '$'' added to assume that a full argument must be matched
|
||||
* tagged template literals (${}) are processed before anything else, ( which we can use for built-in macros)
|
||||
* after that, the seqence of regular expressions is split
|
||||
* when there are more than one, all of the regular expressions should match arguments in order (from the current arg)
|
||||
* so "-D;(?<val>.*)" would be valid if a -D parameter was followed by anything.
|
||||
*
|
||||
* the analysis phase is run, and the compiler args are run thru the list of the regular expressions
|
||||
* if a match is found, the data is applied to the toolset block, and the args are consumed/dropped
|
||||
* (unless keep:true is in the apply block)
|
||||
*
|
||||
* Since they are run in order, the first match wins, and the args are consumed (unless 'keep:true' is specified).
|
||||
*/
|
||||
[key: `commandLineArguments:${string}`]: Record<string, any>;
|
||||
[key: `commandLineArguments#${string}`]: Record<string, any>;
|
||||
[key: `commandLineArgument:${string}`]: Record<string, any>;
|
||||
[key: `commandLineArgument#${string}`]: Record<string, any>;
|
||||
commandLineArgument?: Record<string, any>;
|
||||
commandLineArguments?: Record<string, any>;
|
||||
|
||||
/** Expressions are evaluated, and if 'truthy' will apply the InstallisenseConfiguration block */
|
||||
[key: `expressions:${string}`]: Record<string, any>;
|
||||
[key: `expressions#${string}`]: Record<string, any>;
|
||||
[key: `expression:${string}`]: Record<string, any>;
|
||||
[key: `expression#${string}`]: Record<string, any>;
|
||||
expressions?: Record<string, any>;
|
||||
expression?: Record<string, any>;
|
||||
|
||||
/** Query steps to ask the compiler (by executing it) about its settings */
|
||||
[key: `queries:${string}`]: Record<Expression, Record<string, any>>;
|
||||
[key: `queries#${string}`]: Record<Expression, Record<string, any>>;
|
||||
[key: `query:${string}`]: Record<Expression, Record<string, any>>;
|
||||
[key: `query#${string}`]: Record<Expression, Record<string, any>>;
|
||||
queries?: Record<Expression, Record<string, any>>;
|
||||
query?: Record<Expression, Record<string, any>>;
|
||||
}
|
||||
|
||||
/** A partial definition file */
|
||||
export type PartialDefinitionFile = DeepPartial<DefinitionFile>;
|
||||
@@ -1,122 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import { normalize } from 'path';
|
||||
import { is } from '../Utility/System/guards';
|
||||
import { OneOrMore } from './interfaces';
|
||||
|
||||
export function strings(input: OneOrMore<string> | undefined | Set<string> | (string | undefined)[]): string[] {
|
||||
if (!input) {
|
||||
return [];
|
||||
}
|
||||
if (input instanceof Set) {
|
||||
return [...input];
|
||||
}
|
||||
if (is.string(input)) {
|
||||
return [input];
|
||||
}
|
||||
return input as string[];
|
||||
}
|
||||
|
||||
/** pushes one or more paths to the array if they aren't in there already. */
|
||||
export function appendUniquePath(collection: string[] | Set<string>, elements: (string | undefined)[] | string | undefined) {
|
||||
if (!elements) {
|
||||
return collection;
|
||||
}
|
||||
|
||||
for (let path of is.string(elements) ? [elements] : elements) {
|
||||
// skip empty values
|
||||
if (!path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// normalize path first.
|
||||
path = normalize(path);
|
||||
|
||||
// drop trailing slashes
|
||||
path = path.endsWith('\\') ? path.substring(0, path.length - 1) : path;
|
||||
|
||||
// append if not present.
|
||||
|
||||
// sets are smart
|
||||
if (is.set(collection)) {
|
||||
collection.add(path);
|
||||
continue;
|
||||
}
|
||||
|
||||
// arrays we have to look
|
||||
if (!collection.includes(path)) {
|
||||
collection.push(path);
|
||||
}
|
||||
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
|
||||
export function appendUnique(collection: string[] | string | undefined, elements: (string | undefined)[] | string | undefined) {
|
||||
if (!elements) {
|
||||
return collection;
|
||||
}
|
||||
|
||||
if (!collection) {
|
||||
collection = [];
|
||||
}
|
||||
|
||||
if (is.string(collection)) {
|
||||
collection = [collection];
|
||||
}
|
||||
|
||||
for (let path of is.string(elements) ? [elements] : elements) {
|
||||
// skip empty values
|
||||
if (!path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// normalize path first.
|
||||
path = normalize(path);
|
||||
|
||||
// drop trailing slashes
|
||||
path = path.endsWith('\\') ? path.substring(0, path.length - 1) : path;
|
||||
|
||||
// append if not present.
|
||||
if (!collection.includes(path)) {
|
||||
collection.push(path);
|
||||
}
|
||||
|
||||
}
|
||||
return collection;
|
||||
|
||||
}
|
||||
|
||||
export function getActions<T>(obj: any, actions: [string, string[]][]) {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.entries(obj).map(([expression, block], ndx) => {
|
||||
const [, act, flag, comment] = /^([a-zA-Z]{4})(?:[a-zA-Z]*)(?:[:])?(.*?)(#.*?)?$/.exec(expression) || [];
|
||||
// coerce the action to be one of the valid actions, or empty string.
|
||||
const [action, validFlags] = actions.find(each => each[0].startsWith(act.toLowerCase())) || ['', []];
|
||||
|
||||
// extract the flags
|
||||
const flags = new Map();
|
||||
for (const each of flag.split(',')) {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [,key, value] = /^([^=]+)=*(.*)?$/.exec(each) ?? [];
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
key = key.toLowerCase().trim();
|
||||
|
||||
if (validFlags.includes(key)) {
|
||||
flags.set(key, value?.trim() ?? true);
|
||||
}
|
||||
}
|
||||
// get the priority
|
||||
const priority = parseInt(flags.get('priority') ?? '0') || ndx;
|
||||
return { action, block, flags, priority, comment } as const;
|
||||
}).sort((a, b) => a.priority - b.priority).filter(each => each.action) as { action: string; block: T; flags: Map<string, string | boolean>; priority: number; comment?: string }[];
|
||||
}
|
||||
|
||||
@@ -42,9 +42,8 @@ export class ManualPromise<T = void> implements Promise<T> {
|
||||
/**
|
||||
* A method to manually resolve the Promise.
|
||||
*/
|
||||
public resolve: (value?: T | PromiseLike<T> | undefined) => T = (v) => {
|
||||
public resolve: (value?: T | PromiseLike<T> | undefined) => void = (v) => {
|
||||
void v; /* */
|
||||
return v as T;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -90,7 +89,6 @@ export class ManualPromise<T = void> implements Promise<T> {
|
||||
}
|
||||
this.state = 'resolved';
|
||||
r(v as any);
|
||||
return v as T;
|
||||
};
|
||||
this.reject = (e: any) => {
|
||||
if ((global as any).DEVMODE && this.state !== 'pending') {
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import EventEmitter = require('events');
|
||||
import { ManualPromise } from './manualPromise';
|
||||
|
||||
export class PriorityQueue<T> extends EventEmitter {
|
||||
private queued = new Map<string, () => Promise<T>>();
|
||||
private inProgress = new Map<string, Promise<T>>();
|
||||
private completed = new Map<string, T>();
|
||||
private failed = new Map<string, any>();
|
||||
|
||||
constructor(private maxParallel: number = 10) {
|
||||
super();
|
||||
}
|
||||
|
||||
get completedKeys() {
|
||||
return [...this.completed.keys()];
|
||||
}
|
||||
|
||||
get keys() {
|
||||
return [...this.queued.keys(), ...this.inProgress.keys(), ...this.completed.keys(), ...this.failed.keys()];
|
||||
}
|
||||
|
||||
has(key: string) {
|
||||
return this.queued.has(key) || this.inProgress.has(key) || this.completed.has(key) || this.failed.has(key);
|
||||
}
|
||||
|
||||
/** returns the number of items not yet processed */
|
||||
get length() {
|
||||
return this.queued.size + this.inProgress.size;
|
||||
}
|
||||
|
||||
/** returns the total number of items regardless of status */
|
||||
get size() {
|
||||
return this.failed.size + this.completed.size + this.length;
|
||||
}
|
||||
|
||||
// removes a task from the queue, regardless if it is queued, in progress, or completed
|
||||
async reset(key: string) {
|
||||
// if it's currently running, we have to wait for it to finish what it was doing
|
||||
// so that we can remove it from the results.
|
||||
|
||||
if (this.inProgress.has(key)) {
|
||||
await this.inProgress.get(key);
|
||||
}
|
||||
|
||||
// regardless, remove it from the queue entirely
|
||||
this.queued.delete(key);
|
||||
this.completed.delete(key);
|
||||
this.failed.delete(key);
|
||||
}
|
||||
|
||||
async enqueue(key: string, task: () => Promise<T>): Promise<T> {
|
||||
// reset this if it was already queued
|
||||
await this.reset(key);
|
||||
|
||||
// returning a promise to the thing we're queueing
|
||||
const result = new ManualPromise<T>();
|
||||
|
||||
// add it to the queue, and make it resolve the promise when it's done
|
||||
this.queued.set(key, () => {
|
||||
void task().then(result.resolve, result.reject);
|
||||
return result;
|
||||
});
|
||||
|
||||
// start the queue if it's not already running
|
||||
if (this.inProgress.size === 0) {
|
||||
void this.start();
|
||||
}
|
||||
|
||||
// return the promise to the result
|
||||
return result;
|
||||
}
|
||||
|
||||
async get(key: string): Promise<T | undefined> {
|
||||
// if it's already completed
|
||||
const value = this.completed.get(key);
|
||||
if (value !== undefined) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// if it's in progress
|
||||
const p = this.inProgress.get(key);
|
||||
if (p !== undefined) {
|
||||
return p;
|
||||
}
|
||||
|
||||
// if it failed, throw that to the consumer.
|
||||
const f = this.failed.get(key);
|
||||
if (f !== undefined) {
|
||||
throw f;
|
||||
}
|
||||
|
||||
// if it's queued, let's run it right away and return that result
|
||||
const task = this.queued.get(key);
|
||||
|
||||
// if we do have a task, run it thru exec
|
||||
// so that if we ask for the value again
|
||||
// it will cache the result
|
||||
return task ? this.exec(key, task) : undefined;
|
||||
}
|
||||
|
||||
async getOrEnqueue(key: string, task: () => Promise<T>): Promise<T> {
|
||||
const result = await this.get(key);
|
||||
return result === undefined ? this.enqueue(key, task) : result;
|
||||
}
|
||||
|
||||
private exec(key: string, task: () => Promise<T>) {
|
||||
this.queued.delete(key);
|
||||
const result = new ManualPromise<T>();
|
||||
this.inProgress.set(key, result);
|
||||
|
||||
task().then((value: T) => {
|
||||
// when we're done, remove the task from the in progress list
|
||||
this.inProgress.delete(key);
|
||||
this.completed.set(key, value);
|
||||
this.emit('item', key, value);
|
||||
result.resolve(value);
|
||||
}, (reason: any) => {
|
||||
this.inProgress.delete(key);
|
||||
this.completed.delete(key);
|
||||
this.failed.set(key, reason);
|
||||
result.reject(reason);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async start() {
|
||||
while (this.queued.size || this.inProgress.size) {
|
||||
if (this.inProgress.size > this.maxParallel || this.queued.size === 0) {
|
||||
// if we reached the max parallel tasks, or we have nothing left to queue, wait for one to complete
|
||||
await Promise.any(this.inProgress.values());
|
||||
}
|
||||
|
||||
//* const {value, done} = this.queued.entries().next();
|
||||
// if (!done) {
|
||||
// void this.exec(value[0], value[1]);
|
||||
//}
|
||||
|
||||
// grab the first task from the queue
|
||||
for (const [key, task] of this.queued.entries()) {
|
||||
void this.exec(key, task);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('empty', this);
|
||||
}
|
||||
|
||||
override on(event: 'item', listener: (key: string, value: T) => void): this;
|
||||
override on(event: 'empty', listener: (queue: PriorityQueue<T>) => void): this;
|
||||
override on(eventName: string | symbol, listener: (...args: any[]) => void): this {
|
||||
return super.on(eventName, listener);
|
||||
}
|
||||
|
||||
override once(event: 'item', listener: (key: string, value: T) => void): this;
|
||||
override once(event: 'empty', listener: (queue: PriorityQueue<T>) => void): this;
|
||||
override once(eventName: string | symbol, listener: (...args: any[]) => void): this {
|
||||
return super.once(eventName, listener);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/unified-signatures */
|
||||
|
||||
import { readFile } from 'node:fs/promises';
|
||||
@@ -80,7 +81,7 @@ async function dispatch<TResult>(event: Event<any, TResult>): Promise<void> {
|
||||
resultValue = r as TResult | EventStatus;
|
||||
|
||||
if (is.cancelled(resultValue)) {
|
||||
event.completed.resolve(resultValue); // the event has been cancelled
|
||||
return event.completed.resolve(resultValue); // the event has been cancelled
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
@@ -134,7 +135,7 @@ async function dispatch<TResult>(event: Event<any, TResult>): Promise<void> {
|
||||
|
||||
// wait for all the async handlers to complete
|
||||
// and return the first result that isn't 'Continue'
|
||||
event.completed.resolve((await Promise.all(results)).find((each: any) => each !== Continue));
|
||||
return event.completed.resolve((await Promise.all(results)).find((each: any) => each !== Continue));
|
||||
}
|
||||
|
||||
function* getHandlers<TResult>(event: Event<any, TResult>, category: Map<string, Subscriber[]>): Iterable<[Callback, string[]]> {
|
||||
|
||||
@@ -10,7 +10,7 @@ export class requests {
|
||||
static readonly get = 'get';
|
||||
}
|
||||
|
||||
// event names should be like [noun]-[verb]
|
||||
// [noun]-[verb]
|
||||
export class events {
|
||||
static readonly writing = 'writing';
|
||||
static readonly reading = 'reading';
|
||||
@@ -27,7 +27,8 @@ export class channels {
|
||||
}
|
||||
|
||||
/** Notifications */
|
||||
// should be like [state] or [pastTenseVerb]-[noun]
|
||||
// [state]
|
||||
// [pastTenseVerb]-[noun]
|
||||
export class notifications {
|
||||
static readonly ready = 'ready';
|
||||
static readonly exited = 'exited';
|
||||
|
||||
@@ -1,398 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
/* eslint-disable @typescript-eslint/method-signature-style */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
/**
|
||||
* The code here is borrowed from https://github.com/microsoft/vscode-ripgrep
|
||||
*
|
||||
* The original code is used internally and not designed to be consumed via npm.
|
||||
* Since we needed the same functionality, I've borrowed the code and typescript-ified it.
|
||||
*
|
||||
*/
|
||||
|
||||
import { resolve } from 'path';
|
||||
import { verbose } from '../Text/streams';
|
||||
import { filepath, mkdir } from './filepath';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const https = require('https');
|
||||
const util = require('util');
|
||||
const url = require('url');
|
||||
const child_process = require('child_process');
|
||||
const proxy_from_env = require('proxy-from-env');
|
||||
|
||||
const fsExists = util.promisify(fs.exists);
|
||||
|
||||
const tmpDir = path.join(os.tmpdir(), `vscode-ripgrep-cache`);
|
||||
|
||||
const fsUnlink = util.promisify(fs.unlink);
|
||||
const fsMkdir = util.promisify(fs.mkdir);
|
||||
|
||||
const isWindows = os.platform() === 'win32';
|
||||
|
||||
const REPO = 'microsoft/ripgrep-prebuilt';
|
||||
|
||||
function isGithubUrl(_url: any) {
|
||||
return url.parse(_url).hostname === 'api.github.com';
|
||||
}
|
||||
|
||||
function downloadWin(url: any, dest: any, opts: { headers: { [x: string]: any }; proxy: string | URL }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let userAgent;
|
||||
if (opts.headers['user-agent']) {
|
||||
userAgent = opts.headers['user-agent'];
|
||||
delete opts.headers['user-agent'];
|
||||
}
|
||||
const headerValues = Object.keys(opts.headers)
|
||||
.map(key => `\\"${key}\\"=\\"${opts.headers[key]}\\"`)
|
||||
.join('; ');
|
||||
const headers = `@{${headerValues}}`;
|
||||
verbose('Downloading with Invoke-WebRequest');
|
||||
dest = sanitizePathForPowershell(dest);
|
||||
let iwrCmd = `[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -URI ${url} -UseBasicParsing -OutFile ${dest} -Headers ${headers}`;
|
||||
if (userAgent) {
|
||||
iwrCmd += ' -UserAgent ' + userAgent;
|
||||
}
|
||||
if (opts.proxy) {
|
||||
iwrCmd += ' -Proxy ' + opts.proxy;
|
||||
|
||||
try {
|
||||
const { username, password } = new URL(opts.proxy);
|
||||
if (username && password) {
|
||||
const decodedPassword = decodeURIComponent(password);
|
||||
iwrCmd += ` -ProxyCredential (New-Object PSCredential ('${username}', (ConvertTo-SecureString '${decodedPassword}' -AsPlainText -Force)))`;
|
||||
}
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
iwrCmd = `powershell "${iwrCmd}"`;
|
||||
|
||||
child_process.exec(iwrCmd, (err: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(undefined);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function download(_url: { version: string; token: string | undefined; target: string; destDir: any; force: boolean }, dest?: any, opts?: any) {
|
||||
|
||||
const proxy = proxy_from_env.getProxyForUrl(url.parse(_url));
|
||||
if (proxy !== '') {
|
||||
const HttpsProxyAgent = require('https-proxy-agent');
|
||||
opts = {
|
||||
...opts,
|
||||
"agent": new HttpsProxyAgent(proxy),
|
||||
proxy
|
||||
};
|
||||
}
|
||||
|
||||
if (isWindows) {
|
||||
// This alternative strategy shouldn't be necessary but sometimes on Windows the file does not get closed,
|
||||
// so unzipping it fails, and I don't know why.
|
||||
return downloadWin(_url, dest, opts);
|
||||
}
|
||||
|
||||
if (opts.headers && opts.headers.authorization && !isGithubUrl(_url)) {
|
||||
delete opts.headers.authorization;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
verbose(`Download options: ${JSON.stringify(opts)}`);
|
||||
const outFile = fs.createWriteStream(dest);
|
||||
const mergedOpts = {
|
||||
...url.parse(_url),
|
||||
...opts
|
||||
};
|
||||
https.get(mergedOpts, (response: { statusCode: string | number; headers: Record<string, any>; pipe: (arg0: any) => void }) => {
|
||||
verbose('statusCode: ' + response.statusCode);
|
||||
if (response.statusCode === 302) {
|
||||
verbose('Following redirect to: ' + response.headers.location);
|
||||
return download(response.headers.location, dest, opts)
|
||||
.then(resolve, reject);
|
||||
} else if (response.statusCode !== 200) {
|
||||
reject(new Error('Download failed with ' + response.statusCode));
|
||||
return;
|
||||
}
|
||||
|
||||
response.pipe(outFile);
|
||||
outFile.on('finish', () => {
|
||||
resolve(undefined);
|
||||
});
|
||||
}).on('error', async (err: any) => {
|
||||
await fsUnlink(dest);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function get(_url: string, opts: any) {
|
||||
verbose(`GET ${_url}`);
|
||||
|
||||
const proxy = proxy_from_env.getProxyForUrl(url.parse(_url));
|
||||
if (proxy !== '') {
|
||||
const HttpsProxyAgent = require('https-proxy-agent');
|
||||
opts = {
|
||||
...opts,
|
||||
"agent": new HttpsProxyAgent(proxy)
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let result = '';
|
||||
opts = {
|
||||
...url.parse(_url),
|
||||
...opts
|
||||
};
|
||||
https.get(opts, (response: { statusCode: string | number; on: any }) => {
|
||||
if (response.statusCode !== 200) {
|
||||
reject(new Error('Request failed: ' + response.statusCode));
|
||||
}
|
||||
|
||||
response.on('data', (d: any) => {
|
||||
result += d.toString();
|
||||
});
|
||||
|
||||
response.on('end', () => {
|
||||
resolve(result);
|
||||
});
|
||||
|
||||
response.on('error', (e: any) => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getApiUrl(repo: string, tag: any) {
|
||||
return `https://api.github.com/repos/${repo}/releases/tags/${tag}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param opts
|
||||
* @param assetName
|
||||
* @param downloadFolder
|
||||
*/
|
||||
async function getAssetFromGithubApi(opts: Record<string, any>, assetName: string, downloadFolder: any) {
|
||||
const assetDownloadPath = path.join(downloadFolder, assetName);
|
||||
|
||||
// We can just use the cached binary
|
||||
if (!opts.force && await fsExists(assetDownloadPath)) {
|
||||
verbose('Using cached download: ' + assetDownloadPath);
|
||||
return assetDownloadPath;
|
||||
}
|
||||
|
||||
const downloadOpts = {
|
||||
headers: {
|
||||
'user-agent': 'vscode-ripgrep'
|
||||
} as Record<string, any>
|
||||
}as Record<string, any>;
|
||||
|
||||
if (opts.token) {
|
||||
downloadOpts.headers.authorization = `token ${opts.token}`;
|
||||
}
|
||||
|
||||
verbose(`Finding release for ${opts.version}`);
|
||||
const release = await get(getApiUrl(REPO, opts.version), downloadOpts) as string;
|
||||
let jsonRelease;
|
||||
try {
|
||||
jsonRelease = JSON.parse(release);
|
||||
} catch (e) {
|
||||
throw new Error('Malformed API response: ' + (e as any)?.stack);
|
||||
}
|
||||
|
||||
if (!jsonRelease.assets) {
|
||||
throw new Error('Bad API response: ' + JSON.stringify(release));
|
||||
}
|
||||
|
||||
const asset = jsonRelease.assets.find((a: { name: any }) => a.name === assetName);
|
||||
if (!asset) {
|
||||
throw new Error('Asset not found with name: ' + assetName);
|
||||
}
|
||||
|
||||
verbose(`Downloading from ${asset.url}`);
|
||||
verbose(`Downloading to ${assetDownloadPath}`);
|
||||
|
||||
downloadOpts.headers.accept = 'application/octet-stream';
|
||||
await download(asset.url, assetDownloadPath, downloadOpts);
|
||||
}
|
||||
|
||||
function unzipWindows(zipPath: any, destinationDir: any) {
|
||||
return new Promise((resolve, reject) => {
|
||||
zipPath = sanitizePathForPowershell(zipPath);
|
||||
destinationDir = sanitizePathForPowershell(destinationDir);
|
||||
const expandCmd = 'powershell -ExecutionPolicy Bypass -Command Expand-Archive ' + ['-Path', zipPath, '-DestinationPath', destinationDir, '-Force'].join(' ');
|
||||
child_process.exec(expandCmd, (err: any, _stdout: any, stderr: string | undefined) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
verbose(stderr);
|
||||
reject(new Error(stderr));
|
||||
return;
|
||||
}
|
||||
|
||||
verbose('Expand-Archive completed');
|
||||
resolve(undefined);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle whitespace in filepath as powershell split's path with whitespaces
|
||||
function sanitizePathForPowershell(path: string) {
|
||||
path = path.replace(/ /g, '` '); // replace whitespace with "` " as solution provided here https://stackoverflow.com/a/18537344/7374562
|
||||
return path;
|
||||
}
|
||||
|
||||
function untar(zipPath: any, destinationDir: any) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const unzipProc = child_process.spawn('tar', ['xvf', zipPath, '-C', destinationDir], { stdio: 'inherit' });
|
||||
unzipProc.on('error', (err: any) => {
|
||||
reject(err);
|
||||
});
|
||||
unzipProc.on('close', (code: number) => {
|
||||
verbose(`tar xvf exited with ${code}`);
|
||||
if (code !== 0) {
|
||||
reject(new Error(`tar xvf exited with ${code}`));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(undefined);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function unzipRipgrep(zipPath: any, destinationDir: any) {
|
||||
if (isWindows) {
|
||||
await unzipWindows(zipPath, destinationDir);
|
||||
} else {
|
||||
await untar(zipPath, destinationDir);
|
||||
}
|
||||
|
||||
const expectedName = path.join(destinationDir, 'rg');
|
||||
if (await fsExists(expectedName)) {
|
||||
return expectedName;
|
||||
}
|
||||
|
||||
if (await fsExists(expectedName + '.exe')) {
|
||||
return expectedName + '.exe';
|
||||
}
|
||||
|
||||
throw new Error(`Expecting rg or rg.exe unzipped into ${destinationDir}, didn't find one.`);
|
||||
}
|
||||
|
||||
async function dl (opts: Record<string, any>) {
|
||||
if (!opts.version) {
|
||||
return Promise.reject(new Error('Missing version'));
|
||||
}
|
||||
|
||||
if (!opts.target) {
|
||||
return Promise.reject(new Error('Missing target'));
|
||||
}
|
||||
|
||||
const extension = isWindows ? '.zip' : '.tar.gz';
|
||||
const assetName = ['ripgrep', opts.version, opts.target].join('-') + extension;
|
||||
|
||||
if (!await fsExists(tmpDir)) {
|
||||
await fsMkdir(tmpDir);
|
||||
}
|
||||
|
||||
const assetDownloadPath = path.join(tmpDir, assetName);
|
||||
try {
|
||||
await getAssetFromGithubApi(opts, assetName, tmpDir);
|
||||
} catch (e) {
|
||||
verbose('Deleting invalid download cache');
|
||||
try {
|
||||
await fsUnlink(assetDownloadPath);
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
verbose(`Unzipping to ${opts.destDir}`);
|
||||
try {
|
||||
const destinationPath = await unzipRipgrep(assetDownloadPath, opts.destDir);
|
||||
if (!isWindows) {
|
||||
await util.promisify(fs.chmod)(destinationPath, '755');
|
||||
}
|
||||
return destinationPath;
|
||||
} catch (e) {
|
||||
verbose('Deleting invalid download');
|
||||
|
||||
try {
|
||||
await fsUnlink(assetDownloadPath);
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
const VERSION = 'v13.0.0-10';
|
||||
const ARM32_LINUX_VERSION = 'v13.0.0-4';// use this for arm-unknown-linux-gnueabihf until we can fix https://github.com/microsoft/ripgrep-prebuilt/issues/24
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
verbose('Unhandled rejection: ', promise, 'reason:', reason);
|
||||
});
|
||||
|
||||
async function getTarget() {
|
||||
const arch = process.env.npm_config_arch || os.arch();
|
||||
|
||||
switch (os.platform()) {
|
||||
case 'darwin':
|
||||
return arch === 'arm64' ? 'aarch64-apple-darwin' :
|
||||
'x86_64-apple-darwin';
|
||||
case 'win32':
|
||||
return arch === 'x64' ? 'x86_64-pc-windows-msvc' :
|
||||
arch === 'arm' ? 'aarch64-pc-windows-msvc' :
|
||||
'i686-pc-windows-msvc';
|
||||
case 'linux':
|
||||
return arch === 'x64' ? 'x86_64-unknown-linux-musl' :
|
||||
arch === 'arm' ? 'arm-unknown-linux-gnueabihf' :
|
||||
arch === 'armv7l' ? 'arm-unknown-linux-gnueabihf' :
|
||||
arch === 'arm64' ? 'aarch64-unknown-linux-musl' :
|
||||
arch === 'ppc64' ? 'powerpc64le-unknown-linux-gnu' :
|
||||
arch === 's390x' ? 's390x-unknown-linux-gnu' :
|
||||
'i686-unknown-linux-musl';
|
||||
default: throw new Error('Unknown platform: ' + os.platform());
|
||||
}
|
||||
}
|
||||
|
||||
export async function downloadRipgrep() {
|
||||
|
||||
const BIN_PATH = await mkdir(path.join(__dirname, '../../../../bin'));
|
||||
const targetPath = resolve(BIN_PATH, isWindows ? 'rg.exe' : 'rg');
|
||||
|
||||
if (await filepath.isExecutable(targetPath)) {
|
||||
return targetPath;
|
||||
}
|
||||
|
||||
const target = await getTarget();
|
||||
const opts = {
|
||||
version: target === "arm-unknown-linux-gnueabihf" ? ARM32_LINUX_VERSION : VERSION,
|
||||
token: process.env['GITHUB_TOKEN'],
|
||||
target,
|
||||
destDir: BIN_PATH,
|
||||
force: false
|
||||
};
|
||||
try {
|
||||
return await dl(opts);
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
console.error(`Downloading ripgrep failed: ${err.stack}`);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
return targetPath;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
import { basename, delimiter, extname, join, normalize as norm, resolve } from 'path';
|
||||
|
||||
import { fail, ok } from 'assert';
|
||||
import { fail } from 'assert';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { Stats, constants } from 'fs';
|
||||
import { mkdir as md, stat } from 'fs/promises';
|
||||
import { constants, Stats } from 'fs';
|
||||
import { stat } from 'fs/promises';
|
||||
import { tmpdir } from 'os';
|
||||
import { isWindows } from '../../constants';
|
||||
import { returns } from '../Async/returns';
|
||||
@@ -45,7 +45,6 @@ export interface File extends Entry {
|
||||
isFile: true;
|
||||
isExecutable: boolean;
|
||||
size: number;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface Folder extends Entry {
|
||||
@@ -56,10 +55,6 @@ export interface Folder extends Entry {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export class filepath {
|
||||
static normalize(path: string) {
|
||||
return normalize(path);
|
||||
}
|
||||
|
||||
static async stats(name: string | undefined | Promise<string | undefined>, baseFolder?: string): Promise<[string, Stats | undefined] | [undefined, undefined]> {
|
||||
if (is.promise(name)) {
|
||||
name = await name;
|
||||
@@ -71,12 +66,12 @@ export class filepath {
|
||||
}
|
||||
|
||||
// if we've been given a baseFolder, expand that, otherwise just normalize the value.
|
||||
name = baseFolder ? resolve(baseFolder, name) : filepath.normalize(name);
|
||||
name = baseFolder ? resolve(baseFolder, name) : normalize(name);
|
||||
|
||||
return [name, await stat(name).catch(returns.undefined)];
|
||||
}
|
||||
|
||||
static async info(name: string | undefined | Promise<string | undefined>, baseFolder?: string, executableExtensions: Set<string> = process.platform === 'win32' ? new Set(['.exe']) : new Set()): Promise<undefined | File | Folder> {
|
||||
static async info(name: string | undefined | Promise<string | undefined>, baseFolder?: string, executableExtensions: Set<string> = process.platform === 'win32' ? new Set(['.exe'/* ,'.cmd','.bat' */]) : new Set()): Promise<undefined | File | Folder> {
|
||||
const [fullPath, stats] = await filepath.stats(name, baseFolder);
|
||||
if (!stats) {
|
||||
return undefined;
|
||||
@@ -92,7 +87,6 @@ export class filepath {
|
||||
|
||||
if (entry.isFile) {
|
||||
entry.size = stats.size;
|
||||
entry.timestamp = stats.mtimeMs;
|
||||
|
||||
if (isWindows) {
|
||||
const fp = fullPath.toLowerCase();
|
||||
@@ -137,25 +131,10 @@ export class filepath {
|
||||
static parent(name: Promise<string | undefined>): Promise<string | undefined>;
|
||||
static parent(name: string | undefined): string | undefined;
|
||||
static parent(name: string | undefined | Promise<string | undefined>): string | undefined | Promise<string | undefined> {
|
||||
return is.promise(name) ? name.then(filepath.parent) : name ? filepath.normalize(resolve(name, '..')) : undefined;
|
||||
return is.promise(name) ? name.then(filepath.parent) : name ? normalize(resolve(name, '..')) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function tmpFile(prefix = 'tmp.', suffix = '.tmp', folder = tmpdir()) {
|
||||
return join(folder, prefix + randomBytes(32).toString('hex') + suffix);
|
||||
}
|
||||
|
||||
/** Asnyc recursively create dir if it isn't there, no error if it is there already. */
|
||||
export async function mkdir(filePath: string) {
|
||||
const [fullPath, info] = await filepath.stats(filePath);
|
||||
if (info) {
|
||||
if (info.isDirectory()) {
|
||||
return fullPath;
|
||||
}
|
||||
throw new Error(`Cannot create directory '${filePath}' because there is a file there.`);
|
||||
}
|
||||
ok(fullPath, `Cannot create directory ${filePath} because the path is invalid.`);
|
||||
|
||||
await md(fullPath, { recursive: true });
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { accumulator, foreach } from '../Async/iterators';
|
||||
import { ManualPromise } from '../Async/manualPromise';
|
||||
import { returns } from '../Async/returns';
|
||||
import { is } from '../System/guards';
|
||||
import { File, Folder, filepath } from './filepath';
|
||||
import { File, Folder, normalize } from './filepath';
|
||||
|
||||
interface FolderWithChildren extends Folder {
|
||||
children?: Map<string, File | FolderWithChildren>;
|
||||
@@ -31,7 +31,7 @@ const cache = new Map<string, File | FolderWithChildren | Promise<FolderWithChil
|
||||
* @param executableExtensions a set of file extensions that are considered executable on Windows
|
||||
* @returns a map of the files and folders in the directory, or undefined if the directory doesn't exist or is inaccessible.
|
||||
*/
|
||||
async function readDirectory(fullPath: string, executableExtensions: Set<string> = process.platform === 'win32' ? new Set(['.exe']) : new Set()): Promise<Map<string, File | FolderWithChildren> | undefined> {
|
||||
async function readDirectory(fullPath: string, executableExtensions: Set<string> = process.platform === 'win32' ? new Set(['.exe'/* ,'.cmd','.bat' */]) : new Set()): Promise<Map<string, File | FolderWithChildren> | undefined> {
|
||||
// have we already read this directory?
|
||||
let folder = cache.get(fullPath) as FolderWithChildren | undefined;
|
||||
let promise: ManualPromise<FolderWithChildren | undefined> | undefined;
|
||||
@@ -137,7 +137,7 @@ export async function scanFolder(folder: string, scanDepth: number, filePredicat
|
||||
}
|
||||
|
||||
// normalize the folder
|
||||
folder = filepath.normalize(folder);
|
||||
folder = normalize(folder);
|
||||
|
||||
// if we have already visited this folder, return
|
||||
await foreach(readDirectory(folder), async ([_name, entry]) => {
|
||||
|
||||
@@ -3,86 +3,24 @@
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
|
||||
import { strict } from 'assert';
|
||||
import { existsSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { isWindows } from '../../constants';
|
||||
import { accumulator } from '../Async/iterators';
|
||||
import { logAndReturn } from '../Async/returns';
|
||||
import { Process } from '../Process/process';
|
||||
import { ProcessFunction, Program } from '../Process/program';
|
||||
import { is } from '../System/guards';
|
||||
import { Instance } from '../System/types';
|
||||
import { verbose } from '../Text/streams';
|
||||
import { downloadRipgrep } from './downloadRipgrep';
|
||||
import { filepath } from './filepath';
|
||||
|
||||
let ripgrep: Instance<ProcessFunction>;
|
||||
async function setRipgrepBinaryLocation(filename: string) {
|
||||
let ripgrep: Instance<ProcessFunction> | undefined;
|
||||
export async function initRipGrep(filename: string) {
|
||||
if (!ripgrep) {
|
||||
const rg = await filepath.isExecutable(filename);
|
||||
strict(rg, `File ${filename} is not executable`);
|
||||
verbose(`Using ripgrep at'${filename}'`);
|
||||
ripgrep = await new Program(filename);
|
||||
}
|
||||
return ripgrep;
|
||||
}
|
||||
|
||||
export async function autoInitializeRipGrep() {
|
||||
if (!ripgrep) {
|
||||
try {
|
||||
// if we're in vscode and it thas a copy of ripgrep, let's use that
|
||||
const p = process as any;
|
||||
if (p.resourcesPath) {
|
||||
// if we're running in vscode, this will be there. If it isn't it's not likely that vscode itself is working correctly.
|
||||
return setRipgrepBinaryLocation(resolve((process as any).resourcesPath, `app/node_modules.asar.unpacked/@vscode/ripgrep/bin/rg${isWindows ? '.exe' : ''}`));
|
||||
}
|
||||
} catch {
|
||||
// ignore, move on.
|
||||
}
|
||||
|
||||
// if we get here it might be because we're in a WSL or Remote vscode session,
|
||||
// and the remote host is a bit different than a local instance of vscode.
|
||||
// The vscode/ripgrep package should be in the node_modules folder, which means we can use it to find the ripgrep binary that it has.
|
||||
// let's see if @vscode/ripgrep is installed
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const vs_rg = require('@vscode/ripgrep');
|
||||
if (vs_rg) {
|
||||
return setRipgrepBinaryLocation(vs_rg);
|
||||
}
|
||||
} catch {
|
||||
// ignore, move on
|
||||
}
|
||||
|
||||
// if we get here, ripgrep isn't installed for us and we don't appear to have the @vscode/ripgrep package
|
||||
// we can call the downloadRipgrep function (which was borrowed from the @vscode/ripgrep package)
|
||||
// this is a last resort, and should never happen in production, but could be necessary in development or CI or unit testing.
|
||||
return setRipgrepBinaryLocation(await downloadRipgrep());
|
||||
}
|
||||
}
|
||||
const initialization = autoInitializeRipGrep();
|
||||
|
||||
export async function fastFind(fileGlobs: string | string[], locations: string | string[], depth = 20): Promise<string[]> {
|
||||
depth++;
|
||||
fileGlobs = is.array(fileGlobs) ? fileGlobs : [fileGlobs];
|
||||
locations = is.array(locations) ? locations : [locations];
|
||||
|
||||
fileGlobs.map(glob => glob.includes('**') ? glob : `**/${glob}`);
|
||||
locations = locations.filter(each => existsSync(each.toString()));
|
||||
const results = new Set<string>();
|
||||
|
||||
if (fileGlobs.length && locations.length) {
|
||||
try {
|
||||
const proc = await ripgrep(...fileGlobs.map(each => ['--glob', each]).flat(), '--max-depth', depth, '--null-data', '--no-messages', '-L', '--files', ...locations.map(each => each.toString()));
|
||||
for await (const line of proc.stdio) {
|
||||
results.add(line);
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return [...results];
|
||||
}
|
||||
|
||||
export class FastFinder implements AsyncIterable<string> {
|
||||
@@ -105,6 +43,8 @@ export class FastFinder implements AsyncIterable<string> {
|
||||
}
|
||||
|
||||
constructor(private fileGlobs: string[], options?: { executable?: boolean; executableExtensions?: string[] }) {
|
||||
strict(ripgrep, 'initRipGrep must be called before using FastFinder');
|
||||
|
||||
this.keepOnlyExecutables = options?.executable ?? false;
|
||||
if (this.keepOnlyExecutables && process.platform === 'win32') {
|
||||
this.executableExtensions = options?.executableExtensions ?? ['.exe', '.bat', '.cmd', '.ps1'];
|
||||
@@ -131,30 +71,23 @@ export class FastFinder implements AsyncIterable<string> {
|
||||
// only search if there are globs and locations to search
|
||||
if (globs.length && location.length) {
|
||||
this.pending++;
|
||||
void initialization.then(async () => {
|
||||
try {
|
||||
const proc = await ripgrep(...globs.map(each => ['--glob', each]).flat(), '--max-depth', depth, '--null-data', '--no-messages', '-L', '--files', ...location.map(each => each.toString()));
|
||||
|
||||
const process = proc as unknown as Instance<Process>;
|
||||
this.processes.push(process);
|
||||
for await (const line of process.stdio) {
|
||||
if (this.distinct.has(line)) {
|
||||
continue;
|
||||
}
|
||||
this.distinct.add(line);
|
||||
if (!this.keepOnlyExecutables || await filepath.isExecutable(line)) {
|
||||
this.#files.add(line);
|
||||
}
|
||||
void ripgrep!(...globs.map(each => ['--glob', each]).flat(), '--max-depth', depth, '--null-data', '--no-messages', '-L', '--files', ...location.map(each => each.toString())).then(async proc => {
|
||||
const process = proc as unknown as Instance<Process>;
|
||||
this.processes.push(process);
|
||||
for await (const line of process.stdio) {
|
||||
if (this.distinct.has(line)) {
|
||||
continue;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
this.pending--;
|
||||
if (this.readyToComplete && this.pending === 0) {
|
||||
this.#files.complete();
|
||||
this.distinct.add(line);
|
||||
if (!this.keepOnlyExecutables || await filepath.isExecutable(line)) {
|
||||
this.#files.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
}).catch(logAndReturn.undefined).finally(() => {
|
||||
this.pending--;
|
||||
if (this.readyToComplete && this.pending === 0) {
|
||||
this.#files.complete();
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
@@ -184,7 +117,7 @@ function isMatch(obj: Record<string, any>): obj is RipGrepMatch {
|
||||
|
||||
/** Calls RipGrep looking for strings */
|
||||
export async function* ripGrep(target: string, regex: string, options?: { glob?: string; binary?: boolean; encoding?: 'utf-16' | 'utf-8'; ignoreCase?: boolean }): AsyncGenerator<MatchData> {
|
||||
await initialization;
|
||||
strict(ripgrep, 'initRipGrep must be called before using ripGrep');
|
||||
|
||||
const optionalArguments = new Array<string>();
|
||||
if (options?.binary) {
|
||||
@@ -201,7 +134,6 @@ export async function* ripGrep(target: string, regex: string, options?: { glob?:
|
||||
}
|
||||
regex = regex.replace(/\?\</g, '\?P<');
|
||||
const proc = await ripgrep(regex, '--null-data', '--json', '--no-messages', ...optionalArguments, target);
|
||||
|
||||
for await (const line of proc.stdio) {
|
||||
try {
|
||||
const obj = JSON.parse(line);
|
||||
|
||||
@@ -435,7 +435,6 @@ export class ReadWriteLineStream extends ReadableLineStream {
|
||||
|
||||
this.writeable.on('error', (_error) => {
|
||||
/*
|
||||
uncomment if testing :
|
||||
this is handy for debugging to see if errors are happening.
|
||||
|
||||
if ((global as any).DEVMODE && error) {
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import { is } from './guards';
|
||||
|
||||
export function isExpired(timeoutValue: number) {
|
||||
return timeoutValue && timeoutValue < Date.now();
|
||||
}
|
||||
|
||||
export function isLater(a: number, b: number) {
|
||||
return a ? b ? a > b : true : false;
|
||||
}
|
||||
|
||||
export class Cache<T = any> implements Iterable<[string, T]> {
|
||||
static OneMinute = 60 * 1000;
|
||||
static OneHour = 60 * Cache.OneMinute;
|
||||
static OneDay = 24 * Cache.OneHour;
|
||||
static OneWeek = 7 * Cache.OneDay;
|
||||
static OneMonth = 30 * Cache.OneDay;
|
||||
static OneYear = 365 * Cache.OneDay;
|
||||
|
||||
private map = new Map<string, [number, T]>();
|
||||
private defaultTimeout = 0;
|
||||
|
||||
*[Symbol.iterator](): Iterator<[string, T]> {
|
||||
for (const [key, [timeout, value]] of this.map.entries()) {
|
||||
if (!isExpired(timeout)) {
|
||||
yield [key, value];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(defaultTimeout?: number);
|
||||
constructor(entries?: readonly (readonly [string, [number, T]])[], defaultTimeout?: number);
|
||||
constructor(arg1?: number | readonly (readonly [string, [number, T]])[], defaultTimeout?: number) {
|
||||
if (arg1 === undefined) {
|
||||
// overload #0 : no arguments
|
||||
return;
|
||||
}
|
||||
|
||||
if (is.numeric(arg1)) {
|
||||
// overload #1 : default timeout
|
||||
this.defaultTimeout = arg1 ?? 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// overload #2 : entries and default timeout
|
||||
this.defaultTimeout = defaultTimeout ?? 0;
|
||||
this.loadValues(arg1);
|
||||
}
|
||||
|
||||
/** Loads the values into the cache, overwriting any existing values that are older. */
|
||||
loadValues(values: Iterable<readonly [string, [number, T]]>) {
|
||||
for (const [key, newValue] of values) {
|
||||
if (isExpired(newValue[0])) {
|
||||
// if the current value is expired, skip it
|
||||
continue;
|
||||
}
|
||||
|
||||
const existing = this.map.get(key);
|
||||
// if there is an existing value, and the new value is older, skip it
|
||||
if (existing) {
|
||||
if (isLater(existing[0], newValue[0])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// the new value is either not present, or is newer than the existing value
|
||||
this.map.set(key, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a value for a given key if it exists in the cache (and is not expired) otherwise, returns undefined */
|
||||
get(key: string, timeout?: number): T | undefined {
|
||||
const existing = this.map.get(key);
|
||||
if (!existing) {
|
||||
// no data for this key
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (existing[0] && existing[0] < Date.now()) {
|
||||
// data in this key has expired
|
||||
this.map.delete(key);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// update the timeout for this key
|
||||
existing[0] = Date.now() + (timeout ?? this.defaultTimeout);
|
||||
return existing[1];
|
||||
}
|
||||
|
||||
getCacheEntry(key: string) {
|
||||
return this.map.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value for a given key if it exists in the cache (and is not expired)
|
||||
* or runs the action to get the value, and adds it to the cache, and then returns the value.
|
||||
*/
|
||||
getOrAdd(key: string, action: () => T | undefined, timeout?: number): T | undefined;
|
||||
getOrAdd(key: string, action: () => Promise<T | undefined>, timeout?: number): Promise<T | undefined>;
|
||||
getOrAdd(key: string, action: () => T | undefined | Promise<T | undefined>, timeout?: number): T | undefined | Promise<T | undefined>{
|
||||
const result = this.get(key);
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
const v = action();
|
||||
if (is.promise(v)) {
|
||||
return v.then(v => this.set(key, v, timeout));
|
||||
}
|
||||
return this.set(key, v, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value in the cache to the given value, with an optional timeout
|
||||
*
|
||||
* If the value is undefined, the key is removed from the cache.
|
||||
*
|
||||
*/
|
||||
set(key: string, value: T | undefined, timeout?: number): T | undefined{
|
||||
timeout = timeout ?? this.defaultTimeout;
|
||||
if (timeout && timeout < Cache.OneMonth) {
|
||||
// the timeout value is the number of milliseconds to keep the value in the cache
|
||||
timeout += Date.now();
|
||||
}
|
||||
// temporary: sanity check
|
||||
if (timeout > Date.now() + Cache.OneYear) {
|
||||
// this date is clearly wrong, and too far in the future.
|
||||
throw new Error('Timeout should not be that far in the future');
|
||||
}
|
||||
|
||||
if (timeout && timeout < Date.now()) {
|
||||
// this is already expired.
|
||||
throw new Error('Timeout should not be in the past');
|
||||
}
|
||||
|
||||
if (value === undefined) {
|
||||
// auto delete undefined values
|
||||
this.map.delete(key);
|
||||
return undefined;
|
||||
}
|
||||
// insert the item with the timeout (or 0 for no timeout)
|
||||
this.map.set(key, [timeout, value]);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Clears out the cache of entries that are expired. */
|
||||
clean() {
|
||||
for (const [key, [timeout]] of this.map) {
|
||||
if (isExpired(timeout)) {
|
||||
this.map.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Clear out the Cache of all entries */
|
||||
clear() {
|
||||
this.map.clear();
|
||||
}
|
||||
|
||||
/** Returns the entries in the cache that are not expired as an array of [key,T] */
|
||||
entries() {
|
||||
// filter out entries that are expired before returning
|
||||
return [...this.map.entries()].filter(([,[timeout]]) => !isExpired(timeout)).map(([key, [,value]]) => [key, value] as const);
|
||||
}
|
||||
|
||||
/** Returns the entries in the cache that are not expired as an array of [key, [timeout,T]] */
|
||||
cacheEntries() {
|
||||
// filter out entries that are expired before returning
|
||||
return [...this.map.entries()].filter(([,[timeout]]) => !isExpired(timeout));
|
||||
}
|
||||
|
||||
/** returns the values in the cache as an array of T */
|
||||
values() {
|
||||
// filter out entries that are expired before returning
|
||||
return [...this.map.values()].filter(([timeout]) => !isExpired(timeout)).map(([,value]) => value);
|
||||
}
|
||||
|
||||
/** returns the number of entries in the cache */
|
||||
get size() {
|
||||
return this.map.size;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
// enable typescript disposable types/interfaces
|
||||
/// <reference lib="esnext.disposable" />
|
||||
|
||||
export function dispose(onDispose: () => void): Disposable {
|
||||
return { [Symbol.dispose] : onDispose };
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import { is } from './guards';
|
||||
|
||||
export function equal(a: any, b: any) {
|
||||
return is.array(a) && is.array(b) ? sequenceEqual(a, b) : deepEqual(a, b);
|
||||
}
|
||||
|
||||
/** determines if two collections are equal */
|
||||
export function sequenceEqual(a: any[], b: any[]) {
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i: number = 0; i < a.length; ++i) {
|
||||
if (a[i] !== b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** determines if two arbitrary things are equvalent (deep) */
|
||||
export function deepEqual(a: any, b: any) {
|
||||
if (a === b) {
|
||||
return true; // identical primitives, undefined, null, or same object
|
||||
}
|
||||
|
||||
if (a !== Object(a) || b !== Object(b) || (Object.keys(a).length !== Object.keys(b).length)) {
|
||||
return false; // not object, or objects that have different number of keys
|
||||
}
|
||||
|
||||
// compare objects
|
||||
for (const key in a) {
|
||||
// if the key isn't in both, or the values aren't equal, return false
|
||||
if (!(key in b || deepEqual(a[key], b[key]))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Extending this class makes an easy way for ensuring that a comparison for changed state is cached, and then
|
||||
* when the state is changed, the new state is cached. This is useful for determining
|
||||
*/
|
||||
export class LastKnownState {
|
||||
changed<T extends keyof this, V extends this[T] >(k: T, content: V): boolean {
|
||||
if ((k in this) && equal(content, this[k])) {
|
||||
return false;
|
||||
}
|
||||
this[k] = content;
|
||||
return true;
|
||||
}
|
||||
|
||||
unchanged<T extends keyof this, V extends this[T] >(k: T, content: V): boolean {
|
||||
return !this.changed(k, content);
|
||||
}
|
||||
}
|
||||
@@ -54,9 +54,6 @@ export function finalize(...items: any[]): void {
|
||||
ignore(() => item.dispose?.());
|
||||
});
|
||||
ActiveFinalizers = Promise.all([fin, ActiveFinalizers, DispatcherBusy]).then(() => item.removeAllListeners?.());
|
||||
if (item[Symbol.dispose]) {
|
||||
using _ = item; // make it call the [Symbol.dispose()] method
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -21,10 +21,6 @@ export class is {
|
||||
return false;
|
||||
}
|
||||
|
||||
static numeric(node: any): node is number {
|
||||
return typeof node === 'number' && !isNaN(node) && isFinite(node);
|
||||
}
|
||||
|
||||
static object(node: any): node is Record<string, any> {
|
||||
return typeof node === 'object' && node !== null && !is.array(node);
|
||||
}
|
||||
@@ -57,10 +53,6 @@ export class is {
|
||||
return Array.isArray(instance);
|
||||
}
|
||||
|
||||
static set(instance: any): instance is Set<any> {
|
||||
return instance instanceof Set;
|
||||
}
|
||||
|
||||
static string(instance: any): instance is string {
|
||||
return typeof instance === 'string';
|
||||
}
|
||||
@@ -86,18 +78,4 @@ export class is {
|
||||
static error(instance: any): instance is Error {
|
||||
return instance instanceof Error;
|
||||
}
|
||||
static empty(instance: any): boolean {
|
||||
// if the thing is undefined or an empty array
|
||||
if (instance === undefined || (is.array(instance) && instance.length === 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if it's an object, check if it's empty
|
||||
if (is.object(instance)) {
|
||||
// objects can be 'empty' if all the children are empty
|
||||
return !!Object.entries(instance).find(([,value]) => !is.empty(value));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
import { MessagePort } from 'worker_threads';
|
||||
import { collectGarbage } from './garbageCollector';
|
||||
|
||||
function showActiveHandles() {
|
||||
const open = (process as any)._getActiveHandles().filter(
|
||||
(each: any) =>
|
||||
!each.destroyed && // discard handles that claim they are destroyed.
|
||||
!(each.fd === 0) && // ignore stdin/stdout/stderr
|
||||
!(each.fd === 1) && // ignore stdin/stdout/stderr
|
||||
!(each.fd === 2) && // ignore stdin/stdout/stderr
|
||||
!(each instanceof MessagePort) && // ignore worker thread message ports
|
||||
each.listening // keep servers that are still listening.
|
||||
|
||||
);
|
||||
|
||||
if (open.length) {
|
||||
console.log('################');
|
||||
console.log('Active Handles: ');
|
||||
console.log('################');
|
||||
console.log(open);
|
||||
}
|
||||
}
|
||||
|
||||
let misbehavingPromises: Set<Promise<any>>;
|
||||
|
||||
export function addMisbehavingPromise(promise: Promise<any>) {
|
||||
misbehavingPromises?.add(promise);
|
||||
return promise;
|
||||
}
|
||||
(global as any).addMisbehavingPromise = addMisbehavingPromise;
|
||||
let MAX = 20;
|
||||
|
||||
export function initDevModeChecks() {
|
||||
misbehavingPromises = new Set<Promise<any>>();
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
require('mocha').afterAll?.(() => {
|
||||
collectGarbage();
|
||||
console.log("showing!");
|
||||
showActiveHandles();
|
||||
});
|
||||
} catch {
|
||||
// ignore
|
||||
console.log('oops');
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (reason: any, p) => {
|
||||
|
||||
console.log(`Unhandled Rejection at: Promise ${p} - reason:, ${(reason as any)?.stack ?? reason}`);
|
||||
});
|
||||
|
||||
process.on('multipleResolves', (type, promise, reason) => {
|
||||
if (misbehavingPromises.has(promise)) {
|
||||
return;
|
||||
}
|
||||
if (reason && (reason as any).stack) {
|
||||
console.error((reason as any).stack);
|
||||
return;
|
||||
}
|
||||
if (!MAX--) {
|
||||
throw new Error('MAX MULTIPLE RESOLVED REACHED');
|
||||
}
|
||||
console.error({text: 'Multiple Resolves', type, promise, reason});
|
||||
});
|
||||
|
||||
process.on("beforeExit", () => {
|
||||
console.log("EXITING!!!");
|
||||
});
|
||||
|
||||
process.on("exit", () => {
|
||||
console.log("EXITING!!!");
|
||||
});
|
||||
|
||||
process.on('exit', showActiveHandles);
|
||||
}
|
||||
@@ -33,16 +33,3 @@ export function getOrAdd<TKey, TValue>(map: Map<TKey, TValue> | WeakMap<any, TVa
|
||||
return initializer;
|
||||
}
|
||||
}
|
||||
|
||||
export function entries<TKey, TValue>(map: Map<TKey, TValue>): [TKey, TValue][];
|
||||
export function entries<TKey, TValue>(map: Promise<Map<TKey, TValue>>): Promise<[TKey, TValue][]>;
|
||||
export function entries<TKey, TValue, TKeyOut, TValueOut>(map: Map<TKey, TValue>, selector?: (key: TKey, value: TValue) => [TKeyOut, TValueOut]): [TKeyOut, TValueOut][];
|
||||
export function entries<TKey, TValue, TKeyOut, TValueOut>(map: Promise<Map<TKey, TValue>>, selector?: (key: TKey, value: TValue) => [TKeyOut, TValueOut]): Promise<[TKeyOut, TValueOut][]>;
|
||||
export function entries<TKey, TValue, TKeyOut = TKey, TValueOut = TValue>(map: Promise<Map<TKey, TValue>> | Map<TKey, TValue>, selector?: (key: TKey, value: TValue) => [TKeyOut, TValueOut]): [TKeyOut, TValueOut][] | [TKey, TValue][] | Promise<[TKeyOut, TValueOut][] | [TKey, TValue][]> {
|
||||
return is.promise(map) ?
|
||||
map.then(m => entries(m, selector)) : // async version
|
||||
selector ?
|
||||
[...map.entries()].map(([key, value]) => selector(key, value)) : // map the values with a selector
|
||||
[...map.entries()]; // return the values
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
export function elapsed() {
|
||||
return `[${Date.now() - startTime}msec] `;
|
||||
}
|
||||
|
||||
export function time(fn: (msec: number, elapsed: number) => void) {
|
||||
const now = Date.now();
|
||||
return {
|
||||
[Symbol.dispose]() {
|
||||
fn(Date.now() - now, Date.now() - startTime);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import { normalize } from 'path';
|
||||
import { isWindows } from '../../constants';
|
||||
import { Command, CommandFunction } from '../Process/program';
|
||||
import { Cache } from './cache';
|
||||
|
||||
let powerShell: CommandFunction;
|
||||
|
||||
async function init() {
|
||||
powerShell = await new Command("powershell", '-NoProfile', '-NonInteractive', '-Command');
|
||||
}
|
||||
const initialized = init();
|
||||
|
||||
type QueryResults = { subKeys: string[]; values: Record<string, { data: any; type: string } >};
|
||||
type RegistryProperties = Record<string, string | number | string[] | Buffer >;
|
||||
|
||||
interface RegKey {
|
||||
properties: RegistryProperties;
|
||||
children: string[];
|
||||
}
|
||||
|
||||
const regKeyCache = new Cache<RegKey>(5 * Cache.OneMinute);
|
||||
|
||||
/**
|
||||
* Returns a RegKey containing the registry data from the given hive and path
|
||||
*
|
||||
* @param hive the hive to read from (HKLM, HKCU, etc)
|
||||
* @param path the path to read from (ie, 'SOFTWARE\Microsoft\Windows Kits\Installed Roots')
|
||||
* @returns a RegKey containing the registry data from the given hive and path, or undefined if the key does not exist or the user does not have access
|
||||
*
|
||||
* @remarks if anything goes wrong, this will return undefined
|
||||
*/
|
||||
export async function readKey(hive: string, path: string): Promise<RegKey | undefined> {
|
||||
if (!isWindows) {
|
||||
// registry is only available on windows
|
||||
return undefined;
|
||||
}
|
||||
const cacheKey = `${hive}:${path}`;
|
||||
const result = regKeyCache.get(cacheKey);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
// normalize the hive name
|
||||
switch (hive.toUpperCase()) {
|
||||
case 'HKLM':
|
||||
case 'HKEY_LOCAL_MACHINE':
|
||||
hive = 'HKLM';
|
||||
break;
|
||||
case 'HKCU':
|
||||
case 'HKEY_CURRENT_USER':
|
||||
hive = 'HKCU';
|
||||
break;
|
||||
default:
|
||||
// invalid hive, PS only has HKLM and HKCU PSDrives.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// remove all unprintable characters and normalize the path (backslashes as separators, no leading/trailing slashes)
|
||||
// eslint-disable-next-line no-control-regex
|
||||
path = normalize(path.replace(/[\x00-\x1F]/gm, '')).replace(/(^\\+|\\$)/gm, '');
|
||||
|
||||
// ensure that the command is initialized
|
||||
await initialized;
|
||||
|
||||
// shell out to powershell to get the registry data
|
||||
const data = await powerShell(`
|
||||
$item = (Get-Item -Path "${hive}:${path}" -ea 0)
|
||||
if( $item ) {
|
||||
$result = @{
|
||||
subKeys = $item.getSubKeyNames()
|
||||
values = @{}
|
||||
}
|
||||
$item.GetValueNames() |% {
|
||||
$result.values[$_] = @{
|
||||
data = $item.GetValue($_)
|
||||
type = $item.GetValueKind($_).toString().toLower()
|
||||
}
|
||||
}
|
||||
$result | convertto-json -depth 4
|
||||
} else {
|
||||
exit -1
|
||||
}
|
||||
`);
|
||||
|
||||
// if the command failed, return undefined
|
||||
if (data.code) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// parse the json output
|
||||
const queried = JSON.parse(data.stdio.all().join('')) as QueryResults;
|
||||
|
||||
// return the data in a more usable format
|
||||
return regKeyCache.set(cacheKey, {
|
||||
children: queried.subKeys,
|
||||
properties: Object.entries(queried.values).reduce((result, [key, value]) => {
|
||||
result[key] = value.type === 'binary' ? Buffer.from(value.data) : value.data;
|
||||
return result;
|
||||
}, {} as RegistryProperties)
|
||||
});
|
||||
} catch {
|
||||
// failures will always return undefined
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
import { normalize } from 'path';
|
||||
import { Uri } from 'vscode';
|
||||
import { is } from './guards';
|
||||
|
||||
/**
|
||||
* Adds one or more values to a set (filters out falsy values)
|
||||
* @param set the set to add values to
|
||||
* @param values the one or more values to add
|
||||
* @returns the set
|
||||
*/
|
||||
export function add<T>(set: Set<T>, values: Iterable<T> | T | undefined): Set<T> {
|
||||
if (!is.iterable(values)) {
|
||||
return values ? set.add(values) : set;
|
||||
}
|
||||
|
||||
for (const value of values) {
|
||||
if (value) {
|
||||
set.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
function normalizePath(path: string) {
|
||||
return Uri.file(normalize(path)).fsPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one or more file path values to a set (filters out falsy values)
|
||||
* @param set the set to add values to
|
||||
* @param values the one or more values to add
|
||||
* @returns the set
|
||||
*/
|
||||
export function addNormalizedPath(set: Set<string>, values: Iterable<string> | string | undefined): Set<string> {
|
||||
if (!is.iterable(values)) {
|
||||
return values ? set.add(normalizePath(values)) : set;
|
||||
}
|
||||
|
||||
for (const value of values) {
|
||||
if (value) {
|
||||
set.add(normalizePath(value));
|
||||
}
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
import { fail } from 'assert';
|
||||
import { MessagePort, Worker, isMainThread } from 'worker_threads';
|
||||
import { ManualPromise } from '../Async/manualPromise';
|
||||
import { finalize } from './finalize';
|
||||
import { is } from './guards';
|
||||
|
||||
// enable typescript disposable types/interfaces
|
||||
/// <reference lib="esnext.disposable" />
|
||||
|
||||
// polyfill Symbol.dispose
|
||||
(Symbol as any).dispose ??= Symbol("Symbol.dispose");
|
||||
(Symbol as any).asyncDispose ??= Symbol("Symbol.asyncDispose");
|
||||
|
||||
/*
|
||||
* SNARE: Simple Nodejs Asynchronous Remoting Engine
|
||||
*
|
||||
* SNARE is an extremely lightweight remoting engine that
|
||||
* allows you to call functions in a nodejs worker thread
|
||||
*
|
||||
* It supports:
|
||||
* - notifications (no response)
|
||||
* - requests (async calls with a response)
|
||||
* - error handling
|
||||
* - some simple byref object management
|
||||
*
|
||||
* As long as the values you pass and return are supported by the Structured Clone Algorithm,
|
||||
* (see https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)
|
||||
* you can pass them by value to the remote thread.
|
||||
*
|
||||
* If you need to pass an object by reference, you can manually craft a remote object extending
|
||||
* the MarshalByReference; see the Toolset class for an example.
|
||||
*/
|
||||
|
||||
const results = new Map<number, ManualPromise<any>>();
|
||||
let next = 0;
|
||||
|
||||
interface EventData {
|
||||
id: string;
|
||||
sequence: number;
|
||||
parameters?: any[];
|
||||
result?: any;
|
||||
error?: any;
|
||||
}
|
||||
|
||||
export interface RemoteConnection {
|
||||
connection: Worker | MessagePort;
|
||||
terminate(): void;
|
||||
request(id: string, ...parameters: any[]): Promise<any>;
|
||||
notify(id: string, ...parameters: any[]): void;
|
||||
marshall<T extends MarshalByReference>(ctor: new (remote: RemoteConnection, instance: number) => T, instance: Promise<number>): Promise<T | undefined>;
|
||||
marshall<T extends MarshalByReference>(ctor: new (remote: RemoteConnection, instance: number) => T, instance: number): T | undefined;
|
||||
}
|
||||
|
||||
/** SNARE: Simple Nodejs Asynchronous Remoting Engine */
|
||||
export function startRemoting(connection: Worker | MessagePort, endpoint: Record<string, (...args: any[]) => any>): RemoteConnection {
|
||||
const ready = new ManualPromise<void>();
|
||||
connection.on('online', () => ready.resolve());
|
||||
// the worker threads don't have an 'online' event (the port is already connected)
|
||||
if (!isMainThread) {
|
||||
ready.resolve();
|
||||
}
|
||||
|
||||
connection.on('message', (eventData: EventData) => {
|
||||
// if the event is an error, reject the promise
|
||||
if (eventData.id === '$error') {
|
||||
results.get(eventData.sequence)?.reject(eventData.error);
|
||||
return results.delete(eventData.sequence);
|
||||
}
|
||||
|
||||
// if the event is a result, resolve the promise
|
||||
if (eventData.id === '$result') {
|
||||
results.get(eventData.sequence)?.resolve(eventData.result);
|
||||
return results.delete(eventData.sequence);
|
||||
}
|
||||
|
||||
// otherwise, we're going to call a remote function
|
||||
|
||||
// get the sequence number for the result (0 indicates that it's a notification)
|
||||
const sequence = eventData.sequence;
|
||||
|
||||
try {
|
||||
// call the endpoint
|
||||
const result = endpoint[eventData.id](...eventData.parameters ?? []);
|
||||
|
||||
// is it a request?
|
||||
if (sequence) {
|
||||
if (is.promise(result)) {
|
||||
// wait for the async call to complete, then post the result back
|
||||
void result.then(
|
||||
(result) => connection.postMessage({ id:"$result", sequence, result }),
|
||||
(error) => connection.postMessage({ id:"$error", sequence, error })); // call failed, threw
|
||||
return;
|
||||
}
|
||||
// post the result back (call returned synchronously)
|
||||
connection.postMessage({id:"$result", sequence, result }); // call succeeded
|
||||
}
|
||||
} catch (error) {
|
||||
if (sequence) {
|
||||
connection.postMessage({id:"$error", sequence, error }); // call failed, threw
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connection.on('messageerror', (error) => {
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
connection.on('error', (error) => {
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
const remote = {
|
||||
connection,
|
||||
request: async (id: string, ...parameters: any[]) => {
|
||||
if (isMainThread) {
|
||||
await ready;
|
||||
}
|
||||
const result = new ManualPromise<any>();
|
||||
const eventData: EventData = { id, parameters, sequence: ++next };
|
||||
results.set(eventData.sequence, result);
|
||||
|
||||
// watch call times:
|
||||
// const now = Date.now();
|
||||
// result.then(() => console.log(`REMOTE RESULT ${JSON.stringify(eventData)} ${Date.now() - now}`), () => console.log(`REMOTE ERROR ${JSON.stringify(eventData)} ${Date.now() - now}`));
|
||||
|
||||
connection.postMessage(eventData);
|
||||
return result;
|
||||
},
|
||||
notify: (id: string, ...parameters: any[]) => isMainThread ? void ready.then(() => connection.postMessage({ id, parameters, sequence: 0 })) : connection.postMessage({ id, parameters, sequence: 0 }),
|
||||
marshall: <T extends MarshalByReference>(ctor: new (remote: RemoteConnection, instance: number) => T, instance: number | Promise<number>) => instance ? is.promise(instance) ? instance.then(i => new ctor(remote, i)) : new ctor(remote, instance) : undefined,
|
||||
terminate: () => { if (isMainThread) { void (connection as Worker).terminate(); } }
|
||||
};
|
||||
|
||||
connection.on('close', () => {
|
||||
// disable the remote connection interface so that it can't be used anymore
|
||||
const r = remote as any;
|
||||
r.request = r.marshal = async () => {};
|
||||
r.notify = r.terminate = () => {};
|
||||
r.connection = undefined;
|
||||
|
||||
// the connection is closed, so reject all pending requests
|
||||
for (const result of results.values()) {
|
||||
try {
|
||||
result.reject('Connection closed');
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
// and clear the results map
|
||||
results.clear();
|
||||
|
||||
// clear out any byref objects
|
||||
identityIndex.length = 0;
|
||||
instanceIndex.clear();
|
||||
|
||||
});
|
||||
|
||||
return remote;
|
||||
}
|
||||
|
||||
const identityIndex = new Array<any>();
|
||||
const instanceIndex = new Map<any, [number, number]>();
|
||||
|
||||
export function getByRef<T = any>(identity: number): T {
|
||||
return identityIndex[identity] ?? fail(`Invalid ${identity} for ByRef object`);
|
||||
}
|
||||
|
||||
export function ref(instance: Promise<any>): Promise<number | undefined>;
|
||||
export function ref(instance: any): number | undefined;
|
||||
export function ref(instance: any | Promise<any>): number | undefined | Promise<number | undefined> {
|
||||
if (is.promise(instance)) {
|
||||
return instance.then(ref);
|
||||
}
|
||||
|
||||
if (is.object(instance)) {
|
||||
// lookup the instance in the index
|
||||
const [identity, refcount] = instanceIndex.get(instance) ?? [++next, 0];
|
||||
|
||||
// if refcount is zero, then we need to add it to the index
|
||||
if (!refcount) {
|
||||
identityIndex[identity] = instance;
|
||||
}
|
||||
|
||||
// and increment the refcount
|
||||
instanceIndex.set(instance, [identity, refcount + 1]);
|
||||
|
||||
// and return the identity
|
||||
return identity;
|
||||
}
|
||||
// if it's not an object, we can't ref it.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function unref(identity: number) {
|
||||
// lookup the instance
|
||||
const instance = getByRef(identity);
|
||||
if (instance) {
|
||||
// decrement the refcount
|
||||
const [identity, refcount] = instanceIndex.get(instance) ?? [0, 0];
|
||||
if (refcount > 1) {
|
||||
// reduce the refcount by one
|
||||
return instanceIndex.set(instance, [identity, refcount - 1]);
|
||||
}
|
||||
// it's the last reference, so remove it from the index
|
||||
identityIndex[identity] = undefined;
|
||||
instanceIndex.delete(instance);
|
||||
finalize(instance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A base class for objects that are passed by reference to a remote thread.
|
||||
*
|
||||
* All MarshalByReference wrappers, are references to an object that lives in the remote thread.
|
||||
* It is important to call .dispose() when you are done with it, as this enables the remote
|
||||
* thread to release the object and free up resources.
|
||||
*/
|
||||
export class MarshalByReference implements Disposable {
|
||||
constructor(protected remote: RemoteConnection, protected instance: number) {
|
||||
}
|
||||
|
||||
/**
|
||||
* This disposes the ByRef object, and notifies the remote thread to reduce the refcount,
|
||||
* which would dispose the remote object if it was the last reference.
|
||||
*/
|
||||
[Symbol.dispose]() {
|
||||
void this.remote.notify('unref', this.instance);
|
||||
this.instance = 0;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* This performs a structured clone using the built-in serialization and deserialization
|
||||
* v8 APIs. This is the fastest thing available.
|
||||
*
|
||||
* This can be replaced with the built-in structuredClone() when it is available in
|
||||
* node.js and electron (it's in node V17+)
|
||||
*
|
||||
* for more information, see the Structured Clone Algorithm for JavaScript:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
|
||||
*
|
||||
* @param instance any value to be cloned
|
||||
* @returns a copy of the instance
|
||||
*/
|
||||
export function structuredClone<T>(instance: T): T {
|
||||
/// return instance ? deserialize(serialize(instance)) : instance;
|
||||
return instance ? JSON.parse(JSON.stringify(instance)) : instance;
|
||||
}
|
||||
@@ -6,5 +6,5 @@
|
||||
import { verboseEnabled } from '../../constants';
|
||||
|
||||
export function verbose(...args: any[]): void {
|
||||
return verboseEnabled || process.argv.includes('--verbose') ? console.log(`${args.join(' ')}`) : undefined;
|
||||
return verboseEnabled || process.argv.includes('--verbose') ? console.log(...args) : undefined;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ function parseTaggedLiteral(templateString: string) {
|
||||
result.state = 'text';
|
||||
continue;
|
||||
|
||||
// ...case ' ':
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
case '\n':
|
||||
@@ -78,12 +78,12 @@ function parseTaggedLiteral(templateString: string) {
|
||||
continue;
|
||||
}
|
||||
if (expression) {
|
||||
if (isIdentifierPart(char.codePointAt(0)!) || char === '-' || char === '/' || char === ';' || char === ' ') {
|
||||
if (isIdentifierPart(char.codePointAt(0)!) || char === '-' || char === '/') {
|
||||
expression += char;
|
||||
continue;
|
||||
}
|
||||
// error, fall through
|
||||
} else if (isIdentifierStart(char.codePointAt(0)!) || char === '-' || char === '/' || char === ';' || char === ' ') {
|
||||
} else if (isIdentifierStart(char.codePointAt(0)!) || char === '-' || char === '/') {
|
||||
expression += char;
|
||||
continue;
|
||||
}
|
||||
@@ -117,11 +117,7 @@ function split(expression: string) {
|
||||
return (expression.match(/(.*?):(.*)/) || ['', '', expression]).slice(1);
|
||||
}
|
||||
|
||||
async function resolveValue(expression: string, context: Record<string, any>, customResolver: CustomResolver = async (_prefix: string, _expression: string) => ''): Promise<string> {
|
||||
if (!expression) {
|
||||
return '';
|
||||
}
|
||||
|
||||
function resolveValue(expression: string, context: Record<string, any>, customResolver = (_prefix: string, _expression: string) => ''): string {
|
||||
const [prefix, suffix] = split(expression);
|
||||
|
||||
function joinIfArray(value: any, separator = '\u0007') {
|
||||
@@ -133,16 +129,16 @@ async function resolveValue(expression: string, context: Record<string, any>, cu
|
||||
if (variable !== undefined && variable !== null) { // did we get back an actual value
|
||||
// it's a child of a variable
|
||||
return joinIfArray(suffix.includes(':') ? // is the suffix another expression?
|
||||
await resolveValue(suffix, variable) : // Yeah, resolve it
|
||||
variable[suffix] ?? await customResolver(prefix, suffix) ?? ''); // No, return the member of the variable, or dynamic, or empty string
|
||||
resolveValue(suffix, variable) : // Yeah, resolve it
|
||||
variable[suffix] ?? customResolver(prefix, suffix) ?? ''); // No, return the member of the variable, or dynamic, or empty string
|
||||
}
|
||||
|
||||
// no variable by that name, so return the dynamic value, or an empty string
|
||||
return joinIfArray(await customResolver(prefix, suffix) ?? '');
|
||||
return joinIfArray(customResolver(prefix, suffix) ?? '');
|
||||
}
|
||||
|
||||
// look up the value in the variables, or ask the dynamic function to resolve it, failing that, an empty string
|
||||
return joinIfArray(context[suffix] ?? await customResolver(prefix, suffix) ?? '');
|
||||
return joinIfArray(context[suffix] ?? customResolver(prefix, suffix) ?? '');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
@@ -218,58 +214,38 @@ class as {
|
||||
}
|
||||
}
|
||||
|
||||
export type CustomResolver = (prefix: string, expression: string) => Promise<string>;
|
||||
|
||||
export async function render(templateStrings: string[], context: Record<string, any>, customResolver?: CustomResolver, ensureValuesAreValidJS?: boolean): Promise<string[]>;
|
||||
export async function render(templateString: string, context: Record<string, any>, customResolver?: CustomResolver, ensureValuesAreValidJS?: boolean): Promise<string>;
|
||||
export async function render(templateString: string | string[], context: Record<string, any>, customResolver: CustomResolver = async (_prefix: string, _expression: string) => '', asJs = false): Promise<string | string[]> {
|
||||
export function render(templateStrings: string[], context: Record<string, any>, customResolver?: (prefix: string, expression: string) => string, ensureValuesAreValidJS?: boolean): string[];
|
||||
export function render(templateString: string, context: Record<string, any>, customResolver?: (prefix: string, expression: string) => string, ensureValuesAreValidJS?: boolean): string;
|
||||
export function render(templateString: string | string[], context: Record<string, any>, customResolver = (_prefix: string, _expression: string) => '', asJs = false): string | string[] {
|
||||
if (Array.isArray(templateString)) {
|
||||
return Promise.all(templateString.map(each => render(each, context, customResolver, asJs)));
|
||||
return templateString.map(each => render(each, context, customResolver, asJs));
|
||||
}
|
||||
|
||||
// quick exit if it's not a templated string
|
||||
if (!templateString.includes('${')) {
|
||||
return templateString;
|
||||
}
|
||||
|
||||
const { template, expressions, state, message } = parseTaggedLiteral(templateString);
|
||||
const stabilize = asJs ? as.js : (x: string) => as.string(x) ?? '';
|
||||
|
||||
if (state === 'error') {
|
||||
console.error(`Error parsing tagged literal: ${message}`);
|
||||
return message;
|
||||
}
|
||||
|
||||
let result = '';
|
||||
for (let index = 0; index < template.length; ++index) {
|
||||
const each = template[index];
|
||||
if (index) {
|
||||
const v = await resolveValue(expressions[index - 1], context, customResolver);
|
||||
result = `${result}${stabilize(v)}${each}`;
|
||||
} else {
|
||||
result = `${each}`;
|
||||
}
|
||||
}
|
||||
|
||||
// if the result isn't the same as the original, but still has template strings, resolve any additional ones we can.
|
||||
return result !== templateString && result.includes('${') ? render(result, context, customResolver, asJs) : result;
|
||||
return state === 'error' ?
|
||||
message : // return the error message if the parse failed. (this is fatal anyways)
|
||||
template.reduce((result, each, index) => `${result}${stabilize(resolveValue(expressions[index - 1], context, customResolver))}${each}`); // resolve the inline expressions and join the template
|
||||
}
|
||||
|
||||
export async function evaluateExpression(expression: string, context: Record<string, any>, customResolver: CustomResolver = async (_prefix: string, _expression: string) => ''): Promise<Primitive | undefined> {
|
||||
const result = expression.match(/\!|==|!=|>=|<=|>|<|\?|\|\||&&/) ? safeEval(await render(expression, context, customResolver, true)) as Primitive : await render(expression, context, customResolver);
|
||||
export function evaluateExpression(expression: string, context: Record<string, any>, customResolver = (_prefix: string, _expression: string) => ''): Primitive | undefined {
|
||||
const result = expression.match(/\!|==|!=|>=|<=|>|<|\?|\|\||&&/) ? safeEval(render(expression, context, customResolver, true)) as Primitive : render(expression, context, customResolver);
|
||||
return result === '' || result === 'undefined' || result === 'null' || result === null ? undefined : result;
|
||||
}
|
||||
|
||||
export async function recursiveRender<T extends Record<string, any>>(obj: T, context: Record<string, any>, customResolver = async (_prefix: string, _expression: string) => ''): Promise<T> {
|
||||
export function recursiveRender<T extends Record<string, any>>(obj: T, context: Record<string, any>, customResolver = (_prefix: string, _expression: string) => ''): T {
|
||||
const result = (is.array(obj) ? [] : {}) as Record<string, any>;
|
||||
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
const newKey = is.string(key) && key.includes('${') ? await render(key, context, customResolver) : key;
|
||||
const newKey = is.string(key) && key.includes('${') ? render(key, context, customResolver) : key;
|
||||
|
||||
if (is.string(value)) {
|
||||
result[newKey] = await evaluateExpression(value, context, customResolver);
|
||||
result[newKey] = evaluateExpression(value, context, customResolver);
|
||||
} else if (typeof value === 'object') {
|
||||
result[newKey] = await recursiveRender(value, context, customResolver);
|
||||
result[newKey] = recursiveRender(value, context, customResolver);
|
||||
} else {
|
||||
result[newKey] = value;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,3 @@ export const isLinux = OperatingSystem === 'linux';
|
||||
|
||||
// if you want to see the output of verbose logging, set this to true.
|
||||
export const verboseEnabled = false;
|
||||
|
||||
export const headers = ["*.hpp", "*.hh", "*.hxx", "*.h++", "*.hp", "*.h", "*.ii", "*.inl", "*.ipp", "*.tcc", "*.idl"];
|
||||
export const sources = ["*.c", "*.i", "*.cpp", "*.cc", "*.cxx", "*.c++", "*.cp", "*.ino", "*.cu", "*.cuh"];
|
||||
|
||||
@@ -62,7 +62,7 @@ export async function expandString(input: string, options: ExpansionOptions): Pr
|
||||
return replaceAll(result, '${dollar}', '$');
|
||||
}
|
||||
|
||||
/** @returns a promise to [expandedString, didReplacement] */
|
||||
/** Returns [expandedString, didReplacement] */
|
||||
async function expandStringImpl(input: string, options: ExpansionOptions): Promise<[string, boolean]> {
|
||||
if (!input) {
|
||||
return [input, false];
|
||||
|
||||
+6
-31
@@ -9,7 +9,6 @@ import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { CppSourceStr } from './LanguageServer/extension';
|
||||
import { getLocalizedString, LocalizeStringParams } from './LanguageServer/localization';
|
||||
import { is } from './Utility/System/guards';
|
||||
|
||||
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
|
||||
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
|
||||
@@ -131,37 +130,13 @@ export interface DebugProtocolParams {
|
||||
params?: any;
|
||||
}
|
||||
|
||||
export function logDebugProtocol(output: DebugProtocolParams | string): void {
|
||||
try {
|
||||
if (!output) {
|
||||
return;
|
||||
}
|
||||
if (is.string(output)) {
|
||||
if (output.startsWith("Content")) {
|
||||
output = output.substring(output.indexOf("{"), output.length);
|
||||
}
|
||||
output = JSON.parse(output) as DebugProtocolParams;
|
||||
}
|
||||
|
||||
if (!debugChannel) {
|
||||
debugChannel = vscode.window.createOutputChannel(`${localize("c.cpp.debug.protocol", "C/C++ Debug Protocol")}`, 'javascript');
|
||||
debugChannel.appendLine("const msgs = {};");
|
||||
debugChannel.appendLine("const results = {};");
|
||||
}
|
||||
if ('result' in output) {
|
||||
debugChannel.appendLine("");
|
||||
debugChannel.appendLine("// ************************************************************************************************************************");
|
||||
debugChannel.append(`results["${(output as any).id}"]= ${JSON.stringify((output as any).result, null, 2)};`);
|
||||
return;
|
||||
}
|
||||
if (!["cpptools/debugProtocol", "cpptools/debugLog", "cpptools/onIntervalTimer", "cpptools/logTelemetry" ].includes(output.method)) {
|
||||
debugChannel.appendLine("");
|
||||
debugChannel.appendLine("// ************************************************************************************************************************");
|
||||
debugChannel.append(`msgs["${output.method}"]= ${JSON.stringify(output.params, null, 2)};`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
export function logDebugProtocol(output: DebugProtocolParams): void {
|
||||
if (!debugChannel) {
|
||||
debugChannel = vscode.window.createOutputChannel(`${localize("c.cpp.debug.protocol", "C/C++ Debug Protocol")}`);
|
||||
}
|
||||
debugChannel.appendLine("");
|
||||
debugChannel.appendLine("************************************************************************************************************************");
|
||||
debugChannel.append(`${output}`);
|
||||
}
|
||||
|
||||
export interface ShowWarningParams {
|
||||
|
||||
@@ -20,7 +20,6 @@ import { CppBuildTaskProvider, cppBuildTaskProvider } from './LanguageServer/cpp
|
||||
import { getLocaleId, getLocalizedHtmlPath } from './LanguageServer/localization';
|
||||
import { PersistentState } from './LanguageServer/persistentState';
|
||||
import { CppSettings } from './LanguageServer/settings';
|
||||
import { initialize } from './ToolsetDetection/detection';
|
||||
import { logAndReturn, returns } from './Utility/Async/returns';
|
||||
import { CppTools1 } from './cppTools1';
|
||||
import { logMachineIdMappings } from './id';
|
||||
@@ -57,10 +56,6 @@ export async function activate(context: vscode.ExtensionContext): Promise<CppToo
|
||||
|
||||
vscode.workspace.registerTextDocumentContentProvider('cpptools-schema', new SchemaProvider());
|
||||
|
||||
// initialize the toolset detection
|
||||
|
||||
void initialize([util.getExtensionFilePath("bin/definitions")], {storagePath: context.globalStorageUri.fsPath});
|
||||
|
||||
// Initialize the DebuggerExtension and register the related commands and providers.
|
||||
await DebuggerExtension.initialize(context);
|
||||
|
||||
@@ -151,6 +146,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<CppToo
|
||||
// the message on old Macs that we've already displayed a warning for.
|
||||
log(localize("intellisense.disabled", "intelliSenseEngine is disabled"));
|
||||
}
|
||||
|
||||
return cppTools;
|
||||
}
|
||||
|
||||
|
||||
@@ -451,5 +451,27 @@
|
||||
"refactor_copy_declaration_definition_failed": {
|
||||
"text": "Copying Declaration / Definition to clipboard failed: %s",
|
||||
"hint": "The operation 'Copy Declaration / Definition' on a function was not successful. %s is the error info that has a period at the end of the string."
|
||||
}
|
||||
},
|
||||
"refactor_extract_to_function": "Extract to function",
|
||||
"refactor_extract_to_free_function": "Extract to free function",
|
||||
"refactor_extract_to_member_function_in": {
|
||||
"text": "Extract to member function in '{0}'",
|
||||
"hint": "{0} is the name of the struct or class the member function belongs to, e.g. 'class Foo'."
|
||||
},
|
||||
"refactor_extract_outsidefunc": "The selected text is not inside a function.",
|
||||
"refactor_extract_multifunc": "The selected text cannot span different functions.",
|
||||
"refactor_extract_variable": "Variable '%s' is declared in the selected region and then used below it.",
|
||||
"refactor_extract_macro": "Preprocessor macro '%s' is used below the selected region.",
|
||||
"refactor_extract_inactive": "The selected region spans an inactive preprocessor block.",
|
||||
"refactor_extract_no_il": "The selected region does not contain any code that can be extracted.",
|
||||
"refactor_extract_entirefunc": "The selected region is not entirely within the function's body.",
|
||||
"refactor_extract_errors_selection": "The selection contains IntelliSense errors.",
|
||||
"refactor_extract_reference_c_code": {
|
||||
"text": "'%s' is not declared within the selected code, but is being modified. C code cannot pass arguments by reference.",
|
||||
"hint": "%s is the name of a variable."
|
||||
},
|
||||
"refactor_extract_reference_return_c_code": "The function would have to return a value by reference. C code cannot return references.",
|
||||
"refactor_extract_xborder_jump": "Jumps between the selected code and the surrounding code are present.",
|
||||
"refactor_extract_missing_return": "In the selected code, some control paths exit without setting the return value. This is supported only for scalar, numeric, and pointer return types.",
|
||||
"expand_selection": "Expand selection (to enable 'Extract to function')"
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ export function getExperimentationService(): Promise<IExperimentationService> |
|
||||
return initializationPromise;
|
||||
}
|
||||
|
||||
// @ts-expect-error The function isExperimentEnabled will be used for future experiments.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async function isExperimentEnabled(experimentName: string): Promise<boolean> {
|
||||
if (new CppSettings().experimentalFeatures) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"sourceMap": true,
|
||||
"rootDir": ".",
|
||||
"removeComments": true,
|
||||
"noUnusedLocals": true,
|
||||
"strictNullChecks": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
|
||||
@@ -83,14 +83,6 @@ export function initDevModeChecks() {
|
||||
console.error({text: 'Multiple Resolves', type, promise, reason});
|
||||
});
|
||||
|
||||
process.on("beforeExit", () => {
|
||||
console.log("EXITING!!!");
|
||||
});
|
||||
|
||||
process.on("exit", () => {
|
||||
console.log("EXITING!!!");
|
||||
});
|
||||
|
||||
process.on('exit', showActiveHandles);
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"enableNewIntellisense": true,
|
||||
"compiler": "Microsoft Visual C++/19.*/x64/x64",
|
||||
// "compiler": "C:\\program files\\microsoft visual studio\\2022\\enterprise\\vc\\tools\\msvc\\14.36.32532\\bin\\hostx64\\x64\\cl.exe",
|
||||
// "compiler": "cl.exe",
|
||||
// "compiler": "C:\\program files\\microsoft visual studio\\2022\\enterprise\\vc\\tools\\msvc\\14.36.32532\\bin\\hostx64\\x64\\cl.exe",
|
||||
"name": "newstyle",
|
||||
// "intelliSenseMode": "clang-x64",
|
||||
"compilerArgs": [
|
||||
"-Dfoo=bar",
|
||||
"-I${workspaceFolder}/include",
|
||||
"/MD"
|
||||
]
|
||||
// "intelliSenseMode": "windows-msvc-x64",
|
||||
// "compilerPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC\\14.36.32532\\bin\\Hostx64\\x64\\cl.exe",
|
||||
// "cStandard": "c17",
|
||||
// "cppStandard": "c++17"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"C_Cpp.loggingLevel": "Debug"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
#define APP_NAME "Sample"
|
||||
@@ -1,7 +0,0 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("Hello World!\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
/* --------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All Rights Reserved.
|
||||
* See 'LICENSE' in the project root for license information.
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
export function foo() {
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user