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.rs601
1 files changed, 330 insertions, 271 deletions
diff --git a/helix-term/src/commands/dap.rs b/helix-term/src/commands/dap.rs
index 074bef6d..cba293e2 100644
--- a/helix-term/src/commands/dap.rs
+++ b/helix-term/src/commands/dap.rs
@@ -1,17 +1,19 @@
-use super::{Context, Editor};
+use super::{align_view, Align, 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},
+ Selection,
+};
+use helix_dap::{self as dap, Client, ThreadId};
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 std::collections::HashMap;
use std::future::Future;
@@ -19,7 +21,79 @@ use std::path::PathBuf;
use anyhow::{anyhow, bail};
-use helix_view::handlers::dap::{breakpoints_changed, jump_to_stack_frame, select_thread_id};
+#[macro_export]
+macro_rules! debugger {
+ ($editor:expr) => {{
+ match &mut $editor.debugger {
+ Some(debugger) => debugger,
+ None => return,
+ }
+ }};
+}
+
+// general utils:
+pub fn dap_pos_to_pos(doc: &helix_core::Rope, line: usize, column: usize) -> Option<usize> {
+ // 1-indexing to 0 indexing
+ let line = doc.try_line_to_char(line - 1).ok()?;
+ let pos = line + column.saturating_sub(1);
+ // TODO: this is probably utf-16 offsets
+ Some(pos)
+}
+
+pub async fn select_thread_id(editor: &mut Editor, thread_id: ThreadId, force: bool) {
+ let debugger = debugger!(editor);
+
+ if !force && debugger.thread_id.is_some() {
+ return;
+ }
+
+ debugger.thread_id = Some(thread_id);
+ fetch_stack_trace(debugger, thread_id).await;
+
+ let frame = debugger.stack_frames[&thread_id].get(0).cloned();
+ if let Some(frame) = &frame {
+ jump_to_stack_frame(editor, frame);
+ }
+}
+
+pub async fn fetch_stack_trace(debugger: &mut Client, thread_id: ThreadId) {
+ let (frames, _) = match debugger.stack_trace(thread_id).await {
+ Ok(frames) => frames,
+ Err(_) => return,
+ };
+ debugger.stack_frames.insert(thread_id, frames);
+ debugger.active_frame = Some(0);
+}
+
+pub fn jump_to_stack_frame(editor: &mut Editor, frame: &helix_dap::StackFrame) {
+ let path = if let Some(helix_dap::Source {
+ path: Some(ref path),
+ ..
+ }) = frame.source
+ {
+ path.clone()
+ } else {
+ return;
+ };
+
+ if let Err(e) = editor.open(path, helix_view::editor::Action::Replace) {
+ editor.set_error(format!("Unable to jump to stack frame: {}", e));
+ return;
+ }
+
+ let (view, doc) = current!(editor);
+
+ let text_end = doc.text().len_chars().saturating_sub(1);
+ let start = dap_pos_to_pos(doc.text(), frame.line, frame.column).unwrap_or(0);
+ let end = frame
+ .end_line
+ .and_then(|end_line| dap_pos_to_pos(doc.text(), end_line, frame.end_column.unwrap_or(0)))
+ .unwrap_or(start);
+
+ let selection = Selection::single(start.min(text_end), end.min(text_end));
+ doc.set_selection(view.id, selection);
+ align_view(doc, view, Align::Center);
+}
fn thread_picker(
cx: &mut Context,
@@ -31,7 +105,9 @@ fn thread_picker(
dap_callback(
cx.jobs,
future,
- move |editor, compositor, response: dap::requests::ThreadsResponse| {
+ move |editor: &mut Editor,
+ compositor: &mut Compositor,
+ response: dap::requests::ThreadsResponse| {
let threads = response.threads;
if threads.len() == 1 {
callback_fn(editor, &threads[0]);
@@ -40,38 +116,31 @@ 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 |thread| {
+ format!(
+ "{} ({})",
+ thread.name,
+ thread_states
+ .get(&thread.id)
+ .map(|state| state.as_str())
+ .unwrap_or("unknown")
+ )
+ .into()
+ },
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, pos))
+ },
+ );
compositor.push(Box::new(picker));
},
);
@@ -102,14 +171,11 @@ fn dap_callback<T, F>(
let callback = Box::pin(async move {
let json = call.await?;
let response = serde_json::from_value(json)?;
- let call: Callback = Callback::EditorCompositor(Box::new(
- move |editor: &mut Editor, compositor: &mut Compositor| {
- callback(editor, compositor, response)
- },
- ));
+ let call: Callback = Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
+ callback(editor, compositor, response)
+ });
Ok(call)
});
-
jobs.callback(callback);
}
@@ -120,29 +186,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,39 +248,27 @@ 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())?);
-
let args = to_value(args).unwrap();
let callback = |_editor: &mut Editor, _compositor: &mut Compositor, _response: Value| {
@@ -205,13 +277,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 +290,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 +318,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,
- (),
+ |template| template.name.as_str().into(),
|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 =
+ Box::new(move |_editor: &mut Editor, compositor: &mut 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 +359,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,
};
@@ -365,10 +385,10 @@ fn debug_parameter_prompt(
let params = params.clone();
let callback = Box::pin(async move {
let call: Callback =
- Callback::EditorCompositor(Box::new(move |_editor, compositor| {
+ Box::new(move |_editor: &mut Editor, compositor: &mut Compositor| {
let prompt = debug_parameter_prompt(completions, config_name, params);
compositor.push(Box::new(prompt));
- }));
+ });
Ok(call)
});
cx.jobs.callback(callback);
@@ -399,6 +419,63 @@ pub fn dap_toggle_breakpoint(cx: &mut Context) {
dap_toggle_breakpoint_impl(cx, path, line);
}
+pub fn breakpoints_changed(
+ debugger: &mut dap::Client,
+ path: PathBuf,
+ breakpoints: &mut [Breakpoint],
+) -> Result<(), anyhow::Error> {
+ // TODO: handle capabilities correctly again, by filterin breakpoints when emitting
+ // if breakpoint.condition.is_some()
+ // && !debugger
+ // .caps
+ // .as_ref()
+ // .unwrap()
+ // .supports_conditional_breakpoints
+ // .unwrap_or_default()
+ // {
+ // bail!(
+ // "Can't edit breakpoint: debugger does not support conditional breakpoints"
+ // )
+ // }
+ // if breakpoint.log_message.is_some()
+ // && !debugger
+ // .caps
+ // .as_ref()
+ // .unwrap()
+ // .supports_log_points
+ // .unwrap_or_default()
+ // {
+ // bail!("Can't edit breakpoint: debugger does not support logpoints")
+ // }
+ let source_breakpoints = breakpoints
+ .iter()
+ .map(|breakpoint| helix_dap::SourceBreakpoint {
+ line: breakpoint.line + 1, // convert from 0-indexing to 1-indexing (TODO: could set debugger to 0-indexing on init)
+ ..Default::default()
+ })
+ .collect::<Vec<_>>();
+
+ let request = debugger.set_breakpoints(path, source_breakpoints);
+ match block_on(request) {
+ Ok(Some(dap_breakpoints)) => {
+ for (breakpoint, dap_breakpoint) in breakpoints.iter_mut().zip(dap_breakpoints) {
+ breakpoint.id = dap_breakpoint.id;
+ breakpoint.verified = dap_breakpoint.verified;
+ breakpoint.message = dap_breakpoint.message;
+ // TODO: handle breakpoint.message
+ // TODO: verify source matches
+ breakpoint.line = dap_breakpoint.line.unwrap_or(0).saturating_sub(1); // convert to 0-indexing
+ // TODO: no unwrap
+ breakpoint.column = dap_breakpoint.column;
+ // TODO: verify end_linef/col instruction reference, offset
+ }
+ }
+ Err(e) => anyhow::bail!("Failed to set breakpoints: {}", e),
+ _ => {}
+ };
+ Ok(())
+}
+
pub fn dap_toggle_breakpoint_impl(cx: &mut Context, path: PathBuf, line: usize) {
// TODO: need to map breakpoints over edits and update them?
// we shouldn't really allow editing while debug is running though
@@ -503,37 +580,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) => {
@@ -552,7 +611,7 @@ pub fn dap_variables(cx: &mut Context) {
for scope in scopes.iter() {
// use helix_view::graphics::Style;
- use tui::text::Span;
+ use tui::text::{Span, Spans};
let response = block_on(debugger.variables(scope.variables_reference));
variables.push(Spans::from(Span::styled(
@@ -579,21 +638,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;
});
}
@@ -638,35 +692,37 @@ pub fn dap_edit_condition(cx: &mut Context) {
None => return,
};
let callback = Box::pin(async move {
- let call: Callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
- let mut prompt = Prompt::new(
- "condition:".into(),
- None,
- ui::completers::none,
- move |cx, input: &str, event: PromptEvent| {
- if event != PromptEvent::Validate {
- return;
- }
-
- let breakpoints = &mut cx.editor.breakpoints.get_mut(&path).unwrap();
- breakpoints[pos].condition = match input {
- "" => None,
- input => Some(input.to_owned()),
- };
-
- let debugger = debugger!(cx.editor);
-
- if let Err(e) = breakpoints_changed(debugger, path.clone(), breakpoints) {
- cx.editor
- .set_error(format!("Failed to set breakpoints: {}", e));
- }
- },
- );
- if let Some(condition) = breakpoint.condition {
- prompt.insert_str(&condition, editor)
- }
- compositor.push(Box::new(prompt));
- }));
+ let call: Callback =
+ Box::new(move |_editor: &mut Editor, compositor: &mut Compositor| {
+ let mut prompt = Prompt::new(
+ "condition:".into(),
+ None,
+ ui::completers::none,
+ move |cx, input: &str, event: PromptEvent| {
+ if event != PromptEvent::Validate {
+ return;
+ }
+
+ let breakpoints = &mut cx.editor.breakpoints.get_mut(&path).unwrap();
+ breakpoints[pos].condition = match input {
+ "" => None,
+ input => Some(input.to_owned()),
+ };
+
+ let debugger = debugger!(cx.editor);
+
+ if let Err(e) = breakpoints_changed(debugger, path.clone(), breakpoints)
+ {
+ cx.editor
+ .set_error(format!("Failed to set breakpoints: {}", e));
+ }
+ },
+ );
+ if let Some(condition) = breakpoint.condition {
+ prompt.insert_str(&condition)
+ }
+ compositor.push(Box::new(prompt));
+ });
Ok(call)
});
cx.jobs.callback(callback);
@@ -680,34 +736,36 @@ pub fn dap_edit_log(cx: &mut Context) {
None => return,
};
let callback = Box::pin(async move {
- let call: Callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
- let mut prompt = Prompt::new(
- "log-message:".into(),
- None,
- ui::completers::none,
- move |cx, input: &str, event: PromptEvent| {
- if event != PromptEvent::Validate {
- return;
- }
-
- let breakpoints = &mut cx.editor.breakpoints.get_mut(&path).unwrap();
- breakpoints[pos].log_message = match input {
- "" => None,
- input => Some(input.to_owned()),
- };
-
- let debugger = debugger!(cx.editor);
- if let Err(e) = breakpoints_changed(debugger, path.clone(), breakpoints) {
- cx.editor
- .set_error(format!("Failed to set breakpoints: {}", e));
- }
- },
- );
- if let Some(log_message) = breakpoint.log_message {
- prompt.insert_str(&log_message, editor);
- }
- compositor.push(Box::new(prompt));
- }));
+ let call: Callback =
+ Box::new(move |_editor: &mut Editor, compositor: &mut Compositor| {
+ let mut prompt = Prompt::new(
+ "log-message:".into(),
+ None,
+ ui::completers::none,
+ move |cx, input: &str, event: PromptEvent| {
+ if event != PromptEvent::Validate {
+ return;
+ }
+
+ let breakpoints = &mut cx.editor.breakpoints.get_mut(&path).unwrap();
+ breakpoints[pos].log_message = match input {
+ "" => None,
+ input => Some(input.to_owned()),
+ };
+
+ let debugger = debugger!(cx.editor);
+ if let Err(e) = breakpoints_changed(debugger, path.clone(), breakpoints)
+ {
+ cx.editor
+ .set_error(format!("Failed to set breakpoints: {}", e));
+ }
+ },
+ );
+ if let Some(log_message) = breakpoint.log_message {
+ prompt.insert_str(&log_message);
+ }
+ compositor.push(Box::new(prompt));
+ });
Ok(call)
});
cx.jobs.callback(callback);
@@ -732,38 +790,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,
+ |frame| frame.name.as_str().into(), // TODO: include thread_states in the label
+ 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,
+ Some((
+ frame.line.saturating_sub(1),
+ frame.end_line.unwrap_or(frame.line).saturating_sub(1),
+ )),
+ )
+ })
+ },
+ );
cx.push_layer(Box::new(picker))
}