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
|
@ -1,4 +1,3 @@
|
|||
use anyhow::bail;
|
||||
use gpui::AsyncApp;
|
||||
use std::{ffi::OsStr, path::PathBuf};
|
||||
use task::DebugTaskDefinition;
|
||||
|
@ -63,9 +62,7 @@ impl DebugAdapter for GoDebugAdapter {
|
|||
.and_then(|p| p.to_str().map(|p| p.to_string()))
|
||||
.ok_or(anyhow!("Dlv not found in path"))?;
|
||||
|
||||
let Some(tcp_connection) = config.tcp_connection.clone() else {
|
||||
bail!("Go Debug Adapter expects tcp connection arguments to be provided");
|
||||
};
|
||||
let tcp_connection = config.tcp_connection.clone().unwrap_or_default();
|
||||
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
|
|
|
@ -78,11 +78,7 @@ impl DebugAdapter for JsDebugAdapter {
|
|||
.ok_or_else(|| anyhow!("Couldn't find JavaScript dap directory"))?
|
||||
};
|
||||
|
||||
let Some(tcp_connection) = config.tcp_connection.clone() else {
|
||||
anyhow::bail!(
|
||||
"Javascript Debug Adapter expects tcp connection arguments to be provided"
|
||||
);
|
||||
};
|
||||
let tcp_connection = config.tcp_connection.clone().unwrap_or_default();
|
||||
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use adapters::latest_github_release;
|
||||
use anyhow::bail;
|
||||
use dap::adapters::TcpArguments;
|
||||
use gpui::AsyncApp;
|
||||
use std::path::PathBuf;
|
||||
|
@ -69,9 +68,7 @@ impl DebugAdapter for PhpDebugAdapter {
|
|||
.ok_or_else(|| anyhow!("Couldn't find PHP dap directory"))?
|
||||
};
|
||||
|
||||
let Some(tcp_connection) = config.tcp_connection.clone() else {
|
||||
bail!("PHP Debug Adapter expects tcp connection arguments to be provided");
|
||||
};
|
||||
let tcp_connection = config.tcp_connection.clone().unwrap_or_default();
|
||||
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use crate::*;
|
||||
use anyhow::bail;
|
||||
use dap::DebugRequestType;
|
||||
use gpui::AsyncApp;
|
||||
use std::{ffi::OsStr, path::PathBuf};
|
||||
|
@ -70,9 +69,7 @@ impl DebugAdapter for PythonDebugAdapter {
|
|||
cx: &mut AsyncApp,
|
||||
) -> Result<DebugAdapterBinary> {
|
||||
const BINARY_NAMES: [&str; 3] = ["python3", "python", "py"];
|
||||
let Some(tcp_connection) = config.tcp_connection.clone() else {
|
||||
bail!("Python Debug Adapter expects tcp connection arguments to be provided");
|
||||
};
|
||||
let tcp_connection = config.tcp_connection.clone().unwrap_or_default();
|
||||
let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
|
||||
|
||||
let debugpy_dir = if let Some(user_installed_path) = user_installed_path {
|
||||
|
|
|
@ -159,6 +159,8 @@ impl Render for InertState {
|
|||
}),
|
||||
tcp_connection: Some(TCPHost::default()),
|
||||
initialize_args: None,
|
||||
args: Default::default(),
|
||||
locator: None,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
|
@ -319,6 +321,8 @@ impl InertState {
|
|||
adapter: kind,
|
||||
request: DebugRequestType::Attach(task::AttachConfig { process_id: None }),
|
||||
initialize_args: None,
|
||||
args: Default::default(),
|
||||
locator: None,
|
||||
tcp_connection: Some(TCPHost::default()),
|
||||
};
|
||||
|
||||
|
|
|
@ -89,6 +89,8 @@ async fn test_show_attach_modal_and_select_process(
|
|||
label: "attach example".into(),
|
||||
initialize_args: None,
|
||||
tcp_connection: Some(TCPHost::default()),
|
||||
locator: None,
|
||||
args: Default::default(),
|
||||
},
|
||||
vec![
|
||||
Candidate {
|
||||
|
|
|
@ -4850,6 +4850,7 @@ impl Editor {
|
|||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
|
||||
let action = actions_menu.actions.get(action_ix)?;
|
||||
let title = action.label();
|
||||
|
@ -4858,7 +4859,8 @@ impl Editor {
|
|||
|
||||
match action {
|
||||
CodeActionsItem::Task(task_source_kind, resolved_task) => {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
match resolved_task.task_type() {
|
||||
task::TaskType::Script => workspace.update(cx, |workspace, cx| {
|
||||
workspace::tasks::schedule_resolved_task(
|
||||
workspace,
|
||||
task_source_kind,
|
||||
|
@ -4868,7 +4870,37 @@ impl Editor {
|
|||
);
|
||||
|
||||
Some(Task::ready(Ok(())))
|
||||
}),
|
||||
task::TaskType::Debug(debug_args) => {
|
||||
if debug_args.locator.is_some() {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace::tasks::schedule_resolved_task(
|
||||
workspace,
|
||||
task_source_kind,
|
||||
resolved_task,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
return Some(Task::ready(Ok(())));
|
||||
}
|
||||
|
||||
if let Some(project) = self.project.as_ref() {
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.start_debug_session(
|
||||
resolved_task.resolved_debug_adapter_config().unwrap(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
Some(Task::ready(Ok(())))
|
||||
} else {
|
||||
Some(Task::ready(Ok(())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CodeActionsItem::CodeAction {
|
||||
excerpt_id,
|
||||
|
@ -8600,7 +8632,7 @@ impl Editor {
|
|||
let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
|
||||
let anchor_end = snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_before(Point::new(row, line_len));
|
||||
.anchor_after(Point::new(row, line_len));
|
||||
|
||||
let bp = self
|
||||
.breakpoint_store
|
||||
|
|
|
@ -17,7 +17,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName};
|
||||
use task::{TaskTemplate, TaskTemplates, TaskType, TaskVariables, VariableName};
|
||||
use util::{fs::remove_matching, maybe, ResultExt};
|
||||
|
||||
use crate::language_settings::language_settings;
|
||||
|
@ -574,11 +574,16 @@ impl ContextProvider for RustContextProvider {
|
|||
.variables
|
||||
.get(CUSTOM_TARGET_DIR)
|
||||
.cloned();
|
||||
let run_task_args = if let Some(package_to_run) = package_to_run {
|
||||
let run_task_args = if let Some(package_to_run) = package_to_run.clone() {
|
||||
vec!["run".into(), "-p".into(), package_to_run]
|
||||
} else {
|
||||
vec!["run".into()]
|
||||
};
|
||||
let debug_task_args = if let Some(package_to_run) = package_to_run {
|
||||
vec!["build".into(), "-p".into(), package_to_run]
|
||||
} else {
|
||||
vec!["build".into()]
|
||||
};
|
||||
let mut task_templates = vec![
|
||||
TaskTemplate {
|
||||
label: format!(
|
||||
|
@ -620,6 +625,31 @@ impl ContextProvider for RustContextProvider {
|
|||
cwd: Some("$ZED_DIRNAME".to_owned()),
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: format!(
|
||||
"Debug Test '{}' (package: {})",
|
||||
RUST_TEST_NAME_TASK_VARIABLE.template_value(),
|
||||
RUST_PACKAGE_TASK_VARIABLE.template_value(),
|
||||
),
|
||||
task_type: TaskType::Debug(task::DebugArgs {
|
||||
adapter: "LLDB".to_owned(),
|
||||
request: task::DebugArgsRequest::Launch,
|
||||
locator: Some("cargo".into()),
|
||||
tcp_connection: None,
|
||||
initialize_args: None,
|
||||
}),
|
||||
command: "cargo".into(),
|
||||
args: vec![
|
||||
"test".into(),
|
||||
"-p".into(),
|
||||
RUST_PACKAGE_TASK_VARIABLE.template_value(),
|
||||
RUST_TEST_NAME_TASK_VARIABLE.template_value(),
|
||||
"--no-run".into(),
|
||||
],
|
||||
tags: vec!["rust-test".to_owned()],
|
||||
cwd: Some("$ZED_DIRNAME".to_owned()),
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: format!(
|
||||
"Doc test '{}' (package: {})",
|
||||
|
@ -697,6 +727,21 @@ impl ContextProvider for RustContextProvider {
|
|||
cwd: Some("$ZED_DIRNAME".to_owned()),
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: "Debug".into(),
|
||||
cwd: Some("$ZED_DIRNAME".to_owned()),
|
||||
command: "cargo".into(),
|
||||
task_type: TaskType::Debug(task::DebugArgs {
|
||||
request: task::DebugArgsRequest::Launch,
|
||||
adapter: "LLDB".to_owned(),
|
||||
initialize_args: None,
|
||||
locator: Some("cargo".into()),
|
||||
tcp_connection: None,
|
||||
}),
|
||||
args: debug_task_args,
|
||||
tags: vec!["rust-main".to_owned()],
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: "Clean".into(),
|
||||
command: "cargo".into(),
|
||||
|
|
|
@ -14,4 +14,5 @@
|
|||
pub mod breakpoint_store;
|
||||
pub mod dap_command;
|
||||
pub mod dap_store;
|
||||
mod locator_store;
|
||||
pub mod session;
|
||||
|
|
|
@ -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(),
|
||||
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,
|
||||
local_store.start_debugging_tx.clone(),
|
||||
start_debugging_tx.clone(),
|
||||
initialized_tx,
|
||||
local_store.debug_adapters.clone(),
|
||||
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<()>;
|
||||
}
|
|
@ -165,6 +165,7 @@ pub struct Project {
|
|||
languages: Arc<LanguageRegistry>,
|
||||
debug_adapters: Arc<DapRegistry>,
|
||||
dap_store: Entity<DapStore>,
|
||||
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
client: Arc<client::Client>,
|
||||
join_project_response_message_id: u32,
|
||||
|
@ -952,6 +953,7 @@ impl Project {
|
|||
ssh_client: None,
|
||||
breakpoint_store,
|
||||
dap_store,
|
||||
|
||||
buffers_needing_diff: Default::default(),
|
||||
git_diff_debouncer: DebouncedDelay::new(),
|
||||
terminals: Terminals {
|
||||
|
@ -1450,6 +1452,12 @@ impl Project {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn queue_debug_session(&mut self, config: DebugAdapterConfig, cx: &mut Context<Self>) {
|
||||
if config.locator.is_none() {
|
||||
self.start_debug_session(config, cx).detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_debug_session(
|
||||
&mut self,
|
||||
config: DebugAdapterConfig,
|
||||
|
@ -1490,6 +1498,8 @@ impl Project {
|
|||
request: DebugRequestDisposition::UserConfigured(request),
|
||||
initialize_args: None,
|
||||
tcp_connection: None,
|
||||
locator: None,
|
||||
args: Default::default(),
|
||||
};
|
||||
let caps = caps.unwrap_or(Capabilities {
|
||||
supports_step_back: Some(false),
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::net::Ipv4Addr;
|
|||
use std::path::PathBuf;
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::{TaskTemplate, TaskTemplates, TaskType};
|
||||
use crate::{task_template::DebugArgs, TaskTemplate, TaskTemplates, TaskType};
|
||||
|
||||
impl Default for DebugConnectionType {
|
||||
fn default() -> Self {
|
||||
|
@ -102,6 +102,10 @@ pub struct DebugAdapterConfig {
|
|||
/// spawning a new process. This is useful for connecting to a debug adapter
|
||||
/// that is already running or is started by another process.
|
||||
pub tcp_connection: Option<TCPHost>,
|
||||
/// What Locator to use to configure the debug task
|
||||
pub locator: Option<String>,
|
||||
/// Args to pass to a debug adapter (only used in locator right now)
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
impl From<DebugTaskDefinition> for DebugAdapterConfig {
|
||||
|
@ -112,6 +116,8 @@ impl From<DebugTaskDefinition> for DebugAdapterConfig {
|
|||
request: DebugRequestDisposition::UserConfigured(def.request),
|
||||
initialize_args: def.initialize_args,
|
||||
tcp_connection: def.tcp_connection,
|
||||
locator: def.locator,
|
||||
args: def.args,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -130,6 +136,8 @@ impl TryFrom<DebugAdapterConfig> for DebugTaskDefinition {
|
|||
request,
|
||||
initialize_args: def.initialize_args,
|
||||
tcp_connection: def.tcp_connection,
|
||||
locator: def.locator,
|
||||
args: def.args,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -137,18 +145,30 @@ impl TryFrom<DebugAdapterConfig> for DebugTaskDefinition {
|
|||
impl DebugTaskDefinition {
|
||||
/// Translate from debug definition to a task template
|
||||
pub fn to_zed_format(self) -> anyhow::Result<TaskTemplate> {
|
||||
let command = "".to_string();
|
||||
|
||||
let cwd = if let DebugRequestType::Launch(ref launch) = self.request {
|
||||
launch
|
||||
let (command, cwd, request) = match self.request {
|
||||
DebugRequestType::Launch(launch_config) => (
|
||||
launch_config.program,
|
||||
launch_config
|
||||
.cwd
|
||||
.as_ref()
|
||||
.map(|path| path.to_string_lossy().into_owned())
|
||||
} else {
|
||||
None
|
||||
.map(|cwd| cwd.to_string_lossy().to_string()),
|
||||
crate::task_template::DebugArgsRequest::Launch,
|
||||
),
|
||||
DebugRequestType::Attach(attach_config) => (
|
||||
"".to_owned(),
|
||||
None,
|
||||
crate::task_template::DebugArgsRequest::Attach(attach_config),
|
||||
),
|
||||
};
|
||||
|
||||
let task_type = TaskType::Debug(DebugArgs {
|
||||
adapter: self.adapter,
|
||||
request,
|
||||
initialize_args: self.initialize_args,
|
||||
locator: self.locator,
|
||||
tcp_connection: self.tcp_connection,
|
||||
});
|
||||
|
||||
let label = self.label.clone();
|
||||
let task_type = TaskType::Debug(self);
|
||||
|
||||
Ok(TaskTemplate {
|
||||
label,
|
||||
|
@ -189,6 +209,12 @@ pub struct DebugTaskDefinition {
|
|||
/// spawning a new process. This is useful for connecting to a debug adapter
|
||||
/// that is already running or is started by another process.
|
||||
pub tcp_connection: Option<TCPHost>,
|
||||
/// Locator to use
|
||||
/// -- cargo
|
||||
pub locator: Option<String>,
|
||||
/// Args to pass to a debug adapter (only used in locator right now)
|
||||
#[serde(skip)]
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
/// A group of Debug Tasks defined in a JSON file.
|
||||
|
|
|
@ -19,7 +19,8 @@ pub use debug_format::{
|
|||
DebugRequestType, DebugTaskDefinition, DebugTaskFile, LaunchConfig, TCPHost,
|
||||
};
|
||||
pub use task_template::{
|
||||
HideStrategy, RevealStrategy, TaskModal, TaskTemplate, TaskTemplates, TaskType,
|
||||
DebugArgs, DebugArgsRequest, HideStrategy, RevealStrategy, TaskModal, TaskTemplate,
|
||||
TaskTemplates, TaskType,
|
||||
};
|
||||
pub use vscode_format::VsCodeTaskFile;
|
||||
pub use zed_actions::RevealTarget;
|
||||
|
@ -61,8 +62,6 @@ pub struct SpawnInTerminal {
|
|||
pub hide: HideStrategy,
|
||||
/// Which shell to use when spawning the task.
|
||||
pub shell: Shell,
|
||||
/// Tells debug tasks which program to debug
|
||||
pub program: Option<String>,
|
||||
/// Whether to show the task summary line in the task output (sucess/failure).
|
||||
pub show_summary: bool,
|
||||
/// Whether to show the command line in the task output.
|
||||
|
@ -104,24 +103,50 @@ impl ResolvedTask {
|
|||
}
|
||||
|
||||
/// Get the configuration for the debug adapter that should be used for this task.
|
||||
pub fn resolved_debug_adapter_config(&self) -> Option<DebugTaskDefinition> {
|
||||
pub fn resolved_debug_adapter_config(&self) -> Option<DebugAdapterConfig> {
|
||||
match self.original_task.task_type.clone() {
|
||||
TaskType::Script => None,
|
||||
TaskType::Debug(mut adapter_config) => {
|
||||
if let Some(resolved) = &self.resolved {
|
||||
adapter_config.label = resolved.label.clone();
|
||||
if let DebugRequestType::Launch(ref mut launch) = adapter_config.request {
|
||||
if let Some(program) = resolved.program.clone() {
|
||||
launch.program = program;
|
||||
}
|
||||
if let Some(cwd) = resolved.cwd.clone() {
|
||||
launch.cwd = Some(cwd);
|
||||
}
|
||||
}
|
||||
}
|
||||
TaskType::Debug(debug_args) if self.resolved.is_some() => {
|
||||
let resolved = self
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("We just checked if this was some");
|
||||
|
||||
Some(adapter_config)
|
||||
let args = resolved
|
||||
.args
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|arg| {
|
||||
if arg.starts_with("$") {
|
||||
arg.strip_prefix("$")
|
||||
.and_then(|arg| resolved.env.get(arg).map(ToOwned::to_owned))
|
||||
.unwrap_or_else(|| arg)
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Some(DebugAdapterConfig {
|
||||
label: resolved.label.clone(),
|
||||
adapter: debug_args.adapter.clone(),
|
||||
request: DebugRequestDisposition::UserConfigured(match debug_args.request {
|
||||
crate::task_template::DebugArgsRequest::Launch => {
|
||||
DebugRequestType::Launch(LaunchConfig {
|
||||
program: resolved.command.clone(),
|
||||
cwd: resolved.cwd.clone(),
|
||||
})
|
||||
}
|
||||
crate::task_template::DebugArgsRequest::Attach(attach_config) => {
|
||||
DebugRequestType::Attach(attach_config)
|
||||
}
|
||||
}),
|
||||
initialize_args: debug_args.initialize_args,
|
||||
tcp_connection: debug_args.tcp_connection,
|
||||
args,
|
||||
locator: debug_args.locator.clone(),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ use sha2::{Digest, Sha256};
|
|||
use util::{truncate_and_remove_front, ResultExt};
|
||||
|
||||
use crate::{
|
||||
DebugRequestType, DebugTaskDefinition, ResolvedTask, RevealTarget, Shell, SpawnInTerminal,
|
||||
TaskContext, TaskId, VariableName, ZED_VARIABLE_NAME_PREFIX,
|
||||
AttachConfig, ResolvedTask, RevealTarget, Shell, SpawnInTerminal, TCPHost, TaskContext, TaskId,
|
||||
VariableName, ZED_VARIABLE_NAME_PREFIX,
|
||||
};
|
||||
|
||||
/// A template definition of a Zed task to run.
|
||||
|
@ -75,62 +75,39 @@ pub struct TaskTemplate {
|
|||
pub show_command: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Eq, PartialEq, Clone, Debug)]
|
||||
/// Use to represent debug request type
|
||||
pub enum DebugArgsRequest {
|
||||
/// launch (program, cwd) are stored in TaskTemplate as (command, cwd)
|
||||
Launch,
|
||||
/// Attach
|
||||
Attach(AttachConfig),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Eq, PartialEq, Clone, Debug)]
|
||||
/// This represents the arguments for the debug task.
|
||||
pub struct DebugArgs {
|
||||
/// The launch type
|
||||
pub request: DebugArgsRequest,
|
||||
/// Adapter choice
|
||||
pub adapter: String,
|
||||
/// TCP connection to make with debug adapter
|
||||
pub tcp_connection: Option<TCPHost>,
|
||||
/// Args to send to debug adapter
|
||||
pub initialize_args: Option<serde_json::value::Value>,
|
||||
/// the locator to use
|
||||
pub locator: Option<String>,
|
||||
}
|
||||
|
||||
/// Represents the type of task that is being ran
|
||||
#[derive(Default, Deserialize, Serialize, Eq, PartialEq, JsonSchema, Clone, Debug)]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[derive(Default, Eq, PartialEq, Clone, Debug)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum TaskType {
|
||||
/// Act like a typically task that runs commands
|
||||
#[default]
|
||||
Script,
|
||||
/// This task starts the debugger for a language
|
||||
Debug(DebugTaskDefinition),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod deserialization_tests {
|
||||
use crate::LaunchConfig;
|
||||
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn deserialize_task_type_script() {
|
||||
let json = json!({"type": "script"});
|
||||
|
||||
let task_type: TaskType =
|
||||
serde_json::from_value(json).expect("Failed to deserialize TaskType::Script");
|
||||
assert_eq!(task_type, TaskType::Script);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_task_type_debug() {
|
||||
let adapter_config = DebugTaskDefinition {
|
||||
label: "test config".into(),
|
||||
adapter: "Debugpy".into(),
|
||||
request: crate::DebugRequestType::Launch(LaunchConfig {
|
||||
program: "main".to_string(),
|
||||
cwd: None,
|
||||
}),
|
||||
initialize_args: None,
|
||||
tcp_connection: None,
|
||||
};
|
||||
let json = json!({
|
||||
"label": "test config",
|
||||
"type": "debug",
|
||||
"adapter": "Debugpy",
|
||||
"program": "main",
|
||||
"supports_attach": false,
|
||||
});
|
||||
|
||||
let task_type: TaskType =
|
||||
serde_json::from_value(json).expect("Failed to deserialize TaskType::Debug");
|
||||
if let TaskType::Debug(config) = task_type {
|
||||
assert_eq!(config, adapter_config);
|
||||
} else {
|
||||
panic!("Expected TaskType::Debug");
|
||||
}
|
||||
}
|
||||
Debug(DebugArgs),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
@ -270,22 +247,6 @@ impl TaskTemplate {
|
|||
&mut substituted_variables,
|
||||
)?;
|
||||
|
||||
let program = match &self.task_type {
|
||||
TaskType::Script => None,
|
||||
TaskType::Debug(adapter_config) => {
|
||||
if let DebugRequestType::Launch(ref launch) = &adapter_config.request {
|
||||
Some(substitute_all_template_variables_in_str(
|
||||
&launch.program,
|
||||
&task_variables,
|
||||
&variable_names,
|
||||
&mut substituted_variables,
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let task_hash = to_hex_hash(self)
|
||||
.context("hashing task template")
|
||||
.log_err()?;
|
||||
|
@ -341,7 +302,6 @@ impl TaskTemplate {
|
|||
reveal_target: self.reveal_target,
|
||||
hide: self.hide,
|
||||
shell: self.shell.clone(),
|
||||
program,
|
||||
show_summary: self.show_summary,
|
||||
show_command: self.show_command,
|
||||
show_rerun: true,
|
||||
|
|
|
@ -10,7 +10,8 @@ use gpui::{
|
|||
use picker::{highlighted_match_with_paths::HighlightedMatch, Picker, PickerDelegate};
|
||||
use project::{task_store::TaskStore, TaskSourceKind};
|
||||
use task::{
|
||||
DebugRequestType, ResolvedTask, RevealTarget, TaskContext, TaskModal, TaskTemplate, TaskType,
|
||||
DebugRequestType, DebugTaskDefinition, ResolvedTask, RevealTarget, TaskContext, TaskModal,
|
||||
TaskTemplate, TaskType,
|
||||
};
|
||||
use ui::{
|
||||
div, h_flex, v_flex, ActiveTheme, Button, ButtonCommon, ButtonSize, Clickable, Color,
|
||||
|
@ -320,15 +321,11 @@ impl PickerDelegate for TasksModalDelegate {
|
|||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
match task.task_type() {
|
||||
TaskType::Script => schedule_resolved_task(
|
||||
workspace,
|
||||
task_source_kind,
|
||||
task,
|
||||
omit_history_entry,
|
||||
cx,
|
||||
),
|
||||
TaskType::Debug(_) => {
|
||||
let Some(config) = task.resolved_debug_adapter_config() else {
|
||||
TaskType::Debug(config) if config.locator.is_none() => {
|
||||
let Some(config): Option<DebugTaskDefinition> = task
|
||||
.resolved_debug_adapter_config()
|
||||
.and_then(|config| config.try_into().ok())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let project = workspace.project().clone();
|
||||
|
@ -355,6 +352,13 @@ impl PickerDelegate for TasksModalDelegate {
|
|||
}
|
||||
}
|
||||
}
|
||||
_ => schedule_resolved_task(
|
||||
workspace,
|
||||
task_source_kind,
|
||||
task,
|
||||
omit_history_entry,
|
||||
cx,
|
||||
),
|
||||
};
|
||||
})
|
||||
.ok();
|
||||
|
@ -517,16 +521,30 @@ impl PickerDelegate for TasksModalDelegate {
|
|||
omit_history_entry,
|
||||
cx,
|
||||
),
|
||||
// TODO: Should create a schedule_resolved_debug_task function
|
||||
// todo(debugger): Should create a schedule_resolved_debug_task function
|
||||
// This would allow users to access to debug history and other issues
|
||||
TaskType::Debug(_) => workspace.project().update(cx, |project, cx| {
|
||||
project
|
||||
.start_debug_session(
|
||||
task.resolved_debug_adapter_config().unwrap().into(),
|
||||
TaskType::Debug(debug_args) => {
|
||||
let Some(debug_config) = task.resolved_debug_adapter_config() else {
|
||||
// todo(debugger) log an error, this should never happen
|
||||
return;
|
||||
};
|
||||
|
||||
if debug_args.locator.is_some() {
|
||||
schedule_resolved_task(
|
||||
workspace,
|
||||
task_source_kind,
|
||||
task,
|
||||
omit_history_entry,
|
||||
cx,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
workspace.project().update(cx, |project, cx| {
|
||||
project
|
||||
.start_debug_session(debug_config, cx)
|
||||
.detach_and_log_err(cx);
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
.ok();
|
||||
|
|
|
@ -110,6 +110,7 @@ pub enum Event {
|
|||
SelectionsChanged,
|
||||
NewNavigationTarget(Option<MaybeNavigationTarget>),
|
||||
Open(MaybeNavigationTarget),
|
||||
TaskLocatorReady { task_id: TaskId, success: bool },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -1899,6 +1900,11 @@ impl Terminal {
|
|||
unsafe { append_text_to_term(&mut self.term.lock(), &lines_to_show) };
|
||||
}
|
||||
|
||||
cx.emit(Event::TaskLocatorReady {
|
||||
task_id: task.id.clone(),
|
||||
success: finished_successfully,
|
||||
});
|
||||
|
||||
match task.hide {
|
||||
HideStrategy::Never => {}
|
||||
HideStrategy::Always => {
|
||||
|
|
|
@ -982,6 +982,15 @@ fn subscribe_for_terminal_events(
|
|||
window.invalidate_character_coordinates();
|
||||
cx.emit(SearchEvent::ActiveMatchChanged)
|
||||
}
|
||||
Event::TaskLocatorReady { task_id, success } => {
|
||||
if *success {
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.debug_task_ready(task_id, cx);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
vec![terminal_subscription, terminal_events_subscription]
|
||||
|
|
|
@ -1440,7 +1440,6 @@ impl ShellExec {
|
|||
reveal_target: RevealTarget::Dock,
|
||||
hide: HideStrategy::Never,
|
||||
shell,
|
||||
program: None,
|
||||
show_summary: false,
|
||||
show_command: false,
|
||||
show_rerun: false,
|
||||
|
|
|
@ -46,7 +46,15 @@ pub fn schedule_resolved_task(
|
|||
omit_history: bool,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
let debug_config = resolved_task.resolved_debug_adapter_config();
|
||||
|
||||
if let Some(spawn_in_terminal) = resolved_task.resolved.take() {
|
||||
if let Some(debug_config) = debug_config {
|
||||
workspace
|
||||
.debug_task_queue
|
||||
.insert(resolved_task.id.clone(), debug_config);
|
||||
}
|
||||
|
||||
if !omit_history {
|
||||
resolved_task.resolved = Some(spawn_in_terminal.clone());
|
||||
workspace.project().update(cx, |project, cx| {
|
||||
|
|
|
@ -94,7 +94,7 @@ use std::{
|
|||
sync::{atomic::AtomicUsize, Arc, LazyLock, Weak},
|
||||
time::Duration,
|
||||
};
|
||||
use task::SpawnInTerminal;
|
||||
use task::{DebugAdapterConfig, SpawnInTerminal, TaskId};
|
||||
use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
|
||||
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
|
||||
pub use ui;
|
||||
|
@ -874,6 +874,7 @@ pub struct Workspace {
|
|||
serialized_ssh_project: Option<SerializedSshProject>,
|
||||
_items_serializer: Task<Result<()>>,
|
||||
session_id: Option<String>,
|
||||
debug_task_queue: HashMap<task::TaskId, DebugAdapterConfig>,
|
||||
}
|
||||
|
||||
impl EventEmitter<Event> for Workspace {}
|
||||
|
@ -1185,6 +1186,7 @@ impl Workspace {
|
|||
_items_serializer,
|
||||
session_id: Some(session_id),
|
||||
serialized_ssh_project: None,
|
||||
debug_task_queue: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5191,6 +5193,16 @@ impl Workspace {
|
|||
.update(cx, |_, window, _| window.activate_window())
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn debug_task_ready(&mut self, task_id: &TaskId, cx: &mut App) {
|
||||
if let Some(debug_config) = self.debug_task_queue.remove(task_id) {
|
||||
self.project.update(cx, |project, cx| {
|
||||
project
|
||||
.start_debug_session(debug_config, cx)
|
||||
.detach_and_log_err(cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn leader_border_for_pane(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue