Cargo.lock 🔗
@@ -4053,6 +4053,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
+ "collections",
"dap",
"futures 0.3.31",
"gpui",
Conrad Irwin , Anthony Eid , Piotr Osiewicz , and Cole Miller created
Closes #ISSUE
Release Notes:
- debugger: Fix Ruby (was broken by #30833)
---------
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Piotr Osiewicz <peterosiewicz@gmail.com>
Co-authored-by: Cole Miller <m@cole-miller.net>
Cargo.lock | 1
crates/dap_adapters/Cargo.toml | 1
crates/dap_adapters/src/ruby.rs | 272 ++++++++++-----------------
crates/task/src/lib.rs | 15 +
crates/task/src/vscode_debug_format.rs | 5
5 files changed, 118 insertions(+), 176 deletions(-)
@@ -4053,6 +4053,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
+ "collections",
"dap",
"futures 0.3.31",
"gpui",
@@ -23,6 +23,7 @@ doctest = false
[dependencies]
anyhow.workspace = true
async-trait.workspace = true
+collections.workspace = true
dap.workspace = true
futures.workspace = true
gpui.workspace = true
@@ -1,16 +1,18 @@
-use anyhow::Result;
+use anyhow::{Result, bail};
use async_trait::async_trait;
+use collections::FxHashMap;
use dap::{
- DebugRequest, StartDebuggingRequestArguments,
+ DebugRequest, StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
adapters::{
DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition,
},
};
use gpui::{AsyncApp, SharedString};
use language::LanguageName;
+use serde::{Deserialize, Serialize};
use serde_json::json;
use std::path::PathBuf;
-use std::sync::Arc;
+use std::{ffi::OsStr, sync::Arc};
use task::{DebugScenario, ZedDebugConfig};
use util::command::new_smol_command;
@@ -21,6 +23,18 @@ impl RubyDebugAdapter {
const ADAPTER_NAME: &'static str = "Ruby";
}
+#[derive(Serialize, Deserialize)]
+struct RubyDebugConfig {
+ script_or_command: Option<String>,
+ script: Option<String>,
+ command: Option<String>,
+ #[serde(default)]
+ args: Vec<String>,
+ #[serde(default)]
+ env: FxHashMap<String, String>,
+ cwd: Option<PathBuf>,
+}
+
#[async_trait(?Send)]
impl DebugAdapter for RubyDebugAdapter {
fn name(&self) -> DebugAdapterName {
@@ -31,185 +45,70 @@ impl DebugAdapter for RubyDebugAdapter {
Some(SharedString::new_static("Ruby").into())
}
+ fn request_kind(&self, _: &serde_json::Value) -> Result<StartDebuggingRequestArgumentsRequest> {
+ Ok(StartDebuggingRequestArgumentsRequest::Launch)
+ }
+
async fn dap_schema(&self) -> serde_json::Value {
json!({
- "oneOf": [
- {
- "allOf": [
- {
- "type": "object",
- "required": ["request"],
- "properties": {
- "request": {
- "type": "string",
- "enum": ["launch"],
- "description": "Request to launch a new process"
- }
- }
- },
- {
- "type": "object",
- "required": ["script"],
- "properties": {
- "command": {
- "type": "string",
- "description": "Command name (ruby, rake, bin/rails, bundle exec ruby, etc)",
- "default": "ruby"
- },
- "script": {
- "type": "string",
- "description": "Absolute path to a Ruby file."
- },
- "cwd": {
- "type": "string",
- "description": "Directory to execute the program in",
- "default": "${ZED_WORKTREE_ROOT}"
- },
- "args": {
- "type": "array",
- "description": "Command line arguments passed to the program",
- "items": {
- "type": "string"
- },
- "default": []
- },
- "env": {
- "type": "object",
- "description": "Additional environment variables to pass to the debugging (and debugged) process",
- "default": {}
- },
- "showProtocolLog": {
- "type": "boolean",
- "description": "Show a log of DAP requests, events, and responses",
- "default": false
- },
- "useBundler": {
- "type": "boolean",
- "description": "Execute Ruby programs with `bundle exec` instead of directly",
- "default": false
- },
- "bundlePath": {
- "type": "string",
- "description": "Location of the bundle executable"
- },
- "rdbgPath": {
- "type": "string",
- "description": "Location of the rdbg executable"
- },
- "askParameters": {
- "type": "boolean",
- "description": "Ask parameters at first."
- },
- "debugPort": {
- "type": "string",
- "description": "UNIX domain socket name or TPC/IP host:port"
- },
- "waitLaunchTime": {
- "type": "number",
- "description": "Wait time before connection in milliseconds"
- },
- "localfs": {
- "type": "boolean",
- "description": "true if the VSCode and debugger run on a same machine",
- "default": false
- },
- "useTerminal": {
- "type": "boolean",
- "description": "Create a new terminal and then execute commands there",
- "default": false
- }
- }
- }
- ]
+ "type": "object",
+ "properties": {
+ "command": {
+ "type": "string",
+ "description": "Command name (ruby, rake, bin/rails, bundle exec ruby, etc)",
},
- {
- "allOf": [
- {
- "type": "object",
- "required": ["request"],
- "properties": {
- "request": {
- "type": "string",
- "enum": ["attach"],
- "description": "Request to attach to an existing process"
- }
- }
- },
- {
- "type": "object",
- "properties": {
- "rdbgPath": {
- "type": "string",
- "description": "Location of the rdbg executable"
- },
- "debugPort": {
- "type": "string",
- "description": "UNIX domain socket name or TPC/IP host:port"
- },
- "showProtocolLog": {
- "type": "boolean",
- "description": "Show a log of DAP requests, events, and responses",
- "default": false
- },
- "localfs": {
- "type": "boolean",
- "description": "true if the VSCode and debugger run on a same machine",
- "default": false
- },
- "localfsMap": {
- "type": "string",
- "description": "Specify pairs of remote root path and local root path like `/remote_dir:/local_dir`. You can specify multiple pairs like `/rem1:/loc1,/rem2:/loc2` by concatenating with `,`."
- },
- "env": {
- "type": "object",
- "description": "Additional environment variables to pass to the rdbg process",
- "default": {}
- }
- }
- }
- ]
- }
- ]
+ "script": {
+ "type": "string",
+ "description": "Absolute path to a Ruby file."
+ },
+ "cwd": {
+ "type": "string",
+ "description": "Directory to execute the program in",
+ "default": "${ZED_WORKTREE_ROOT}"
+ },
+ "args": {
+ "type": "array",
+ "description": "Command line arguments passed to the program",
+ "items": {
+ "type": "string"
+ },
+ "default": []
+ },
+ "env": {
+ "type": "object",
+ "description": "Additional environment variables to pass to the debugging (and debugged) process",
+ "default": {}
+ },
+ }
})
}
fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
- let mut config = serde_json::Map::new();
-
- match &zed_scenario.request {
+ match zed_scenario.request {
DebugRequest::Launch(launch) => {
- config.insert("request".to_string(), json!("launch"));
- config.insert("script".to_string(), json!(launch.program));
- config.insert("command".to_string(), json!("ruby"));
-
- if !launch.args.is_empty() {
- config.insert("args".to_string(), json!(launch.args));
- }
-
- if !launch.env.is_empty() {
- config.insert("env".to_string(), json!(launch.env));
- }
-
- if let Some(cwd) = &launch.cwd {
- config.insert("cwd".to_string(), json!(cwd));
- }
-
- // Ruby stops on entry so there's no need to handle that case
+ let config = RubyDebugConfig {
+ script_or_command: Some(launch.program),
+ script: None,
+ command: None,
+ args: launch.args,
+ env: launch.env,
+ cwd: launch.cwd.clone(),
+ };
+
+ let config = serde_json::to_value(config)?;
+
+ Ok(DebugScenario {
+ adapter: zed_scenario.adapter,
+ label: zed_scenario.label,
+ config,
+ tcp_connection: None,
+ build: None,
+ })
}
- DebugRequest::Attach(attach) => {
- config.insert("request".to_string(), json!("attach"));
-
- config.insert("processId".to_string(), json!(attach.process_id));
+ DebugRequest::Attach(_) => {
+ anyhow::bail!("Attach requests are unsupported");
}
}
-
- Ok(DebugScenario {
- adapter: zed_scenario.adapter,
- label: zed_scenario.label,
- config: serde_json::Value::Object(config),
- tcp_connection: None,
- build: None,
- })
}
async fn get_binary(
@@ -247,13 +146,34 @@ impl DebugAdapter for RubyDebugAdapter {
let tcp_connection = definition.tcp_connection.clone().unwrap_or_default();
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
+ let ruby_config = serde_json::from_value::<RubyDebugConfig>(definition.config.clone())?;
- let arguments = vec![
+ let mut arguments = vec![
"--open".to_string(),
format!("--port={}", port),
format!("--host={}", host),
];
+ if let Some(script) = &ruby_config.script {
+ arguments.push(script.clone());
+ } else if let Some(command) = &ruby_config.command {
+ arguments.push("--command".to_string());
+ arguments.push(command.clone());
+ } else if let Some(command_or_script) = &ruby_config.script_or_command {
+ if delegate
+ .which(OsStr::new(&command_or_script))
+ .await
+ .is_some()
+ {
+ arguments.push("--command".to_string());
+ }
+ arguments.push(command_or_script.clone());
+ } else {
+ bail!("Ruby debug config must have 'script' or 'command' args");
+ }
+
+ arguments.extend(ruby_config.args);
+
Ok(DebugAdapterBinary {
command: rdbg_path.to_string_lossy().to_string(),
arguments,
@@ -262,8 +182,12 @@ impl DebugAdapter for RubyDebugAdapter {
port,
timeout,
}),
- cwd: None,
- envs: std::collections::HashMap::default(),
+ cwd: Some(
+ ruby_config
+ .cwd
+ .unwrap_or(delegate.worktree_root_path().to_owned()),
+ ),
+ envs: ruby_config.env.into_iter().collect(),
request_args: StartDebuggingRequestArguments {
request: self.request_kind(&definition.config)?,
configuration: definition.config.clone(),
@@ -530,6 +530,21 @@ impl EnvVariableReplacer {
fn new(variables: HashMap<VsCodeEnvVariable, ZedEnvVariable>) -> Self {
Self { variables }
}
+
+ fn replace_value(&self, input: serde_json::Value) -> serde_json::Value {
+ match input {
+ serde_json::Value::String(s) => serde_json::Value::String(self.replace(&s)),
+ serde_json::Value::Array(arr) => {
+ serde_json::Value::Array(arr.into_iter().map(|v| self.replace_value(v)).collect())
+ }
+ serde_json::Value::Object(obj) => serde_json::Value::Object(
+ obj.into_iter()
+ .map(|(k, v)| (self.replace(&k), self.replace_value(v)))
+ .collect(),
+ ),
+ _ => input,
+ }
+ }
// Replaces occurrences of VsCode-specific environment variables with Zed equivalents.
fn replace(&self, input: &str) -> String {
shellexpand::env_with_context_no_errors(&input, |var: &str| {
@@ -53,7 +53,7 @@ impl VsCodeDebugTaskDefinition {
host: None,
timeout: None,
}),
- config: self.other_attributes,
+ config: replacer.replace_value(self.other_attributes),
};
Ok(definition)
}
@@ -75,7 +75,7 @@ impl TryFrom<VsCodeDebugTaskFile> for DebugTaskFile {
"workspaceFolder".to_owned(),
VariableName::WorktreeRoot.to_string(),
),
- // TODO other interesting variables?
+ ("file".to_owned(), VariableName::Filename.to_string()), // TODO other interesting variables?
]));
let templates = file
.configurations
@@ -94,6 +94,7 @@ fn task_type_to_adapter_name(task_type: &str) -> SharedString {
"php" => "PHP",
"cppdbg" | "lldb" => "CodeLLDB",
"debugpy" => "Debugpy",
+ "rdbg" => "Ruby",
_ => task_type,
}
.to_owned()