tinymist_debug/
debugger.rs

1//! Tinymist breakpoint support for Typst.
2
3mod instr;
4
5use std::sync::Arc;
6
7use comemo::Tracked;
8use parking_lot::RwLock;
9use tinymist_std::hash::{FxHashMap, FxHashSet};
10use tinymist_world::vfs::FileId;
11use typst::World;
12use typst::diag::FileResult;
13use typst::engine::Engine;
14use typst::foundations::{Binding, Context, Dict, Scopes, func};
15use typst::syntax::{Source, Span};
16
17use crate::instrument::Instrumenter;
18
19#[derive(Default)]
20pub struct BreakpointInstr {}
21
22/// The kind of breakpoint.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum BreakpointKind {
25    // Expr,
26    // Line,
27    /// A call breakpoint.
28    CallStart,
29    /// A call breakpoint.
30    CallEnd,
31    /// A function breakpoint.
32    Function,
33    /// A break breakpoint.
34    Break,
35    /// A continue breakpoint.
36    Continue,
37    /// A return breakpoint.
38    Return,
39    /// A block start breakpoint.
40    BlockStart,
41    /// A block end breakpoint.
42    BlockEnd,
43    /// A show start breakpoint.
44    ShowStart,
45    /// A show end breakpoint.
46    ShowEnd,
47    /// A doc start breakpoint.
48    DocStart,
49    /// A doc end breakpoint.
50    DocEnd,
51    /// A before compile breakpoint.
52    BeforeCompile,
53    /// A after compile breakpoint.
54    AfterCompile,
55}
56
57impl BreakpointKind {
58    /// Converts the breakpoint kind to a string.
59    pub fn to_str(self) -> &'static str {
60        match self {
61            BreakpointKind::CallStart => "call_start",
62            BreakpointKind::CallEnd => "call_end",
63            BreakpointKind::Function => "function",
64            BreakpointKind::Break => "break",
65            BreakpointKind::Continue => "continue",
66            BreakpointKind::Return => "return",
67            BreakpointKind::BlockStart => "block_start",
68            BreakpointKind::BlockEnd => "block_end",
69            BreakpointKind::ShowStart => "show_start",
70            BreakpointKind::ShowEnd => "show_end",
71            BreakpointKind::DocStart => "doc_start",
72            BreakpointKind::DocEnd => "doc_end",
73            BreakpointKind::BeforeCompile => "before_compile",
74            BreakpointKind::AfterCompile => "after_compile",
75        }
76    }
77}
78
79#[derive(Default)]
80pub struct BreakpointInfo {
81    pub meta: Vec<BreakpointItem>,
82}
83
84pub struct BreakpointItem {
85    pub origin_span: Span,
86}
87
88static DEBUG_SESSION: RwLock<Option<DebugSession>> = RwLock::new(None);
89
90/// The debug session handler.
91pub trait DebugSessionHandler: Send + Sync {
92    /// Called when a breakpoint is hit.
93    fn on_breakpoint(
94        &self,
95        engine: &Engine,
96        context: Tracked<Context>,
97        scopes: Scopes,
98        span: Span,
99        kind: BreakpointKind,
100    );
101}
102
103/// The debug session.
104pub struct DebugSession {
105    enabled: FxHashSet<(FileId, usize, BreakpointKind)>,
106    /// The breakpoint meta.
107    breakpoints: FxHashMap<FileId, Arc<BreakpointInfo>>,
108
109    /// The handler.
110    pub handler: Arc<dyn DebugSessionHandler>,
111}
112
113impl DebugSession {
114    /// Creates a new debug session.
115    pub fn new(handler: Arc<dyn DebugSessionHandler>) -> Self {
116        Self {
117            enabled: FxHashSet::default(),
118            breakpoints: FxHashMap::default(),
119            handler,
120        }
121    }
122}
123
124/// Runs function with the debug session.
125pub fn with_debug_session<F, R>(f: F) -> Option<R>
126where
127    F: FnOnce(&DebugSession) -> R,
128{
129    Some(f(DEBUG_SESSION.read().as_ref()?))
130}
131
132/// Sets the debug session.
133pub fn set_debug_session(session: Option<DebugSession>) -> bool {
134    let mut lock = DEBUG_SESSION.write();
135
136    if session.is_some() {
137        return false;
138    }
139
140    let _ = std::mem::replace(&mut *lock, session);
141    true
142}
143
144/// Software breakpoints
145fn check_soft_breakpoint(span: Span, id: usize, kind: BreakpointKind) -> Option<bool> {
146    let fid = span.id()?;
147
148    let session = DEBUG_SESSION.read();
149    let session = session.as_ref()?;
150
151    let bp_feature = (fid, id, kind);
152    Some(session.enabled.contains(&bp_feature))
153}
154
155/// Software breakpoints
156fn soft_breakpoint_handle(
157    engine: &Engine,
158    context: Tracked<Context>,
159    span: Span,
160    id: usize,
161    kind: BreakpointKind,
162    scope: Option<Dict>,
163) -> Option<()> {
164    let fid = span.id()?;
165
166    let (handler, origin_span) = {
167        let session = DEBUG_SESSION.read();
168        let session = session.as_ref()?;
169
170        let bp_feature = (fid, id, kind);
171        if !session.enabled.contains(&bp_feature) {
172            return None;
173        }
174
175        let item = session.breakpoints.get(&fid)?.meta.get(id)?;
176        (session.handler.clone(), item.origin_span)
177    };
178
179    let mut scopes = Scopes::new(Some(engine.world.library()));
180    if let Some(scope) = scope {
181        for (key, value) in scope.into_iter() {
182            scopes.top.bind(key.into(), Binding::detached(value));
183        }
184    }
185
186    handler.on_breakpoint(engine, context, scopes, origin_span, kind);
187    Some(())
188}
189
190pub mod breakpoints {
191
192    use super::*;
193
194    macro_rules! bp_handler {
195        ($name:ident, $name2:expr, $name3:ident, $name4:expr, $title:expr, $kind:ident) => {
196            #[func(name = $name2, title = $title)]
197            pub fn $name(span: Span, id: usize) -> bool {
198                check_soft_breakpoint(span, id, BreakpointKind::$kind).unwrap_or_default()
199            }
200            #[func(name = $name4, title = $title)]
201            pub fn $name3(
202                engine: &Engine,
203                context: Tracked<Context>,
204                span: Span,
205                id: usize,
206                scope: Option<Dict>,
207            ) {
208                soft_breakpoint_handle(engine, context, span, id, BreakpointKind::$kind, scope);
209            }
210        };
211    }
212
213    bp_handler!(
214        __breakpoint_call_start,
215        "__breakpoint_call_start",
216        __breakpoint_call_start_handle,
217        "__breakpoint_call_start_handle",
218        "A Software Breakpoint at the start of a call.",
219        CallStart
220    );
221    bp_handler!(
222        __breakpoint_call_end,
223        "__breakpoint_call_end",
224        __breakpoint_call_end_handle,
225        "__breakpoint_call_end_handle",
226        "A Software Breakpoint at the end of a call.",
227        CallEnd
228    );
229    bp_handler!(
230        __breakpoint_function,
231        "__breakpoint_function",
232        __breakpoint_function_handle,
233        "__breakpoint_function_handle",
234        "A Software Breakpoint at the start of a function.",
235        Function
236    );
237    bp_handler!(
238        __breakpoint_break,
239        "__breakpoint_break",
240        __breakpoint_break_handle,
241        "__breakpoint_break_handle",
242        "A Software Breakpoint at a break.",
243        Break
244    );
245    bp_handler!(
246        __breakpoint_continue,
247        "__breakpoint_continue",
248        __breakpoint_continue_handle,
249        "__breakpoint_continue_handle",
250        "A Software Breakpoint at a continue.",
251        Continue
252    );
253    bp_handler!(
254        __breakpoint_return,
255        "__breakpoint_return",
256        __breakpoint_return_handle,
257        "__breakpoint_return_handle",
258        "A Software Breakpoint at a return.",
259        Return
260    );
261    bp_handler!(
262        __breakpoint_block_start,
263        "__breakpoint_block_start",
264        __breakpoint_block_start_handle,
265        "__breakpoint_block_start_handle",
266        "A Software Breakpoint at the start of a block.",
267        BlockStart
268    );
269    bp_handler!(
270        __breakpoint_block_end,
271        "__breakpoint_block_end",
272        __breakpoint_block_end_handle,
273        "__breakpoint_block_end_handle",
274        "A Software Breakpoint at the end of a block.",
275        BlockEnd
276    );
277    bp_handler!(
278        __breakpoint_show_start,
279        "__breakpoint_show_start",
280        __breakpoint_show_start_handle,
281        "__breakpoint_show_start_handle",
282        "A Software Breakpoint at the start of a show.",
283        ShowStart
284    );
285    bp_handler!(
286        __breakpoint_show_end,
287        "__breakpoint_show_end",
288        __breakpoint_show_end_handle,
289        "__breakpoint_show_end_handle",
290        "A Software Breakpoint at the end of a show.",
291        ShowEnd
292    );
293    bp_handler!(
294        __breakpoint_doc_start,
295        "__breakpoint_doc_start",
296        __breakpoint_doc_start_handle,
297        "__breakpoint_doc_start_handle",
298        "A Software Breakpoint at the start of a doc.",
299        DocStart
300    );
301    bp_handler!(
302        __breakpoint_doc_end,
303        "__breakpoint_doc_end",
304        __breakpoint_doc_end_handle,
305        "__breakpoint_doc_end_handle",
306        "A Software Breakpoint at the end of a doc.",
307        DocEnd
308    );
309    bp_handler!(
310        __breakpoint_before_compile,
311        "__breakpoint_before_compile",
312        __breakpoint_before_compile_handle,
313        "__breakpoint_before_compile_handle",
314        "A Software Breakpoint before compilation.",
315        BeforeCompile
316    );
317    bp_handler!(
318        __breakpoint_after_compile,
319        "__breakpoint_after_compile",
320        __breakpoint_after_compile_handle,
321        "__breakpoint_after_compile_handle",
322        "A Software Breakpoint after compilation.",
323        AfterCompile
324    );
325}