Skip to content

Enhanced clangd options UI #122

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 50 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -86,21 +86,21 @@
],
"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",
"default": [],
"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",
Expand Down Expand Up @@ -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",
Expand Down
126 changes: 126 additions & 0 deletions src/arguments.ts
Original file line number Diff line number Diff line change
@@ -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<string[]>('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<string>('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<string[]>('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<boolean>('backgroundIndex');
args.push(`${backgroundIndexFlag}=${backgroundIndex}`);
} else {
overridenOptionWarning = true;
}
}

if (version >= 9) {
let clangTidyFlag = '--clang-tidy';

if (!argsContainFlag(args, clangTidyFlag)) {
let clangTidy = config.get<boolean>('clangTidy');
args.push(`${clangTidyFlag}=${clangTidy}`)
} else {
overridenOptionWarning = true;
}
}

if (version >= 10) {
let crossFileRenameFlag = '--cross-file-rename';

if (!argsContainFlag(args, crossFileRenameFlag)) {
let crossFileRename = config.get<boolean>('crossFileRename');
args.push(`${crossFileRenameFlag}=${crossFileRename}`);
} else {
overridenOptionWarning = true;
}
}

if (version >= 9) {
let headerInsertionFlag = '--header-insertion';

if (!argsContainFlag(args, headerInsertionFlag)) {
let headerInsertion = config.get<string>('headerInsertion');
args.push(`${headerInsertionFlag}=${headerInsertion}`);
} else {
overridenOptionWarning = true;
}
}

let limitResultsFlag = '--limit-results';

if (!argsContainFlag(args, limitResultsFlag)) {
let limitResults = config.get<number>('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;
}
3 changes: 2 additions & 1 deletion src/clangd-context.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -52,7 +53,7 @@ export class ClangdContext implements vscode.Disposable {

const clangd: vscodelc.Executable = {
command: clangdPath,
args: config.get<string[]>('arguments')
args: await args.getClangdArguments(clangdPath)
};
const traceFile = config.get<string>('trace');
if (!!traceFile) {
Expand Down