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
|
@ -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;
|
||||
}
|
||||
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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue