diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index a91490fd..03a71fd3 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1,3 +1,7 @@ +mod dap; + +pub use dap::*; + use helix_core::{ comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes, indent, indent::IndentStyle, @@ -24,13 +28,11 @@ use helix_lsp::{ }; use insert::*; use movement::Movement; -use serde_json::Value; use crate::{ compositor::{self, Component, Compositor}, ui::{self, FilePicker, Picker, Popup, Prompt, PromptEvent}, }; -use tokio_stream::wrappers::UnboundedReceiverStream; use crate::job::{self, Job, Jobs}; use futures_util::FutureExt; @@ -4490,366 +4492,3 @@ fn suspend(_cx: &mut Context) { #[cfg(not(windows))] signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP).unwrap(); } - -// DAP -pub fn dap_start_impl( - editor: &mut Editor, - name: Option<&str>, - socket: Option, - params: Option>, -) { - use helix_dap::Client; - use helix_lsp::block_on; - use serde_json::to_value; - - 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); -} - -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(), - ); -} - -fn dap_toggle_breakpoint(cx: &mut Context) { - use helix_lsp::block_on; - - 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)); - } - } -} - -fn dap_run(cx: &mut Context) { - use helix_lsp::block_on; - - 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; - } -} - -fn dap_continue(cx: &mut Context) { - use helix_lsp::block_on; - - 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; - } -} - -fn dap_pause(cx: &mut Context) { - use helix_lsp::block_on; - - 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)); - } - } -} - -fn dap_step_in(cx: &mut Context) { - use helix_lsp::block_on; - - 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)); - } - } -} - -fn dap_step_out(cx: &mut Context) { - use helix_lsp::block_on; - - 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)); - } - } -} - -fn dap_next(cx: &mut Context) { - use helix_lsp::block_on; - - 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)); - } - } -} - -fn dap_variables(cx: &mut Context) { - use helix_lsp::block_on; - - 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; - } - } -} - -fn dap_terminate(cx: &mut Context) { - use helix_lsp::block_on; - - 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; - } -} 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; + } +}