mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-31 04:54:24 +00:00
## 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
249 lines
8.5 KiB
Rust
249 lines
8.5 KiB
Rust
use dap_types::StartDebuggingRequestArguments;
|
|
use schemars::{gen::SchemaSettings, JsonSchema};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::net::Ipv4Addr;
|
|
use std::path::PathBuf;
|
|
use util::ResultExt;
|
|
|
|
use crate::{task_template::DebugArgs, TaskTemplate, TaskTemplates, TaskType};
|
|
|
|
impl Default for DebugConnectionType {
|
|
fn default() -> Self {
|
|
DebugConnectionType::TCP(TCPHost::default())
|
|
}
|
|
}
|
|
|
|
/// Represents the host information of the debug adapter
|
|
#[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
|
pub struct TCPHost {
|
|
/// The port that the debug adapter is listening on
|
|
///
|
|
/// Default: We will try to find an open port
|
|
pub port: Option<u16>,
|
|
/// The host that the debug adapter is listening too
|
|
///
|
|
/// Default: 127.0.0.1
|
|
pub host: Option<Ipv4Addr>,
|
|
/// The max amount of time in milliseconds to connect to a tcp DAP before returning an error
|
|
///
|
|
/// Default: 2000ms
|
|
pub timeout: Option<u64>,
|
|
}
|
|
|
|
impl TCPHost {
|
|
/// Get the host or fallback to the default host
|
|
pub fn host(&self) -> Ipv4Addr {
|
|
self.host.unwrap_or_else(|| Ipv4Addr::new(127, 0, 0, 1))
|
|
}
|
|
}
|
|
|
|
/// Represents the attach request information of the debug adapter
|
|
#[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
|
pub struct AttachConfig {
|
|
/// The processId to attach to, if left empty we will show a process picker
|
|
#[serde(default)]
|
|
pub process_id: Option<u32>,
|
|
}
|
|
|
|
/// Represents the launch request information of the debug adapter
|
|
#[derive(Deserialize, Serialize, Default, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
|
pub struct LaunchConfig {
|
|
/// The program that you trying to debug
|
|
pub program: String,
|
|
/// The current working directory of your project
|
|
pub cwd: Option<PathBuf>,
|
|
}
|
|
|
|
/// Represents the type that will determine which request to call on the debug adapter
|
|
#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
|
#[serde(rename_all = "lowercase", untagged)]
|
|
pub enum DebugRequestType {
|
|
/// Call the `launch` request on the debug adapter
|
|
Launch(LaunchConfig),
|
|
/// Call the `attach` request on the debug adapter
|
|
Attach(AttachConfig),
|
|
}
|
|
|
|
/// Represents a request for starting the debugger.
|
|
/// Contrary to `DebugRequestType`, `DebugRequestDisposition` is not Serializable.
|
|
#[derive(PartialEq, Eq, Clone, Debug)]
|
|
pub enum DebugRequestDisposition {
|
|
/// Debug session configured by the user.
|
|
UserConfigured(DebugRequestType),
|
|
/// Debug session configured by the debug adapter
|
|
ReverseRequest(StartDebuggingRequestArguments),
|
|
}
|
|
|
|
impl DebugRequestDisposition {
|
|
/// Get the current working directory from request if it's a launch request and exits
|
|
pub fn cwd(&self) -> Option<PathBuf> {
|
|
match self {
|
|
Self::UserConfigured(DebugRequestType::Launch(launch_config)) => {
|
|
launch_config.cwd.clone()
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
/// Represents the configuration for the debug adapter
|
|
#[derive(PartialEq, Eq, Clone, Debug)]
|
|
pub struct DebugAdapterConfig {
|
|
/// Name of the debug task
|
|
pub label: String,
|
|
/// The type of adapter you want to use
|
|
pub adapter: String,
|
|
/// The type of request that should be called on the debug adapter
|
|
pub request: DebugRequestDisposition,
|
|
/// Additional initialization arguments to be sent on DAP initialization
|
|
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.
|
|
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 {
|
|
fn from(def: DebugTaskDefinition) -> Self {
|
|
Self {
|
|
label: def.label,
|
|
adapter: def.adapter,
|
|
request: DebugRequestDisposition::UserConfigured(def.request),
|
|
initialize_args: def.initialize_args,
|
|
tcp_connection: def.tcp_connection,
|
|
locator: def.locator,
|
|
args: def.args,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<DebugAdapterConfig> for DebugTaskDefinition {
|
|
type Error = ();
|
|
fn try_from(def: DebugAdapterConfig) -> Result<Self, Self::Error> {
|
|
let request = match def.request {
|
|
DebugRequestDisposition::UserConfigured(debug_request_type) => debug_request_type,
|
|
DebugRequestDisposition::ReverseRequest(_) => return Err(()),
|
|
};
|
|
|
|
Ok(Self {
|
|
label: def.label,
|
|
adapter: def.adapter,
|
|
request,
|
|
initialize_args: def.initialize_args,
|
|
tcp_connection: def.tcp_connection,
|
|
locator: def.locator,
|
|
args: def.args,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl DebugTaskDefinition {
|
|
/// Translate from debug definition to a task template
|
|
pub fn to_zed_format(self) -> anyhow::Result<TaskTemplate> {
|
|
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();
|
|
|
|
Ok(TaskTemplate {
|
|
label,
|
|
command,
|
|
args: vec![],
|
|
task_type,
|
|
cwd,
|
|
..Default::default()
|
|
})
|
|
}
|
|
}
|
|
/// Represents the type of the debugger adapter connection
|
|
#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
|
#[serde(rename_all = "lowercase", tag = "connection")]
|
|
pub enum DebugConnectionType {
|
|
/// Connect to the debug adapter via TCP
|
|
TCP(TCPHost),
|
|
/// Connect to the debug adapter via STDIO
|
|
STDIO,
|
|
}
|
|
|
|
/// 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: DebugRequestType,
|
|
/// Name of the debug task
|
|
pub label: String,
|
|
/// Additional initialization arguments to be sent on DAP initialization
|
|
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.
|
|
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.
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
|
#[serde(transparent)]
|
|
pub struct DebugTaskFile(pub Vec<DebugTaskDefinition>);
|
|
|
|
impl DebugTaskFile {
|
|
/// Generates JSON schema of Tasks JSON template format.
|
|
pub fn generate_json_schema() -> serde_json_lenient::Value {
|
|
let schema = SchemaSettings::draft07()
|
|
.with(|settings| settings.option_add_null_type = false)
|
|
.into_generator()
|
|
.into_root_schema_for::<Self>();
|
|
|
|
serde_json_lenient::to_value(schema).unwrap()
|
|
}
|
|
}
|
|
|
|
impl TryFrom<DebugTaskFile> for TaskTemplates {
|
|
type Error = anyhow::Error;
|
|
|
|
fn try_from(value: DebugTaskFile) -> Result<Self, Self::Error> {
|
|
let templates = value
|
|
.0
|
|
.into_iter()
|
|
.filter_map(|debug_definition| debug_definition.to_zed_format().log_err())
|
|
.collect();
|
|
|
|
Ok(Self(templates))
|
|
}
|
|
}
|