From 2bd8754f2d3d666f290b01329b58be453ec245bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 8 Apr 2026 09:41:54 -0400 Subject: [PATCH] fix(vscode): support debugger command/file config with program fallback Agent-Logs-Url: https://github.com/Shopify/ruby-lsp/sessions/c58776f4-6c4e-431c-9029-cac8f3db34a5 Co-authored-by: rafaelfranca <47848+rafaelfranca@users.noreply.github.com> --- jekyll/vscode-extension.markdown | 5 ++-- vscode/README.md | 5 ++-- vscode/src/debugger.ts | 28 +++++++++++++++--- vscode/src/test/suite/debugger.test.ts | 39 ++++++++++++++++++++++---- 4 files changed, 64 insertions(+), 13 deletions(-) diff --git a/jekyll/vscode-extension.markdown b/jekyll/vscode-extension.markdown index e9ad1ef823..db9e3a58c7 100644 --- a/jekyll/vscode-extension.markdown +++ b/jekyll/vscode-extension.markdown @@ -227,13 +227,14 @@ This command would generate the following configuration: "type": "ruby_lsp", "name": "Debug", "request": "launch", - "program": "ruby ${file}", + "command": "ruby", }, { "type": "ruby_lsp", "request": "launch", "name": "Debug test file", - "program": "ruby -Itest ${relativeFile}", + "command": "ruby -Itest", + "file": "${relativeFile}", }, { "type": "ruby_lsp", diff --git a/vscode/README.md b/vscode/README.md index 62fe5400b6..6583973159 100644 --- a/vscode/README.md +++ b/vscode/README.md @@ -222,13 +222,14 @@ This command would generate the following configuration: "type": "ruby_lsp", "name": "Debug", "request": "launch", - "program": "ruby ${file}", + "command": "ruby", }, { "type": "ruby_lsp", "request": "launch", "name": "Debug test file", - "program": "ruby -Itest ${relativeFile}", + "command": "ruby -Itest", + "file": "${relativeFile}", }, { "type": "ruby_lsp", diff --git a/vscode/src/debugger.ts b/vscode/src/debugger.ts index 6b2ed7d35b..0cff789fff 100644 --- a/vscode/src/debugger.ts +++ b/vscode/src/debugger.ts @@ -65,15 +65,15 @@ export class Debugger implements vscode.DebugAdapterDescriptorFactory, vscode.De type: "ruby_lsp", name: "Debug script", request: "launch", - // eslint-disable-next-line no-template-curly-in-string - program: "ruby ${file}", + command: "ruby", }, { type: "ruby_lsp", name: "Debug test", request: "launch", + command: "ruby -Itest", // eslint-disable-next-line no-template-curly-in-string - program: "ruby -Itest ${relativeFile}", + file: "${relativeFile}", }, { type: "ruby_lsp", @@ -98,6 +98,11 @@ export class Debugger implements vscode.DebugAdapterDescriptorFactory, vscode.De throw new Error(`Couldn't find a workspace for URI: ${uri?.toString()}`); } + if (!debugConfiguration.program && debugConfiguration.command && !debugConfiguration.file) { + // eslint-disable-next-line no-template-curly-in-string + debugConfiguration.file = "${file}"; + } + if (debugConfiguration.env) { // If the user has their own debug launch configurations, we still need to inject the Ruby environment debugConfiguration.env = Object.assign(workspace.ruby.env, debugConfiguration.env); @@ -225,12 +230,23 @@ export class Debugger implements vscode.DebugAdapterDescriptorFactory, vscode.De return new Promise((resolve, reject) => { const args = ["exec", "rdbg"]; + const program = + configuration.program ?? + (configuration.command + ? `${configuration.command} ${this.quoteForShell(String(configuration.file))}` + : undefined); + + if (!program) { + reject(new Error("Either `program` or `command` must be configured in launch.json")); + return; + } + // On Windows, we spawn the debugger with any available port. On Linux and macOS, we spawn it with a UNIX socket if (port) { args.push("--port", port.toString()); } - args.push("--open", "--command", "--", configuration.program); + args.push("--open", "--command", "--", program); this.logDebuggerMessage(`Spawning debugger in directory ${cwd}`); this.logDebuggerMessage(` Command bundle ${args.join(" ")}`); @@ -324,4 +340,8 @@ export class Debugger implements vscode.DebugAdapterDescriptorFactory, vscode.De // so we preserve the original message format including any newlines this.console.append(message); } + + private quoteForShell(argument: string): string { + return `"${argument.replace(/["\\$`]/g, "\\$&")}"`; + } } diff --git a/vscode/src/test/suite/debugger.test.ts b/vscode/src/test/suite/debugger.test.ts index 6ab3ba49e7..345c9a6342 100644 --- a/vscode/src/test/suite/debugger.test.ts +++ b/vscode/src/test/suite/debugger.test.ts @@ -44,15 +44,15 @@ suite("Debugger", () => { type: "ruby_lsp", name: "Debug script", request: "launch", - // eslint-disable-next-line no-template-curly-in-string - program: "ruby ${file}", + command: "ruby", }, { type: "ruby_lsp", name: "Debug test", request: "launch", + command: "ruby -Itest", // eslint-disable-next-line no-template-curly-in-string - program: "ruby -Itest ${relativeFile}", + file: "${relativeFile}", }, { type: "ruby_lsp", @@ -99,6 +99,34 @@ suite("Debugger", () => { context.subscriptions.forEach((subscription) => subscription.dispose()); }); + test("Resolve configuration defaults file to active file when command is used", async () => { + const ruby = { + env: { bogus: "hello!" }, + } as unknown as Ruby; + const workspaceFolder = { + name: "fake", + uri: vscode.Uri.file("fake"), + index: 0, + }; + const debug = new Debugger(context, () => { + return { + ruby, + workspaceFolder, + } as Workspace; + }); + const configs: any = await debug.resolveDebugConfiguration!(workspaceFolder, { + type: "ruby_lsp", + name: "Debug", + request: "launch", + command: "ruby", + }); + + // eslint-disable-next-line no-template-curly-in-string + assert.strictEqual(configs.file, "${file}"); + debug.dispose(); + context.subscriptions.forEach((subscription) => subscription.dispose()); + }); + test("Resolve configuration injects Ruby environment and allows users custom environment", async () => { const ruby = { env: { bogus: "hello!" } } as unknown as Ruby; const workspaceFolder = { @@ -174,7 +202,7 @@ suite("Debugger", () => { createRubySymlinks(); } - const tmpPath = fs.mkdtempSync(path.join(os.tmpdir(), "ruby-lsp-test-debugger")); + const tmpPath = fs.mkdtempSync(path.join(os.tmpdir(), "ruby-lsp test debugger with spaces-")); fs.writeFileSync(path.join(tmpPath, "test.rb"), "1 + 1"); fs.writeFileSync(path.join(tmpPath, ".ruby-version"), RUBY_VERSION); fs.writeFileSync(path.join(tmpPath, "Gemfile"), 'source "https://rubygems.org"\ngem "debug"'); @@ -210,7 +238,8 @@ suite("Debugger", () => { type: "ruby_lsp", name: "Debug", request: "launch", - program: `ruby ${path.join(tmpPath, "test.rb")}`, + command: "ruby", + file: path.join(tmpPath, "test.rb"), }); } catch (error: any) { assert.fail(`Failed to launch debugger: ${error.message}`);