From 6d143bb4909ac293108bff98f1374fd35426b44d Mon Sep 17 00:00:00 2001 From: Stone Tickle Date: Fri, 14 Mar 2025 10:37:17 -0400 Subject: [PATCH 1/5] add muon as an available language server --- package.json | 1 + src/lsp/common.ts | 3 +++ src/lsp/muon.ts | 24 ++++++++++++++++++++++++ src/types.ts | 2 +- 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 src/lsp/muon.ts diff --git a/package.json b/package.json index f597456..0e71fe8 100644 --- a/package.json +++ b/package.json @@ -255,6 +255,7 @@ "enum": [ "Swift-MesonLSP", "mesonlsp", + "muon", null ], "description": "Select which language server to use. Swift-MesonLSP is a legacy alias for mesonlsp." diff --git a/src/lsp/common.ts b/src/lsp/common.ts index 0ee66f3..98cb81c 100644 --- a/src/lsp/common.ts +++ b/src/lsp/common.ts @@ -2,6 +2,7 @@ import * as vscode from "vscode"; import { LanguageServerClient } from "."; import { LanguageServer } from "../types"; import { MesonLSPLanguageClient } from "./mesonlsp"; +import { MuonLanguageClient } from "./muon"; import { Uri } from "vscode"; export function serverToClass(server: LanguageServer): any { @@ -9,6 +10,8 @@ export function serverToClass(server: LanguageServer): any { case "Swift-MesonLSP": case "mesonlsp": return MesonLSPLanguageClient; + case "muon": + return MuonLanguageClient; default: return null; } diff --git a/src/lsp/muon.ts b/src/lsp/muon.ts new file mode 100644 index 0000000..a3902cb --- /dev/null +++ b/src/lsp/muon.ts @@ -0,0 +1,24 @@ +import * as vscode from "vscode"; + +import type { Executable } from "vscode-languageclient/node"; +import { LanguageServerClient } from "../lsp"; + +export class MuonLanguageClient extends LanguageServerClient { + get runExe(): Executable { + return { + command: this.languageServerPath!.fsPath, + args: ["analyze", "lsp"], + }; + } + + get debugExe(): Executable { + return { + command: this.languageServerPath!.fsPath, + args: ["analyze", "lsp"], + }; + } + + constructor(languageServerPath: vscode.Uri, context: vscode.ExtensionContext, referenceVersion: string) { + super("muon", languageServerPath, context, referenceVersion); + } +} diff --git a/src/types.ts b/src/types.ts index dd340e5..c27f97d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -60,7 +60,7 @@ export type LinterConfiguration = { enabled: boolean; }; -export type LanguageServer = "Swift-MesonLSP" | "mesonlsp" | null; +export type LanguageServer = "Swift-MesonLSP" | "mesonlsp" | "muon" | null; export type ModifiableExtension = "ms-vscode.cpptools" | "rust-lang.rust-analyzer"; export type FormattingProvider = "muon" | "meson"; From 87c2585f4cdce9f7ee5dcccabbe1ebfcf89e972c Mon Sep 17 00:00:00 2001 From: Stone Tickle Date: Fri, 14 Mar 2025 10:39:47 -0400 Subject: [PATCH 2/5] fix language server trace output channel --- src/lsp/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lsp/index.ts b/src/lsp/index.ts index 60ccb83..3b82244 100644 --- a/src/lsp/index.ts +++ b/src/lsp/index.ts @@ -203,7 +203,7 @@ export abstract class LanguageServerClient { const options = LanguageServerClient.clientOptions; options.initializationOptions = vscode.workspace.getConfiguration(`mesonbuild.${this.server}`); this.ls = new LanguageClient( - this.server!, + "mesonbuild", `Meson Language Server (${this.server})`, serverOptions, LanguageServerClient.clientOptions, From d7727375688e6cd834db558360c322d1e05270f5 Mon Sep 17 00:00:00 2001 From: Stone Tickle Date: Fri, 14 Mar 2025 10:40:51 -0400 Subject: [PATCH 3/5] allow lsp server to be changed dynamically The current server is now queried inside the createLanguageServerClient function rather than once at startup. --- src/extension.ts | 15 +++++---------- src/lsp/common.ts | 6 ++++-- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index af77dec..6850add 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -239,14 +239,11 @@ export async function activate(ctx: vscode.ExtensionContext) { await regenerateTests(controller); } - const server = extensionConfiguration(SettingsKey.languageServer); - let client = await createLanguageServerClient(server, await askShouldDownloadLanguageServer(), ctx); - // Basically every server supports formatting... - const serverSupportsFormatting = server == "mesonlsp" || server == "Swift-MesonLSP"; - if (client !== null && serverSupportsFormatting) { + let client = await createLanguageServerClient(await askShouldDownloadLanguageServer(), ctx); + if (client !== null) { ctx.subscriptions.push( vscode.workspace.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration(`mesonbuild.${server}`)) { + if (e.affectsConfiguration(`mesonbuild.${client!.server}`)) { client?.reloadConfig(); } }), @@ -257,9 +254,7 @@ export async function activate(ctx: vscode.ExtensionContext) { client.start(); await client.reloadConfig(); - getOutputChannel().appendLine( - "Not enabling the muon linter/formatter because a language server supporting formatting is active.", - ); + getOutputChannel().appendLine("Linters and formatters disabled because a language server is active."); } else { activateLinters(sourceDir, ctx); activateFormatters(sourceDir, ctx); @@ -268,7 +263,7 @@ export async function activate(ctx: vscode.ExtensionContext) { ctx.subscriptions.push( vscode.commands.registerCommand("mesonbuild.restartLanguageServer", async () => { if (client === null) { - client = await createLanguageServerClient(server, await askShouldDownloadLanguageServer(), ctx); + client = await createLanguageServerClient(await askShouldDownloadLanguageServer(), ctx); if (client !== null) { ctx.subscriptions.push(client); client.start(); diff --git a/src/lsp/common.ts b/src/lsp/common.ts index 98cb81c..596e9d3 100644 --- a/src/lsp/common.ts +++ b/src/lsp/common.ts @@ -1,9 +1,10 @@ import * as vscode from "vscode"; import { LanguageServerClient } from "."; -import { LanguageServer } from "../types"; +import { LanguageServer, SettingsKey } from "../types"; import { MesonLSPLanguageClient } from "./mesonlsp"; import { MuonLanguageClient } from "./muon"; import { Uri } from "vscode"; +import { extensionConfiguration } from "../utils"; export function serverToClass(server: LanguageServer): any { switch (server) { @@ -18,10 +19,11 @@ export function serverToClass(server: LanguageServer): any { } export async function createLanguageServerClient( - server: LanguageServer, download: boolean, context: vscode.ExtensionContext, ): Promise { + const server = extensionConfiguration(SettingsKey.languageServer); + const klass = serverToClass(server); if (klass == null) { return null; From 03447991bc005b914ad83cf6e9716d1102ddca3a Mon Sep 17 00:00:00 2001 From: Stone Tickle Date: Fri, 14 Mar 2025 10:42:37 -0400 Subject: [PATCH 4/5] add config option to enable lsp traces --- package.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/package.json b/package.json index 0e71fe8..64349e3 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,17 @@ "configuration": { "title": "Meson build configuration", "properties": { + "mesonbuild.trace.server": { + "type": "string", + "default": "off", + "scope": "window", + "enum": [ + "off", + "messages", + "verbose" + ], + "description": "Traces the communication between VS Code and the current Meson language server." + }, "mesonbuild.selectRootDir": { "type": "boolean", "default": true, From 7e727dd5e2c2231a022461957baa020866206f78 Mon Sep 17 00:00:00 2001 From: Stone Tickle Date: Tue, 29 Jul 2025 11:59:20 -0400 Subject: [PATCH 5/5] allow all Path settings to have multiple elements Also, substitute ${workspaceDirectory} in all string settings values. --- package.json | 15 ++++++++--- src/lsp/common.ts | 4 +-- src/lsp/index.ts | 36 ++++++++++++++++---------- src/lsp/mesonlsp.ts | 13 +++++++--- src/lsp/muon.ts | 13 +++++++--- src/tasks.ts | 38 ++++++++++++++------------- src/tools/meson.ts | 5 ++-- src/types.ts | 8 +++--- src/utils.ts | 62 ++++++++++++++++++++++++++++++++++++++++----- 9 files changed, 137 insertions(+), 57 deletions(-) diff --git a/package.json b/package.json index 64349e3..17bc0ae 100644 --- a/package.json +++ b/package.json @@ -183,12 +183,18 @@ "description": "Specify the list of options used for running benchmarks. --benchmark is implicit." }, "mesonbuild.mesonPath": { - "type": "string", + "type": [ + "string", + "array" + ], "default": "meson", "description": "Specify the meson executable to use" }, "mesonbuild.muonPath": { - "type": "string", + "type": [ + "string", + "array" + ], "default": "muon", "description": "Specify the muon executable to use" }, @@ -272,7 +278,10 @@ "description": "Select which language server to use. Swift-MesonLSP is a legacy alias for mesonlsp." }, "mesonbuild.languageServerPath": { - "type": "string", + "type": [ + "string", + "array" + ], "description": "Binary name or path to language server", "default": "" }, diff --git a/src/lsp/common.ts b/src/lsp/common.ts index 596e9d3..0b0bec7 100644 --- a/src/lsp/common.ts +++ b/src/lsp/common.ts @@ -33,7 +33,7 @@ export async function createLanguageServerClient( return null; } - let languageServerPath = LanguageServerClient.resolveLanguageServerPath(server, context); + let [languageServerPath, args] = LanguageServerClient.resolveLanguageServerPath(server, context); if (languageServerPath === null) { if (klass.artifact() == null) { enum Options { @@ -60,5 +60,5 @@ export async function createLanguageServerClient( } } - return new klass(languageServerPath, context, klass.version); + return new klass(languageServerPath, args, context, klass.version); } diff --git a/src/lsp/index.ts b/src/lsp/index.ts index 3b82244..7c378f9 100644 --- a/src/lsp/index.ts +++ b/src/lsp/index.ts @@ -16,8 +16,9 @@ import { TransportKind, } from "vscode-languageclient/node"; import * as storage from "../storage"; -import { LanguageServer } from "../types"; +import { ExtensionConfiguration, LanguageServer } from "../types"; import { serverToClass } from "./common"; +import { extensionConfiguration, resolveCommandAndArgs } from "../utils"; export abstract class LanguageServerClient { private static readonly clientOptions: LanguageClientOptions = { @@ -27,6 +28,7 @@ export abstract class LanguageServerClient { private readonly context: vscode.ExtensionContext; protected languageServerPath: vscode.Uri | null; + protected extraArgs: string[]; protected referenceVersion: string; readonly server: LanguageServer; @@ -40,11 +42,13 @@ export abstract class LanguageServerClient { protected constructor( server: LanguageServer, languageServerPath: vscode.Uri, + extraArgs: string[], context: vscode.ExtensionContext, referenceVersion: string, ) { this.server = server; this.languageServerPath = languageServerPath; + this.extraArgs = extraArgs; this.context = context; this.referenceVersion = referenceVersion; } @@ -146,25 +150,26 @@ export abstract class LanguageServerClient { return uri; } - static resolveLanguageServerPath(server: LanguageServer, context: vscode.ExtensionContext): vscode.Uri | null { - const config = vscode.workspace.getConfiguration("mesonbuild"); - - const configLanguageServerPath = config["languageServerPath"]; - if (configLanguageServerPath !== null && configLanguageServerPath != "") { + static resolveLanguageServerPath( + server: LanguageServer, + context: vscode.ExtensionContext, + ): [vscode.Uri | null, string[]] { + const [configLanguageServerPath, args] = resolveCommandAndArgs(extensionConfiguration("languageServerPath")); + if (configLanguageServerPath !== null && configLanguageServerPath !== "") { if (!path.isAbsolute(configLanguageServerPath)) { const binary = which.sync(configLanguageServerPath, { nothrow: true }); - if (binary !== null) return vscode.Uri.from({ scheme: "file", path: binary }); + if (binary !== null) return [vscode.Uri.from({ scheme: "file", path: binary }), args]; } - return vscode.Uri.from({ scheme: "file", path: configLanguageServerPath }); + return [vscode.Uri.from({ scheme: "file", path: configLanguageServerPath }), args]; } const cached = LanguageServerClient.cachedLanguageServer(server, context); - if (cached !== null) return cached; + if (cached !== null) return [cached, args]; const binary = which.sync(server!, { nothrow: true }); - if (binary !== null) return vscode.Uri.from({ scheme: "file", path: binary }); + if (binary !== null) return [vscode.Uri.from({ scheme: "file", path: binary }), args]; - return null; + return [null, args]; } protected static supportsSystem(): boolean { @@ -184,7 +189,10 @@ export abstract class LanguageServerClient { async restart(): Promise { await this.dispose(); - this.languageServerPath = LanguageServerClient.resolveLanguageServerPath(this.server, this.context); + [this.languageServerPath, this.extraArgs] = LanguageServerClient.resolveLanguageServerPath( + this.server, + this.context, + ); if (this.languageServerPath === null) { vscode.window.showErrorMessage( "Failed to restart the language server because a binary was not found and could not be downloaded", @@ -201,7 +209,7 @@ export abstract class LanguageServerClient { transport: TransportKind.stdio, }; const options = LanguageServerClient.clientOptions; - options.initializationOptions = vscode.workspace.getConfiguration(`mesonbuild.${this.server}`); + options.initializationOptions = extensionConfiguration(`mesonbuild.${this.server}` as keyof ExtensionConfiguration); this.ls = new LanguageClient( "mesonbuild", `Meson Language Server (${this.server})`, @@ -213,7 +221,7 @@ export abstract class LanguageServerClient { } async reloadConfig(): Promise { - const config = vscode.workspace.getConfiguration(`mesonbuild.${this.server}`); + const config = extensionConfiguration(`mesonbuild.${this.server}` as keyof ExtensionConfiguration); const params: DidChangeConfigurationParams = { settings: config, }; diff --git a/src/lsp/mesonlsp.ts b/src/lsp/mesonlsp.ts index 6ebbe9d..24130d8 100644 --- a/src/lsp/mesonlsp.ts +++ b/src/lsp/mesonlsp.ts @@ -35,19 +35,24 @@ export class MesonLSPLanguageClient extends LanguageServerClient { get runExe(): Executable { return { command: this.languageServerPath!.fsPath, - args: ["--lsp"], + args: [...this.extraArgs, "--lsp"], }; } get debugExe(): Executable { return { command: this.languageServerPath!.fsPath, - args: ["--lsp"], + args: [...this.extraArgs, "--lsp"], }; } - constructor(languageServerPath: vscode.Uri, context: vscode.ExtensionContext, referenceVersion: string) { - super("mesonlsp", languageServerPath, context, referenceVersion); + constructor( + languageServerPath: vscode.Uri, + extraArgs: string[], + context: vscode.ExtensionContext, + referenceVersion: string, + ) { + super("mesonlsp", languageServerPath, extraArgs, context, referenceVersion); } static override artifact(): { url: string; hash: string } | null { diff --git a/src/lsp/muon.ts b/src/lsp/muon.ts index a3902cb..4b77ac1 100644 --- a/src/lsp/muon.ts +++ b/src/lsp/muon.ts @@ -7,18 +7,23 @@ export class MuonLanguageClient extends LanguageServerClient { get runExe(): Executable { return { command: this.languageServerPath!.fsPath, - args: ["analyze", "lsp"], + args: [...this.extraArgs, "analyze", "lsp"], }; } get debugExe(): Executable { return { command: this.languageServerPath!.fsPath, - args: ["analyze", "lsp"], + args: [...this.extraArgs, "analyze", "lsp"], }; } - constructor(languageServerPath: vscode.Uri, context: vscode.ExtensionContext, referenceVersion: string) { - super("muon", languageServerPath, context, referenceVersion); + constructor( + languageServerPath: vscode.Uri, + extraArgs: string[], + context: vscode.ExtensionContext, + referenceVersion: string, + ) { + super("muon", languageServerPath, extraArgs, context, referenceVersion); } } diff --git a/src/tasks.ts b/src/tasks.ts index 80dc192..435c3b7 100644 --- a/src/tasks.ts +++ b/src/tasks.ts @@ -12,7 +12,12 @@ interface MesonTaskDefinition extends vscode.TaskDefinition { filename?: string; } -function createTestTask(meson: string, t: Test, buildDir: string, isBenchmark: boolean) { +function mesonTaskExecution(extraArgs: string[], options?: vscode.ProcessExecutionOptions) { + const [meson, args] = mesonProgram(); + return new vscode.ProcessExecution(meson, [...args, ...extraArgs], options); +} + +function createTestTask(t: Test, buildDir: string, isBenchmark: boolean) { const project = t.suite[0].split(":")[0]; const name = `${project}:${t.name}`; const mode = isBenchmark ? "benchmark" : "test"; @@ -23,7 +28,7 @@ function createTestTask(meson: string, t: Test, buildDir: string, isBenchmark: b { type: "meson", mode, target: name }, `Test ${name}`, "Meson", - new vscode.ProcessExecution(meson, args, { + mesonTaskExecution(args, { cwd: buildDir, }), ); @@ -32,7 +37,7 @@ function createTestTask(meson: string, t: Test, buildDir: string, isBenchmark: b return testTask; } -function createRunTask(meson: string, t: Target, targetName: string, buildDir: string) { +function createRunTask(t: Target, targetName: string, buildDir: string) { const targetDisplayName = targetName.split(":")[0]; let runTask = new vscode.Task( { @@ -43,7 +48,7 @@ function createRunTask(meson: string, t: Target, targetName: string, buildDir: s }, `Run ${targetDisplayName}`, "Meson", - new vscode.ProcessExecution(meson, ["devenv", t.filename[0]], { + mesonTaskExecution(["devenv", t.filename[0]], { cwd: buildDir, }), ); @@ -51,7 +56,7 @@ function createRunTask(meson: string, t: Target, targetName: string, buildDir: s return runTask; } -function createReconfigureTask(meson: string, buildDir: string, sourceDir: string) { +function createReconfigureTask(buildDir: string, sourceDir: string) { const configureOpts = extensionConfiguration("configureOptions"); const setupOpts = extensionConfiguration("setupOptions"); const reconfigureOpts = checkMesonIsConfigured(buildDir) ? ["--reconfigure"] : []; @@ -62,25 +67,24 @@ function createReconfigureTask(meson: string, buildDir: string, sourceDir: strin { type: "meson", mode: "reconfigure" }, "Reconfigure", "Meson", - new vscode.ProcessExecution(meson, args, options), + mesonTaskExecution(args, options), ); } export async function getMesonTasks(buildDir: string, sourceDir: string) { try { - const meson = mesonProgram(); const defaultBuildTask = new vscode.Task( { type: "meson", mode: "build" }, "Build all targets", "Meson", - new vscode.ProcessExecution(meson, ["compile", "-C", buildDir]), + mesonTaskExecution(["compile", "-C", buildDir]), "$meson-gcc", ); const defaultTestTask = new vscode.Task( { type: "meson", mode: "test" }, "Run all tests", "Meson", - new vscode.ProcessExecution(meson, ["test", ...extensionConfiguration("testOptions")], { + mesonTaskExecution(["test", ...extensionConfiguration("testOptions")], { cwd: buildDir, env: extensionConfiguration("testEnvironment"), }), @@ -89,22 +93,22 @@ export async function getMesonTasks(buildDir: string, sourceDir: string) { { type: "meson", mode: "benchmark" }, "Run all benchmarks", "Meson", - new vscode.ProcessExecution(meson, ["test", "--benchmark", ...extensionConfiguration("benchmarkOptions")], { + mesonTaskExecution(["test", "--benchmark", ...extensionConfiguration("benchmarkOptions")], { cwd: buildDir, }), ); - const defaultReconfigureTask = createReconfigureTask(meson, buildDir, sourceDir); + const defaultReconfigureTask = createReconfigureTask(buildDir, sourceDir); const defaultInstallTask = new vscode.Task( { type: "meson", mode: "install" }, "Run install", "Meson", - new vscode.ProcessExecution(meson, ["install"], { cwd: buildDir }), + mesonTaskExecution(["install"], { cwd: buildDir }), ); const defaultCleanTask = new vscode.Task( { type: "meson", mode: "clean" }, "Clean", "Meson", - new vscode.ProcessExecution(meson, ["compile", "--clean"], { cwd: buildDir }), + mesonTaskExecution(["compile", "--clean"], { cwd: buildDir }), ); defaultBuildTask.group = vscode.TaskGroup.Build; defaultTestTask.group = vscode.TaskGroup.Test; @@ -145,7 +149,7 @@ export async function getMesonTasks(buildDir: string, sourceDir: string) { def, `Build ${targetName}`, "Meson", - new vscode.ProcessExecution(meson, ["compile", targetName], { + mesonTaskExecution(["compile", targetName], { cwd: buildDir, }), "$meson-gcc", @@ -155,15 +159,15 @@ export async function getMesonTasks(buildDir: string, sourceDir: string) { // Create run tasks for executables that are not tests, // both installed and uninstalled (eg: examples) if (!tests.some((test) => test.name === t.name)) { - return [buildTask, createRunTask(meson, t, targetName, buildDir)]; + return [buildTask, createRunTask(t, targetName, buildDir)]; } } return buildTask; }), ) ).flat(1), - ...tests.map((t) => createTestTask(meson, t, buildDir, false)), - ...benchmarks.map((b) => createTestTask(meson, b, buildDir, true)), + ...tests.map((t) => createTestTask(t, buildDir, false)), + ...benchmarks.map((b) => createTestTask(b, buildDir, true)), ); return tasks; } catch (e: any) { diff --git a/src/tools/meson.ts b/src/tools/meson.ts index 9ab9154..4e393de 100644 --- a/src/tools/meson.ts +++ b/src/tools/meson.ts @@ -3,6 +3,7 @@ import { execFeed, extensionConfiguration, getOutputChannel, mesonProgram } from import { Tool, ToolCheckResult } from "../types"; import { getMesonVersion } from "../introspection"; import { Version } from "../version"; +import { extensionPath } from "../extension"; export async function format(meson: Tool, root: string, document: vscode.TextDocument): Promise { const originalDocumentText = document.getText(); @@ -37,8 +38,6 @@ const formattingSupportedSinceVersion = new Version([1, 5, 0]); const formattingWithStdinSupportedSinceVersion = new Version([1, 7, 0]); export async function check(): Promise { - const meson_path = mesonProgram(); - let mesonVersion; try { mesonVersion = await getMesonVersion(); @@ -63,5 +62,5 @@ export async function check(): Promise { ); } - return ToolCheckResult.newTool({ path: meson_path, version: mesonVersion }); + return ToolCheckResult.newTool({ path: extensionConfiguration("mesonPath"), version: mesonVersion }); } diff --git a/src/types.ts b/src/types.ts index c27f97d..0bdf5b4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,7 +3,7 @@ import type { Version } from "./version"; type Dict = { [x: string]: T }; -export type Tool = { path: string; version: Version }; +export type Tool = { path: string[]; version: Version }; export type ToolCheckSuccessResult = { tool: Tool; @@ -75,8 +75,8 @@ export interface ExtensionConfiguration { testJobs: number; benchmarkOptions: string[]; buildFolder: string; - mesonPath: string; - muonPath: string; + mesonPath: string[]; + muonPath: string[]; linting: { enabled: boolean }; linter: { muon: LinterConfiguration; @@ -90,7 +90,7 @@ export interface ExtensionConfiguration { debuggerExtension: "cpptools" | "vscode-lldb" | "lldb-dap" | "auto"; debugOptions: object; languageServer: LanguageServer; - languageServerPath: string; + languageServerPath: string[]; downloadLanguageServer: boolean | "ask"; selectRootDir: boolean; modifySettings: boolean | ModifiableExtension[]; diff --git a/src/utils.ts b/src/utils.ts index ab831d1..dc809ab 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -15,6 +15,7 @@ import { } from "./types"; import { getMesonBuildOptions } from "./introspection"; import { extensionPath, workspaceState } from "./extension"; +import { off } from "process"; export interface ExecResult { stdout: string; @@ -23,12 +24,23 @@ export interface ExecResult { error?: cp.ExecFileException; } +export function resolveCommandAndArgs(command: string | string[], args: string[] = []): [string, string[]] { + if (Array.isArray(command)) { + args = [...command.slice(1), ...args]; + command = command[0]; + } + + return [command, args]; +} + export async function exec( - command: string, + command: string | string[], args: string[], extraEnv: { [key: string]: string } | undefined = undefined, options: cp.ExecFileOptions = { shell: true }, ) { + [command, args] = resolveCommandAndArgs(command, args); + if (extraEnv) { options.env = { ...(options.env ?? process.env), ...extraEnv }; } @@ -46,11 +58,13 @@ export async function exec( } export async function execFeed( - command: string, + command: string | string[], args: string[], options: cp.ExecFileOptions = { shell: true }, stdin: string, ) { + [command, args] = resolveCommandAndArgs(command, args); + return new Promise((resolve) => { const timeStart = Date.now(); const p = cp.execFile(command, args, options, (error, stdout, stderr) => { @@ -127,12 +141,47 @@ export function hash(input: BinaryLike) { return hashObj.digest("hex"); } -export function getConfiguration() { +function getConfiguration() { return vscode.workspace.getConfiguration("mesonbuild"); } +type ConfigValue = NonNullable; + +function resolveConfigurationStringValue( + value: ConfigValue, +): ConfigValue { + if (typeof value === "string") { + const workspaceFolder = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0] : null; + + if (workspaceFolder && value) { + const workspacePath = workspaceFolder.uri.fsPath; + value = value.replace(/\${workspaceFolder}/gi, workspacePath) as ConfigValue; + } + return value; + } else { + return value; + } +} + +const deprecateToStringArray = { + mesonPath: true, + muonPath: true, + languageServerPath: true, +}; + export function extensionConfiguration(key: K) { - return getConfiguration().get(key)!; + let value = getConfiguration().get(key)!; + + if (key in deprecateToStringArray && typeof value === "string") { + value = [value] as ConfigValue; + } + + if (typeof value === "string") { + value = resolveConfigurationStringValue(value); + } else if (Array.isArray(value)) { + value = value.map(resolveConfigurationStringValue) as ConfigValue; + } + return value; } export function extensionConfigurationSet( @@ -218,6 +267,7 @@ export function whenFileExists(ctx: vscode.ExtensionContext, file: string, liste } } -export function mesonProgram(): string { - return which.sync(extensionConfiguration("mesonPath")); +export function mesonProgram(): [string, string[]] { + const cmdArray = extensionConfiguration("mesonPath"); + return [which.sync(cmdArray[0]), cmdArray.slice(1)]; }