diff --git a/src/dispextern.h b/src/dispextern.h index bd48005b83f..19ab104d2e6 100644 --- a/src/dispextern.h +++ b/src/dispextern.h @@ -3559,6 +3559,7 @@ int partial_line_height (struct it *it_origin); bool in_display_vector_p (struct it *); int frame_mode_line_height (struct frame *); extern bool redisplaying_p; +extern unsigned int redisplay_counter; extern bool display_working_on_window_p; extern void unwind_display_working_on_window (void); extern bool help_echo_showing_p; diff --git a/src/eval.c b/src/eval.c index 5e2b5bff796..204adb62472 100644 --- a/src/eval.c +++ b/src/eval.c @@ -302,6 +302,7 @@ call_debugger (Lisp_Object arg) /* Resetting redisplaying_p to 0 makes sure that debug output is displayed if the debugger is invoked during redisplay. */ debug_while_redisplaying = redisplaying_p; + int redisplay_counter_before = redisplay_counter; redisplaying_p = 0; specbind (Qdebugger_may_continue, debug_while_redisplaying ? Qnil : Qt); @@ -323,9 +324,10 @@ call_debugger (Lisp_Object arg) /* Interrupting redisplay and resuming it later is not safe under all circumstances. So, when the debugger returns, abort the interrupted redisplay by going back to the top-level. */ - /* FIXME: Move this to the redisplay code? */ if (debug_while_redisplaying - && !EQ (Vdebugger, Qdebug_early)) + && redisplay_counter_before != redisplay_counter) + /* FIXME: Rather than jump all the way to `top-level` + we should exit only the current redisplay. */ Ftop_level (); return unbind_to (count, val); diff --git a/src/xdisp.c b/src/xdisp.c index 992d162f2e3..8eec2757226 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -3054,6 +3054,17 @@ hscrolling_current_line_p (struct window *w) Lisp form evaluation ***********************************************************************/ +/* The redisplay is not re-entrant. But we can run ELisp code during + redisplay, which in turn can trigger redisplay. + We try to make this inner redisplay work correctly, but it messes up + the state of the outer redisplay, so when we return to this outer + redisplay, we need to abort it. + To dect this case, we keep a counter that identifies every call to the + redisplay, so we can detect when a nested redisplay happened by the + fact that the counter has changed. */ + +unsigned int redisplay_counter = 0; + /* Error handler for dsafe_eval and dsafe_call. */ static Lisp_Object @@ -3085,10 +3096,17 @@ dsafe__call (bool inhibit_quit, Lisp_Object (f) (ptrdiff_t, Lisp_Object *), specbind (Qinhibit_redisplay, Qt); if (inhibit_quit) specbind (Qinhibit_quit, Qt); + int redisplay_counter_before = redisplay_counter; /* Use Qt to ensure debugger does not run, - so there is no possibility of wanting to redisplay. */ + to reduce the risk of wanting to redisplay. */ val = internal_condition_case_n (f, nargs, args, Qt, dsafe_eval_handler); + if (redisplay_counter_before != redisplay_counter) + /* A nested redisplay happened, abort this one! */ + /* FIXME: Rather than jump all the way to `top-level` + we should exit only the current redisplay. */ + Ftop_level (); + val = unbind_to (count, val); } @@ -17109,6 +17127,8 @@ redisplay_internal (void) bool polling_stopped_here = false; Lisp_Object tail, frame; + redisplay_counter++; + /* Set a limit to the number of retries we perform due to horizontal scrolling, this avoids getting stuck in an uninterruptible infinite loop (Bug #24633). */