use crate::ui::slash::{SlashCtx, c_agent, c_error, c_result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DapOp {
Launch,
Attach,
Terminate,
Continue,
StepOver,
StepIn,
StepOut,
Breakpoint,
Evaluate,
Sessions,
Help,
}
pub(crate) fn resolve_alias(alias: &str) -> Option<DapOp> {
Some(match alias {
"launch" | "l" => DapOp::Launch,
"attach" | "a" => DapOp::Attach,
"terminate" | "term" | "q" | "quit" | "kill" => DapOp::Terminate,
"c" | "cont" | "continue" => DapOp::Continue,
"n" | "next" | "step" | "over" => DapOp::StepOver,
"s" | "si" | "stepin" | "step_in" => DapOp::StepIn,
"o" | "so" | "stepout" | "step_out" => DapOp::StepOut,
"bp" | "break" | "breakpoint" => DapOp::Breakpoint,
"p" | "print" | "eval" | "evaluate" => DapOp::Evaluate,
"bt" | "status" | "sessions" | "info" => DapOp::Sessions,
"help" | "?" => DapOp::Help,
_ => return None,
})
}
pub(crate) async fn cmd_dap_repl(ctx: &mut SlashCtx<'_>, parts: &[&str]) -> anyhow::Result<()> {
let Some(alias) = parts.get(1) else {
return print_usage(ctx).await;
};
let args = &parts[2..];
let Some(op) = resolve_alias(alias) else {
ctx.renderer
.write_line(&format!("unknown dap-repl command: {alias}"), c_error())?;
return print_usage(ctx).await;
};
match op {
DapOp::Launch => super::launch::cmd_launch(ctx, args).await?,
DapOp::Attach => super::attach::cmd_attach(ctx, args).await?,
DapOp::Terminate => super::terminate::cmd_terminate(ctx).await?,
DapOp::Continue => super::cont::cmd_continue(ctx).await?,
DapOp::StepOver => super::stepping::cmd_step_over(ctx).await?,
DapOp::StepIn => super::stepping::cmd_step_in(ctx).await?,
DapOp::StepOut => super::stepping::cmd_step_out(ctx).await?,
DapOp::Breakpoint => super::breakpoint::cmd_breakpoint(ctx, args).await?,
DapOp::Evaluate => super::evaluate::cmd_evaluate(ctx, args).await?,
DapOp::Sessions => super::sessions::cmd_sessions(ctx).await?,
DapOp::Help => print_usage(ctx).await?,
}
Ok(())
}
async fn print_usage(ctx: &mut SlashCtx<'_>) -> anyhow::Result<()> {
ctx.renderer.write_line(
"usage: /dap-repl <cmd> [args…] (terse aliases for /debug)",
c_agent(),
)?;
for (alias, desc) in DAP_REPL_HELP {
ctx.renderer
.write_line(&format!(" {alias:<22}{desc}"), c_result())?;
}
Ok(())
}
const DAP_REPL_HELP: &[(&str, &str)] = &[
("launch <file> | l", "start debugging a program"),
("attach <pid> | a", "attach to a running process"),
("bp <file> <line>", "set a breakpoint"),
("c | continue", "resume execution"),
("n | next | step", "step over current line"),
("s | si | step_in", "step into a call"),
("o | so | step_out", "step out of the current function"),
("p <expr> | print", "evaluate an expression"),
("bt | status", "show the active session"),
("terminate | q", "end the debug session"),
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn completion_aliases_all_resolve() {
const COMPLETION_TOKENS: &[&str] = &[
"launch",
"attach",
"bp",
"c",
"n",
"s",
"o",
"p",
"bt",
"status",
"terminate",
"help",
];
for alias in COMPLETION_TOKENS {
assert!(
resolve_alias(alias).is_some(),
"completion advertises '{alias}' but resolve_alias rejects it",
);
}
}
#[test]
fn documented_aliases_route_correctly() {
assert_eq!(resolve_alias("launch"), Some(DapOp::Launch));
assert_eq!(resolve_alias("bp"), Some(DapOp::Breakpoint));
assert_eq!(resolve_alias("c"), Some(DapOp::Continue));
assert_eq!(resolve_alias("p"), Some(DapOp::Evaluate));
assert_eq!(resolve_alias("n"), Some(DapOp::StepOver));
assert_eq!(resolve_alias("terminate"), Some(DapOp::Terminate));
assert_eq!(resolve_alias("nonsense"), None);
}
}