
This PR allows DAPs to define their own schema so users can see completion items when editing their debug.json files. Users facing this aren’t the biggest chance, but behind the scenes, this affected a lot of code because we manually translated common fields from Zed's config format to be adapter-specific. Now we store the raw JSON from a user's configuration file and just send that. I'm ignoring the Protobuf CICD error because the DebugTaskDefinition message is not yet user facing and we need to deprecate some fields in it. Release Notes: - debugger beta: Show completion items when editing debug.json - debugger beta: Breaking change, debug.json schema now relays on what DAP you have selected instead of always having the same based values. --------- Co-authored-by: Remco Smits <djsmits12@gmail.com> Co-authored-by: Cole Miller <m@cole-miller.net> Co-authored-by: Cole Miller <cole@zed.dev>
310 lines
10 KiB
Rust
310 lines
10 KiB
Rust
use anyhow::{Context as _, Result};
|
|
use collections::FxHashMap;
|
|
use gpui::SharedString;
|
|
use schemars::JsonSchema;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::net::Ipv4Addr;
|
|
use std::path::PathBuf;
|
|
|
|
use crate::{TaskTemplate, adapter_schema::AdapterSchemas};
|
|
|
|
/// Represents the host information of the debug adapter
|
|
#[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
|
pub struct TcpArgumentsTemplate {
|
|
/// 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 TcpArgumentsTemplate {
|
|
/// 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))
|
|
}
|
|
|
|
pub fn from_proto(proto: proto::TcpHost) -> Result<Self> {
|
|
Ok(Self {
|
|
port: proto.port.map(|p| p.try_into()).transpose()?,
|
|
host: proto.host.map(|h| h.parse()).transpose()?,
|
|
timeout: proto.timeout,
|
|
})
|
|
}
|
|
|
|
pub fn to_proto(&self) -> proto::TcpHost {
|
|
proto::TcpHost {
|
|
port: self.port.map(|p| p.into()),
|
|
host: self.host.map(|h| h.to_string()),
|
|
timeout: self.timeout,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Represents the attach request information of the debug adapter
|
|
#[derive(Default, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
|
pub struct AttachRequest {
|
|
/// The processId to attach to, if left empty we will show a process picker
|
|
pub process_id: Option<u32>,
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for AttachRequest {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
#[derive(Deserialize)]
|
|
struct Helper {
|
|
process_id: Option<u32>,
|
|
}
|
|
|
|
let helper = Helper::deserialize(deserializer)?;
|
|
|
|
// Skip creating an AttachRequest if process_id is None
|
|
if helper.process_id.is_none() {
|
|
return Err(serde::de::Error::custom("process_id is required"));
|
|
}
|
|
|
|
Ok(AttachRequest {
|
|
process_id: helper.process_id,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Represents the launch request information of the debug adapter
|
|
#[derive(Deserialize, Serialize, Default, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
|
pub struct LaunchRequest {
|
|
/// The program that you trying to debug
|
|
pub program: String,
|
|
/// The current working directory of your project
|
|
#[serde(default)]
|
|
pub cwd: Option<PathBuf>,
|
|
/// Arguments to pass to a debuggee
|
|
#[serde(default)]
|
|
pub args: Vec<String>,
|
|
#[serde(default)]
|
|
pub env: FxHashMap<String, String>,
|
|
}
|
|
|
|
impl LaunchRequest {
|
|
pub fn env_json(&self) -> serde_json::Value {
|
|
serde_json::Value::Object(
|
|
self.env
|
|
.iter()
|
|
.map(|(k, v)| (k.clone(), v.to_owned().into()))
|
|
.collect::<serde_json::Map<String, serde_json::Value>>(),
|
|
)
|
|
}
|
|
}
|
|
|
|
/// 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", tag = "request")]
|
|
pub enum DebugRequest {
|
|
/// Call the `launch` request on the debug adapter
|
|
Launch(LaunchRequest),
|
|
/// Call the `attach` request on the debug adapter
|
|
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.context("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)
|
|
}
|
|
}
|
|
|
|
impl From<AttachRequest> for DebugRequest {
|
|
fn from(attach_config: AttachRequest) -> Self {
|
|
DebugRequest::Attach(attach_config)
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
|
|
#[serde(untagged)]
|
|
pub enum BuildTaskDefinition {
|
|
ByName(SharedString),
|
|
Template {
|
|
#[serde(flatten)]
|
|
task_template: TaskTemplate,
|
|
#[serde(skip)]
|
|
locator_name: Option<SharedString>,
|
|
},
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, JsonSchema)]
|
|
pub enum Request {
|
|
Launch,
|
|
Attach,
|
|
}
|
|
|
|
/// This struct represent a user created debug task from the new session modal
|
|
#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, JsonSchema)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub struct ZedDebugConfig {
|
|
/// Name of the debug task
|
|
pub label: SharedString,
|
|
/// The debug adapter to use
|
|
pub adapter: SharedString,
|
|
#[serde(flatten)]
|
|
pub request: DebugRequest,
|
|
/// Whether to tell the debug adapter to stop on entry
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub stop_on_entry: Option<bool>,
|
|
}
|
|
|
|
/// This struct represent a user created debug task
|
|
#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Debug, JsonSchema)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub struct DebugScenario {
|
|
pub adapter: SharedString,
|
|
/// Name of the debug task
|
|
pub label: SharedString,
|
|
/// A task to run prior to spawning the debuggee.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub build: Option<BuildTaskDefinition>,
|
|
/// The main arguments to be sent to the debug adapter
|
|
#[serde(default, flatten)]
|
|
pub config: 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, skip_serializing_if = "Option::is_none")]
|
|
pub tcp_connection: Option<TcpArgumentsTemplate>,
|
|
}
|
|
|
|
/// 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<DebugScenario>);
|
|
|
|
impl DebugTaskFile {
|
|
/// Generates JSON schema of Tasks JSON template format.
|
|
pub fn generate_json_schema(schemas: &AdapterSchemas) -> serde_json_lenient::Value {
|
|
schemas.generate_json_schema().unwrap_or_default()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::DebugScenario;
|
|
use serde_json::json;
|
|
|
|
#[test]
|
|
fn test_empty_scenario_has_none_request() {
|
|
let json = r#"{
|
|
"label": "Build & debug rust",
|
|
"build": "rust",
|
|
"adapter": "CodeLLDB"
|
|
}"#;
|
|
|
|
let deserialized: DebugScenario = serde_json::from_str(json).unwrap();
|
|
|
|
assert_eq!(json!({}), deserialized.config);
|
|
assert_eq!("CodeLLDB", deserialized.adapter.as_ref());
|
|
assert_eq!("Build & debug rust", deserialized.label.as_ref());
|
|
}
|
|
|
|
#[test]
|
|
fn test_launch_scenario_deserialization() {
|
|
let json = r#"{
|
|
"label": "Launch program",
|
|
"adapter": "CodeLLDB",
|
|
"request": "launch",
|
|
"program": "target/debug/myapp",
|
|
"args": ["--test"]
|
|
}"#;
|
|
|
|
let deserialized: DebugScenario = serde_json::from_str(json).unwrap();
|
|
|
|
assert_eq!(
|
|
json!({ "request": "launch", "program": "target/debug/myapp", "args": ["--test"] }),
|
|
deserialized.config
|
|
);
|
|
assert_eq!("CodeLLDB", deserialized.adapter.as_ref());
|
|
assert_eq!("Launch program", deserialized.label.as_ref());
|
|
}
|
|
|
|
#[test]
|
|
fn test_attach_scenario_deserialization() {
|
|
let json = r#"{
|
|
"label": "Attach to process",
|
|
"adapter": "CodeLLDB",
|
|
"process_id": 1234,
|
|
"request": "attach"
|
|
}"#;
|
|
|
|
let deserialized: DebugScenario = serde_json::from_str(json).unwrap();
|
|
|
|
assert_eq!(
|
|
json!({ "request": "attach", "process_id": 1234 }),
|
|
deserialized.config
|
|
);
|
|
assert_eq!("CodeLLDB", deserialized.adapter.as_ref());
|
|
assert_eq!("Attach to process", deserialized.label.as_ref());
|
|
}
|
|
}
|