debugger/tasks: Remove TaskType enum (#29208)

Closes #ISSUE

Release Notes:

- N/A

---------

Co-authored-by: Cole Miller <m@cole-miller.net>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Anthony <anthony@zed.dev>
Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
Piotr Osiewicz 2025-04-26 01:44:56 +02:00 committed by GitHub
parent 053fafa90e
commit 67615b968b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 1272 additions and 1114 deletions

View file

@ -1,11 +1,11 @@
use anyhow::Result;
use collections::FxHashMap;
use gpui::SharedString;
use schemars::{JsonSchema, r#gen::SchemaSettings};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::{net::Ipv4Addr, path::Path};
use crate::{TaskTemplate, TaskType, task_template::DebugArgs};
/// Represents the host information of the debug adapter
#[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
pub struct TcpArgumentsTemplate {
@ -63,6 +63,8 @@ pub struct LaunchRequest {
/// Arguments to pass to a debuggee
#[serde(default)]
pub args: Vec<String>,
#[serde(default)]
pub env: FxHashMap<String, String>,
}
/// Represents the type that will determine which request to call on the debug adapter
@ -75,6 +77,64 @@ pub enum DebugRequest {
Attach(AttachRequest),
}
impl DebugRequest {
pub fn to_proto(&self) -> proto::DebugRequest {
match self {
DebugRequest::Launch(launch_request) => proto::DebugRequest {
request: Some(proto::debug_request::Request::DebugLaunchRequest(
proto::DebugLaunchRequest {
program: launch_request.program.clone(),
cwd: launch_request
.cwd
.as_ref()
.map(|cwd| cwd.to_string_lossy().into_owned()),
args: launch_request.args.clone(),
env: launch_request
.env
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
},
)),
},
DebugRequest::Attach(attach_request) => proto::DebugRequest {
request: Some(proto::debug_request::Request::DebugAttachRequest(
proto::DebugAttachRequest {
process_id: attach_request
.process_id
.expect("The process ID to be already filled out."),
},
)),
},
}
}
pub fn from_proto(val: proto::DebugRequest) -> Result<DebugRequest> {
let request = val
.request
.ok_or_else(|| anyhow::anyhow!("Missing debug request"))?;
match request {
proto::debug_request::Request::DebugLaunchRequest(proto::DebugLaunchRequest {
program,
cwd,
args,
env,
}) => Ok(DebugRequest::Launch(LaunchRequest {
program,
cwd: cwd.map(From::from),
args,
env: env.into_iter().collect(),
})),
proto::debug_request::Request::DebugAttachRequest(proto::DebugAttachRequest {
process_id,
}) => Ok(DebugRequest::Attach(AttachRequest {
process_id: Some(process_id),
})),
}
}
}
impl From<LaunchRequest> for DebugRequest {
fn from(launch_config: LaunchRequest) -> Self {
DebugRequest::Launch(launch_config)
@ -87,180 +147,46 @@ impl From<AttachRequest> for DebugRequest {
}
}
impl TryFrom<TaskTemplate> for DebugTaskTemplate {
type Error = ();
fn try_from(value: TaskTemplate) -> Result<Self, Self::Error> {
let TaskType::Debug(debug_args) = value.task_type else {
return Err(());
};
let request = match debug_args.request {
crate::DebugArgsRequest::Launch => DebugRequest::Launch(LaunchRequest {
program: value.command,
cwd: value.cwd.map(PathBuf::from),
args: value.args,
}),
crate::DebugArgsRequest::Attach(attach_config) => DebugRequest::Attach(attach_config),
};
Ok(DebugTaskTemplate {
locator: debug_args.locator,
definition: DebugTaskDefinition {
adapter: debug_args.adapter,
request,
label: value.label,
initialize_args: debug_args.initialize_args,
tcp_connection: debug_args.tcp_connection,
stop_on_entry: debug_args.stop_on_entry,
},
})
}
}
impl DebugTaskTemplate {
/// Translate from debug definition to a task template
pub fn to_zed_format(self) -> TaskTemplate {
let (command, cwd, request) = match self.definition.request {
DebugRequest::Launch(launch_config) => (
launch_config.program,
launch_config
.cwd
.map(|cwd| cwd.to_string_lossy().to_string()),
crate::task_template::DebugArgsRequest::Launch,
),
DebugRequest::Attach(attach_config) => (
"".to_owned(),
None,
crate::task_template::DebugArgsRequest::Attach(attach_config),
),
};
let task_type = TaskType::Debug(DebugArgs {
adapter: self.definition.adapter,
request,
initialize_args: self.definition.initialize_args,
locator: self.locator,
tcp_connection: self.definition.tcp_connection,
stop_on_entry: self.definition.stop_on_entry,
});
let label = self.definition.label.clone();
TaskTemplate {
label,
command,
args: vec![],
task_type,
cwd,
..Default::default()
}
}
}
#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub struct DebugTaskTemplate {
pub locator: Option<String>,
#[serde(flatten)]
pub definition: DebugTaskDefinition,
}
/// This struct represent a user created debug task
#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub struct DebugTaskDefinition {
/// The adapter to run
pub adapter: String,
/// The type of request that should be called on the debug adapter
#[serde(flatten)]
pub request: DebugRequest,
pub struct DebugScenario {
pub adapter: SharedString,
/// Name of the debug task
pub label: String,
pub label: SharedString,
/// A task to run prior to spawning the debuggee.
pub build: Option<SharedString>,
#[serde(flatten)]
pub request: Option<DebugRequest>,
/// Additional initialization arguments to be sent on DAP initialization
#[serde(default)]
pub initialize_args: Option<serde_json::Value>,
/// Optional TCP connection information
///
/// If provided, this will be used to connect to the debug adapter instead of
/// spawning a new process. This is useful for connecting to a debug adapter
/// that is already running or is started by another process.
#[serde(default)]
pub tcp_connection: Option<TcpArgumentsTemplate>,
/// Whether to tell the debug adapter to stop on entry
#[serde(default)]
pub stop_on_entry: Option<bool>,
}
impl DebugTaskDefinition {
impl DebugScenario {
pub fn cwd(&self) -> Option<&Path> {
if let DebugRequest::Launch(config) = &self.request {
config.cwd.as_deref()
if let Some(DebugRequest::Launch(config)) = &self.request {
config.cwd.as_ref().map(Path::new)
} else {
None
}
}
pub fn to_proto(&self) -> proto::DebugTaskDefinition {
proto::DebugTaskDefinition {
adapter: self.adapter.clone(),
request: Some(match &self.request {
DebugRequest::Launch(config) => {
proto::debug_task_definition::Request::DebugLaunchRequest(
proto::DebugLaunchRequest {
program: config.program.clone(),
cwd: config.cwd.as_ref().map(|c| c.to_string_lossy().to_string()),
args: config.args.clone(),
},
)
}
DebugRequest::Attach(attach_request) => {
proto::debug_task_definition::Request::DebugAttachRequest(
proto::DebugAttachRequest {
process_id: attach_request.process_id.unwrap_or_default(),
},
)
}
}),
label: self.label.clone(),
initialize_args: self.initialize_args.as_ref().map(|v| v.to_string()),
tcp_connection: self.tcp_connection.as_ref().map(|t| t.to_proto()),
stop_on_entry: self.stop_on_entry,
}
}
pub fn from_proto(proto: proto::DebugTaskDefinition) -> Result<Self> {
let request = proto
.request
.ok_or_else(|| anyhow::anyhow!("request is required"))?;
Ok(Self {
label: proto.label,
initialize_args: proto.initialize_args.map(|v| v.into()),
tcp_connection: proto
.tcp_connection
.map(TcpArgumentsTemplate::from_proto)
.transpose()?,
stop_on_entry: proto.stop_on_entry,
adapter: proto.adapter.clone(),
request: match request {
proto::debug_task_definition::Request::DebugAttachRequest(config) => {
DebugRequest::Attach(AttachRequest {
process_id: Some(config.process_id),
})
}
proto::debug_task_definition::Request::DebugLaunchRequest(config) => {
DebugRequest::Launch(LaunchRequest {
program: config.program,
cwd: config.cwd.map(|cwd| cwd.into()),
args: config.args,
})
}
},
})
}
}
/// A group of Debug Tasks defined in a JSON file.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(transparent)]
pub struct DebugTaskFile(pub Vec<DebugTaskTemplate>);
pub struct DebugTaskFile(pub Vec<DebugScenario>);
impl DebugTaskFile {
/// Generates JSON schema of Tasks JSON template format.

View file

@ -16,12 +16,10 @@ use std::path::PathBuf;
use std::str::FromStr;
pub use debug_format::{
AttachRequest, DebugRequest, DebugTaskDefinition, DebugTaskFile, DebugTaskTemplate,
LaunchRequest, TcpArgumentsTemplate,
AttachRequest, DebugRequest, DebugScenario, DebugTaskFile, LaunchRequest, TcpArgumentsTemplate,
};
pub use task_template::{
DebugArgs, DebugArgsRequest, HideStrategy, RevealStrategy, TaskModal, TaskTemplate,
TaskTemplates, TaskType,
DebugArgsRequest, HideStrategy, RevealStrategy, TaskModal, TaskTemplate, TaskTemplates,
};
pub use vscode_debug_format::VsCodeDebugTaskFile;
pub use vscode_format::VsCodeTaskFile;
@ -29,11 +27,11 @@ pub use zed_actions::RevealTarget;
/// Task identifier, unique within the application.
/// Based on it, task reruns and terminal tabs are managed.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize)]
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize)]
pub struct TaskId(pub String);
/// Contains all information needed by Zed to spawn a new terminal tab for the given task.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct SpawnInTerminal {
/// Id of the task to use when determining task tab affinity.
pub id: TaskId,
@ -72,6 +70,36 @@ pub struct SpawnInTerminal {
pub show_rerun: bool,
}
impl SpawnInTerminal {
pub fn to_proto(&self) -> proto::SpawnInTerminal {
proto::SpawnInTerminal {
label: self.label.clone(),
command: self.command.clone(),
args: self.args.clone(),
env: self
.env
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
cwd: self
.cwd
.clone()
.map(|cwd| cwd.to_string_lossy().into_owned()),
}
}
pub fn from_proto(proto: proto::SpawnInTerminal) -> Self {
Self {
label: proto.label.clone(),
command: proto.command.clone(),
args: proto.args.clone(),
env: proto.env.into_iter().collect(),
cwd: proto.cwd.map(PathBuf::from).clone(),
..Default::default()
}
}
}
/// A final form of the [`TaskTemplate`], that got resolved with a particular [`TaskContext`] and now is ready to spawn the actual task.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResolvedTask {
@ -89,7 +117,7 @@ pub struct ResolvedTask {
substituted_variables: HashSet<VariableName>,
/// Further actions that need to take place after the resolved task is spawned,
/// with all task variables resolved.
pub resolved: Option<SpawnInTerminal>,
pub resolved: SpawnInTerminal,
}
impl ResolvedTask {
@ -98,63 +126,6 @@ impl ResolvedTask {
&self.original_task
}
/// Get the task type that determines what this task is used for
/// And where is it shown in the UI
pub fn task_type(&self) -> TaskType {
self.original_task.task_type.clone()
}
/// Get the configuration for the debug adapter that should be used for this task.
pub fn resolved_debug_adapter_config(&self) -> Option<DebugTaskTemplate> {
match self.original_task.task_type.clone() {
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
}
})
.collect();
Some(DebugTaskTemplate {
locator: debug_args.locator.clone(),
definition: DebugTaskDefinition {
label: resolved.label.clone(),
adapter: debug_args.adapter.clone(),
request: match debug_args.request {
crate::task_template::DebugArgsRequest::Launch => {
DebugRequest::Launch(LaunchRequest {
program: resolved.command.clone(),
cwd: resolved.cwd.clone(),
args,
})
}
crate::task_template::DebugArgsRequest::Attach(attach_config) => {
DebugRequest::Attach(attach_config)
}
},
initialize_args: debug_args.initialize_args,
tcp_connection: debug_args.tcp_connection,
stop_on_entry: debug_args.stop_on_entry,
},
})
}
_ => None,
}
}
/// Variables that were substituted during the task template resolution.
pub fn substituted_variables(&self) -> &HashSet<VariableName> {
&self.substituted_variables
@ -162,10 +133,7 @@ impl ResolvedTask {
/// A human-readable label to display in the UI.
pub fn display_label(&self) -> &str {
self.resolved
.as_ref()
.map(|resolved| resolved.label.as_str())
.unwrap_or_else(|| self.resolved_label.as_str())
self.resolved.label.as_str()
}
}

View file

@ -7,7 +7,6 @@ use std::path::PathBuf;
use util::serde::default_true;
use util::{ResultExt, truncate_and_remove_front};
use crate::debug_format::TcpArgumentsTemplate;
use crate::{
AttachRequest, ResolvedTask, RevealTarget, Shell, SpawnInTerminal, TaskContext, TaskId,
VariableName, ZED_VARIABLE_NAME_PREFIX,
@ -59,9 +58,6 @@ pub struct TaskTemplate {
/// * `on_success` — hide the terminal tab on task success only, otherwise behaves similar to `always`.
#[serde(default)]
pub hide: HideStrategy,
/// If this task should start a debugger or not
#[serde(default, skip)]
pub task_type: TaskType,
/// Represents the tags which this template attaches to.
/// Adding this removes this task from other UI and gives you ability to run it by tag.
#[serde(default, deserialize_with = "non_empty_string_vec")]
@ -87,34 +83,6 @@ pub enum DebugArgsRequest {
Attach(AttachRequest),
}
#[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<TcpArgumentsTemplate>,
/// Args to send to debug adapter
pub initialize_args: Option<serde_json::value::Value>,
/// the locator to use
pub locator: Option<String>,
/// Whether to tell the debug adapter to stop on entry
pub stop_on_entry: Option<bool>,
}
/// Represents the type of task that is being ran
#[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(DebugArgs),
}
#[derive(Clone, Debug, PartialEq, Eq)]
/// The type of task modal to spawn
pub enum TaskModal {
@ -174,9 +142,7 @@ impl TaskTemplate {
/// Every [`ResolvedTask`] gets a [`TaskId`], based on the `id_base` (to avoid collision with various task sources),
/// and hashes of its template and [`TaskContext`], see [`ResolvedTask`] fields' documentation for more details.
pub fn resolve_task(&self, id_base: &str, cx: &TaskContext) -> Option<ResolvedTask> {
if self.label.trim().is_empty()
|| (self.command.trim().is_empty() && matches!(self.task_type, TaskType::Script))
{
if self.label.trim().is_empty() || self.command.trim().is_empty() {
return None;
}
@ -285,7 +251,7 @@ impl TaskTemplate {
substituted_variables,
original_task: self.clone(),
resolved_label: full_label.clone(),
resolved: Some(SpawnInTerminal {
resolved: SpawnInTerminal {
id,
cwd,
full_label,
@ -310,7 +276,7 @@ impl TaskTemplate {
show_summary: self.show_summary,
show_command: self.show_command,
show_rerun: true,
}),
},
})
}
}
@ -474,12 +440,7 @@ mod tests {
.resolve_task(TEST_ID_BASE, task_cx)
.unwrap_or_else(|| panic!("failed to resolve task {task_without_cwd:?}"));
assert_substituted_variables(&resolved_task, Vec::new());
resolved_task
.resolved
.clone()
.unwrap_or_else(|| {
panic!("failed to get resolve data for resolved task. Template: {task_without_cwd:?} Resolved: {resolved_task:?}")
})
resolved_task.resolved
};
let cx = TaskContext {
@ -626,10 +587,7 @@ mod tests {
all_variables.iter().map(|(name, _)| name.clone()).collect(),
);
let spawn_in_terminal = resolved_task
.resolved
.as_ref()
.expect("should have resolved a spawn in terminal task");
let spawn_in_terminal = &resolved_task.resolved;
assert_eq!(
spawn_in_terminal.label,
format!(
@ -713,7 +671,7 @@ mod tests {
.resolve_task(TEST_ID_BASE, &TaskContext::default())
.unwrap();
assert_substituted_variables(&resolved_task, Vec::new());
let resolved = resolved_task.resolved.unwrap();
let resolved = resolved_task.resolved;
assert_eq!(resolved.label, task.label);
assert_eq!(resolved.command, task.command);
assert_eq!(resolved.args, task.args);
@ -882,8 +840,7 @@ mod tests {
let resolved = template
.resolve_task(TEST_ID_BASE, &context)
.unwrap()
.resolved
.unwrap();
.resolved;
assert_eq!(resolved.env["TASK_ENV_VAR1"], "TASK_ENV_VAR1_VALUE");
assert_eq!(resolved.env["TASK_ENV_VAR2"], "env_var_2 1234 5678");

View file

@ -2,12 +2,13 @@ use std::path::PathBuf;
use anyhow::anyhow;
use collections::HashMap;
use gpui::SharedString;
use serde::Deserialize;
use util::ResultExt as _;
use crate::{
AttachRequest, DebugRequest, DebugTaskDefinition, DebugTaskFile, DebugTaskTemplate,
EnvVariableReplacer, LaunchRequest, TcpArgumentsTemplate, VariableName,
AttachRequest, DebugRequest, DebugScenario, DebugTaskFile, EnvVariableReplacer, LaunchRequest,
TcpArgumentsTemplate, VariableName,
};
#[derive(Clone, Debug, Deserialize, PartialEq)]
@ -43,11 +44,12 @@ struct VsCodeDebugTaskDefinition {
}
impl VsCodeDebugTaskDefinition {
fn try_to_zed(self, replacer: &EnvVariableReplacer) -> anyhow::Result<DebugTaskTemplate> {
let label = replacer.replace(&self.name);
fn try_to_zed(self, replacer: &EnvVariableReplacer) -> anyhow::Result<DebugScenario> {
let label = replacer.replace(&self.name).into();
// TODO based on grep.app results it seems that vscode supports whitespace-splitting this field (ugh)
let definition = DebugTaskDefinition {
let definition = DebugScenario {
label,
build: None,
request: match self.request {
Request::Launch => {
let cwd = self.cwd.map(|cwd| PathBuf::from(replacer.replace(&cwd)));
@ -60,11 +62,22 @@ impl VsCodeDebugTaskDefinition {
.into_iter()
.map(|arg| replacer.replace(&arg))
.collect();
DebugRequest::Launch(LaunchRequest { program, cwd, args })
let env = self
.env
.into_iter()
.filter_map(|(k, v)| v.map(|v| (k, v)))
.collect();
DebugRequest::Launch(LaunchRequest {
program,
cwd,
args,
env,
})
.into()
}
Request::Attach => DebugRequest::Attach(AttachRequest { process_id: None }),
Request::Attach => DebugRequest::Attach(AttachRequest { process_id: None }).into(),
},
adapter: task_type_to_adapter_name(self.r#type),
adapter: task_type_to_adapter_name(&self.r#type),
// TODO host?
tcp_connection: self.port.map(|port| TcpArgumentsTemplate {
port: Some(port),
@ -75,11 +88,7 @@ impl VsCodeDebugTaskDefinition {
// TODO
initialize_args: None,
};
let template = DebugTaskTemplate {
locator: None,
definition,
};
Ok(template)
Ok(definition)
}
}
@ -110,24 +119,26 @@ impl TryFrom<VsCodeDebugTaskFile> for DebugTaskFile {
}
}
// TODO figure out how to make JsDebugAdapter::ADAPTER_NAME et al available here
fn task_type_to_adapter_name(task_type: String) -> String {
match task_type.as_str() {
"node" => "JavaScript".to_owned(),
"go" => "Delve".to_owned(),
"php" => "PHP".to_owned(),
"cppdbg" | "lldb" => "CodeLLDB".to_owned(),
"debugpy" => "Debugpy".to_owned(),
// todo(debugger) figure out how to make JsDebugAdapter::ADAPTER_NAME et al available here
fn task_type_to_adapter_name(task_type: &str) -> SharedString {
match task_type {
"node" => "JavaScript",
"go" => "Delve",
"php" => "PHP",
"cppdbg" | "lldb" => "CodeLLDB",
"debugpy" => "Debugpy",
_ => task_type,
}
.to_owned()
.into()
}
#[cfg(test)]
mod tests {
use crate::{
DebugRequest, DebugTaskDefinition, DebugTaskFile, DebugTaskTemplate, LaunchRequest,
TcpArgumentsTemplate,
};
use collections::FxHashMap;
use crate::{DebugRequest, DebugScenario, DebugTaskFile, LaunchRequest, TcpArgumentsTemplate};
use super::VsCodeDebugTaskFile;
@ -159,24 +170,23 @@ mod tests {
let zed = DebugTaskFile::try_from(parsed).expect("converting to Zed debug templates");
pretty_assertions::assert_eq!(
zed,
DebugTaskFile(vec![DebugTaskTemplate {
locator: None,
definition: DebugTaskDefinition {
label: "Debug my JS app".into(),
adapter: "JavaScript".into(),
stop_on_entry: Some(true),
initialize_args: None,
tcp_connection: Some(TcpArgumentsTemplate {
port: Some(17),
host: None,
timeout: None,
}),
request: DebugRequest::Launch(LaunchRequest {
program: "${ZED_WORKTREE_ROOT}/xyz.js".into(),
args: vec!["--foo".into(), "${ZED_WORKTREE_ROOT}/thing".into()],
cwd: Some("${ZED_WORKTREE_ROOT}/${FOO}/sub".into()),
}),
}
DebugTaskFile(vec![DebugScenario {
label: "Debug my JS app".into(),
adapter: "JavaScript".into(),
stop_on_entry: Some(true),
initialize_args: None,
tcp_connection: Some(TcpArgumentsTemplate {
port: Some(17),
host: None,
timeout: None,
}),
request: Some(DebugRequest::Launch(LaunchRequest {
program: "${ZED_WORKTREE_ROOT}/xyz.js".into(),
args: vec!["--foo".into(), "${ZED_WORKTREE_ROOT}/thing".into()],
cwd: Some("${ZED_WORKTREE_ROOT}/${FOO}/sub".into()),
env: FxHashMap::from_iter([("X".into(), "Y".into())])
})),
build: None
}])
);
}