Fix ruby debugger (#32407)
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>
This commit is contained in:
parent
b103d7621b
commit
3dfbd9e57c
5 changed files with 116 additions and 174 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4053,6 +4053,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"collections",
|
||||||
"dap",
|
"dap",
|
||||||
"futures 0.3.31",
|
"futures 0.3.31",
|
||||||
"gpui",
|
"gpui",
|
||||||
|
|
|
@ -23,6 +23,7 @@ doctest = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
|
collections.workspace = true
|
||||||
dap.workspace = true
|
dap.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
use anyhow::Result;
|
use anyhow::{Result, bail};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use collections::FxHashMap;
|
||||||
use dap::{
|
use dap::{
|
||||||
DebugRequest, StartDebuggingRequestArguments,
|
DebugRequest, StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
|
||||||
adapters::{
|
adapters::{
|
||||||
DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition,
|
DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use gpui::{AsyncApp, SharedString};
|
use gpui::{AsyncApp, SharedString};
|
||||||
use language::LanguageName;
|
use language::LanguageName;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::{ffi::OsStr, sync::Arc};
|
||||||
use task::{DebugScenario, ZedDebugConfig};
|
use task::{DebugScenario, ZedDebugConfig};
|
||||||
use util::command::new_smol_command;
|
use util::command::new_smol_command;
|
||||||
|
|
||||||
|
@ -21,6 +23,18 @@ impl RubyDebugAdapter {
|
||||||
const ADAPTER_NAME: &'static str = "Ruby";
|
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)]
|
#[async_trait(?Send)]
|
||||||
impl DebugAdapter for RubyDebugAdapter {
|
impl DebugAdapter for RubyDebugAdapter {
|
||||||
fn name(&self) -> DebugAdapterName {
|
fn name(&self) -> DebugAdapterName {
|
||||||
|
@ -31,185 +45,70 @@ impl DebugAdapter for RubyDebugAdapter {
|
||||||
Some(SharedString::new_static("Ruby").into())
|
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 {
|
async fn dap_schema(&self) -> serde_json::Value {
|
||||||
json!({
|
json!({
|
||||||
"oneOf": [
|
"type": "object",
|
||||||
{
|
"properties": {
|
||||||
"allOf": [
|
"command": {
|
||||||
{
|
"type": "string",
|
||||||
"type": "object",
|
"description": "Command name (ruby, rake, bin/rails, bundle exec ruby, etc)",
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
"script": {
|
||||||
"allOf": [
|
"type": "string",
|
||||||
{
|
"description": "Absolute path to a Ruby file."
|
||||||
"type": "object",
|
},
|
||||||
"required": ["request"],
|
"cwd": {
|
||||||
"properties": {
|
"type": "string",
|
||||||
"request": {
|
"description": "Directory to execute the program in",
|
||||||
"type": "string",
|
"default": "${ZED_WORKTREE_ROOT}"
|
||||||
"enum": ["attach"],
|
},
|
||||||
"description": "Request to attach to an existing process"
|
"args": {
|
||||||
}
|
"type": "array",
|
||||||
}
|
"description": "Command line arguments passed to the program",
|
||||||
},
|
"items": {
|
||||||
{
|
"type": "string"
|
||||||
"type": "object",
|
},
|
||||||
"properties": {
|
"default": []
|
||||||
"rdbgPath": {
|
},
|
||||||
"type": "string",
|
"env": {
|
||||||
"description": "Location of the rdbg executable"
|
"type": "object",
|
||||||
},
|
"description": "Additional environment variables to pass to the debugging (and debugged) process",
|
||||||
"debugPort": {
|
"default": {}
|
||||||
"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": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
|
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) => {
|
DebugRequest::Launch(launch) => {
|
||||||
config.insert("request".to_string(), json!("launch"));
|
let config = RubyDebugConfig {
|
||||||
config.insert("script".to_string(), json!(launch.program));
|
script_or_command: Some(launch.program),
|
||||||
config.insert("command".to_string(), json!("ruby"));
|
script: None,
|
||||||
|
command: None,
|
||||||
|
args: launch.args,
|
||||||
|
env: launch.env,
|
||||||
|
cwd: launch.cwd.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
if !launch.args.is_empty() {
|
let config = serde_json::to_value(config)?;
|
||||||
config.insert("args".to_string(), json!(launch.args));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !launch.env.is_empty() {
|
Ok(DebugScenario {
|
||||||
config.insert("env".to_string(), json!(launch.env));
|
adapter: zed_scenario.adapter,
|
||||||
}
|
label: zed_scenario.label,
|
||||||
|
config,
|
||||||
if let Some(cwd) = &launch.cwd {
|
tcp_connection: None,
|
||||||
config.insert("cwd".to_string(), json!(cwd));
|
build: None,
|
||||||
}
|
})
|
||||||
|
|
||||||
// Ruby stops on entry so there's no need to handle that case
|
|
||||||
}
|
}
|
||||||
DebugRequest::Attach(attach) => {
|
DebugRequest::Attach(_) => {
|
||||||
config.insert("request".to_string(), json!("attach"));
|
anyhow::bail!("Attach requests are unsupported");
|
||||||
|
|
||||||
config.insert("processId".to_string(), json!(attach.process_id));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
async fn get_binary(
|
||||||
|
@ -247,13 +146,34 @@ impl DebugAdapter for RubyDebugAdapter {
|
||||||
|
|
||||||
let tcp_connection = definition.tcp_connection.clone().unwrap_or_default();
|
let tcp_connection = definition.tcp_connection.clone().unwrap_or_default();
|
||||||
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
|
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(),
|
"--open".to_string(),
|
||||||
format!("--port={}", port),
|
format!("--port={}", port),
|
||||||
format!("--host={}", host),
|
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 {
|
Ok(DebugAdapterBinary {
|
||||||
command: rdbg_path.to_string_lossy().to_string(),
|
command: rdbg_path.to_string_lossy().to_string(),
|
||||||
arguments,
|
arguments,
|
||||||
|
@ -262,8 +182,12 @@ impl DebugAdapter for RubyDebugAdapter {
|
||||||
port,
|
port,
|
||||||
timeout,
|
timeout,
|
||||||
}),
|
}),
|
||||||
cwd: None,
|
cwd: Some(
|
||||||
envs: std::collections::HashMap::default(),
|
ruby_config
|
||||||
|
.cwd
|
||||||
|
.unwrap_or(delegate.worktree_root_path().to_owned()),
|
||||||
|
),
|
||||||
|
envs: ruby_config.env.into_iter().collect(),
|
||||||
request_args: StartDebuggingRequestArguments {
|
request_args: StartDebuggingRequestArguments {
|
||||||
request: self.request_kind(&definition.config)?,
|
request: self.request_kind(&definition.config)?,
|
||||||
configuration: definition.config.clone(),
|
configuration: definition.config.clone(),
|
||||||
|
|
|
@ -530,6 +530,21 @@ impl EnvVariableReplacer {
|
||||||
fn new(variables: HashMap<VsCodeEnvVariable, ZedEnvVariable>) -> Self {
|
fn new(variables: HashMap<VsCodeEnvVariable, ZedEnvVariable>) -> Self {
|
||||||
Self { variables }
|
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.
|
// Replaces occurrences of VsCode-specific environment variables with Zed equivalents.
|
||||||
fn replace(&self, input: &str) -> String {
|
fn replace(&self, input: &str) -> String {
|
||||||
shellexpand::env_with_context_no_errors(&input, |var: &str| {
|
shellexpand::env_with_context_no_errors(&input, |var: &str| {
|
||||||
|
|
|
@ -53,7 +53,7 @@ impl VsCodeDebugTaskDefinition {
|
||||||
host: None,
|
host: None,
|
||||||
timeout: None,
|
timeout: None,
|
||||||
}),
|
}),
|
||||||
config: self.other_attributes,
|
config: replacer.replace_value(self.other_attributes),
|
||||||
};
|
};
|
||||||
Ok(definition)
|
Ok(definition)
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ impl TryFrom<VsCodeDebugTaskFile> for DebugTaskFile {
|
||||||
"workspaceFolder".to_owned(),
|
"workspaceFolder".to_owned(),
|
||||||
VariableName::WorktreeRoot.to_string(),
|
VariableName::WorktreeRoot.to_string(),
|
||||||
),
|
),
|
||||||
// TODO other interesting variables?
|
("file".to_owned(), VariableName::Filename.to_string()), // TODO other interesting variables?
|
||||||
]));
|
]));
|
||||||
let templates = file
|
let templates = file
|
||||||
.configurations
|
.configurations
|
||||||
|
@ -94,6 +94,7 @@ fn task_type_to_adapter_name(task_type: &str) -> SharedString {
|
||||||
"php" => "PHP",
|
"php" => "PHP",
|
||||||
"cppdbg" | "lldb" => "CodeLLDB",
|
"cppdbg" | "lldb" => "CodeLLDB",
|
||||||
"debugpy" => "Debugpy",
|
"debugpy" => "Debugpy",
|
||||||
|
"rdbg" => "Ruby",
|
||||||
_ => task_type,
|
_ => task_type,
|
||||||
}
|
}
|
||||||
.to_owned()
|
.to_owned()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue