Add rdbg for Ruby (#30126)

Release Notes:

- N/A
This commit is contained in:
Conrad Irwin 2025-05-08 09:37:20 +01:00 committed by GitHub
parent 1ec466b728
commit d39c220f26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 121 additions and 7 deletions

View File

@ -580,21 +580,31 @@ impl TcpTransport {
.unwrap_or(2000u64)
});
let (rx, tx) = select! {
let (mut process, (rx, tx)) = select! {
_ = cx.background_executor().timer(Duration::from_millis(timeout)).fuse() => {
return Err(anyhow!(format!("Connection to TCP DAP timeout {}:{}", host, port)))
},
result = cx.spawn(async move |cx| {
loop {
match TcpStream::connect(address).await {
Ok(stream) => return stream.split(),
Ok(stream) => return Ok((process, stream.split())),
Err(_) => {
if let Ok(Some(_)) = process.try_status() {
let output = process.output().await?;
let output = if output.stderr.is_empty() {
String::from_utf8_lossy(&output.stdout).to_string()
} else {
String::from_utf8_lossy(&output.stderr).to_string()
};
return Err(anyhow!("{}\nerror: process exited before debugger attached.", output));
}
cx.background_executor().timer(Duration::from_millis(100)).await;
}
}
}
}).fuse() => result
}).fuse() => result?
};
log::info!(
"Debug adapter has connected to TCP server {}:{}",
host,

View File

@ -4,6 +4,7 @@ mod go;
mod javascript;
mod php;
mod python;
mod ruby;
use std::{net::Ipv4Addr, sync::Arc};
@ -24,6 +25,7 @@ use gpui::{App, BorrowAppContext};
use javascript::JsDebugAdapter;
use php::PhpDebugAdapter;
use python::PythonDebugAdapter;
use ruby::RubyDebugAdapter;
use serde_json::{Value, json};
use task::TcpArgumentsTemplate;
@ -33,6 +35,7 @@ pub fn init(cx: &mut App) {
registry.add_adapter(Arc::from(PythonDebugAdapter::default()));
registry.add_adapter(Arc::from(PhpDebugAdapter::default()));
registry.add_adapter(Arc::from(JsDebugAdapter::default()));
registry.add_adapter(Arc::from(RubyDebugAdapter));
registry.add_adapter(Arc::from(GoDebugAdapter));
registry.add_adapter(Arc::from(GdbDebugAdapter));

View File

@ -0,0 +1,102 @@
use anyhow::{Result, anyhow};
use async_trait::async_trait;
use dap::{
DebugRequest, StartDebuggingRequestArguments,
adapters::{
self, DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition,
},
};
use gpui::AsyncApp;
use std::path::PathBuf;
use util::command::new_smol_command;
use crate::ToDap;
#[derive(Default)]
pub(crate) struct RubyDebugAdapter;
impl RubyDebugAdapter {
const ADAPTER_NAME: &'static str = "Ruby";
}
#[async_trait(?Send)]
impl DebugAdapter for RubyDebugAdapter {
fn name(&self) -> DebugAdapterName {
DebugAdapterName(Self::ADAPTER_NAME.into())
}
async fn get_binary(
&self,
delegate: &dyn DapDelegate,
definition: &DebugTaskDefinition,
_user_installed_path: Option<PathBuf>,
_cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());
let mut rdbg_path = adapter_path.join("rdbg");
if !delegate.fs().is_file(&rdbg_path).await {
match delegate.which("rdbg".as_ref()) {
Some(path) => rdbg_path = path,
None => {
delegate.output_to_console(
"rdbg not found on path, trying `gem install debug`".to_string(),
);
let output = new_smol_command("gem")
.arg("install")
.arg("--no-document")
.arg("--bindir")
.arg(adapter_path)
.arg("debug")
.output()
.await?;
if !output.status.success() {
return Err(anyhow!(
"Failed to install rdbg:\n{}",
String::from_utf8_lossy(&output.stderr).to_string()
));
}
}
}
}
let tcp_connection = definition.tcp_connection.clone().unwrap_or_default();
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
let DebugRequest::Launch(mut launch) = definition.request.clone() else {
anyhow::bail!("rdbg does not yet support attaching");
};
let mut arguments = vec![
"--open".to_string(),
format!("--port={}", port),
format!("--host={}", host),
];
if launch.args.is_empty() {
let program = launch.program.clone();
let mut split = program.split(" ");
launch.program = split.next().unwrap().to_string();
launch.args = split.map(|s| s.to_string()).collect();
}
if delegate.which(launch.program.as_ref()).is_some() {
arguments.push("--command".to_string())
}
arguments.push(launch.program);
arguments.extend(launch.args);
Ok(DebugAdapterBinary {
command: rdbg_path.to_string_lossy().to_string(),
arguments,
connection: Some(adapters::TcpArguments {
host,
port,
timeout,
}),
cwd: launch.cwd,
envs: launch.env.into_iter().collect(),
request_args: StartDebuggingRequestArguments {
configuration: serde_json::Value::Object(Default::default()),
request: definition.request.to_dap(),
},
})
}
}

View File

@ -152,7 +152,7 @@ impl Console {
session
.evaluate(
expression,
Some(dap::EvaluateArgumentsContext::Variables),
Some(dap::EvaluateArgumentsContext::Repl),
self.stack_frame_list.read(cx).selected_stack_frame_id(),
None,
cx,

View File

@ -10,7 +10,7 @@ use super::dap_command::{
TerminateThreadsCommand, ThreadsCommand, VariablesCommand,
};
use super::dap_store::DapStore;
use anyhow::{Context as _, Result, anyhow};
use anyhow::{Result, anyhow};
use collections::{HashMap, HashSet, IndexMap, IndexSet};
use dap::adapters::{DebugAdapterBinary, DebugAdapterName};
use dap::messages::Response;
@ -169,8 +169,7 @@ impl LocalMode {
.await?
} else {
DebugAdapterClient::start(session_id, binary.clone(), message_handler, cx.clone())
.await
.with_context(|| format!("Failed to start {:?}", &binary.command))?
.await?
},
);