Set up Rust debugger code runner tasks (#27571)
## Summary This PR starts the process of adding debug task locators to Zed's debugger system. A task locator is a secondary resolution phase that allows a debug task to run a command before starting a debug session and then uses the output of the run command to configure itself. Locators are most applicable when debugging a compiled language but will be helpful for any language as well. ## Architecture At a high level, this works by adding a debug task queue to `Workspace`. Which add's a debug configuration associated with a `TaskId` whenever a resolved task with a debug config is added to `TaskInventory`'s queue. Then, when the `SpawnInTerminal` task finishes running, it emits its task_id and the result of the ran task. When a ran task exits successfully, `Workspace` tells `Project` to start a debug session using its stored debug config, then `DapStore` queries the `LocatorStore` to configure the debug configuration if it has a valid locator argument. Release Notes: - N/A
This commit is contained in:
parent
141a6c3915
commit
8add90d7cb
24 changed files with 441 additions and 168 deletions
|
@ -12,7 +12,7 @@ use rpc::{
|
|||
AnyProtoClient, TypedEnvelope,
|
||||
};
|
||||
use std::{hash::Hash, ops::Range, path::Path, sync::Arc};
|
||||
use text::{Point, PointUtf16};
|
||||
use text::PointUtf16;
|
||||
|
||||
use crate::{buffer_store::BufferStore, worktree_store::WorktreeStore, Project, ProjectPath};
|
||||
|
||||
|
@ -494,7 +494,7 @@ impl BreakpointStore {
|
|||
this.update(cx, |_, cx| BreakpointsInFile::new(buffer, cx))?;
|
||||
|
||||
for bp in bps {
|
||||
let position = snapshot.anchor_after(Point::new(bp.row, 0));
|
||||
let position = snapshot.anchor_after(PointUtf16::new(bp.row, 0));
|
||||
breakpoints_for_file.breakpoints.push((
|
||||
position,
|
||||
Breakpoint {
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
use super::{
|
||||
breakpoint_store::BreakpointStore,
|
||||
// Will need to uncomment this once we implement rpc message handler again
|
||||
// dap_command::{
|
||||
// ContinueCommand, DapCommand, DisconnectCommand, NextCommand, PauseCommand, RestartCommand,
|
||||
// RestartStackFrameCommand, StepBackCommand, StepCommand, StepInCommand, StepOutCommand,
|
||||
// TerminateCommand, TerminateThreadsCommand, VariablesCommand,
|
||||
// },
|
||||
locator_store::LocatorStore,
|
||||
session::{self, Session},
|
||||
};
|
||||
use crate::{debugger, worktree_store::WorktreeStore, ProjectEnvironment};
|
||||
|
@ -87,6 +82,7 @@ pub struct LocalDapStore {
|
|||
language_registry: Arc<LanguageRegistry>,
|
||||
debug_adapters: Arc<DapRegistry>,
|
||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||
locator_store: Arc<LocatorStore>,
|
||||
start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>,
|
||||
_start_debugging_task: Task<()>,
|
||||
}
|
||||
|
@ -179,6 +175,7 @@ impl DapStore {
|
|||
debug_adapters,
|
||||
start_debugging_tx,
|
||||
_start_debugging_task,
|
||||
locator_store: Arc::from(LocatorStore::new()),
|
||||
next_session_id: Default::default(),
|
||||
}),
|
||||
downstream_client: None,
|
||||
|
@ -324,7 +321,7 @@ impl DapStore {
|
|||
|
||||
pub fn new_session(
|
||||
&mut self,
|
||||
config: DebugAdapterConfig,
|
||||
mut config: DebugAdapterConfig,
|
||||
worktree: &Entity<Worktree>,
|
||||
parent_session: Option<Entity<Session>>,
|
||||
cx: &mut Context<Self>,
|
||||
|
@ -354,22 +351,39 @@ impl DapStore {
|
|||
}
|
||||
|
||||
let (initialized_tx, initialized_rx) = oneshot::channel();
|
||||
let locator_store = local_store.locator_store.clone();
|
||||
let debug_adapters = local_store.debug_adapters.clone();
|
||||
|
||||
let start_client_task = Session::local(
|
||||
self.breakpoint_store.clone(),
|
||||
session_id,
|
||||
parent_session,
|
||||
delegate,
|
||||
config,
|
||||
local_store.start_debugging_tx.clone(),
|
||||
initialized_tx,
|
||||
local_store.debug_adapters.clone(),
|
||||
cx,
|
||||
);
|
||||
let start_debugging_tx = local_store.start_debugging_tx.clone();
|
||||
|
||||
let task = cx.spawn(async move |this, cx| {
|
||||
if config.locator.is_some() {
|
||||
locator_store.resolve_debug_config(&mut config).await?;
|
||||
}
|
||||
|
||||
let start_client_task = this.update(cx, |this, cx| {
|
||||
Session::local(
|
||||
this.breakpoint_store.clone(),
|
||||
session_id,
|
||||
parent_session,
|
||||
delegate,
|
||||
config,
|
||||
start_debugging_tx.clone(),
|
||||
initialized_tx,
|
||||
debug_adapters,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
|
||||
this.update(cx, |_, cx| {
|
||||
create_new_session(session_id, initialized_rx, start_client_task, cx)
|
||||
})?
|
||||
.await
|
||||
});
|
||||
|
||||
let task = create_new_session(session_id, initialized_rx, start_client_task, cx);
|
||||
(session_id, task)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn new_fake_session(
|
||||
&mut self,
|
||||
|
@ -456,7 +470,10 @@ impl DapStore {
|
|||
request: DebugRequestDisposition::ReverseRequest(args),
|
||||
initialize_args: config.initialize_args.clone(),
|
||||
tcp_connection: config.tcp_connection.clone(),
|
||||
locator: None,
|
||||
args: Default::default(),
|
||||
};
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
let new_session_task = {
|
||||
let caps = parent_session.read(cx).capabilities.clone();
|
||||
|
|
39
crates/project/src/debugger/locator_store.rs
Normal file
39
crates/project/src/debugger/locator_store.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use cargo::CargoLocator;
|
||||
use collections::HashMap;
|
||||
use dap::DebugAdapterConfig;
|
||||
use gpui::SharedString;
|
||||
use locators::DapLocator;
|
||||
|
||||
mod cargo;
|
||||
mod locators;
|
||||
|
||||
pub(super) struct LocatorStore {
|
||||
locators: HashMap<SharedString, Box<dyn DapLocator>>,
|
||||
}
|
||||
|
||||
impl LocatorStore {
|
||||
pub(super) fn new() -> Self {
|
||||
let locators = HashMap::from_iter([(
|
||||
SharedString::new("cargo"),
|
||||
Box::new(CargoLocator {}) as Box<dyn DapLocator>,
|
||||
)]);
|
||||
Self { locators }
|
||||
}
|
||||
|
||||
pub(super) async fn resolve_debug_config(
|
||||
&self,
|
||||
debug_config: &mut DebugAdapterConfig,
|
||||
) -> Result<()> {
|
||||
let Some(ref locator_name) = &debug_config.locator else {
|
||||
log::debug!("Attempted to resolve debug config without a locator field");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if let Some(locator) = self.locators.get(locator_name as &str) {
|
||||
locator.run_locator(debug_config).await
|
||||
} else {
|
||||
Err(anyhow!("Couldn't find locator {}", locator_name))
|
||||
}
|
||||
}
|
||||
}
|
65
crates/project/src/debugger/locator_store/cargo.rs
Normal file
65
crates/project/src/debugger/locator_store/cargo.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use super::DapLocator;
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use dap::DebugAdapterConfig;
|
||||
use serde_json::Value;
|
||||
use smol::{
|
||||
io::AsyncReadExt,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
pub(super) struct CargoLocator {}
|
||||
|
||||
#[async_trait]
|
||||
impl DapLocator for CargoLocator {
|
||||
async fn run_locator(&self, debug_config: &mut DebugAdapterConfig) -> Result<()> {
|
||||
let Some(launch_config) = (match &mut debug_config.request {
|
||||
task::DebugRequestDisposition::UserConfigured(task::DebugRequestType::Launch(
|
||||
launch_config,
|
||||
)) => Some(launch_config),
|
||||
_ => None,
|
||||
}) else {
|
||||
return Err(anyhow!("Couldn't get launch config in locator"));
|
||||
};
|
||||
|
||||
let Some(cwd) = launch_config.cwd.clone() else {
|
||||
return Err(anyhow!(
|
||||
"Couldn't get cwd from debug config which is needed for locators"
|
||||
));
|
||||
};
|
||||
|
||||
let mut child = Command::new("cargo")
|
||||
.args(&debug_config.args)
|
||||
.arg("--message-format=json")
|
||||
.current_dir(cwd)
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
let mut output = String::new();
|
||||
if let Some(mut stdout) = child.stdout.take() {
|
||||
stdout.read_to_string(&mut output).await?;
|
||||
}
|
||||
|
||||
let status = child.status().await?;
|
||||
if !status.success() {
|
||||
return Err(anyhow::anyhow!("Cargo command failed"));
|
||||
}
|
||||
|
||||
let Some(executable) = output
|
||||
.lines()
|
||||
.filter(|line| !line.trim().is_empty())
|
||||
.filter_map(|line| serde_json::from_str(line).ok())
|
||||
.find_map(|json: Value| {
|
||||
json.get("executable")
|
||||
.and_then(Value::as_str)
|
||||
.map(String::from)
|
||||
})
|
||||
else {
|
||||
return Err(anyhow!("Couldn't get executable in cargo locator"));
|
||||
};
|
||||
|
||||
launch_config.program = executable;
|
||||
debug_config.args.clear();
|
||||
Ok(())
|
||||
}
|
||||
}
|
8
crates/project/src/debugger/locator_store/locators.rs
Normal file
8
crates/project/src/debugger/locator_store/locators.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use dap::DebugAdapterConfig;
|
||||
|
||||
#[async_trait]
|
||||
pub(super) trait DapLocator {
|
||||
async fn run_locator(&self, debug_config: &mut DebugAdapterConfig) -> Result<()>;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue