Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/commands/dap.rs')
| -rw-r--r-- | helix-term/src/commands/dap.rs | 222 |
1 files changed, 111 insertions, 111 deletions
diff --git a/helix-term/src/commands/dap.rs b/helix-term/src/commands/dap.rs index 074bef6d..d62b0a4e 100644 --- a/helix-term/src/commands/dap.rs +++ b/helix-term/src/commands/dap.rs @@ -5,13 +5,14 @@ use crate::{ ui::{self, overlay::overlaid, Picker, Popup, Prompt, PromptEvent, Text}, }; use dap::{StackFrame, Thread, ThreadStates}; -use helix_core::syntax::config::{DebugArgumentValue, DebugConfigCompletion, DebugTemplate}; -use helix_dap::{self as dap, requests::TerminateArguments}; +use helix_core::syntax::{DebugArgumentValue, DebugConfigCompletion, DebugTemplate}; +use helix_dap::{self as dap, Client}; use helix_lsp::block_on; -use helix_view::editor::Breakpoint; +use helix_view::{editor::Breakpoint, graphics::Margin}; use serde_json::{to_value, Value}; -use tui::text::Spans; +use tokio_stream::wrappers::UnboundedReceiverStream; +use tui::{text::Spans, widgets::Row}; use std::collections::HashMap; use std::future::Future; @@ -21,6 +22,38 @@ use anyhow::{anyhow, bail}; use helix_view::handlers::dap::{breakpoints_changed, jump_to_stack_frame, select_thread_id}; +impl ui::menu::Item for StackFrame { + type Data = (); + + fn format(&self, _data: &Self::Data) -> Row { + self.name.as_str().into() // TODO: include thread_states in the label + } +} + +impl ui::menu::Item for DebugTemplate { + type Data = (); + + fn format(&self, _data: &Self::Data) -> Row { + self.name.as_str().into() + } +} + +impl ui::menu::Item for Thread { + type Data = ThreadStates; + + fn format(&self, thread_states: &Self::Data) -> Row { + format!( + "{} ({})", + self.name, + thread_states + .get(&self.id) + .map(|state| state.as_str()) + .unwrap_or("unknown") + ) + .into() + } +} + fn thread_picker( cx: &mut Context, callback_fn: impl Fn(&mut Editor, &dap::Thread) + Send + 'static, @@ -40,32 +73,13 @@ fn thread_picker( let debugger = debugger!(editor); let thread_states = debugger.thread_states.clone(); - let columns = [ - ui::PickerColumn::new("name", |item: &Thread, _| item.name.as_str().into()), - ui::PickerColumn::new("state", |item: &Thread, thread_states: &ThreadStates| { - thread_states - .get(&item.id) - .map(|state| state.as_str()) - .unwrap_or("unknown") - .into() - }), - ]; - let picker = Picker::new( - columns, - 0, - threads, - thread_states, - move |cx, thread, _action| callback_fn(cx.editor, thread), - ) + let picker = Picker::new(threads, thread_states, move |cx, thread, _action| { + callback_fn(cx.editor, thread) + }) .with_preview(move |editor, thread| { - let frames = editor - .debug_adapters - .get_active_client() - .as_ref()? - .stack_frames - .get(&thread.id)?; + let frames = editor.debugger.as_ref()?.stack_frames.get(&thread.id)?; let frame = frames.first()?; - let path = frame.source.as_ref()?.path.as_ref()?.as_path(); + let path = frame.source.as_ref()?.path.clone()?; let pos = Some(( frame.line.saturating_sub(1), frame.end_line.unwrap_or(frame.line).saturating_sub(1), @@ -120,16 +134,34 @@ pub fn dap_start_impl( params: Option<Vec<std::borrow::Cow<str>>>, ) -> Result<(), anyhow::Error> { let doc = doc!(cx.editor); + let config = doc .language_config() .and_then(|config| config.debugger.as_ref()) .ok_or_else(|| anyhow!("No debug adapter available for language"))?; - let id = cx - .editor - .debug_adapters - .start_client(socket, config) - .map_err(|e| anyhow!("Failed to start debug client: {}", e))?; + let result = match socket { + Some(socket) => block_on(Client::tcp(socket, 0)), + None => block_on(Client::process( + &config.transport, + &config.command, + config.args.iter().map(|arg| arg.as_str()).collect(), + config.port_arg.as_deref(), + 0, + )), + }; + + let (mut debugger, events) = match result { + Ok(r) => r, + Err(e) => bail!("Failed to start debug session: {}", e), + }; + + let request = debugger.initialize(config.name.clone()); + if let Err(e) = block_on(request) { + bail!("Failed to initialize debug adapter: {}", e); + } + + debugger.quirks = config.quirks.clone(); // TODO: avoid refetching all of this... pass a config in let template = match name { @@ -140,9 +172,9 @@ pub fn dap_start_impl( let mut args: HashMap<&str, Value> = HashMap::new(); - for (k, t) in &template.args { - let mut value = t.clone(); - if let Some(ref params) = params { + if let Some(params) = params { + for (k, t) in &template.args { + let mut value = t.clone(); for (i, x) in params.iter().enumerate() { let mut param = x.to_string(); if let Some(DebugConfigCompletion::Advanced(cfg)) = template.completion.get(i) { @@ -164,33 +196,23 @@ pub fn dap_start_impl( arr.iter().map(|v| v.replace(&pattern, ¶m)).collect(), ), DebugArgumentValue::Boolean(_) => value, - DebugArgumentValue::Table(map) => DebugArgumentValue::Table( - map.into_iter() - .map(|(mk, mv)| { - (mk.replace(&pattern, ¶m), mv.replace(&pattern, ¶m)) - }) - .collect(), - ), }; } - } - match value { - DebugArgumentValue::String(string) => { - if let Ok(integer) = string.parse::<usize>() { - args.insert(k, to_value(integer).unwrap()); - } else { - args.insert(k, to_value(string).unwrap()); + match value { + DebugArgumentValue::String(string) => { + if let Ok(integer) = string.parse::<usize>() { + args.insert(k, to_value(integer).unwrap()); + } else { + args.insert(k, to_value(string).unwrap()); + } + } + DebugArgumentValue::Array(arr) => { + args.insert(k, to_value(arr).unwrap()); + } + DebugArgumentValue::Boolean(bool) => { + args.insert(k, to_value(bool).unwrap()); } - } - DebugArgumentValue::Array(arr) => { - args.insert(k, to_value(arr).unwrap()); - } - DebugArgumentValue::Boolean(bool) => { - args.insert(k, to_value(bool).unwrap()); - } - DebugArgumentValue::Table(map) => { - args.insert(k, to_value(map).unwrap()); } } } @@ -205,13 +227,6 @@ pub fn dap_start_impl( // } }; - let debugger = match cx.editor.debug_adapters.get_client_mut(id) { - Some(child) => child, - None => { - bail!("Failed to get child debugger."); - } - }; - match &template.request[..] { "launch" => { let call = debugger.launch(args); @@ -225,12 +240,14 @@ pub fn dap_start_impl( }; // TODO: either await "initialized" or buffer commands until event is received + cx.editor.debugger = Some(debugger); + let stream = UnboundedReceiverStream::new(events); + cx.editor.debugger_events.push(stream); Ok(()) } pub fn dap_launch(cx: &mut Context) { - // TODO: Now that we support multiple Clients, we could run multiple debuggers at once but for now keep this as is - if cx.editor.debug_adapters.get_active_client().is_some() { + if cx.editor.debugger.is_some() { cx.editor.set_error("Debugger is already running"); return; } @@ -251,40 +268,27 @@ pub fn dap_launch(cx: &mut Context) { let templates = config.templates.clone(); - let columns = [ui::PickerColumn::new( - "template", - |item: &DebugTemplate, _| item.name.as_str().into(), - )]; - cx.push_layer(Box::new(overlaid(Picker::new( - columns, - 0, templates, (), |cx, template, _action| { - if template.completion.is_empty() { - if let Err(err) = dap_start_impl(cx, Some(&template.name), None, None) { - cx.editor.set_error(err.to_string()); - } - } else { - let completions = template.completion.clone(); - let name = template.name.clone(); - let callback = Box::pin(async move { - let call: Callback = - Callback::EditorCompositor(Box::new(move |_editor, compositor| { - let prompt = debug_parameter_prompt(completions, name, Vec::new()); - compositor.push(Box::new(prompt)); - })); - Ok(call) - }); - cx.jobs.callback(callback); - } + let completions = template.completion.clone(); + let name = template.name.clone(); + let callback = Box::pin(async move { + let call: Callback = + Callback::EditorCompositor(Box::new(move |_editor, compositor| { + let prompt = debug_parameter_prompt(completions, name, Vec::new()); + compositor.push(Box::new(prompt)); + })); + Ok(call) + }); + cx.jobs.callback(callback); }, )))); } pub fn dap_restart(cx: &mut Context) { - let debugger = match cx.editor.debug_adapters.get_active_client() { + let debugger = match &cx.editor.debugger { Some(debugger) => debugger, None => { cx.editor.set_error("Debugger is not running"); @@ -519,16 +523,15 @@ pub fn dap_variables(cx: &mut Context) { Some(thread_frame) => thread_frame, None => { cx.editor - .set_error(format!("Failed to get stack frame for thread: {thread_id}")); + .set_error("Failed to get stack frame for thread: {thread_id}"); return; } }; let stack_frame = match thread_frame.get(frame) { Some(stack_frame) => stack_frame, None => { - cx.editor.set_error(format!( - "Failed to get stack frame for thread {thread_id} and frame {frame}." - )); + cx.editor + .set_error("Failed to get stack frame for thread {thread_id} and frame {frame}."); return; } }; @@ -578,22 +581,22 @@ pub fn dap_variables(cx: &mut Context) { } let contents = Text::from(tui::text::Text::from(variables)); - let popup = Popup::new("dap-variables", contents); + let margin = if cx.editor.popup_border() { + Margin::all(1) + } else { + Margin::none() + }; + let popup = Popup::new("dap-variables", contents).margin(margin); cx.replace_or_push_layer("dap-variables", popup); } pub fn dap_terminate(cx: &mut Context) { - cx.editor.set_status("Terminating debug session..."); let debugger = debugger!(cx.editor); - let terminate_arguments = Some(TerminateArguments { - restart: Some(false), - }); - - let request = debugger.terminate(terminate_arguments); + let request = debugger.disconnect(None); dap_callback(cx.jobs, request, |editor, _compositor, _response: ()| { // editor.set_error(format!("Failed to disconnect: {}", e)); - editor.debug_adapters.unset_active_client(); + editor.debugger = None; }); } @@ -732,10 +735,7 @@ pub fn dap_switch_stack_frame(cx: &mut Context) { let frames = debugger.stack_frames[&thread_id].clone(); - let columns = [ui::PickerColumn::new("frame", |item: &StackFrame, _| { - item.name.as_str().into() // TODO: include thread_states in the label - })]; - let picker = Picker::new(columns, 0, frames, (), move |cx, frame, _action| { + let picker = Picker::new(frames, (), move |cx, frame, _action| { let debugger = debugger!(cx.editor); // TODO: this should be simpler to find let pos = debugger.stack_frames[&thread_id] @@ -754,10 +754,10 @@ pub fn dap_switch_stack_frame(cx: &mut Context) { frame .source .as_ref() - .and_then(|source| source.path.as_ref()) + .and_then(|source| source.path.clone()) .map(|path| { ( - path.as_path().into(), + path.into(), Some(( frame.line.saturating_sub(1), frame.end_line.unwrap_or(frame.line).saturating_sub(1), |