@@ -52,6 +52,8 @@ pub(crate) use extra::ExtraData;
5252#[ doc( hidden) ]
5353pub use raw:: RawLua ;
5454pub ( crate ) use util:: callback_error_ext;
55+ #[ cfg( feature = "luau" ) ]
56+ pub ( crate ) use util:: debug_callback;
5557
5658/// Top level Lua struct which represents an instance of Lua VM.
5759pub struct Lua {
@@ -842,6 +844,92 @@ impl Lua {
842844 }
843845 }
844846
847+ /// Sets the callback Luau invokes when a breakpoint (set via [`Function::set_breakpoint`]) is
848+ /// hit.
849+ ///
850+ /// The callback receives a [`Debug`] for the paused frame, so it can read the current line and
851+ /// inspect locals. Returning [`VmState::Yield`] suspends the running coroutine at the
852+ /// breakpoint (the yield happens only at yieldable points, exactly as [`Lua::set_interrupt`]),
853+ /// which is what makes a non-blocking, single-threaded debugger possible.
854+ ///
855+ /// Note: Luau re-evaluates the breakpoint when the coroutine is resumed, so the callback fires
856+ /// again on the same line. Return [`VmState::Continue`] on resume to step past it (a debugger
857+ /// typically yields on the first hit and continues once the user resumes).
858+ ///
859+ /// [`Function::set_breakpoint`]: crate::Function::set_breakpoint
860+ #[ cfg( any( feature = "luau" , doc) ) ]
861+ #[ cfg_attr( docsrs, doc( cfg( feature = "luau" ) ) ) ]
862+ pub fn set_debug_break < F > ( & self , callback : F )
863+ where
864+ F : Fn ( & Lua , & Debug ) -> Result < VmState > + MaybeSend + ' static ,
865+ {
866+ unsafe extern "C-unwind" fn debug_break_proc ( state : * mut ffi:: lua_State , ar : * mut ffi:: lua_Debug ) {
867+ debug_callback ( state, ar, |extra| ( * extra) . debug_break_callback . clone ( ) ) ;
868+ }
869+
870+ let lua = self . lock ( ) ;
871+ unsafe {
872+ ( * lua. extra . get ( ) ) . debug_break_callback = Some ( XRc :: new ( callback) ) ;
873+ ( * ffi:: lua_callbacks ( lua. main_state ( ) ) ) . debugbreak = Some ( debug_break_proc) ;
874+ }
875+ }
876+
877+ /// Sets the per-line single-step callback, invoked for each line while single-stepping is
878+ /// enabled via [`Lua::set_single_step`].
879+ ///
880+ /// Like [`Lua::set_debug_break`], the callback may return [`VmState::Yield`] to suspend the
881+ /// running coroutine.
882+ #[ cfg( any( feature = "luau" , doc) ) ]
883+ #[ cfg_attr( docsrs, doc( cfg( feature = "luau" ) ) ) ]
884+ pub fn set_debug_step < F > ( & self , callback : F )
885+ where
886+ F : Fn ( & Lua , & Debug ) -> Result < VmState > + MaybeSend + ' static ,
887+ {
888+ unsafe extern "C-unwind" fn debug_step_proc ( state : * mut ffi:: lua_State , ar : * mut ffi:: lua_Debug ) {
889+ debug_callback ( state, ar, |extra| ( * extra) . debug_step_callback . clone ( ) ) ;
890+ }
891+
892+ let lua = self . lock ( ) ;
893+ unsafe {
894+ ( * lua. extra . get ( ) ) . debug_step_callback = Some ( XRc :: new ( callback) ) ;
895+ ( * ffi:: lua_callbacks ( lua. main_state ( ) ) ) . debugstep = Some ( debug_step_proc) ;
896+ }
897+ }
898+
899+ /// Enables or disables single-stepping (wraps `lua_singlestep`).
900+ ///
901+ /// While enabled, the callback registered with [`Lua::set_debug_step`] fires for every line.
902+ /// The flag is per-thread; threads created afterwards inherit it, so enable it before creating
903+ /// the thread you want to step.
904+ #[ cfg( any( feature = "luau" , doc) ) ]
905+ #[ cfg_attr( docsrs, doc( cfg( feature = "luau" ) ) ) ]
906+ pub fn set_single_step ( & self , enabled : bool ) {
907+ let lua = self . lock ( ) ;
908+ unsafe { ffi:: lua_singlestep ( lua. main_state ( ) , enabled as c_int ) } ;
909+ }
910+
911+ /// Removes the breakpoint callback previously set by [`Lua::set_debug_break`].
912+ #[ cfg( any( feature = "luau" , doc) ) ]
913+ #[ cfg_attr( docsrs, doc( cfg( feature = "luau" ) ) ) ]
914+ pub fn remove_debug_break ( & self ) {
915+ let lua = self . lock ( ) ;
916+ unsafe {
917+ ( * lua. extra . get ( ) ) . debug_break_callback = None ;
918+ ( * ffi:: lua_callbacks ( lua. main_state ( ) ) ) . debugbreak = None ;
919+ }
920+ }
921+
922+ /// Removes the single-step callback previously set by [`Lua::set_debug_step`].
923+ #[ cfg( any( feature = "luau" , doc) ) ]
924+ #[ cfg_attr( docsrs, doc( cfg( feature = "luau" ) ) ) ]
925+ pub fn remove_debug_step ( & self ) {
926+ let lua = self . lock ( ) ;
927+ unsafe {
928+ ( * lua. extra . get ( ) ) . debug_step_callback = None ;
929+ ( * ffi:: lua_callbacks ( lua. main_state ( ) ) ) . debugstep = None ;
930+ }
931+ }
932+
845933 /// Sets a callback invoked when thread lifecycle events occur.
846934 ///
847935 /// `triggers` controls which events trigger the callback, see [`ThreadTriggers`] for more
0 commit comments