mod instr;
use std::sync::Arc;
use comemo::Tracked;
use parking_lot::RwLock;
use tinymist_std::hash::{FxHashMap, FxHashSet};
use tinymist_world::vfs::FileId;
use typst::diag::FileResult;
use typst::engine::Engine;
use typst::foundations::{func, Binding, Context, Dict, Scopes};
use typst::syntax::{Source, Span};
use typst::World;
use crate::instrument::Instrumenter;
#[derive(Default)]
pub struct BreakpointInstr {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BreakpointKind {
CallStart,
CallEnd,
Function,
Break,
Continue,
Return,
BlockStart,
BlockEnd,
ShowStart,
ShowEnd,
DocStart,
DocEnd,
BeforeCompile,
AfterCompile,
}
impl BreakpointKind {
pub fn to_str(self) -> &'static str {
match self {
BreakpointKind::CallStart => "call_start",
BreakpointKind::CallEnd => "call_end",
BreakpointKind::Function => "function",
BreakpointKind::Break => "break",
BreakpointKind::Continue => "continue",
BreakpointKind::Return => "return",
BreakpointKind::BlockStart => "block_start",
BreakpointKind::BlockEnd => "block_end",
BreakpointKind::ShowStart => "show_start",
BreakpointKind::ShowEnd => "show_end",
BreakpointKind::DocStart => "doc_start",
BreakpointKind::DocEnd => "doc_end",
BreakpointKind::BeforeCompile => "before_compile",
BreakpointKind::AfterCompile => "after_compile",
}
}
}
#[derive(Default)]
pub struct BreakpointInfo {
pub meta: Vec<BreakpointItem>,
}
pub struct BreakpointItem {
pub origin_span: Span,
}
static DEBUG_SESSION: RwLock<Option<DebugSession>> = RwLock::new(None);
pub trait DebugSessionHandler: Send + Sync {
fn on_breakpoint(
&self,
engine: &Engine,
context: Tracked<Context>,
scopes: Scopes,
span: Span,
kind: BreakpointKind,
);
}
pub struct DebugSession {
enabled: FxHashSet<(FileId, usize, BreakpointKind)>,
breakpoints: FxHashMap<FileId, Arc<BreakpointInfo>>,
pub handler: Arc<dyn DebugSessionHandler>,
}
impl DebugSession {
pub fn new(handler: Arc<dyn DebugSessionHandler>) -> Self {
Self {
enabled: FxHashSet::default(),
breakpoints: FxHashMap::default(),
handler,
}
}
}
pub fn with_debug_session<F, R>(f: F) -> Option<R>
where
F: FnOnce(&DebugSession) -> R,
{
Some(f(DEBUG_SESSION.read().as_ref()?))
}
pub fn set_debug_session(session: Option<DebugSession>) -> bool {
let mut lock = DEBUG_SESSION.write();
if session.is_some() {
return false;
}
let _ = std::mem::replace(&mut *lock, session);
true
}
fn check_soft_breakpoint(span: Span, id: usize, kind: BreakpointKind) -> Option<bool> {
let fid = span.id()?;
let session = DEBUG_SESSION.read();
let session = session.as_ref()?;
let bp_feature = (fid, id, kind);
Some(session.enabled.contains(&bp_feature))
}
fn soft_breakpoint_handle(
engine: &Engine,
context: Tracked<Context>,
span: Span,
id: usize,
kind: BreakpointKind,
scope: Option<Dict>,
) -> Option<()> {
let fid = span.id()?;
let (handler, origin_span) = {
let session = DEBUG_SESSION.read();
let session = session.as_ref()?;
let bp_feature = (fid, id, kind);
if !session.enabled.contains(&bp_feature) {
return None;
}
let item = session.breakpoints.get(&fid)?.meta.get(id)?;
(session.handler.clone(), item.origin_span)
};
let mut scopes = Scopes::new(Some(engine.world.library()));
if let Some(scope) = scope {
for (key, value) in scope.into_iter() {
scopes.top.bind(key.into(), Binding::detached(value));
}
}
handler.on_breakpoint(engine, context, scopes, origin_span, kind);
Some(())
}
pub mod breakpoints {
use super::*;
macro_rules! bp_handler {
($name:ident, $name2:expr, $name3:ident, $name4:expr, $title:expr, $kind:ident) => {
#[func(name = $name2, title = $title)]
pub fn $name(span: Span, id: usize) -> bool {
check_soft_breakpoint(span, id, BreakpointKind::$kind).unwrap_or_default()
}
#[func(name = $name4, title = $title)]
pub fn $name3(
engine: &Engine,
context: Tracked<Context>,
span: Span,
id: usize,
scope: Option<Dict>,
) {
soft_breakpoint_handle(engine, context, span, id, BreakpointKind::$kind, scope);
}
};
}
bp_handler!(
__breakpoint_call_start,
"__breakpoint_call_start",
__breakpoint_call_start_handle,
"__breakpoint_call_start_handle",
"A Software Breakpoint at the start of a call.",
CallStart
);
bp_handler!(
__breakpoint_call_end,
"__breakpoint_call_end",
__breakpoint_call_end_handle,
"__breakpoint_call_end_handle",
"A Software Breakpoint at the end of a call.",
CallEnd
);
bp_handler!(
__breakpoint_function,
"__breakpoint_function",
__breakpoint_function_handle,
"__breakpoint_function_handle",
"A Software Breakpoint at the start of a function.",
Function
);
bp_handler!(
__breakpoint_break,
"__breakpoint_break",
__breakpoint_break_handle,
"__breakpoint_break_handle",
"A Software Breakpoint at a break.",
Break
);
bp_handler!(
__breakpoint_continue,
"__breakpoint_continue",
__breakpoint_continue_handle,
"__breakpoint_continue_handle",
"A Software Breakpoint at a continue.",
Continue
);
bp_handler!(
__breakpoint_return,
"__breakpoint_return",
__breakpoint_return_handle,
"__breakpoint_return_handle",
"A Software Breakpoint at a return.",
Return
);
bp_handler!(
__breakpoint_block_start,
"__breakpoint_block_start",
__breakpoint_block_start_handle,
"__breakpoint_block_start_handle",
"A Software Breakpoint at the start of a block.",
BlockStart
);
bp_handler!(
__breakpoint_block_end,
"__breakpoint_block_end",
__breakpoint_block_end_handle,
"__breakpoint_block_end_handle",
"A Software Breakpoint at the end of a block.",
BlockEnd
);
bp_handler!(
__breakpoint_show_start,
"__breakpoint_show_start",
__breakpoint_show_start_handle,
"__breakpoint_show_start_handle",
"A Software Breakpoint at the start of a show.",
ShowStart
);
bp_handler!(
__breakpoint_show_end,
"__breakpoint_show_end",
__breakpoint_show_end_handle,
"__breakpoint_show_end_handle",
"A Software Breakpoint at the end of a show.",
ShowEnd
);
bp_handler!(
__breakpoint_doc_start,
"__breakpoint_doc_start",
__breakpoint_doc_start_handle,
"__breakpoint_doc_start_handle",
"A Software Breakpoint at the start of a doc.",
DocStart
);
bp_handler!(
__breakpoint_doc_end,
"__breakpoint_doc_end",
__breakpoint_doc_end_handle,
"__breakpoint_doc_end_handle",
"A Software Breakpoint at the end of a doc.",
DocEnd
);
bp_handler!(
__breakpoint_before_compile,
"__breakpoint_before_compile",
__breakpoint_before_compile_handle,
"__breakpoint_before_compile_handle",
"A Software Breakpoint before compilation.",
BeforeCompile
);
bp_handler!(
__breakpoint_after_compile,
"__breakpoint_after_compile",
__breakpoint_after_compile_handle,
"__breakpoint_after_compile_handle",
"A Software Breakpoint after compilation.",
AfterCompile
);
}