1mod 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum BreakpointKind {
25 CallStart,
29 CallEnd,
31 Function,
33 Break,
35 Continue,
37 Return,
39 BlockStart,
41 BlockEnd,
43 ShowStart,
45 ShowEnd,
47 DocStart,
49 DocEnd,
51 BeforeCompile,
53 AfterCompile,
55}
56
57impl BreakpointKind {
58 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
90pub trait DebugSessionHandler: Send + Sync {
92 fn on_breakpoint(
94 &self,
95 engine: &Engine,
96 context: Tracked<Context>,
97 scopes: Scopes,
98 span: Span,
99 kind: BreakpointKind,
100 );
101}
102
103pub struct DebugSession {
105 enabled: FxHashSet<(FileId, usize, BreakpointKind)>,
106 breakpoints: FxHashMap<FileId, Arc<BreakpointInfo>>,
108
109 pub handler: Arc<dyn DebugSessionHandler>,
111}
112
113impl DebugSession {
114 pub fn new(handler: Arc<dyn DebugSessionHandler>) -> Self {
116 Self {
117 enabled: FxHashSet::default(),
118 breakpoints: FxHashMap::default(),
119 handler,
120 }
121 }
122}
123
124pub 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
132pub 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
144fn 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
155fn 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}