diff --git a/utils/vscode/package-lock.json b/utils/vscode/package-lock.json index f4d73b87df53c..6577a44e497cb 100644 --- a/utils/vscode/package-lock.json +++ b/utils/vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "carbon-vscode", - "version": "0.0.5", + "version": "0.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "carbon-vscode", - "version": "0.0.5", + "version": "0.0.6", "dependencies": { "vscode-languageclient": "^9.0.1" }, diff --git a/utils/vscode/package.json b/utils/vscode/package.json index 0590ae279e327..a29f19741e75f 100644 --- a/utils/vscode/package.json +++ b/utils/vscode/package.json @@ -1,7 +1,7 @@ { "name": "carbon-vscode", "displayName": "Carbon Language", - "version": "0.0.5", + "version": "0.0.6", "publisher": "carbon-lang", "description": "Carbon language support for Visual Studio Code.", "repository": { @@ -42,8 +42,18 @@ "properties": { "carbon.carbonPath": { "type": "string", - "description": "The path to the 'carbon' binary.", + "description": "The path to the `carbon` binary.", "default": "./bazel-bin/toolchain/carbon" + }, + "carbon.carbonServerCommandArgs": { + "type": "string", + "description": "Extra flags to pass to `carbon` before the `language-server` subcommand, such as `-v` for debugging.", + "default": "" + }, + "carbon.carbonServerSubcommandArgs": { + "type": "string", + "description": "Extra flags to pass to the `language-server` subcommand.", + "default": "" } } }, diff --git a/utils/vscode/src/extension.ts b/utils/vscode/src/extension.ts index 04f9805e841d6..6711d4e2b679a 100644 --- a/utils/vscode/src/extension.ts +++ b/utils/vscode/src/extension.ts @@ -8,7 +8,12 @@ * This is the main launcher for the LSP extension. */ -import { workspace, ExtensionContext, commands } from 'vscode'; +import { + workspace, + ExtensionContext, + commands, + WorkspaceConfiguration, +} from 'vscode'; import { LanguageClient, @@ -18,6 +23,88 @@ import { let client: LanguageClient; +/** + * Splits a CLI-style quoted string. + */ +function splitQuotedString(argsString: string): string[] { + const args: string[] = []; + let arg = ''; + // Track whether there's an arg to handle `""` and similar. + let hasArg = true; + // Whether this is in a quote-delimited section. + let inSingleQuotes = false; + let inDoubleQuotes = false; + // Whether this is a `\`-escaped character. + let inEscape = false; + + for (const char of argsString) { + // While spaces can appear in arguments, they can only be an argument in + // combination with other characters. + hasArg = hasArg || char != ' '; + + if (inEscape) { + // After an escape, directly append the character. + arg += char; + inEscape = false; + continue; + } + switch (char) { + case '\\': + // First character of an escape. + inEscape = true; + continue; + case "'": + if (!inDoubleQuotes) { + // Single-quoted section. + inSingleQuotes = !inSingleQuotes; + continue; + } + break; + case '"': + if (!inSingleQuotes) { + // Double-quoted section. + inDoubleQuotes = !inDoubleQuotes; + continue; + } + break; + case ' ': + if (!inSingleQuotes && !inDoubleQuotes) { + // Space between arguments (but possibly multiple spaces). + if (hasArg) { + args.push(arg); + arg = ''; + hasArg = false; + } + continue; + } + break; + } + arg += char; + } + + // Finish any pending argument. + if (hasArg) { + args.push(arg); + } + + return args; +} + +/** + * Combines the `language-server` command with args from settings. + */ +function buildServerArgs(settings: WorkspaceConfiguration): string[] { + const result: string[] = []; + result.push( + ...splitQuotedString(settings.get('carbonServerCommandArgs', '')) + ); + result.push('language-server'); + result.push( + ...splitQuotedString(settings.get('carbonServerSubcommandArgs', '')) + ); + return result; +} + export function activate(context: ExtensionContext) { const settings = workspace.getConfiguration('carbon'); @@ -28,7 +115,7 @@ export function activate(context: ExtensionContext) { 'carbonPath', context.asAbsolutePath('./bazel-bin/toolchain/carbon') ), - args: ['language-server'], + args: buildServerArgs(settings), }; const clientOptions: LanguageClientOptions = {