Detailed changes
@@ -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,17 +4859,48 @@ impl Editor {
match action {
CodeActionsItem::Task(task_source_kind, resolved_task) => {
- workspace.update(cx, |workspace, cx| {
- workspace::tasks::schedule_resolved_task(
- workspace,
- task_source_kind,
- resolved_task,
- false,
- cx,
- );
+ match resolved_task.task_type() {
+ task::TaskType::Script => workspace.update(cx, |workspace, cx| {
+ workspace::tasks::schedule_resolved_task(
+ workspace,
+ task_source_kind,
+ resolved_task,
+ false,
+ cx,
+ );
- Some(Task::ready(Ok(())))
- })
+ 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(),
- 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();
@@ -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))
+ }
+ }
+}
@@ -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(())
+ }
+}
@@ -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
- .cwd
- .as_ref()
- .map(|path| path.to_string_lossy().into_owned())
- } else {
- None
+ let (command, cwd, request) = match self.request {
+ DebugRequestType::Launch(launch_config) => (
+ launch_config.program,
+ launch_config
+ .cwd
+ .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;
+ TaskType::Debug(debug_args) if self.resolved.is_some() => {
+ let resolved = self
+ .resolved
+ .as_ref()
+ .expect("We just checked if this was some");
+
+ 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
}
- if let Some(cwd) = resolved.cwd.clone() {
- launch.cwd = Some(cwd);
+ })
+ .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(),
+ })
}
- }
- }
-
- Some(adapter_config)
+ 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,
- )
- .detach_and_log_err(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(