@@ -0,0 +1,352 @@
2+ transport::{Event, Payload, Request, Response, Transport},
5+use serde::{Deserialize, Serialize};
6+use serde_json::{from_value, to_value, Value};
7+use std::process::Stdio;
8+use std::sync::atomic::{AtomicU64, Ordering};
10+ io::{BufReader, BufWriter},
11+ process::{Child, Command},
12+ sync::mpsc::{channel, UnboundedReceiver, UnboundedSender},
15+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
16+#[serde(rename_all = "camelCase")]
17+pub struct DebuggerCapabilities {
18+ supports_configuration_done_request: bool,
19+ supports_function_breakpoints: bool,
20+ supports_conditional_breakpoints: bool,
21+ supports_exception_info_request: bool,
22+ support_terminate_debuggee: bool,
23+ supports_delayed_stack_trace_loading: bool,
26+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
27+#[serde(rename_all = "camelCase")]
28+struct InitializeArguments {
33+ #[serde(rename = "linesStartAt1")]
34+ lines_start_at_one: bool,
35+ #[serde(rename = "columnsStartAt1")]
36+ columns_start_at_one: bool,
38+ supports_variable_type: bool,
39+ supports_variable_paging: bool,
40+ supports_run_in_terminal_request: bool,
41+ supports_memory_references: bool,
42+ supports_progress_reporting: bool,
43+ supports_invalidated_event: bool,
47+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
48+#[serde(rename_all = "camelCase")]
49+struct LaunchArguments {
54+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
55+#[serde(rename_all = "camelCase")]
57+ path: Option<String>,
60+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
61+#[serde(rename_all = "camelCase")]
62+pub struct SourceBreakpoint {
64+ pub column: Option<usize>,
67+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
68+#[serde(rename_all = "camelCase")]
69+struct SetBreakpointsArguments {
71+ breakpoints: Option<Vec<SourceBreakpoint>>,
74+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
75+#[serde(rename_all = "camelCase")]
76+pub struct Breakpoint {
77+ pub id: Option<usize>,
79+ pub message: Option<String>,
80+ pub source: Option<Source>,
81+ pub line: Option<usize>,
82+ pub column: Option<usize>,
83+ pub end_line: Option<usize>,
84+ pub end_column: Option<usize>,
85+ pub instruction_reference: Option<String>,
86+ pub offset: Option<usize>,
89+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
90+#[serde(rename_all = "camelCase")]
91+struct SetBreakpointsResponseBody {
92+ breakpoints: Option<Vec<Breakpoint>>,
95+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
96+#[serde(rename_all = "camelCase")]
97+struct ContinueArguments {
101+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
102+#[serde(rename_all = "camelCase")]
103+struct ContinueResponseBody {
104+ all_threads_continued: Option<bool>,
107+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
108+#[serde(rename_all = "camelCase")]
109+struct StackFrameFormat {
110+ parameters: Option<bool>,
111+ parameter_types: Option<bool>,
112+ parameter_names: Option<bool>,
113+ parameter_values: Option<bool>,
115+ module: Option<bool>,
116+ include_all: Option<bool>,
119+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
120+#[serde(rename_all = "camelCase")]
121+struct StackTraceArguments {
123+ start_frame: Option<usize>,
124+ levels: Option<usize>,
125+ format: Option<StackFrameFormat>,
128+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
129+#[serde(rename_all = "camelCase")]
130+pub struct StackFrame {
133+ source: Option<Source>,
136+ end_line: Option<usize>,
137+ end_column: Option<usize>,
138+ can_restart: Option<bool>,
139+ instruction_pointer_reference: Option<String>,
141+ presentation_hint: Option<String>,
144+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
145+#[serde(rename_all = "camelCase")]
146+struct StackTraceResponseBody {
147+ total_frames: Option<usize>,
148+ stack_frames: Vec<StackFrame>,
155+ server_tx: UnboundedSender<Request>,
156+ server_rx: UnboundedReceiver<Payload>,
157+ request_counter: AtomicU64,
158+ capabilities: Option<DebuggerCapabilities>,
162+ pub fn start(cmd: &str, args: Vec<&str>, id: usize) -> Result<Self> {
163+ let process = Command::new(cmd)
165+ .stdin(Stdio::piped())
166+ .stdout(Stdio::piped())
167+ // make sure the process is reaped on drop
171+ let mut process = process?;
173+ // TODO: do we need bufreader/writer here? or do we use async wrappers on unblock?
174+ let writer = BufWriter::new(process.stdin.take().expect("Failed to open stdin"));
175+ let reader = BufReader::new(process.stdout.take().expect("Failed to open stdout"));
177+ let (server_rx, server_tx) = Transport::start(reader, writer, id);
184+ request_counter: AtomicU64::new(0),
188+ // TODO: async client.initialize()
189+ // maybe use an arc<atomic> flag
194+ pub fn id(&self) -> usize {
198+ fn next_request_id(&self) -> u64 {
199+ self.request_counter.fetch_add(1, Ordering::Relaxed)
202+ async fn request(&self, command: String, arguments: Option<Value>) -> Result<Response> {
203+ let (callback_rx, mut callback_tx) = channel(1);
206+ back_ch: Some(callback_rx),
207+ seq: self.next_request_id(),
208+ msg_type: "request".to_owned(),
215+ .expect("Failed to send request to debugger");
220+ .expect("Failed to receive response")
223+ pub fn capabilities(&self) -> &DebuggerCapabilities {
226+ .expect("language server not yet initialized!")
229+ pub async fn initialize(&mut self) -> Result<()> {
230+ let args = InitializeArguments {
231+ client_id: "hx".to_owned(),
232+ client_name: "helix".to_owned(),
233+ adapter_id: "go".to_owned(),
234+ locale: "en-us".to_owned(),
235+ lines_start_at_one: true,
236+ columns_start_at_one: true,
237+ path_format: "path".to_owned(),
238+ supports_variable_type: false,
239+ supports_variable_paging: false,
240+ supports_run_in_terminal_request: false,
241+ supports_memory_references: false,
242+ supports_progress_reporting: true,
243+ supports_invalidated_event: true,
247+ .request("initialize".to_owned(), to_value(args).ok())
249+ self.capabilities = from_value(response.body.unwrap()).ok();
254+ pub async fn disconnect(&mut self) -> Result<()> {
255+ self.request("disconnect".to_owned(), None).await?;
259+ pub async fn launch(&mut self, executable: String) -> Result<()> {
260+ let args = LaunchArguments {
261+ mode: "exec".to_owned(),
262+ program: executable,
265+ self.request("launch".to_owned(), to_value(args).ok())
272+ .expect("Expected initialized event")
274+ Payload::Event(Event { event, .. }) => {
275+ if event == "initialized".to_owned() {
281+ _ => unreachable!(),
285+ pub async fn set_breakpoints(
288+ breakpoints: Vec<SourceBreakpoint>,
289+ ) -> Result<Option<Vec<Breakpoint>>> {
290+ let args = SetBreakpointsArguments {
291+ source: Source { path: Some(file) },
292+ breakpoints: Some(breakpoints),
296+ .request("setBreakpoints".to_owned(), to_value(args).ok())
298+ let body: Option<SetBreakpointsResponseBody> = from_value(response.body.unwrap()).ok();
300+ Ok(body.map(|b| b.breakpoints).unwrap())
303+ pub async fn configuration_done(&mut self) -> Result<()> {
304+ self.request("configurationDone".to_owned(), None).await?;
308+ pub async fn wait_for_stopped(&mut self) -> Result<()> {
309+ match self.server_rx.recv().await.expect("Expected stopped event") {
310+ Payload::Event(Event { event, .. }) => {
311+ if event == "stopped".to_owned() {
317+ _ => unreachable!(),
321+ pub async fn continue_thread(&mut self, thread_id: usize) -> Result<Option<bool>> {
322+ let args = ContinueArguments { thread_id };
325+ .request("continue".to_owned(), to_value(args).ok())
328+ let body: Option<ContinueResponseBody> = from_value(response.body.unwrap()).ok();
330+ Ok(body.map(|b| b.all_threads_continued).unwrap())
333+ pub async fn stack_trace(
336+ ) -> Result<(Vec<StackFrame>, Option<usize>)> {
337+ let args = StackTraceArguments {
345+ .request("stackTrace".to_owned(), to_value(args).ok())
348+ let body: StackTraceResponseBody = from_value(response.body.unwrap()).unwrap();
350+ Ok((body.stack_frames, body.total_frames))