diff --git a/package-lock.json b/package-lock.json index 28159ccd..9fb5acd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -79,9 +79,9 @@ "dev": true }, "@types/node": { - "version": "6.14.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-6.14.9.tgz", - "integrity": "sha512-leP/gxHunuazPdZaCvsCefPQxinqUDsCxCR5xaDUrY2MkYxQRFZZwU5e7GojyYsGB7QVtCi7iVEl/hoFXQYc+w==", + "version": "8.10.66", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", + "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", "dev": true }, "@types/vscode": { diff --git a/package.json b/package.json index 40c84295..3a95148a 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "devDependencies": { "@types/glob": "^7.1.1", "@types/mocha": "^7.0.2", - "@types/node": "^6.0.40", + "@types/node": "^8.10.66", "@types/vscode": "1.52.*", "clang-format": "1.4.0", "glob": "^7.1.4", @@ -86,13 +86,13 @@ ], "configuration": { "type": "object", - "title": "clangd", + "title": "Clangd", "properties": { "clangd.path": { "type": "string", "default": "clangd", "scope": "machine-overridable", - "description": "The path to clangd executable, e.g.: /usr/bin/clangd." + "markdownDescription": "The path to clangd executable, e.g.: `/usr/bin/clangd`." }, "clangd.arguments": { "type": "array", @@ -100,7 +100,7 @@ "items": { "type": "string" }, - "description": "Arguments for clangd server." + "description": "Arguments for clangd server. For backwards compatibility it has precedence over other UI specified options." }, "clangd.trace": { "type": "string", @@ -134,6 +134,52 @@ "default": false, "description": "Check for language server updates on startup." }, + "clangd.compileCommandsDir": { + "type": "string", + "default": "", + "markdownDescription": "Specify a path to look for `compile_commands.json`. If path is invalid, clangd will look in the current directory and parent paths of each source file. If not specified clangd will look in the parent folders of each opened file. Corresponds to `--compile-commands-dir` flag." + }, + "clangd.queryDrivers": { + "type": "array", + "default": [], + "items": { + "type": "string" + }, + "markdownDescription": "Globs for white-listing gcc-compatible drivers that are safe to execute. Drivers matching any of these globs will be used to extract system includes. For example `/usr/bin/**/clang-*` or `/path/to/repo/**/g++-*`. Corresponds to `--query-driver` flag. Supported since clangd version 9." + }, + "clangd.backgroundIndex": { + "type": "boolean", + "default": true, + "markdownDescription": "Index project code in the background and persist index on disk. Corresponds to `--background-index` flag. Supported since clangd version 8." + }, + "clangd.clangTidy": { + "type": "boolean", + "default": true, + "markdownDescription": "Enable clang-tidy diagnostics. Corresponds to `--clang-tidy` flag. Supported since clangd version 9." + }, + "clangd.crossFileRename": { + "type": "boolean", + "default": true, + "markdownDescription": "Enable cross-file rename feature. Corresponds to `--cross-file-rename` flag. Supported since clangd version 10." + }, + "clangd.headerInsertion": { + "type": "string", + "enum": [ + "iwyu", + "never" + ], + "default": "iwyu", + "markdownEnumDescriptions": [ + "Include what you use. Insert the owning header for top-level symbols, unless the header is already directly included or the symbol is forward-declared", + "Never insert `#include` directives as part of code completion" + ], + "markdownDescription": "Add `#include` directives when accepting code completions. Corresponds to `--header-insertion` flag. Supported since clangd version 9." + }, + "clangd.limitResults": { + "type": "integer", + "default": 100, + "markdownDescription": "Limit the number of results returned by clangd. 0 means no limit. Corresponds to `--limit-results` flag." + }, "clangd.onConfigChanged": { "type": "string", "default": "prompt", diff --git a/src/arguments.ts b/src/arguments.ts new file mode 100644 index 00000000..8be95f87 --- /dev/null +++ b/src/arguments.ts @@ -0,0 +1,126 @@ +import * as cp from 'child_process'; +import * as util from 'util'; +import * as vscode from 'vscode'; + +import * as config from './config'; + +const exec = util.promisify(cp.exec); + +function argsContainFlag(args: string[], flag: string) { + for (let arg of args) { + if (arg.startsWith(flag)) + return true; + } + + return false; +} + +async function getClangdMajorVersion(clangdPath: string) { + const output = await exec(`${clangdPath} --version`); + const regexv = /clangd version ([0-9]+)\..*/; + const match = output.stdout.match(regexv); + + if (output.stderr || !match) { + throw new Error('Could not determine clangd version') + } + + return Number.parseInt(match[1]); +} + +export async function getClangdArguments(clangdPath: string) { + let args = config.get('arguments'); + // Versions before clangd 7 are not checked + let version = await getClangdMajorVersion(clangdPath); + let overridenOptionWarning = false; + + let compileCommandsDirFlag = '--compile-commands-dir'; + + if (!argsContainFlag(args, compileCommandsDirFlag)) { + let compileCommandsDir = config.get('compileCommandsDir'); + if (compileCommandsDir.length > 0) + args.push(`${compileCommandsDirFlag}=${compileCommandsDir}`); + } else { + overridenOptionWarning = true; + } + + if (version >= 9) { + let queryDriversFlag = '--query-driver'; + + if (!argsContainFlag(args, queryDriversFlag)) { + let queryDrivers = config.get('queryDrivers'); + if (queryDrivers.length > 0) { + let drivers = queryDrivers.join(','); + args.push(`${queryDriversFlag}=${drivers}`); + } + } else { + overridenOptionWarning = true; + } + } + + if (version >= 8) { + let backgroundIndexFlag = '--background-index'; + + if (!argsContainFlag(args, backgroundIndexFlag)) { + let backgroundIndex = config.get('backgroundIndex'); + args.push(`${backgroundIndexFlag}=${backgroundIndex}`); + } else { + overridenOptionWarning = true; + } + } + + if (version >= 9) { + let clangTidyFlag = '--clang-tidy'; + + if (!argsContainFlag(args, clangTidyFlag)) { + let clangTidy = config.get('clangTidy'); + args.push(`${clangTidyFlag}=${clangTidy}`) + } else { + overridenOptionWarning = true; + } + } + + if (version >= 10) { + let crossFileRenameFlag = '--cross-file-rename'; + + if (!argsContainFlag(args, crossFileRenameFlag)) { + let crossFileRename = config.get('crossFileRename'); + args.push(`${crossFileRenameFlag}=${crossFileRename}`); + } else { + overridenOptionWarning = true; + } + } + + if (version >= 9) { + let headerInsertionFlag = '--header-insertion'; + + if (!argsContainFlag(args, headerInsertionFlag)) { + let headerInsertion = config.get('headerInsertion'); + args.push(`${headerInsertionFlag}=${headerInsertion}`); + } else { + overridenOptionWarning = true; + } + } + + let limitResultsFlag = '--limit-results'; + + if (!argsContainFlag(args, limitResultsFlag)) { + let limitResults = config.get('limitResults'); + args.push(`${limitResultsFlag}=${limitResults}`); + } else { + overridenOptionWarning = true; + } + + if (overridenOptionWarning) { + let action = await vscode.window.showWarningMessage( + 'Setting "clangd.arguments" overrides one or more options that can now be set with more specific settings. This does not cause any error, but updating your configuration is advised.', + 'Open settings'); + + if (action == 'Open settings') { + vscode.commands.executeCommand( + 'workbench.action.openSettings', + '@ext:llvm-vs-code-extensions.vscode-clangd') + } + } + + return args; +} \ No newline at end of file diff --git a/src/clangd-context.ts b/src/clangd-context.ts index 23ca437f..1d817843 100644 --- a/src/clangd-context.ts +++ b/src/clangd-context.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode'; import * as vscodelc from 'vscode-languageclient/node'; +import * as args from './arguments'; import * as ast from './ast'; import * as config from './config'; import * as configFileWatcher from './config-file-watcher'; @@ -52,7 +53,7 @@ export class ClangdContext implements vscode.Disposable { const clangd: vscodelc.Executable = { command: clangdPath, - args: config.get('arguments') + args: await args.getClangdArguments(clangdPath) }; const traceFile = config.get('trace'); if (!!traceFile) {