2021-08-30 03:58:15 +02:00
|
|
|
use super::{align_view, Align, Context, Editor};
|
2021-09-04 21:57:58 +02:00
|
|
|
use crate::{
|
2021-12-03 05:27:00 +01:00
|
|
|
compositor::{self, Compositor},
|
|
|
|
job::{Callback, Jobs},
|
2022-02-15 02:33:55 +01:00
|
|
|
ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent, Text},
|
2021-09-04 21:57:58 +02:00
|
|
|
};
|
2021-10-24 16:24:18 +02:00
|
|
|
use helix_core::{
|
|
|
|
syntax::{DebugArgumentValue, DebugConfigCompletion},
|
|
|
|
Selection,
|
|
|
|
};
|
2021-10-17 07:06:52 +02:00
|
|
|
use helix_dap::{self as dap, Client, ThreadId};
|
2021-08-29 15:43:00 +02:00
|
|
|
use helix_lsp::block_on;
|
2021-11-30 09:52:39 +01:00
|
|
|
use helix_view::editor::Breakpoint;
|
2021-08-29 15:43:00 +02:00
|
|
|
|
|
|
|
use serde_json::{to_value, Value};
|
|
|
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
|
|
|
|
|
|
|
use std::collections::HashMap;
|
2021-12-03 05:27:00 +01:00
|
|
|
use std::future::Future;
|
2021-11-30 09:52:39 +01:00
|
|
|
use std::path::PathBuf;
|
2021-08-29 15:43:00 +02:00
|
|
|
|
2021-12-05 06:52:56 +01:00
|
|
|
use anyhow::{anyhow, bail};
|
2021-12-03 05:27:00 +01:00
|
|
|
|
2021-12-07 16:59:11 +01:00
|
|
|
#[macro_export]
|
|
|
|
macro_rules! debugger {
|
|
|
|
($editor:expr) => {{
|
|
|
|
match &mut $editor.debugger {
|
|
|
|
Some(debugger) => debugger,
|
|
|
|
None => return,
|
|
|
|
}
|
|
|
|
}};
|
|
|
|
}
|
|
|
|
|
2021-08-30 03:58:15 +02:00
|
|
|
// 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()?;
|
2021-12-07 16:22:48 +01:00
|
|
|
let pos = line + column.saturating_sub(1);
|
2021-08-30 03:58:15 +02:00
|
|
|
// TODO: this is probably utf-16 offsets
|
|
|
|
Some(pos)
|
|
|
|
}
|
|
|
|
|
2021-10-17 07:06:52 +02:00
|
|
|
pub async fn select_thread_id(editor: &mut Editor, thread_id: ThreadId, force: bool) {
|
2021-12-07 16:59:11 +01:00
|
|
|
let debugger = debugger!(editor);
|
2021-08-30 03:58:15 +02:00
|
|
|
|
|
|
|
if !force && debugger.thread_id.is_some() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
debugger.thread_id = Some(thread_id);
|
2021-09-05 15:09:38 +02:00
|
|
|
fetch_stack_trace(debugger, thread_id).await;
|
2021-08-30 03:58:15 +02:00
|
|
|
|
2021-09-05 15:09:38 +02:00
|
|
|
let frame = debugger.stack_frames[&thread_id].get(0).cloned();
|
|
|
|
if let Some(frame) = &frame {
|
|
|
|
jump_to_stack_frame(editor, frame);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-17 07:06:52 +02:00
|
|
|
pub async fn fetch_stack_trace(debugger: &mut Client, thread_id: ThreadId) {
|
2021-09-04 09:28:11 +02:00
|
|
|
let (frames, _) = match debugger.stack_trace(thread_id).await {
|
|
|
|
Ok(frames) => frames,
|
|
|
|
Err(_) => return,
|
|
|
|
};
|
2021-08-30 03:58:15 +02:00
|
|
|
debugger.stack_frames.insert(thread_id, frames);
|
2021-09-05 15:09:38 +02:00
|
|
|
debugger.active_frame = Some(0);
|
2021-08-30 03:58:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2021-12-02 02:20:19 +01:00
|
|
|
if let Err(e) = editor.open(path, helix_view::editor::Action::Replace) {
|
|
|
|
editor.set_error(format!("Unable to jump to stack frame: {}", e));
|
|
|
|
return;
|
|
|
|
}
|
2021-08-30 03:58:15 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-12-05 06:52:56 +01:00
|
|
|
fn thread_picker(
|
|
|
|
cx: &mut Context,
|
|
|
|
callback_fn: impl Fn(&mut Editor, &dap::Thread) + Send + 'static,
|
|
|
|
) {
|
2021-12-07 16:59:11 +01:00
|
|
|
let debugger = debugger!(cx.editor);
|
2021-09-03 04:40:49 +02:00
|
|
|
|
2021-12-05 06:52:56 +01:00
|
|
|
let future = debugger.threads();
|
|
|
|
dap_callback(
|
|
|
|
cx.jobs,
|
|
|
|
future,
|
|
|
|
move |editor: &mut Editor,
|
|
|
|
compositor: &mut Compositor,
|
|
|
|
response: dap::requests::ThreadsResponse| {
|
|
|
|
let threads = response.threads;
|
|
|
|
if threads.len() == 1 {
|
|
|
|
callback_fn(editor, &threads[0]);
|
|
|
|
return;
|
|
|
|
}
|
2021-12-08 02:21:58 +01:00
|
|
|
let debugger = debugger!(editor);
|
2021-12-05 06:52:56 +01:00
|
|
|
|
|
|
|
let thread_states = debugger.thread_states.clone();
|
|
|
|
let picker = FilePicker::new(
|
|
|
|
threads,
|
|
|
|
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),
|
|
|
|
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));
|
2021-09-05 12:39:27 +02:00
|
|
|
},
|
2021-09-03 04:40:49 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-12-02 02:21:28 +01:00
|
|
|
fn get_breakpoint_at_current_line(editor: &mut Editor) -> Option<(usize, Breakpoint)> {
|
|
|
|
let (view, doc) = current!(editor);
|
|
|
|
let text = doc.text().slice(..);
|
|
|
|
|
2021-12-02 02:24:17 +01:00
|
|
|
let line = doc.selection(view.id).primary().cursor_line(text);
|
2021-12-02 02:31:19 +01:00
|
|
|
let path = doc.path()?;
|
2021-12-02 02:21:28 +01:00
|
|
|
editor.breakpoints.get(path).and_then(|breakpoints| {
|
|
|
|
let i = breakpoints.iter().position(|b| b.line == line);
|
|
|
|
i.map(|i| (i, breakpoints[i].clone()))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-09-03 04:40:49 +02:00
|
|
|
// -- DAP
|
|
|
|
|
2021-12-03 05:27:00 +01:00
|
|
|
fn dap_callback<T, F>(
|
|
|
|
jobs: &mut Jobs,
|
|
|
|
call: impl Future<Output = helix_dap::Result<serde_json::Value>> + 'static + Send,
|
|
|
|
callback: F,
|
|
|
|
) where
|
|
|
|
T: for<'de> serde::Deserialize<'de> + Send + 'static,
|
|
|
|
F: FnOnce(&mut Editor, &mut Compositor, T) + Send + 'static,
|
|
|
|
{
|
|
|
|
let callback = Box::pin(async move {
|
|
|
|
let json = call.await?;
|
|
|
|
let response = serde_json::from_value(json)?;
|
|
|
|
let call: Callback = Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
|
|
|
|
callback(editor, compositor, response)
|
|
|
|
});
|
|
|
|
Ok(call)
|
|
|
|
});
|
|
|
|
jobs.callback(callback);
|
|
|
|
}
|
|
|
|
|
2021-08-29 15:43:00 +02:00
|
|
|
pub fn dap_start_impl(
|
2021-12-03 05:27:00 +01:00
|
|
|
cx: &mut compositor::Context,
|
2021-08-29 15:43:00 +02:00
|
|
|
name: Option<&str>,
|
|
|
|
socket: Option<std::net::SocketAddr>,
|
2022-02-13 10:31:51 +01:00
|
|
|
params: Option<Vec<std::borrow::Cow<str>>>,
|
2021-12-03 05:27:00 +01:00
|
|
|
) -> Result<(), anyhow::Error> {
|
|
|
|
let doc = doc!(cx.editor);
|
2021-08-29 15:43:00 +02:00
|
|
|
|
2021-12-06 01:32:11 +01:00
|
|
|
let config = doc
|
2021-11-07 13:26:03 +01:00
|
|
|
.language_config()
|
2021-11-07 10:37:42 +01:00
|
|
|
.and_then(|config| config.debugger.as_ref())
|
2021-12-06 01:32:11 +01:00
|
|
|
.ok_or(anyhow!("No debug adapter available for language"))?;
|
2021-08-29 15:43:00 +02:00
|
|
|
|
|
|
|
let result = match socket {
|
|
|
|
Some(socket) => block_on(Client::tcp(socket, 0)),
|
|
|
|
None => block_on(Client::process(
|
2021-11-07 10:37:42 +01:00
|
|
|
&config.transport,
|
|
|
|
&config.command,
|
|
|
|
config.args.iter().map(|arg| arg.as_str()).collect(),
|
|
|
|
config.port_arg.as_deref(),
|
2021-08-29 15:43:00 +02:00
|
|
|
0,
|
|
|
|
)),
|
|
|
|
};
|
|
|
|
|
|
|
|
let (mut debugger, events) = match result {
|
|
|
|
Ok(r) => r,
|
2021-12-03 05:27:00 +01:00
|
|
|
Err(e) => bail!("Failed to start debug session: {}", e),
|
2021-08-29 15:43:00 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
let request = debugger.initialize(config.name.clone());
|
|
|
|
if let Err(e) = block_on(request) {
|
2021-12-03 05:27:00 +01:00
|
|
|
bail!("Failed to initialize debug adapter: {}", e);
|
2021-08-29 15:43:00 +02:00
|
|
|
}
|
|
|
|
|
2021-11-07 10:37:42 +01:00
|
|
|
debugger.quirks = config.quirks.clone();
|
2021-09-26 20:36:06 +02:00
|
|
|
|
2021-11-07 10:57:44 +01:00
|
|
|
// TODO: avoid refetching all of this... pass a config in
|
2021-11-07 13:47:44 +01:00
|
|
|
let template = match name {
|
2021-08-29 15:43:00 +02:00
|
|
|
Some(name) => config.templates.iter().find(|t| t.name == name),
|
|
|
|
None => config.templates.get(0),
|
2021-12-06 01:32:11 +01:00
|
|
|
}
|
|
|
|
.ok_or(anyhow!("No debug config with given name"))?;
|
2021-08-29 15:43:00 +02:00
|
|
|
|
2021-11-07 13:47:44 +01:00
|
|
|
let mut args: HashMap<&str, Value> = HashMap::new();
|
2021-08-29 15:43:00 +02:00
|
|
|
|
|
|
|
if let Some(params) = params {
|
2021-11-07 13:47:44 +01:00
|
|
|
for (k, t) in &template.args {
|
|
|
|
let mut value = t.clone();
|
2021-08-29 15:43:00 +02:00
|
|
|
for (i, x) in params.iter().enumerate() {
|
2021-09-26 20:36:06 +02:00
|
|
|
let mut param = x.to_string();
|
2021-11-07 13:47:44 +01:00
|
|
|
if let Some(DebugConfigCompletion::Advanced(cfg)) = template.completion.get(i) {
|
2021-11-07 13:55:57 +01:00
|
|
|
if matches!(cfg.completion.as_deref(), Some("filename" | "directory")) {
|
2022-02-13 10:31:51 +01:00
|
|
|
param = std::fs::canonicalize(x.as_ref())
|
2021-09-26 20:54:36 +02:00
|
|
|
.ok()
|
2021-09-26 20:36:06 +02:00
|
|
|
.and_then(|pb| pb.into_os_string().into_string().ok())
|
2021-09-26 20:54:36 +02:00
|
|
|
.unwrap_or_else(|| x.to_string());
|
2021-09-26 20:36:06 +02:00
|
|
|
}
|
|
|
|
}
|
2021-08-29 15:43:00 +02:00
|
|
|
// For param #0 replace {0} in args
|
2021-11-07 13:47:44 +01:00
|
|
|
let pattern = format!("{{{}}}", i);
|
2021-10-24 16:24:18 +02:00
|
|
|
value = match value {
|
2021-12-03 03:59:44 +01:00
|
|
|
// TODO: just use toml::Value -> json::Value
|
2021-10-24 16:24:18 +02:00
|
|
|
DebugArgumentValue::String(v) => {
|
2021-11-07 13:47:44 +01:00
|
|
|
DebugArgumentValue::String(v.replace(&pattern, ¶m))
|
2021-10-24 16:24:18 +02:00
|
|
|
}
|
|
|
|
DebugArgumentValue::Array(arr) => DebugArgumentValue::Array(
|
2021-11-07 13:47:44 +01:00
|
|
|
arr.iter().map(|v| v.replace(&pattern, ¶m)).collect(),
|
2021-10-24 16:24:18 +02:00
|
|
|
),
|
2021-12-03 03:59:44 +01:00
|
|
|
DebugArgumentValue::Boolean(_) => value,
|
2021-10-24 16:24:18 +02:00
|
|
|
};
|
2021-08-29 15:43:00 +02:00
|
|
|
}
|
|
|
|
|
2021-12-03 03:59:44 +01:00
|
|
|
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());
|
2021-10-24 16:24:18 +02:00
|
|
|
}
|
2021-08-29 15:43:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let args = to_value(args).unwrap();
|
|
|
|
|
2021-12-03 05:27:00 +01:00
|
|
|
let callback = |_editor: &mut Editor, _compositor: &mut Compositor, _response: Value| {
|
|
|
|
// if let Err(e) = result {
|
|
|
|
// editor.set_error(format!("Failed {} target: {}", template.request, e));
|
|
|
|
// }
|
|
|
|
};
|
2021-12-03 03:59:44 +01:00
|
|
|
|
2021-12-03 05:27:00 +01:00
|
|
|
match &template.request[..] {
|
|
|
|
"launch" => {
|
|
|
|
let call = debugger.launch(args);
|
|
|
|
dap_callback(cx.jobs, call, callback);
|
|
|
|
}
|
|
|
|
"attach" => {
|
|
|
|
let call = debugger.attach(args);
|
|
|
|
dap_callback(cx.jobs, call, callback);
|
2021-08-29 15:43:00 +02:00
|
|
|
}
|
2021-12-03 05:27:00 +01:00
|
|
|
request => bail!("Unsupported request '{}'", request),
|
2021-08-29 15:43:00 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// TODO: either await "initialized" or buffer commands until event is received
|
2021-12-03 05:27:00 +01:00
|
|
|
cx.editor.debugger = Some(debugger);
|
2021-08-29 15:43:00 +02:00
|
|
|
let stream = UnboundedReceiverStream::new(events);
|
2021-12-03 05:27:00 +01:00
|
|
|
cx.editor.debugger_events.push(stream);
|
|
|
|
Ok(())
|
2021-08-29 15:43:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn dap_launch(cx: &mut Context) {
|
|
|
|
if cx.editor.debugger.is_some() {
|
|
|
|
cx.editor
|
2021-11-07 13:37:00 +01:00
|
|
|
.set_error("Debugger is already running".to_string());
|
2021-08-29 15:43:00 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-07 13:26:03 +01:00
|
|
|
let doc = doc!(cx.editor);
|
2021-08-29 15:43:00 +02:00
|
|
|
|
2021-11-07 13:26:03 +01:00
|
|
|
let config = match doc
|
|
|
|
.language_config()
|
2021-11-07 10:37:42 +01:00
|
|
|
.and_then(|config| config.debugger.as_ref())
|
|
|
|
{
|
2021-08-29 15:43:00 +02:00
|
|
|
Some(c) => c,
|
|
|
|
None => {
|
2021-11-07 13:37:00 +01:00
|
|
|
cx.editor
|
|
|
|
.set_error("No debug adapter available for language".to_string());
|
2021-08-29 15:43:00 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-11-07 13:26:03 +01:00
|
|
|
let templates = config.templates.clone();
|
|
|
|
|
2022-02-15 02:33:55 +01:00
|
|
|
cx.push_layer(Box::new(overlayed(Picker::new(
|
2021-11-07 13:26:03 +01:00
|
|
|
templates,
|
2021-10-17 08:14:16 +02:00
|
|
|
|template| template.name.as_str().into(),
|
2021-11-07 10:03:04 +01:00
|
|
|
|cx, template, _action| {
|
2021-10-17 08:14:16 +02:00
|
|
|
let completions = template.completion.clone();
|
2021-11-07 10:12:30 +01:00
|
|
|
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);
|
2021-10-17 08:14:16 +02:00
|
|
|
},
|
2022-02-15 03:37:33 +01:00
|
|
|
))));
|
2021-08-29 15:43:00 +02:00
|
|
|
}
|
|
|
|
|
2021-11-07 10:12:30 +01:00
|
|
|
fn debug_parameter_prompt(
|
|
|
|
completions: Vec<DebugConfigCompletion>,
|
|
|
|
config_name: String,
|
|
|
|
mut params: Vec<String>,
|
|
|
|
) -> Prompt {
|
2021-11-07 13:55:57 +01:00
|
|
|
let completion = completions.get(params.len()).unwrap();
|
2021-11-07 10:12:30 +01:00
|
|
|
let field_type = if let DebugConfigCompletion::Advanced(cfg) = completion {
|
2021-11-07 13:55:57 +01:00
|
|
|
cfg.completion.as_deref().unwrap_or("")
|
2021-11-07 10:12:30 +01:00
|
|
|
} else {
|
2021-11-07 13:55:57 +01:00
|
|
|
""
|
2021-11-07 10:12:30 +01:00
|
|
|
};
|
|
|
|
let name = match completion {
|
2021-11-07 13:55:57 +01:00
|
|
|
DebugConfigCompletion::Advanced(cfg) => cfg.name.as_deref().unwrap_or(field_type),
|
|
|
|
DebugConfigCompletion::Named(name) => name.as_str(),
|
2021-11-07 10:12:30 +01:00
|
|
|
};
|
|
|
|
let default_val = match completion {
|
2021-11-07 13:55:57 +01:00
|
|
|
DebugConfigCompletion::Advanced(cfg) => cfg.default.as_deref().unwrap_or(""),
|
|
|
|
_ => "",
|
|
|
|
}
|
|
|
|
.to_owned();
|
2021-11-07 10:12:30 +01:00
|
|
|
|
2021-11-07 13:55:57 +01:00
|
|
|
let completer = match field_type {
|
2021-11-07 10:12:30 +01:00
|
|
|
"filename" => ui::completers::filename,
|
|
|
|
"directory" => ui::completers::directory,
|
2021-11-07 10:15:11 +01:00
|
|
|
_ => |_input: &str| Vec::new(),
|
2021-11-07 10:12:30 +01:00
|
|
|
};
|
|
|
|
Prompt::new(
|
|
|
|
format!("{}: ", name).into(),
|
|
|
|
None,
|
|
|
|
completer,
|
2021-11-22 03:22:08 +01:00
|
|
|
move |cx, input: &str, event: PromptEvent| {
|
2021-11-07 10:12:30 +01:00
|
|
|
if event != PromptEvent::Validate {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut value = input.to_owned();
|
|
|
|
if value.is_empty() {
|
|
|
|
value = default_val.clone();
|
|
|
|
}
|
|
|
|
params.push(value);
|
|
|
|
|
|
|
|
if params.len() < completions.len() {
|
|
|
|
let completions = completions.clone();
|
|
|
|
let config_name = config_name.clone();
|
|
|
|
let params = params.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, config_name, params);
|
|
|
|
compositor.push(Box::new(prompt));
|
|
|
|
});
|
|
|
|
Ok(call)
|
|
|
|
});
|
|
|
|
cx.jobs.callback(callback);
|
2021-12-05 06:55:35 +01:00
|
|
|
} else if let Err(e) = dap_start_impl(
|
|
|
|
cx,
|
|
|
|
Some(&config_name),
|
|
|
|
None,
|
2022-02-13 10:31:51 +01:00
|
|
|
Some(params.iter().map(|x| x.into()).collect()),
|
2021-12-05 06:55:35 +01:00
|
|
|
) {
|
|
|
|
cx.editor.set_error(e.to_string());
|
2021-11-07 10:12:30 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-08-29 15:43:00 +02:00
|
|
|
pub fn dap_toggle_breakpoint(cx: &mut Context) {
|
|
|
|
let (view, doc) = current!(cx.editor);
|
|
|
|
let path = match doc.path() {
|
2021-11-22 08:30:35 +01:00
|
|
|
Some(path) => path.clone(),
|
2021-08-29 15:43:00 +02:00
|
|
|
None => {
|
|
|
|
cx.editor
|
|
|
|
.set_error("Can't set breakpoint: document has no path".to_string());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
2021-11-22 08:30:35 +01:00
|
|
|
let text = doc.text().slice(..);
|
2021-12-02 02:24:17 +01:00
|
|
|
let line = doc.selection(view.id).primary().cursor_line(text);
|
2021-11-22 08:30:35 +01:00
|
|
|
dap_toggle_breakpoint_impl(cx, path, line);
|
|
|
|
}
|
|
|
|
|
2021-11-30 09:52:39 +01:00
|
|
|
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),
|
|
|
|
_ => {}
|
2021-11-22 08:30:35 +01:00
|
|
|
};
|
2021-11-30 09:52:39 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
2021-08-29 15:43:00 +02:00
|
|
|
|
2021-11-30 09:52:39 +01:00
|
|
|
pub fn dap_toggle_breakpoint_impl(cx: &mut Context, path: PathBuf, line: usize) {
|
2021-08-29 15:43:00 +02:00
|
|
|
// TODO: need to map breakpoints over edits and update them?
|
|
|
|
// we shouldn't really allow editing while debug is running though
|
|
|
|
|
2021-09-03 06:02:09 +02:00
|
|
|
let breakpoints = cx.editor.breakpoints.entry(path.clone()).or_default();
|
2021-11-30 09:52:39 +01:00
|
|
|
// TODO: always keep breakpoints sorted and use binary search to determine insertion point
|
|
|
|
if let Some(pos) = breakpoints
|
|
|
|
.iter()
|
|
|
|
.position(|breakpoint| breakpoint.line == line)
|
|
|
|
{
|
2021-09-03 04:10:30 +02:00
|
|
|
breakpoints.remove(pos);
|
|
|
|
} else {
|
2021-11-30 09:52:39 +01:00
|
|
|
breakpoints.push(Breakpoint {
|
|
|
|
line,
|
|
|
|
..Default::default()
|
|
|
|
});
|
2021-09-03 04:10:30 +02:00
|
|
|
}
|
2021-08-29 15:43:00 +02:00
|
|
|
|
2021-12-07 16:59:11 +01:00
|
|
|
let debugger = debugger!(cx.editor);
|
2021-11-30 09:52:39 +01:00
|
|
|
|
|
|
|
if let Err(e) = breakpoints_changed(debugger, path, breakpoints) {
|
|
|
|
cx.editor
|
|
|
|
.set_error(format!("Failed to set breakpoints: {}", e));
|
|
|
|
}
|
2021-08-29 15:43:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn dap_continue(cx: &mut Context) {
|
2021-12-07 16:59:11 +01:00
|
|
|
let debugger = debugger!(cx.editor);
|
2021-08-29 15:43:00 +02:00
|
|
|
|
2021-09-03 04:10:30 +02:00
|
|
|
if let Some(thread_id) = debugger.thread_id {
|
|
|
|
let request = debugger.continue_thread(thread_id);
|
2022-02-15 08:30:23 +01:00
|
|
|
|
|
|
|
dap_callback(
|
|
|
|
cx.jobs,
|
|
|
|
request,
|
|
|
|
|editor, _compositor, _response: dap::requests::ContinueResponse| {
|
|
|
|
debugger!(editor).resume_application();
|
|
|
|
},
|
|
|
|
);
|
2021-09-03 04:10:30 +02:00
|
|
|
} else {
|
|
|
|
cx.editor
|
|
|
|
.set_error("Currently active thread is not stopped. Switch the thread.".into());
|
2021-08-29 15:43:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn dap_pause(cx: &mut Context) {
|
2021-09-03 04:40:49 +02:00
|
|
|
thread_picker(cx, |editor, thread| {
|
2021-12-08 02:21:58 +01:00
|
|
|
let debugger = debugger!(editor);
|
2021-09-03 04:40:49 +02:00
|
|
|
let request = debugger.pause(thread.id);
|
|
|
|
// NOTE: we don't need to set active thread id here because DAP will emit a "stopped" event
|
|
|
|
if let Err(e) = block_on(request) {
|
2021-11-07 13:20:58 +01:00
|
|
|
editor.set_error(format!("Failed to pause: {}", e));
|
2021-09-03 04:40:49 +02:00
|
|
|
}
|
|
|
|
})
|
2021-08-29 15:43:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn dap_step_in(cx: &mut Context) {
|
2021-12-07 16:59:11 +01:00
|
|
|
let debugger = debugger!(cx.editor);
|
2021-08-29 15:43:00 +02:00
|
|
|
|
2021-09-03 04:10:30 +02:00
|
|
|
if let Some(thread_id) = debugger.thread_id {
|
|
|
|
let request = debugger.step_in(thread_id);
|
2022-02-15 08:30:23 +01:00
|
|
|
|
|
|
|
dap_callback(cx.jobs, request, |editor, _compositor, _response: ()| {
|
|
|
|
debugger!(editor).resume_application();
|
|
|
|
});
|
2021-09-03 04:10:30 +02:00
|
|
|
} else {
|
|
|
|
cx.editor
|
|
|
|
.set_error("Currently active thread is not stopped. Switch the thread.".into());
|
2021-08-29 15:43:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn dap_step_out(cx: &mut Context) {
|
2021-12-07 16:59:11 +01:00
|
|
|
let debugger = debugger!(cx.editor);
|
2021-08-29 15:43:00 +02:00
|
|
|
|
2021-09-03 04:10:30 +02:00
|
|
|
if let Some(thread_id) = debugger.thread_id {
|
|
|
|
let request = debugger.step_out(thread_id);
|
2022-02-15 08:30:23 +01:00
|
|
|
dap_callback(cx.jobs, request, |editor, _compositor, _response: ()| {
|
|
|
|
debugger!(editor).resume_application();
|
|
|
|
});
|
2021-09-03 04:10:30 +02:00
|
|
|
} else {
|
|
|
|
cx.editor
|
|
|
|
.set_error("Currently active thread is not stopped. Switch the thread.".into());
|
2021-08-29 15:43:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn dap_next(cx: &mut Context) {
|
2021-12-07 16:59:11 +01:00
|
|
|
let debugger = debugger!(cx.editor);
|
2021-08-29 15:43:00 +02:00
|
|
|
|
2021-09-03 04:10:30 +02:00
|
|
|
if let Some(thread_id) = debugger.thread_id {
|
|
|
|
let request = debugger.next(thread_id);
|
2022-02-15 08:30:23 +01:00
|
|
|
dap_callback(cx.jobs, request, |editor, _compositor, _response: ()| {
|
|
|
|
debugger!(editor).resume_application();
|
|
|
|
});
|
2021-09-03 04:10:30 +02:00
|
|
|
} else {
|
|
|
|
cx.editor
|
|
|
|
.set_error("Currently active thread is not stopped. Switch the thread.".into());
|
2021-08-29 15:43:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn dap_variables(cx: &mut Context) {
|
2021-12-07 16:59:11 +01:00
|
|
|
let debugger = debugger!(cx.editor);
|
2021-09-03 04:10:30 +02:00
|
|
|
|
2021-09-03 04:30:25 +02:00
|
|
|
if debugger.thread_id.is_none() {
|
2021-09-03 04:10:30 +02:00
|
|
|
cx.editor
|
|
|
|
.set_status("Cannot access variables while target is running".to_owned());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let (frame, thread_id) = match (debugger.active_frame, debugger.thread_id) {
|
|
|
|
(Some(frame), Some(thread_id)) => (frame, thread_id),
|
|
|
|
_ => {
|
2021-08-29 15:43:00 +02:00
|
|
|
cx.editor
|
2021-09-03 04:10:30 +02:00
|
|
|
.set_status("Cannot find current stack frame to access variables".to_owned());
|
2021-08-29 15:43:00 +02:00
|
|
|
return;
|
|
|
|
}
|
2021-09-03 04:10:30 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
let frame_id = debugger.stack_frames[&thread_id][frame].id;
|
|
|
|
let scopes = match block_on(debugger.scopes(frame_id)) {
|
|
|
|
Ok(s) => s,
|
|
|
|
Err(e) => {
|
2021-11-07 13:20:58 +01:00
|
|
|
cx.editor.set_error(format!("Failed to get scopes: {}", e));
|
2021-09-03 04:10:30 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
2021-12-08 06:50:20 +01:00
|
|
|
|
2021-12-13 16:41:51 +01:00
|
|
|
// TODO: allow expanding variables into sub-fields
|
|
|
|
let mut variables = Vec::new();
|
2021-12-09 03:28:53 +01:00
|
|
|
|
|
|
|
let theme = &cx.editor.theme;
|
|
|
|
let scope_style = theme.get("ui.linenr.selected");
|
|
|
|
let type_style = theme.get("ui.text");
|
|
|
|
let text_style = theme.get("ui.text.focus");
|
|
|
|
|
2021-09-03 04:10:30 +02:00
|
|
|
for scope in scopes.iter() {
|
2021-12-09 03:28:53 +01:00
|
|
|
// use helix_view::graphics::Style;
|
|
|
|
use tui::text::{Span, Spans};
|
2021-09-03 04:10:30 +02:00
|
|
|
let response = block_on(debugger.variables(scope.variables_reference));
|
|
|
|
|
2021-12-09 03:28:53 +01:00
|
|
|
variables.push(Spans::from(Span::styled(
|
|
|
|
format!("▸ {}", scope.name),
|
|
|
|
scope_style,
|
|
|
|
)));
|
|
|
|
|
2021-09-03 04:10:30 +02:00
|
|
|
if let Ok(vars) = response {
|
|
|
|
variables.reserve(vars.len());
|
|
|
|
for var in vars {
|
2021-12-09 03:28:53 +01:00
|
|
|
let mut spans = Vec::with_capacity(5);
|
|
|
|
|
|
|
|
spans.push(Span::styled(var.name.to_owned(), text_style));
|
|
|
|
if let Some(ty) = var.ty {
|
|
|
|
spans.push(Span::raw(": "));
|
|
|
|
spans.push(Span::styled(ty.to_owned(), type_style));
|
|
|
|
}
|
|
|
|
spans.push(Span::raw(" = "));
|
|
|
|
spans.push(Span::styled(var.value.to_owned(), text_style));
|
|
|
|
variables.push(Spans::from(spans));
|
2021-08-29 15:43:00 +02:00
|
|
|
}
|
|
|
|
}
|
2021-09-03 04:10:30 +02:00
|
|
|
}
|
2021-08-29 15:43:00 +02:00
|
|
|
|
2021-12-09 03:28:53 +01:00
|
|
|
let contents = Text::from(tui::text::Text::from(variables));
|
2022-02-13 10:31:51 +01:00
|
|
|
let popup = Popup::new("dap-variables", contents);
|
2021-11-07 09:55:01 +01:00
|
|
|
cx.push_layer(Box::new(popup));
|
2021-08-29 15:43:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn dap_terminate(cx: &mut Context) {
|
2021-12-07 16:59:11 +01:00
|
|
|
let debugger = debugger!(cx.editor);
|
2021-09-03 04:10:30 +02:00
|
|
|
|
|
|
|
let request = debugger.disconnect();
|
2022-02-15 08:30:23 +01:00
|
|
|
dap_callback(cx.jobs, request, |editor, _compositor, _response: ()| {
|
|
|
|
// editor.set_error(format!("Failed to disconnect: {}", e));
|
|
|
|
editor.debugger = None;
|
|
|
|
});
|
2021-08-29 15:43:00 +02:00
|
|
|
}
|
2021-08-29 16:28:31 +02:00
|
|
|
|
2021-09-26 09:24:58 +02:00
|
|
|
pub fn dap_enable_exceptions(cx: &mut Context) {
|
2021-12-07 16:59:11 +01:00
|
|
|
let debugger = debugger!(cx.editor);
|
2021-09-26 09:24:58 +02:00
|
|
|
|
|
|
|
let filters = match &debugger.capabilities().exception_breakpoint_filters {
|
2021-11-21 16:02:58 +01:00
|
|
|
Some(filters) => filters.iter().map(|f| f.filter.clone()).collect(),
|
2021-09-26 09:24:58 +02:00
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
|
2022-02-15 08:30:23 +01:00
|
|
|
let request = debugger.set_exception_breakpoints(filters);
|
|
|
|
|
|
|
|
dap_callback(
|
|
|
|
cx.jobs,
|
|
|
|
request,
|
|
|
|
|_editor, _compositor, _response: dap::requests::SetExceptionBreakpointsResponse| {
|
|
|
|
// editor.set_error(format!("Failed to set up exception breakpoints: {}", e));
|
|
|
|
},
|
|
|
|
)
|
2021-09-26 09:24:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn dap_disable_exceptions(cx: &mut Context) {
|
2021-12-07 16:59:11 +01:00
|
|
|
let debugger = debugger!(cx.editor);
|
2021-09-26 09:24:58 +02:00
|
|
|
|
2022-02-15 08:30:23 +01:00
|
|
|
let request = debugger.set_exception_breakpoints(Vec::new());
|
|
|
|
|
|
|
|
dap_callback(
|
|
|
|
cx.jobs,
|
|
|
|
request,
|
|
|
|
|_editor, _compositor, _response: dap::requests::SetExceptionBreakpointsResponse| {
|
|
|
|
// editor.set_error(format!("Failed to set up exception breakpoints: {}", e));
|
|
|
|
},
|
|
|
|
)
|
2021-09-26 09:24:58 +02:00
|
|
|
}
|
|
|
|
|
2021-11-22 03:09:09 +01:00
|
|
|
// TODO: both edit condition and edit log need to be stable: we might get new breakpoints from the debugger which can change offsets
|
2021-09-04 21:57:58 +02:00
|
|
|
pub fn dap_edit_condition(cx: &mut Context) {
|
2021-12-02 02:21:28 +01:00
|
|
|
if let Some((pos, breakpoint)) = get_breakpoint_at_current_line(cx.editor) {
|
2021-11-22 03:22:08 +01:00
|
|
|
let path = match doc!(cx.editor).path() {
|
|
|
|
Some(path) => path.clone(),
|
|
|
|
None => return,
|
|
|
|
};
|
2021-09-05 07:14:17 +02:00
|
|
|
let callback = Box::pin(async move {
|
|
|
|
let call: Callback =
|
|
|
|
Box::new(move |_editor: &mut Editor, compositor: &mut Compositor| {
|
2021-11-22 03:13:22 +01:00
|
|
|
let mut prompt = Prompt::new(
|
|
|
|
"condition:".into(),
|
2021-09-05 07:14:17 +02:00
|
|
|
None,
|
|
|
|
|_input: &str| Vec::new(),
|
2021-11-22 03:22:08 +01:00
|
|
|
move |cx, input: &str, event: PromptEvent| {
|
2021-09-05 07:14:17 +02:00
|
|
|
if event != PromptEvent::Validate {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-22 03:22:08 +01:00
|
|
|
let breakpoints = &mut cx.editor.breakpoints.get_mut(&path).unwrap();
|
2021-11-22 03:09:09 +01:00
|
|
|
breakpoints[pos].condition = match input {
|
2021-09-05 07:14:17 +02:00
|
|
|
"" => None,
|
|
|
|
input => Some(input.to_owned()),
|
|
|
|
};
|
|
|
|
|
2021-12-07 16:59:11 +01:00
|
|
|
let debugger = debugger!(cx.editor);
|
2021-11-30 09:52:39 +01:00
|
|
|
|
|
|
|
if let Err(e) = breakpoints_changed(debugger, path.clone(), breakpoints)
|
|
|
|
{
|
|
|
|
cx.editor
|
|
|
|
.set_error(format!("Failed to set breakpoints: {}", e));
|
2021-09-05 07:14:17 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
2021-11-22 03:13:22 +01:00
|
|
|
if let Some(condition) = breakpoint.condition {
|
|
|
|
prompt.insert_str(&condition)
|
|
|
|
}
|
2021-09-05 07:14:17 +02:00
|
|
|
compositor.push(Box::new(prompt));
|
2021-09-05 07:50:03 +02:00
|
|
|
});
|
|
|
|
Ok(call)
|
|
|
|
});
|
|
|
|
cx.jobs.callback(callback);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn dap_edit_log(cx: &mut Context) {
|
2021-12-02 02:21:28 +01:00
|
|
|
if let Some((pos, breakpoint)) = get_breakpoint_at_current_line(cx.editor) {
|
2021-11-22 03:22:08 +01:00
|
|
|
let path = match doc!(cx.editor).path() {
|
|
|
|
Some(path) => path.clone(),
|
|
|
|
None => return,
|
|
|
|
};
|
2021-09-05 07:50:03 +02:00
|
|
|
let callback = Box::pin(async move {
|
|
|
|
let call: Callback =
|
|
|
|
Box::new(move |_editor: &mut Editor, compositor: &mut Compositor| {
|
2021-11-22 03:13:22 +01:00
|
|
|
let mut prompt = Prompt::new(
|
|
|
|
"log-message:".into(),
|
2021-09-05 07:50:03 +02:00
|
|
|
None,
|
|
|
|
|_input: &str| Vec::new(),
|
2021-11-22 03:22:08 +01:00
|
|
|
move |cx, input: &str, event: PromptEvent| {
|
2021-09-05 07:50:03 +02:00
|
|
|
if event != PromptEvent::Validate {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-22 03:22:08 +01:00
|
|
|
let breakpoints = &mut cx.editor.breakpoints.get_mut(&path).unwrap();
|
2021-11-22 03:09:09 +01:00
|
|
|
breakpoints[pos].log_message = match input {
|
2021-09-05 07:50:03 +02:00
|
|
|
"" => None,
|
|
|
|
input => Some(input.to_owned()),
|
|
|
|
};
|
|
|
|
|
2021-12-07 16:59:11 +01:00
|
|
|
let debugger = debugger!(cx.editor);
|
2021-11-30 09:52:39 +01:00
|
|
|
if let Err(e) = breakpoints_changed(debugger, path.clone(), breakpoints)
|
|
|
|
{
|
|
|
|
cx.editor
|
|
|
|
.set_error(format!("Failed to set breakpoints: {}", e));
|
2021-09-05 07:50:03 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
2021-11-22 03:13:22 +01:00
|
|
|
if let Some(log_message) = breakpoint.log_message {
|
|
|
|
prompt.insert_str(&log_message);
|
|
|
|
}
|
2021-09-05 07:50:03 +02:00
|
|
|
compositor.push(Box::new(prompt));
|
2021-09-05 07:14:17 +02:00
|
|
|
});
|
|
|
|
Ok(call)
|
|
|
|
});
|
|
|
|
cx.jobs.callback(callback);
|
2021-09-04 21:57:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-29 16:28:31 +02:00
|
|
|
pub fn dap_switch_thread(cx: &mut Context) {
|
2021-09-03 04:40:49 +02:00
|
|
|
thread_picker(cx, |editor, thread| {
|
|
|
|
block_on(select_thread_id(editor, thread.id, true));
|
|
|
|
})
|
2021-08-29 16:28:31 +02:00
|
|
|
}
|
2021-09-03 10:25:11 +02:00
|
|
|
pub fn dap_switch_stack_frame(cx: &mut Context) {
|
2021-12-07 16:59:11 +01:00
|
|
|
let debugger = debugger!(cx.editor);
|
2021-09-03 10:25:11 +02:00
|
|
|
|
|
|
|
let thread_id = match debugger.thread_id {
|
|
|
|
Some(thread_id) => thread_id,
|
|
|
|
None => {
|
|
|
|
cx.editor
|
2021-09-04 08:08:52 +02:00
|
|
|
.set_error("No thread is currently active".to_owned());
|
2021-09-03 10:25:11 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let frames = debugger.stack_frames[&thread_id].clone();
|
|
|
|
|
|
|
|
let picker = FilePicker::new(
|
|
|
|
frames,
|
|
|
|
|frame| frame.name.clone().into(), // TODO: include thread_states in the label
|
2021-11-07 10:03:04 +01:00
|
|
|
move |cx, frame, _action| {
|
2021-12-08 02:21:58 +01:00
|
|
|
let debugger = debugger!(cx.editor);
|
2021-09-03 10:25:11 +02:00
|
|
|
// 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;
|
2021-09-04 09:24:00 +02:00
|
|
|
|
|
|
|
let frame = debugger.stack_frames[&thread_id]
|
|
|
|
.get(pos.unwrap_or(0))
|
|
|
|
.cloned();
|
|
|
|
if let Some(frame) = &frame {
|
2021-11-07 10:03:04 +01:00
|
|
|
jump_to_stack_frame(cx.editor, frame);
|
2021-09-04 09:24:00 +02:00
|
|
|
}
|
2021-09-03 10:25:11 +02:00
|
|
|
},
|
|
|
|
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))
|
|
|
|
}
|