From d6ccc150c78adf72f77f26185ea7a559983ed2d0 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Sun, 29 Aug 2021 22:43:00 +0900 Subject: Extract dap commands into a separate file --- helix-term/src/commands/dap.rs | 349 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 349 insertions(+) create mode 100644 helix-term/src/commands/dap.rs (limited to 'helix-term/src/commands/dap.rs') diff --git a/helix-term/src/commands/dap.rs b/helix-term/src/commands/dap.rs new file mode 100644 index 00000000..9509ada1 --- /dev/null +++ b/helix-term/src/commands/dap.rs @@ -0,0 +1,349 @@ +use super::{Context, Editor}; +use helix_dap::Client; +use helix_lsp::block_on; + +use serde_json::{to_value, Value}; +use tokio_stream::wrappers::UnboundedReceiverStream; + +use std::collections::HashMap; + +// DAP +pub fn dap_start_impl( + editor: &mut Editor, + name: Option<&str>, + socket: Option, + params: Option>, +) { + let (_, doc) = current!(editor); + + let path = match doc.path() { + Some(path) => path.to_path_buf(), + None => { + editor.set_error("Can't start debug: document has no path".to_string()); + return; + } + }; + + let config = editor + .syn_loader + .language_config_for_file_name(&path) + .and_then(|x| x.debugger.clone()); + let config = match config { + Some(c) => c, + None => { + editor.set_error( + "Can't start debug: no debug adapter available for language".to_string(), + ); + return; + } + }; + + let result = match socket { + Some(socket) => block_on(Client::tcp(socket, 0)), + None => block_on(Client::process( + config.transport.clone(), + config.command.clone(), + config.args.clone(), + config.port_arg.clone(), + 0, + )), + }; + + let (mut debugger, events) = match result { + Ok(r) => r, + Err(e) => { + editor.set_error(format!("Failed to start debug session: {:?}", e)); + return; + } + }; + + let request = debugger.initialize(config.name.clone()); + if let Err(e) = block_on(request) { + editor.set_error(format!("Failed to initialize debug adapter: {:?}", e)); + return; + } + + let start_config = match name { + Some(name) => config.templates.iter().find(|t| t.name == name), + None => config.templates.get(0), + }; + let start_config = match start_config { + Some(c) => c, + None => { + editor.set_error("Can't start debug: no debug config with given name".to_string()); + return; + } + }; + + let template = start_config.args.clone(); + let mut args: HashMap = HashMap::new(); + + if let Some(params) = params { + for (k, t) in template { + let mut value = t; + for (i, x) in params.iter().enumerate() { + // For param #0 replace {0} in args + value = value.replace(format!("{{{}}}", i).as_str(), x); + } + + if let Ok(integer) = value.parse::() { + args.insert(k, Value::Number(serde_json::Number::from(integer))); + } else { + args.insert(k, Value::String(value)); + } + } + } + + let args = to_value(args).unwrap(); + + let result = match &start_config.request[..] { + "launch" => block_on(debugger.launch(args)), + "attach" => block_on(debugger.attach(args)), + _ => { + editor.set_error("Unsupported request".to_string()); + return; + } + }; + if let Err(e) = result { + editor.set_error(format!("Failed {} target: {:?}", start_config.request, e)); + return; + } + + // TODO: either await "initialized" or buffer commands until event is received + editor.debugger = Some(debugger); + let stream = UnboundedReceiverStream::new(events); + editor.debugger_events.push(stream); +} + +pub fn dap_launch(cx: &mut Context) { + if cx.editor.debugger.is_some() { + cx.editor + .set_error("Can't start debug: debugger is running".to_string()); + return; + } + + let (_, doc) = current!(cx.editor); + let path = match doc.path() { + Some(path) => path.to_path_buf(), + None => { + cx.editor + .set_error("Can't start debug: document has no path".to_string()); + return; + } + }; + + let config = cx + .editor + .syn_loader + .language_config_for_file_name(&path) + .and_then(|x| x.debugger.clone()); + let config = match config { + Some(c) => c, + None => { + cx.editor.set_error( + "Can't start debug: no debug adapter available for language".to_string(), + ); + return; + } + }; + + cx.editor.debug_config_picker = Some(config.templates.iter().map(|t| t.name.clone()).collect()); + cx.editor.debug_config_completions = Some( + config + .templates + .iter() + .map(|t| t.completion.clone()) + .collect(), + ); +} + +pub fn dap_toggle_breakpoint(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let text = doc.text().slice(..); + let pos = doc.selection(view.id).primary().cursor(text); + + let breakpoint = helix_dap::SourceBreakpoint { + line: text.char_to_line(pos) + 1, // convert from 0-indexing to 1-indexing (TODO: could set debugger to 0-indexing on init) + ..Default::default() + }; + + let path = match doc.path() { + Some(path) => path.to_path_buf(), + None => { + cx.editor + .set_error("Can't set breakpoint: document has no path".to_string()); + return; + } + }; + + // TODO: need to map breakpoints over edits and update them? + // we shouldn't really allow editing while debug is running though + + if let Some(debugger) = &mut cx.editor.debugger { + let breakpoints = debugger.breakpoints.entry(path.clone()).or_default(); + if let Some(pos) = breakpoints.iter().position(|b| b.line == breakpoint.line) { + breakpoints.remove(pos); + } else { + breakpoints.push(breakpoint); + } + + let breakpoints = breakpoints.clone(); + + let request = debugger.set_breakpoints(path, breakpoints); + if let Err(e) = block_on(request) { + cx.editor + .set_error(format!("Failed to set breakpoints: {:?}", e)); + } + } +} + +pub fn dap_run(cx: &mut Context) { + if let Some(debugger) = &mut cx.editor.debugger { + if debugger.is_running { + cx.editor + .set_status("Debuggee is already running".to_owned()); + return; + } + let request = debugger.configuration_done(); + if let Err(e) = block_on(request) { + cx.editor.set_error(format!("Failed to run: {:?}", e)); + return; + } + debugger.is_running = true; + } +} + +pub fn dap_continue(cx: &mut Context) { + if let Some(debugger) = &mut cx.editor.debugger { + if debugger.is_running { + cx.editor + .set_status("Debuggee is already running".to_owned()); + return; + } + + let request = debugger.continue_thread(debugger.stopped_thread.unwrap()); + if let Err(e) = block_on(request) { + cx.editor.set_error(format!("Failed to continue: {:?}", e)); + return; + } + debugger.is_running = true; + debugger.stack_pointer = None; + } +} + +pub fn dap_pause(cx: &mut Context) { + if let Some(debugger) = &mut cx.editor.debugger { + if !debugger.is_running { + cx.editor.set_status("Debuggee is not running".to_owned()); + return; + } + + // FIXME: correct number here + let request = debugger.pause(0); + if let Err(e) = block_on(request) { + cx.editor.set_error(format!("Failed to pause: {:?}", e)); + } + } +} + +pub fn dap_step_in(cx: &mut Context) { + if let Some(debugger) = &mut cx.editor.debugger { + if debugger.is_running { + cx.editor + .set_status("Debuggee is already running".to_owned()); + return; + } + + let request = debugger.step_in(debugger.stopped_thread.unwrap()); + if let Err(e) = block_on(request) { + cx.editor.set_error(format!("Failed to step: {:?}", e)); + } + } +} + +pub fn dap_step_out(cx: &mut Context) { + if let Some(debugger) = &mut cx.editor.debugger { + if debugger.is_running { + cx.editor + .set_status("Debuggee is already running".to_owned()); + return; + } + + let request = debugger.step_out(debugger.stopped_thread.unwrap()); + if let Err(e) = block_on(request) { + cx.editor.set_error(format!("Failed to step: {:?}", e)); + } + } +} + +pub fn dap_next(cx: &mut Context) { + if let Some(debugger) = &mut cx.editor.debugger { + if debugger.is_running { + cx.editor + .set_status("Debuggee is already running".to_owned()); + return; + } + + let request = debugger.next(debugger.stopped_thread.unwrap()); + if let Err(e) = block_on(request) { + cx.editor.set_error(format!("Failed to step: {:?}", e)); + } + } +} + +pub fn dap_variables(cx: &mut Context) { + if let Some(debugger) = &mut cx.editor.debugger { + if debugger.is_running { + cx.editor + .set_status("Cannot access variables while target is running".to_owned()); + return; + } + if debugger.stack_pointer.is_none() { + cx.editor + .set_status("Cannot find current stack pointer to access variables".to_owned()); + return; + } + + let frame_id = debugger.stack_pointer.clone().unwrap().id; + let scopes = match block_on(debugger.scopes(frame_id)) { + Ok(s) => s, + Err(e) => { + cx.editor + .set_error(format!("Failed to get scopes: {:?}", e)); + return; + } + }; + let mut variables = Vec::new(); + + for scope in scopes.iter() { + let response = block_on(debugger.variables(scope.variables_reference)); + + if let Ok(vars) = response { + for var in vars { + let prefix = match var.data_type { + Some(data_type) => format!("{} ", data_type), + None => "".to_owned(), + }; + variables.push(format!("{}{} = {}\n", prefix, var.name, var.value)); + } + } + } + + if !variables.is_empty() { + cx.editor.variables = Some(variables); + cx.editor.variables_page = 0; + } + } +} + +pub fn dap_terminate(cx: &mut Context) { + if let Some(debugger) = &mut cx.editor.debugger { + let request = debugger.disconnect(); + if let Err(e) = block_on(request) { + cx.editor + .set_error(format!("Failed to disconnect: {:?}", e)); + return; + } + cx.editor.debugger = None; + } +} -- cgit v1.2.3-70-g09d2