aboutsummaryrefslogtreecommitdiff
path: root/helix-dap
diff options
context:
space:
mode:
Diffstat (limited to 'helix-dap')
-rw-r--r--helix-dap/Cargo.toml24
-rw-r--r--helix-dap/src/client.rs416
-rw-r--r--helix-dap/src/lib.rs24
-rw-r--r--helix-dap/src/transport.rs279
-rw-r--r--helix-dap/src/types.rs678
5 files changed, 1421 insertions, 0 deletions
diff --git a/helix-dap/Cargo.toml b/helix-dap/Cargo.toml
new file mode 100644
index 00000000..42dd29a8
--- /dev/null
+++ b/helix-dap/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "helix-dap"
+version = "0.5.0"
+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
+edition = "2018"
+license = "MPL-2.0"
+description = "DAP client implementation for Helix project"
+categories = ["editor"]
+repository = "https://github.com/helix-editor/helix"
+homepage = "https://helix-editor.com"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+helix-core = { version = "0.5", path = "../helix-core" }
+anyhow = "1.0"
+log = "0.4"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+thiserror = "1.0"
+tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "net", "sync"] }
+
+[dev-dependencies]
+fern = "0.6"
diff --git a/helix-dap/src/client.rs b/helix-dap/src/client.rs
new file mode 100644
index 00000000..5e6d8d53
--- /dev/null
+++ b/helix-dap/src/client.rs
@@ -0,0 +1,416 @@
+use crate::{
+ transport::{Payload, Request, Transport},
+ types::*,
+ Error, Result, ThreadId,
+};
+use helix_core::syntax::DebuggerQuirks;
+
+use anyhow::anyhow;
+pub use log::{error, info};
+use std::{
+ collections::HashMap,
+ net::{IpAddr, Ipv4Addr, SocketAddr},
+ path::PathBuf,
+ process::Stdio,
+ sync::atomic::{AtomicU64, Ordering},
+};
+use tokio::{
+ io::{AsyncBufRead, AsyncWrite, BufReader, BufWriter},
+ net::TcpStream,
+ process::{Child, Command},
+ sync::mpsc::{channel, unbounded_channel, UnboundedReceiver, UnboundedSender},
+ time,
+};
+
+#[derive(Debug)]
+pub struct Client {
+ id: usize,
+ _process: Option<Child>,
+ server_tx: UnboundedSender<Request>,
+ request_counter: AtomicU64,
+ pub caps: Option<DebuggerCapabilities>,
+ // thread_id -> frames
+ pub stack_frames: HashMap<ThreadId, Vec<StackFrame>>,
+ pub thread_states: HashMap<ThreadId, String>,
+ pub thread_id: Option<ThreadId>,
+ /// Currently active frame for the current thread.
+ pub active_frame: Option<usize>,
+ pub breakpoints: Vec<Breakpoint>,
+ pub quirks: DebuggerQuirks,
+}
+
+impl Client {
+ // Spawn a process and communicate with it by either TCP or stdio
+ pub async fn process(
+ transport: &str,
+ command: &str,
+ args: Vec<&str>,
+ port_arg: Option<&str>,
+ id: usize,
+ ) -> Result<(Self, UnboundedReceiver<Payload>)> {
+ if command.is_empty() {
+ return Result::Err(Error::Other(anyhow!("Command not provided")));
+ }
+ if transport == "tcp" && port_arg.is_some() {
+ Self::tcp_process(command, args, port_arg.unwrap(), id).await
+ } else if transport == "stdio" {
+ Self::stdio(command, args, id)
+ } else {
+ Result::Err(Error::Other(anyhow!("Incorrect transport {}", transport)))
+ }
+ }
+
+ pub fn streams(
+ rx: Box<dyn AsyncBufRead + Unpin + Send>,
+ tx: Box<dyn AsyncWrite + Unpin + Send>,
+ err: Option<Box<dyn AsyncBufRead + Unpin + Send>>,
+ id: usize,
+ process: Option<Child>,
+ ) -> Result<(Self, UnboundedReceiver<Payload>)> {
+ let (server_rx, server_tx) = Transport::start(rx, tx, err, id);
+ let (client_rx, client_tx) = unbounded_channel();
+
+ let client = Self {
+ id,
+ _process: process,
+ server_tx,
+ request_counter: AtomicU64::new(0),
+ caps: None,
+ //
+ stack_frames: HashMap::new(),
+ thread_states: HashMap::new(),
+ thread_id: None,
+ active_frame: None,
+ breakpoints: vec![],
+ quirks: DebuggerQuirks::default(),
+ };
+
+ tokio::spawn(Self::recv(server_rx, client_rx));
+
+ Ok((client, client_tx))
+ }
+
+ pub async fn tcp(
+ addr: std::net::SocketAddr,
+ id: usize,
+ ) -> Result<(Self, UnboundedReceiver<Payload>)> {
+ let stream = TcpStream::connect(addr).await?;
+ let (rx, tx) = stream.into_split();
+ Self::streams(Box::new(BufReader::new(rx)), Box::new(tx), None, id, None)
+ }
+
+ pub fn stdio(
+ cmd: &str,
+ args: Vec<&str>,
+ id: usize,
+ ) -> Result<(Self, UnboundedReceiver<Payload>)> {
+ let process = Command::new(cmd)
+ .args(args)
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ // make sure the process is reaped on drop
+ .kill_on_drop(true)
+ .spawn();
+
+ let mut process = process?;
+
+ // TODO: do we need bufreader/writer here? or do we use async wrappers on unblock?
+ let writer = BufWriter::new(process.stdin.take().expect("Failed to open stdin"));
+ let reader = BufReader::new(process.stdout.take().expect("Failed to open stdout"));
+ let errors = process.stderr.take().map(BufReader::new);
+
+ Self::streams(
+ Box::new(BufReader::new(reader)),
+ Box::new(writer),
+ // errors.map(|errors| Box::new(BufReader::new(errors))),
+ match errors {
+ Some(errors) => Some(Box::new(BufReader::new(errors))),
+ None => None,
+ },
+ id,
+ Some(process),
+ )
+ }
+
+ async fn get_port() -> Option<u16> {
+ Some(
+ tokio::net::TcpListener::bind(SocketAddr::new(
+ IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
+ 0,
+ ))
+ .await
+ .ok()?
+ .local_addr()
+ .ok()?
+ .port(),
+ )
+ }
+
+ pub async fn tcp_process(
+ cmd: &str,
+ args: Vec<&str>,
+ port_format: &str,
+ id: usize,
+ ) -> Result<(Self, UnboundedReceiver<Payload>)> {
+ let port = Self::get_port().await.unwrap();
+
+ let process = Command::new(cmd)
+ .args(args)
+ .args(port_format.replace("{}", &port.to_string()).split(' '))
+ // silence messages
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .stderr(Stdio::null())
+ // Do not kill debug adapter when leaving, it should exit automatically
+ .spawn()?;
+
+ // Wait for adapter to become ready for connection
+ time::sleep(time::Duration::from_millis(500)).await;
+
+ let stream = TcpStream::connect(SocketAddr::new(
+ IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
+ port,
+ ))
+ .await?;
+
+ let (rx, tx) = stream.into_split();
+ Self::streams(
+ Box::new(BufReader::new(rx)),
+ Box::new(tx),
+ None,
+ id,
+ Some(process),
+ )
+ }
+
+ async fn recv(mut server_rx: UnboundedReceiver<Payload>, client_tx: UnboundedSender<Payload>) {
+ while let Some(msg) = server_rx.recv().await {
+ match msg {
+ Payload::Event(ev) => {
+ client_tx.send(Payload::Event(ev)).expect("Failed to send");
+ }
+ Payload::Response(_) => unreachable!(),
+ Payload::Request(req) => {
+ client_tx
+ .send(Payload::Request(req))
+ .expect("Failed to send");
+ }
+ }
+ }
+ }
+
+ pub fn id(&self) -> usize {
+ self.id
+ }
+
+ fn next_request_id(&self) -> u64 {
+ self.request_counter.fetch_add(1, Ordering::Relaxed)
+ }
+
+ async fn request<R: crate::types::Request>(
+ &self,
+ arguments: R::Arguments,
+ ) -> Result<R::Result> {
+ let (callback_tx, mut callback_rx) = channel(1);
+
+ let arguments = Some(serde_json::to_value(arguments)?);
+
+ let req = Request {
+ back_ch: Some(callback_tx),
+ seq: self.next_request_id(),
+ command: R::COMMAND.to_string(),
+ arguments,
+ };
+
+ self.server_tx
+ .send(req)
+ .expect("Failed to send request to debugger");
+
+ let response = callback_rx.recv().await.unwrap()?;
+ let response = serde_json::from_value(response.body.unwrap_or_default())?;
+ Ok(response)
+ }
+
+ pub fn capabilities(&self) -> &DebuggerCapabilities {
+ self.caps.as_ref().expect("debugger not yet initialized!")
+ }
+
+ pub async fn initialize(&mut self, adapter_id: String) -> Result<()> {
+ let args = requests::InitializeArguments {
+ client_id: Some("hx".to_owned()),
+ client_name: Some("helix".to_owned()),
+ adapter_id,
+ locale: Some("en-us".to_owned()),
+ lines_start_at_one: Some(true),
+ columns_start_at_one: Some(true),
+ path_format: Some("path".to_owned()),
+ supports_variable_type: Some(true),
+ supports_variable_paging: Some(false),
+ supports_run_in_terminal_request: Some(false),
+ supports_memory_references: Some(false),
+ supports_progress_reporting: Some(false),
+ supports_invalidated_event: Some(false),
+ };
+
+ let response = self.request::<requests::Initialize>(args).await?;
+ self.caps = Some(response);
+
+ Ok(())
+ }
+
+ pub async fn disconnect(&mut self) -> Result<()> {
+ self.request::<requests::Disconnect>(()).await
+ }
+
+ pub async fn launch(&mut self, args: serde_json::Value) -> Result<()> {
+ let response = self.request::<requests::Launch>(args).await?;
+ log::error!("launch response {}", response);
+
+ Ok(())
+ }
+
+ pub async fn attach(&mut self, args: serde_json::Value) -> Result<()> {
+ let response = self.request::<requests::Attach>(args).await?;
+ log::error!("attach response {}", response);
+
+ Ok(())
+ }
+
+ pub async fn set_breakpoints(
+ &mut self,
+ file: PathBuf,
+ breakpoints: Vec<SourceBreakpoint>,
+ ) -> Result<Option<Vec<Breakpoint>>> {
+ let args = requests::SetBreakpointsArguments {
+ source: Source {
+ path: Some(file),
+ name: None,
+ source_reference: None,
+ presentation_hint: None,
+ origin: None,
+ sources: None,
+ adapter_data: None,
+ checksums: None,
+ },
+ breakpoints: Some(breakpoints),
+ source_modified: Some(false),
+ };
+
+ let response = self.request::<requests::SetBreakpoints>(args).await?;
+
+ Ok(response.breakpoints)
+ }
+
+ pub async fn configuration_done(&mut self) -> Result<()> {
+ self.request::<requests::ConfigurationDone>(()).await
+ }
+
+ pub async fn continue_thread(&mut self, thread_id: ThreadId) -> Result<Option<bool>> {
+ let args = requests::ContinueArguments { thread_id };
+
+ let response = self.request::<requests::Continue>(args).await?;
+ Ok(response.all_threads_continued)
+ }
+
+ pub async fn stack_trace(
+ &mut self,
+ thread_id: ThreadId,
+ ) -> Result<(Vec<StackFrame>, Option<usize>)> {
+ let args = requests::StackTraceArguments {
+ thread_id,
+ start_frame: None,
+ levels: None,
+ format: None,
+ };
+
+ let response = self.request::<requests::StackTrace>(args).await?;
+ Ok((response.stack_frames, response.total_frames))
+ }
+
+ pub async fn threads(&mut self) -> Result<Vec<Thread>> {
+ let response = self.request::<requests::Threads>(()).await?;
+ Ok(response.threads)
+ }
+
+ pub async fn scopes(&mut self, frame_id: usize) -> Result<Vec<Scope>> {
+ let args = requests::ScopesArguments { frame_id };
+
+ let response = self.request::<requests::Scopes>(args).await?;
+ Ok(response.scopes)
+ }
+
+ pub async fn variables(&mut self, variables_reference: usize) -> Result<Vec<Variable>> {
+ let args = requests::VariablesArguments {
+ variables_reference,
+ filter: None,
+ start: None,
+ count: None,
+ format: None,
+ };
+
+ let response = self.request::<requests::Variables>(args).await?;
+ Ok(response.variables)
+ }
+
+ pub async fn step_in(&mut self, thread_id: ThreadId) -> Result<()> {
+ let args = requests::StepInArguments {
+ thread_id,
+ target_id: None,
+ granularity: None,
+ };
+
+ self.request::<requests::StepIn>(args).await
+ }
+
+ pub async fn step_out(&mut self, thread_id: ThreadId) -> Result<()> {
+ let args = requests::StepOutArguments {
+ thread_id,
+ granularity: None,
+ };
+
+ self.request::<requests::StepOut>(args).await
+ }
+
+ pub async fn next(&mut self, thread_id: ThreadId) -> Result<()> {
+ let args = requests::NextArguments {
+ thread_id,
+ granularity: None,
+ };
+
+ self.request::<requests::Next>(args).await
+ }
+
+ pub async fn pause(&mut self, thread_id: ThreadId) -> Result<()> {
+ let args = requests::PauseArguments { thread_id };
+
+ self.request::<requests::Pause>(args).await
+ }
+
+ pub async fn eval(
+ &mut self,
+ expression: String,
+ frame_id: Option<usize>,
+ ) -> Result<requests::EvaluateResponse> {
+ let args = requests::EvaluateArguments {
+ expression,
+ frame_id,
+ context: None,
+ format: None,
+ };
+
+ self.request::<requests::Evaluate>(args).await
+ }
+
+ pub async fn set_exception_breakpoints(
+ &mut self,
+ filters: Vec<String>,
+ ) -> Result<Option<Vec<Breakpoint>>> {
+ let args = requests::SetExceptionBreakpointsArguments { filters };
+
+ let response = self
+ .request::<requests::SetExceptionBreakpoints>(args)
+ .await;
+
+ Ok(response.ok().map(|r| r.breakpoints).unwrap_or_default())
+ }
+}
diff --git a/helix-dap/src/lib.rs b/helix-dap/src/lib.rs
new file mode 100644
index 00000000..f60b102c
--- /dev/null
+++ b/helix-dap/src/lib.rs
@@ -0,0 +1,24 @@
+mod client;
+mod transport;
+mod types;
+
+pub use client::Client;
+pub use events::Event;
+pub use transport::{Payload, Response, Transport};
+pub use types::*;
+
+use thiserror::Error;
+#[derive(Error, Debug)]
+pub enum Error {
+ #[error("failed to parse: {0}")]
+ Parse(#[from] serde_json::Error),
+ #[error("IO Error: {0}")]
+ IO(#[from] std::io::Error),
+ #[error("request timed out")]
+ Timeout,
+ #[error("server closed the stream")]
+ StreamClosed,
+ #[error(transparent)]
+ Other(#[from] anyhow::Error),
+}
+pub type Result<T> = core::result::Result<T, Error>;
diff --git a/helix-dap/src/transport.rs b/helix-dap/src/transport.rs
new file mode 100644
index 00000000..3f4eec43
--- /dev/null
+++ b/helix-dap/src/transport.rs
@@ -0,0 +1,279 @@
+use crate::{Error, Event, Result};
+use anyhow::Context;
+use log::{error, info, warn};
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+use std::collections::HashMap;
+use std::sync::Arc;
+use tokio::{
+ io::{AsyncBufRead, AsyncBufReadExt, AsyncReadExt, AsyncWrite, AsyncWriteExt},
+ sync::{
+ mpsc::{unbounded_channel, Sender, UnboundedReceiver, UnboundedSender},
+ Mutex,
+ },
+};
+
+#[derive(Debug, Clone, Deserialize, Serialize)]
+pub struct Request {
+ #[serde(skip)]
+ pub back_ch: Option<Sender<Result<Response>>>,
+ pub seq: u64,
+ pub command: String,
+ pub arguments: Option<Value>,
+}
+
+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+pub struct Response {
+ // seq is omitted as unused and is not sent by some implementations
+ pub request_seq: u64,
+ pub success: bool,
+ pub command: String,
+ pub message: Option<String>,
+ pub body: Option<Value>,
+}
+
+#[derive(Debug, Clone, Deserialize, Serialize)]
+#[serde(tag = "type", rename_all = "camelCase")]
+pub enum Payload {
+ // type = "event"
+ Event(Event),
+ // type = "response"
+ Response(Response),
+ // type = "request"
+ Request(Request),
+}
+
+#[derive(Debug)]
+pub struct Transport {
+ id: usize,
+ pending_requests: Mutex<HashMap<u64, Sender<Result<Response>>>>,
+}
+
+impl Transport {
+ pub fn start(
+ server_stdout: Box<dyn AsyncBufRead + Unpin + Send>,
+ server_stdin: Box<dyn AsyncWrite + Unpin + Send>,
+ server_stderr: Option<Box<dyn AsyncBufRead + Unpin + Send>>,
+ id: usize,
+ ) -> (UnboundedReceiver<Payload>, UnboundedSender<Request>) {
+ let (client_tx, rx) = unbounded_channel();
+ let (tx, client_rx) = unbounded_channel();
+
+ let transport = Self {
+ id,
+ pending_requests: Mutex::new(HashMap::default()),
+ };
+
+ let transport = Arc::new(transport);
+
+ tokio::spawn(Self::recv(transport.clone(), server_stdout, client_tx));
+ tokio::spawn(Self::send(transport, server_stdin, client_rx));
+ if let Some(stderr) = server_stderr {
+ tokio::spawn(Self::err(stderr));
+ }
+
+ (rx, tx)
+ }
+
+ async fn recv_server_message(
+ reader: &mut Box<dyn AsyncBufRead + Unpin + Send>,
+ buffer: &mut String,
+ ) -> Result<Payload> {
+ let mut content_length = None;
+ loop {
+ buffer.truncate(0);
+ if reader.read_line(buffer).await? == 0 {
+ return Err(Error::StreamClosed);
+ };
+
+ if buffer == "\r\n" {
+ // look for an empty CRLF line
+ break;
+ }
+
+ let header = buffer.trim();
+ let parts = header.split_once(": ");
+
+ match parts {
+ Some(("Content-Length", value)) => {
+ content_length = Some(value.parse().context("invalid content length")?);
+ }
+ Some((_, _)) => {}
+ None => {
+ // Workaround: Some non-conformant language servers will output logging and other garbage
+ // into the same stream as JSON-RPC messages. This can also happen from shell scripts that spawn
+ // the server. Skip such lines and log a warning.
+
+ // warn!("Failed to parse header: {:?}", header);
+ }
+ }
+ }
+
+ let content_length = content_length.context("missing content length")?;
+
+ //TODO: reuse vector
+ let mut content = vec![0; content_length];
+ reader.read_exact(&mut content).await?;
+ let msg = std::str::from_utf8(&content).context("invalid utf8 from server")?;
+
+ info!("<- DAP {}", msg);
+
+ // try parsing as output (server response) or call (server request)
+ let output: serde_json::Result<Payload> = serde_json::from_str(msg);
+
+ Ok(output?)
+ }
+
+ async fn recv_server_error(
+ err: &mut (impl AsyncBufRead + Unpin + Send),
+ buffer: &mut String,
+ ) -> Result<()> {
+ buffer.truncate(0);
+ if err.read_line(buffer).await? == 0 {
+ return Err(Error::StreamClosed);
+ };
+ error!("err <- {}", buffer);
+
+ Ok(())
+ }
+
+ async fn send_payload_to_server(
+ &self,
+ server_stdin: &mut Box<dyn AsyncWrite + Unpin + Send>,
+ mut req: Request,
+ ) -> Result<()> {
+ let back_ch = req.back_ch.take();
+ let seq = req.seq;
+ let json = serde_json::to_string(&Payload::Request(req))?;
+ if let Some(back) = back_ch {
+ self.pending_requests.lock().await.insert(seq, back);
+ }
+ self.send_string_to_server(server_stdin, json).await
+ }
+
+ async fn send_string_to_server(
+ &self,
+ server_stdin: &mut Box<dyn AsyncWrite + Unpin + Send>,
+ request: String,
+ ) -> Result<()> {
+ info!("-> DAP {}", request);
+
+ // send the headers
+ server_stdin
+ .write_all(format!("Content-Length: {}\r\n\r\n", request.len()).as_bytes())
+ .await?;
+
+ // send the body
+ server_stdin.write_all(request.as_bytes()).await?;
+
+ server_stdin.flush().await?;
+
+ Ok(())
+ }
+
+ fn process_response(res: Response) -> Result<Response> {
+ if res.success {
+ info!("<- DAP success in response to {}", res.request_seq);
+
+ Ok(res)
+ } else {
+ error!(
+ "<- DAP error {:?} ({:?}) for command #{} {}",
+ res.message, res.body, res.request_seq, res.command
+ );
+
+ Err(Error::Other(anyhow::format_err!("{:?}", res.body)))
+ }
+ }
+
+ async fn process_server_message(
+ &self,
+ client_tx: &UnboundedSender<Payload>,
+ msg: Payload,
+ ) -> Result<()> {
+ match msg {
+ Payload::Response(res) => {
+ let request_seq = res.request_seq;
+ let tx = self.pending_requests.lock().await.remove(&request_seq);
+
+ match tx {
+ Some(tx) => match tx.send(Self::process_response(res)).await {
+ Ok(_) => (),
+ Err(_) => error!(
+ "Tried sending response into a closed channel (id={:?}), original request likely timed out",
+ request_seq
+ ),
+ }
+ None => {
+ warn!("Response to nonexistent request #{}", res.request_seq);
+ client_tx.send(Payload::Response(res)).expect("Failed to send");
+ }
+ }
+
+ Ok(())
+ }
+ Payload::Request(Request {
+ ref command,
+ ref seq,
+ ..
+ }) => {
+ info!("<- DAP request {} #{}", command, seq);
+ client_tx.send(msg).expect("Failed to send");
+ Ok(())
+ }
+ Payload::Event(ref event) => {
+ info!("<- DAP event {:?}", event);
+ client_tx.send(msg).expect("Failed to send");
+ Ok(())
+ }
+ }
+ }
+
+ async fn recv(
+ transport: Arc<Self>,
+ mut server_stdout: Box<dyn AsyncBufRead + Unpin + Send>,
+ client_tx: UnboundedSender<Payload>,
+ ) {
+ let mut recv_buffer = String::new();
+ loop {
+ match Self::recv_server_message(&mut server_stdout, &mut recv_buffer).await {
+ Ok(msg) => {
+ transport
+ .process_server_message(&client_tx, msg)
+ .await
+ .unwrap();
+ }
+ Err(err) => {
+ error!("err: <- {:?}", err);
+ break;
+ }
+ }
+ }
+ }
+
+ async fn send(
+ transport: Arc<Self>,
+ mut server_stdin: Box<dyn AsyncWrite + Unpin + Send>,
+ mut client_rx: UnboundedReceiver<Request>,
+ ) {
+ while let Some(req) = client_rx.recv().await {
+ transport
+ .send_payload_to_server(&mut server_stdin, req)
+ .await
+ .unwrap()
+ }
+ }
+
+ async fn err(mut server_stderr: Box<dyn AsyncBufRead + Unpin + Send>) {
+ let mut recv_buffer = String::new();
+ loop {
+ match Self::recv_server_error(&mut server_stderr, &mut recv_buffer).await {
+ Ok(_) => {}
+ Err(err) => {
+ error!("err: <- {:?}", err);
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/helix-dap/src/types.rs b/helix-dap/src/types.rs
new file mode 100644
index 00000000..453fa558
--- /dev/null
+++ b/helix-dap/src/types.rs
@@ -0,0 +1,678 @@
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+use std::path::PathBuf;
+
+#[derive(
+ Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize,
+)]
+pub struct ThreadId(isize);
+
+impl std::fmt::Display for ThreadId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+pub trait Request {
+ type Arguments: serde::de::DeserializeOwned + serde::Serialize;
+ type Result: serde::de::DeserializeOwned + serde::Serialize;
+ const COMMAND: &'static str;
+}
+
+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ColumnDescriptor {
+ pub attribute_name: String,
+ pub label: String,
+ pub format: Option<String>,
+ #[serde(rename = "type")]
+ pub ty: Option<String>,
+ pub width: Option<usize>,
+}
+
+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ExceptionBreakpointsFilter {
+ pub filter: String,
+ pub label: String,
+ pub description: Option<String>,
+ pub default: Option<bool>,
+ pub supports_condition: Option<bool>,
+ pub condition_description: Option<String>,
+}
+
+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct DebuggerCapabilities {
+ pub supports_configuration_done_request: Option<bool>,
+ pub supports_function_breakpoints: Option<bool>,
+ pub supports_conditional_breakpoints: Option<bool>,
+ pub supports_hit_conditional_breakpoints: Option<bool>,
+ pub supports_evaluate_for_hovers: Option<bool>,
+ pub supports_step_back: Option<bool>,
+ pub supports_set_variable: Option<bool>,
+ pub supports_restart_frame: Option<bool>,
+ pub supports_goto_targets_request: Option<bool>,
+ pub supports_step_in_targets_request: Option<bool>,
+ pub supports_completions_request: Option<bool>,
+ pub supports_modules_request: Option<bool>,
+ pub supports_restart_request: Option<bool>,
+ pub supports_exception_options: Option<bool>,
+ pub supports_value_formatting_options: Option<bool>,
+ pub supports_exception_info_request: Option<bool>,
+ pub support_terminate_debuggee: Option<bool>,
+ pub support_suspend_debuggee: Option<bool>,
+ pub supports_delayed_stack_trace_loading: Option<bool>,
+ pub supports_loaded_sources_request: Option<bool>,
+ pub supports_log_points: Option<bool>,
+ pub supports_terminate_threads_request: Option<bool>,
+ pub supports_set_expression: Option<bool>,
+ pub supports_terminate_request: Option<bool>,
+ pub supports_data_breakpoints: Option<bool>,
+ pub supports_read_memory_request: Option<bool>,
+ pub supports_write_memory_request: Option<bool>,
+ pub supports_disassemble_request: Option<bool>,
+ pub supports_cancel_request: Option<bool>,
+ pub supports_breakpoint_locations_request: Option<bool>,
+ pub supports_clipboard_context: Option<bool>,
+ pub supports_stepping_granularity: Option<bool>,
+ pub supports_instruction_breakpoints: Option<bool>,
+ pub supports_exception_filter_options: Option<bool>,
+ pub exception_breakpoint_filters: Option<Vec<ExceptionBreakpointsFilter>>,
+ pub completion_trigger_characters: Option<Vec<String>>,
+ pub additional_module_columns: Option<Vec<ColumnDescriptor>>,
+ pub supported_checksum_algorithms: Option<Vec<String>>,
+}
+
+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Checksum {
+ pub algorithm: String,
+ pub checksum: String,
+}
+
+#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Source {
+ pub name: Option<String>,
+ pub path: Option<PathBuf>,
+ pub source_reference: Option<usize>,
+ pub presentation_hint: Option<String>,
+ pub origin: Option<String>,
+ pub sources: Option<Vec<Source>>,
+ pub adapter_data: Option<Value>,
+ pub checksums: Option<Vec<Checksum>>,
+}
+
+#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SourceBreakpoint {
+ pub line: usize,
+ pub column: Option<usize>,
+ pub condition: Option<String>,
+ pub hit_condition: Option<String>,
+ pub log_message: Option<String>,
+}
+
+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Breakpoint {
+ pub id: Option<usize>,
+ pub verified: bool,
+ pub message: Option<String>,
+ pub source: Option<Source>,
+ pub line: Option<usize>,
+ pub column: Option<usize>,
+ pub end_line: Option<usize>,
+ pub end_column: Option<usize>,
+ pub instruction_reference: Option<String>,
+ pub offset: Option<usize>,
+}
+
+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct StackFrameFormat {
+ pub parameters: Option<bool>,
+ pub parameter_types: Option<bool>,
+ pub parameter_names: Option<bool>,
+ pub parameter_values: Option<bool>,
+ pub line: Option<bool>,
+ pub module: Option<bool>,
+ pub include_all: Option<bool>,
+}
+
+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct StackFrame {
+ pub id: usize,
+ pub name: String,
+ pub source: Option<Source>,
+ pub line: usize,
+ pub column: usize,
+ pub end_line: Option<usize>,
+ pub end_column: Option<usize>,
+ pub can_restart: Option<bool>,
+ pub instruction_pointer_reference: Option<String>,
+ pub module_id: Option<Value>,
+ pub presentation_hint: Option<String>,
+}
+
+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Thread {
+ pub id: ThreadId,
+ pub name: String,
+}
+
+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Scope {
+ pub name: String,
+ pub presentation_hint: Option<String>,
+ pub variables_reference: usize,
+ pub named_variables: Option<usize>,
+ pub indexed_variables: Option<usize>,
+ pub expensive: bool,
+ pub source: Option<Source>,
+ pub line: Option<usize>,
+ pub column: Option<usize>,
+ pub end_line: Option<usize>,
+ pub end_column: Option<usize>,
+}
+
+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ValueFormat {
+ pub hex: Option<bool>,
+}
+
+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct VariablePresentationHint {
+ pub kind: Option<String>,
+ pub attributes: Option<Vec<String>>,
+ pub visibility: Option<String>,
+}
+
+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Variable {
+ pub name: String,
+ pub value: String,
+ #[serde(rename = "type")]
+ pub ty: Option<String>,
+ pub presentation_hint: Option<VariablePresentationHint>,
+ pub evaluate_name: Option<String>,
+ pub variables_reference: usize,
+ pub named_variables: Option<usize>,
+ pub indexed_variables: Option<usize>,
+ pub memory_reference: Option<String>,
+}
+
+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Module {
+ pub id: String, // TODO: || number
+ pub name: String,
+ pub path: Option<PathBuf>,
+ pub is_optimized: Option<bool>,
+ pub is_user_code: Option<bool>,
+ pub version: Option<String>,
+ pub symbol_status: Option<String>,
+ pub symbol_file_path: Option<String>,
+ pub date_time_stamp: Option<String>,
+ pub address_range: Option<String>,
+}
+
+pub mod requests {
+ use super::*;
+ #[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct InitializeArguments {
+ #[serde(rename = "clientID")]
+ pub client_id: Option<String>,
+ pub client_name: Option<String>,
+ #[serde(rename = "adapterID")]
+ pub adapter_id: String,
+ pub locale: Option<String>,
+ #[serde(rename = "linesStartAt1")]
+ pub lines_start_at_one: Option<bool>,
+ #[serde(rename = "columnsStartAt1")]
+ pub columns_start_at_one: Option<bool>,
+ pub path_format: Option<String>,
+ pub supports_variable_type: Option<bool>,
+ pub supports_variable_paging: Option<bool>,
+ pub supports_run_in_terminal_request: Option<bool>,
+ pub supports_memory_references: Option<bool>,
+ pub supports_progress_reporting: Option<bool>,
+ pub supports_invalidated_event: Option<bool>,
+ }
+
+ #[derive(Debug)]
+ pub enum Initialize {}
+
+ impl Request for Initialize {
+ type Arguments = InitializeArguments;
+ type Result = DebuggerCapabilities;
+ const COMMAND: &'static str = "initialize";
+ }
+
+ #[derive(Debug)]
+ pub enum Launch {}
+
+ impl Request for Launch {
+ type Arguments = Value;
+ type Result = Value;
+ const COMMAND: &'static str = "launch";
+ }
+
+ #[derive(Debug)]
+ pub enum Attach {}
+
+ impl Request for Attach {
+ type Arguments = Value;
+ type Result = Value;
+ const COMMAND: &'static str = "attach";
+ }
+
+ #[derive(Debug)]
+ pub enum Disconnect {}
+
+ impl Request for Disconnect {
+ type Arguments = ();
+ type Result = ();
+ const COMMAND: &'static str = "disconnect";
+ }
+
+ #[derive(Debug)]
+ pub enum ConfigurationDone {}
+
+ impl Request for ConfigurationDone {
+ type Arguments = ();
+ type Result = ();
+ const COMMAND: &'static str = "configurationDone";
+ }
+
+ #[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct SetBreakpointsArguments {
+ pub source: Source,
+ pub breakpoints: Option<Vec<SourceBreakpoint>>,
+ // lines is deprecated
+ pub source_modified: Option<bool>,
+ }
+
+ #[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct SetBreakpointsResponse {
+ pub breakpoints: Option<Vec<Breakpoint>>,
+ }
+
+ #[derive(Debug)]
+ pub enum SetBreakpoints {}
+
+ impl Request for SetBreakpoints {
+ type Arguments = SetBreakpointsArguments;
+ type Result = SetBreakpointsResponse;
+ const COMMAND: &'static str = "setBreakpoints";
+ }
+
+ #[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct ContinueArguments {
+ pub thread_id: ThreadId,
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct ContinueResponse {
+ pub all_threads_continued: Option<bool>,
+ }
+
+ #[derive(Debug)]
+ pub enum Continue {}
+
+ impl Request for Continue {
+ type Arguments = ContinueArguments;
+ type Result = ContinueResponse;
+ const COMMAND: &'static str = "continue";
+ }
+
+ #[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct StackTraceArguments {
+ pub thread_id: ThreadId,
+ pub start_frame: Option<usize>,
+ pub levels: Option<usize>,
+ pub format: Option<StackFrameFormat>,
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct StackTraceResponse {
+ pub total_frames: Option<usize>,
+ pub stack_frames: Vec<StackFrame>,
+ }
+
+ #[derive(Debug)]
+ pub enum StackTrace {}
+
+ impl Request for StackTrace {
+ type Arguments = StackTraceArguments;
+ type Result = StackTraceResponse;
+ const COMMAND: &'static str = "stackTrace";
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct ThreadsResponse {
+ pub threads: Vec<Thread>,
+ }
+
+ #[derive(Debug)]
+ pub enum Threads {}
+
+ impl Request for Threads {
+ type Arguments = ();
+ type Result = ThreadsResponse;
+ const COMMAND: &'static str = "threads";
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct ScopesArguments {
+ pub frame_id: usize,
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct ScopesResponse {
+ pub scopes: Vec<Scope>,
+ }
+
+ #[derive(Debug)]
+ pub enum Scopes {}
+
+ impl Request for Scopes {
+ type Arguments = ScopesArguments;
+ type Result = ScopesResponse;
+ const COMMAND: &'static str = "scopes";
+ }
+
+ #[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct VariablesArguments {
+ pub variables_reference: usize,
+ pub filter: Option<String>,
+ pub start: Option<usize>,
+ pub count: Option<usize>,
+ pub format: Option<ValueFormat>,
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct VariablesResponse {
+ pub variables: Vec<Variable>,
+ }
+
+ #[derive(Debug)]
+ pub enum Variables {}
+
+ impl Request for Variables {
+ type Arguments = VariablesArguments;
+ type Result = VariablesResponse;
+ const COMMAND: &'static str = "variables";
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct StepInArguments {
+ pub thread_id: ThreadId,
+ pub target_id: Option<usize>,
+ pub granularity: Option<String>,
+ }
+
+ #[derive(Debug)]
+ pub enum StepIn {}
+
+ impl Request for StepIn {
+ type Arguments = StepInArguments;
+ type Result = ();
+ const COMMAND: &'static str = "stepIn";
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct StepOutArguments {
+ pub thread_id: ThreadId,
+ pub granularity: Option<String>,
+ }
+
+ #[derive(Debug)]
+ pub enum StepOut {}
+
+ impl Request for StepOut {
+ type Arguments = StepOutArguments;
+ type Result = ();
+ const COMMAND: &'static str = "stepOut";
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct NextArguments {
+ pub thread_id: ThreadId,
+ pub granularity: Option<String>,
+ }
+
+ #[derive(Debug)]
+ pub enum Next {}
+
+ impl Request for Next {
+ type Arguments = NextArguments;
+ type Result = ();
+ const COMMAND: &'static str = "next";
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct PauseArguments {
+ pub thread_id: ThreadId,
+ }
+
+ #[derive(Debug)]
+ pub enum Pause {}
+
+ impl Request for Pause {
+ type Arguments = PauseArguments;
+ type Result = ();
+ const COMMAND: &'static str = "pause";
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct EvaluateArguments {
+ pub expression: String,
+ pub frame_id: Option<usize>,
+ pub context: Option<String>,
+ pub format: Option<ValueFormat>,
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct EvaluateResponse {
+ pub result: String,
+ #[serde(rename = "type")]
+ pub ty: Option<String>,
+ pub presentation_hint: Option<VariablePresentationHint>,
+ pub variables_reference: usize,
+ pub named_variables: Option<usize>,
+ pub indexed_variables: Option<usize>,
+ pub memory_reference: Option<String>,
+ }
+
+ #[derive(Debug)]
+ pub enum Evaluate {}
+
+ impl Request for Evaluate {
+ type Arguments = EvaluateArguments;
+ type Result = EvaluateResponse;
+ const COMMAND: &'static str = "evaluate";
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct SetExceptionBreakpointsArguments {
+ pub filters: Vec<String>,
+ // pub filterOptions: Option<Vec<ExceptionFilterOptions>>, // needs capability
+ // pub exceptionOptions: Option<Vec<ExceptionOptions>>, // needs capability
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct SetExceptionBreakpointsResponse {
+ pub breakpoints: Option<Vec<Breakpoint>>,
+ }
+
+ #[derive(Debug)]
+ pub enum SetExceptionBreakpoints {}
+
+ impl Request for SetExceptionBreakpoints {
+ type Arguments = SetExceptionBreakpointsArguments;
+ type Result = SetExceptionBreakpointsResponse;
+ const COMMAND: &'static str = "setExceptionBreakpoints";
+ }
+}
+
+// Events
+
+pub mod events {
+ use super::*;
+
+ #[derive(Debug, Clone, Serialize, Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ #[serde(tag = "event", content = "body")]
+ // seq is omitted as unused and is not sent by some implementations
+ pub enum Event {
+ Initialized,
+ Stopped(Stopped),
+ Continued(Continued),
+ Exited(Exited),
+ Terminated(Option<Terminated>),
+ Thread(Thread),
+ Output(Output),
+ Breakpoint(Breakpoint),
+ Module(Module),
+ LoadedSource(LoadedSource),
+ Process(Process),
+ Capabilities(Capabilities),
+ // ProgressStart(),
+ // ProgressUpdate(),
+ // ProgressEnd(),
+ // Invalidated(),
+ Memory(Memory),
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct Stopped {
+ pub reason: String,
+ pub description: Option<String>,
+ pub thread_id: Option<ThreadId>,
+ pub preserve_focus_hint: Option<bool>,
+ pub text: Option<String>,
+ pub all_threads_stopped: Option<bool>,
+ pub hit_breakpoint_ids: Option<Vec<usize>>,
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct Continued {
+ pub thread_id: ThreadId,
+ pub all_threads_continued: Option<bool>,
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct Exited {
+ pub exit_code: usize,
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct Terminated {
+ pub restart: Option<Value>,
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct Thread {
+ pub reason: String,
+ pub thread_id: ThreadId,
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct Output {
+ pub output: String,
+ pub category: Option<String>,
+ pub group: Option<String>,
+ pub line: Option<usize>,
+ pub column: Option<usize>,
+ pub variables_reference: Option<usize>,
+ pub source: Option<Source>,
+ pub data: Option<Value>,
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct Breakpoint {
+ pub reason: String,
+ pub breakpoint: super::Breakpoint,
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct Module {
+ pub reason: String,
+ pub module: super::Module,
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct LoadedSource {
+ pub reason: String,
+ pub source: super::Source,
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct Process {
+ pub name: String,
+ pub system_process_id: Option<usize>,
+ pub is_local_process: Option<bool>,
+ pub start_method: Option<String>, // TODO: use enum
+ pub pointer_size: Option<usize>,
+ }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct Capabilities {
+ pub capabilities: super::DebuggerCapabilities,
+ }
+
+ // #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ // #[serde(rename_all = "camelCase")]
+ // pub struct Invalidated {
+ // pub areas: Vec<InvalidatedArea>,
+ // pub thread_id: Option<ThreadId>,
+ // pub stack_frame_id: Option<usize>,
+ // }
+
+ #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+ #[serde(rename_all = "camelCase")]
+ pub struct Memory {
+ pub memory_reference: String,
+ pub offset: usize,
+ pub count: usize,
+ }
+}