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.rs227
1 files changed, 109 insertions, 118 deletions
diff --git a/helix-term/src/commands/dap.rs b/helix-term/src/commands/dap.rs
index 074bef6d..70a5ec21 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 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 frame = frames.first()?;
- let path = frame.source.as_ref()?.path.as_ref()?.as_path();
+ let frames = editor.debugger.as_ref()?.stack_frames.get(&thread.id)?;
+ let frame = frames.get(0)?;
+ 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,29 +134,47 @@ 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 {
Some(name) => config.templates.iter().find(|t| t.name == name),
- None => config.templates.first(),
+ None => config.templates.get(0),
}
.ok_or_else(|| anyhow!("No debug config with given name"))?;
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,38 +196,28 @@ pub fn dap_start_impl(
arr.iter().map(|v| v.replace(&pattern, &param)).collect(),
),
DebugArgumentValue::Boolean(_) => value,
- DebugArgumentValue::Table(map) => DebugArgumentValue::Table(
- map.into_iter()
- .map(|(mk, mv)| {
- (mk.replace(&pattern, &param), mv.replace(&pattern, &param))
- })
- .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());
}
}
}
- args.insert("cwd", to_value(helix_stdx::env::current_working_dir())?);
+ args.insert("cwd", to_value(std::env::current_dir().unwrap())?);
let args = to_value(args).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");
@@ -335,12 +339,8 @@ fn debug_parameter_prompt(
.to_owned();
let completer = match field_type {
- "filename" => |editor: &Editor, input: &str| {
- ui::completers::filename_with_git_ignore(editor, input, false)
- },
- "directory" => |editor: &Editor, input: &str| {
- ui::completers::directory_with_git_ignore(editor, input, false)
- },
+ "filename" => ui::completers::filename,
+ "directory" => ui::completers::directory,
_ => ui::completers::none,
};
@@ -519,16 +519,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;
}
};
@@ -583,17 +582,12 @@ pub fn dap_variables(cx: &mut Context) {
}
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 +726,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 +745,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),