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.rs344
1 files changed, 147 insertions, 197 deletions
diff --git a/helix-term/src/commands/dap.rs b/helix-term/src/commands/dap.rs
index 074bef6d..b182f28c 100644
--- a/helix-term/src/commands/dap.rs
+++ b/helix-term/src/commands/dap.rs
@@ -2,15 +2,16 @@ use super::{Context, Editor};
use crate::{
compositor::{self, Compositor},
job::{Callback, Jobs},
- ui::{self, overlay::overlaid, Picker, Popup, Prompt, PromptEvent, Text},
+ ui::{self, overlay::overlayed, FilePicker, 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 tokio_stream::wrappers::UnboundedReceiverStream;
use tui::text::Spans;
use std::collections::HashMap;
@@ -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 label(&self, _data: &Self::Data) -> Spans {
+ self.name.as_str().into() // TODO: include thread_states in the label
+ }
+}
+
+impl ui::menu::Item for DebugTemplate {
+ type Data = ();
+
+ fn label(&self, _data: &Self::Data) -> Spans {
+ self.name.as_str().into()
+ }
+}
+
+impl ui::menu::Item for Thread {
+ type Data = ThreadStates;
+
+ fn label(&self, thread_states: &Self::Data) -> Spans {
+ 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,38 +73,21 @@ 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,
+ let picker = FilePicker::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 pos = Some((
- frame.line.saturating_sub(1),
- frame.end_line.unwrap_or(frame.line).saturating_sub(1),
- ));
- Some((path.into(), pos))
- });
+ move |editor, thread| {
+ 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),
+ ));
+ Some((path.into(), pos))
+ },
+ );
compositor.push(Box::new(picker));
},
);
@@ -120,29 +136,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 +198,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 +229,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 +242,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,68 +270,25 @@ 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,
+ cx.push_layer(Box::new(overlayed(Picker::new(
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() {
- Some(debugger) => debugger,
- None => {
- cx.editor.set_error("Debugger is not running");
- return;
- }
- };
- if !debugger
- .capabilities()
- .supports_restart_request
- .unwrap_or(false)
- {
- cx.editor
- .set_error("Debugger does not support session restarts");
- return;
- }
- if debugger.starting_request_args().is_none() {
- cx.editor
- .set_error("No arguments found with which to restart the sessions");
- return;
- }
-
- dap_callback(
- cx.jobs,
- debugger.restart(),
- |editor, _compositor, _resp: ()| editor.set_status("Debugging session restarted"),
- );
-}
-
fn debug_parameter_prompt(
completions: Vec<DebugConfigCompletion>,
config_name: String,
@@ -335,12 +311,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,
};
@@ -503,37 +475,19 @@ pub fn dap_variables(cx: &mut Context) {
if debugger.thread_id.is_none() {
cx.editor
- .set_status("Cannot access variables while target is running.");
+ .set_status("Cannot access variables while target is running");
return;
}
let (frame, thread_id) = match (debugger.active_frame, debugger.thread_id) {
(Some(frame), Some(thread_id)) => (frame, thread_id),
_ => {
cx.editor
- .set_status("Cannot find current stack frame to access variables.");
+ .set_status("Cannot find current stack frame to access variables");
return;
}
};
- let thread_frame = match debugger.stack_frames.get(&thread_id) {
- Some(thread_frame) => thread_frame,
- None => {
- cx.editor
- .set_error(format!("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}."
- ));
- return;
- }
- };
-
- let frame_id = stack_frame.id;
+ let frame_id = debugger.stack_frames[&thread_id][frame].id;
let scopes = match block_on(debugger.scopes(frame_id)) {
Ok(s) => s,
Err(e) => {
@@ -579,21 +533,16 @@ pub fn dap_variables(cx: &mut Context) {
let contents = Text::from(tui::text::Text::from(variables));
let popup = Popup::new("dap-variables", contents);
- cx.replace_or_push_layer("dap-variables", popup);
+ cx.push_layer(Box::new(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();
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,38 +681,39 @@ 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 debugger = debugger!(cx.editor);
- // TODO: this should be simpler to find
- let pos = debugger.stack_frames[&thread_id]
- .iter()
- .position(|f| f.id == frame.id);
- debugger.active_frame = pos;
-
- let frame = debugger.stack_frames[&thread_id]
- .get(pos.unwrap_or(0))
- .cloned();
- if let Some(frame) = &frame {
- jump_to_stack_frame(cx.editor, frame);
- }
- })
- .with_preview(move |_editor, frame| {
- frame
- .source
- .as_ref()
- .and_then(|source| source.path.as_ref())
- .map(|path| {
- (
- path.as_path().into(),
- Some((
- frame.line.saturating_sub(1),
- frame.end_line.unwrap_or(frame.line).saturating_sub(1),
- )),
- )
- })
- });
+ let picker = FilePicker::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]
+ .iter()
+ .position(|f| f.id == frame.id);
+ debugger.active_frame = pos;
+
+ let frame = debugger.stack_frames[&thread_id]
+ .get(pos.unwrap_or(0))
+ .cloned();
+ if let Some(frame) = &frame {
+ jump_to_stack_frame(cx.editor, frame);
+ }
+ },
+ move |_editor, frame| {
+ frame
+ .source
+ .as_ref()
+ .and_then(|source| source.path.clone())
+ .map(|path| {
+ (
+ path.into(),
+ Some((
+ frame.line.saturating_sub(1),
+ frame.end_line.unwrap_or(frame.line).saturating_sub(1),
+ )),
+ )
+ })
+ },
+ );
cx.push_layer(Box::new(picker))
}