Subject: Re: bug#79306: Broken follow-mode

From: Eli Zaretskii <[email-masked]>
Date: Mon, 25 Aug 2025 15:54:17 +0300

> From: Juri Linkov <[email-masked]>
> Date: Mon, 25 Aug 2025 09:30:10 +0300
> 
> Some recent change in master broke display for follow-mode:
> 
> 0. emacs -Q
> 1. visit a file in fundamental-mode, e.g.
>    C-x C-f etc/COPYING RET
> 2. split horizontally
>    C-x 3
> 3. M-x follow-mode RET
> 4. scroll down a few screens
> 5. type 'M-<' (beginning-of-buffer)
> 
> It displays the message "Back to top level",
> but doesn't move to the beginning of the buffer.

Looks like the trap set by Stefan a month ago finally caught its first
victim: follow-mode calls redisplay recursively, from
pre-redisplay-function:

  Thread 1 hit Breakpoint 4, redisplay_internal () at xdisp.c:17122
  17122     struct window *w = XWINDOW (selected_window);
  (gdb) bt
  #0  redisplay_internal () at xdisp.c:17122
  #1  0x0104197e in redisplay_preserve_echo_area (from_where=2) at xdisp.c:18040
  #2  0x00fe51f4 in Fredisplay (force=XIL(0)) at dispnew.c:7004
  #3  0x0123da19 in funcall_subr (subr=0x13ee240 <Sredisplay>, numargs=0,
      args=0xa019120) at eval.c:3242
  #4  0x012aa6a8 in exec_byte_code (fun=XIL(0xa0000000103368c0),
      args_template=514, nargs=2, args=0xa019128) at bytecode.c:805
  #5  0x0123e30c in funcall_lambda (fun=XIL(0xa000000010336cd0), nargs=1,
      arg_vector=0xa019050) at eval.c:3333
  #6  0x0123d3d2 in funcall_general (fun=XIL(0xa000000010336cd0), numargs=1,
      args=0xa019050) at eval.c:3125
  #7  0x0123d6fb in Ffuncall (nargs=2, args=0xa019048) at eval.c:3174
  #8  0x0123c472 in Fapply (nargs=2, args=0xa019048) at eval.c:2788
  #9  0x0123dea4 in funcall_subr (subr=0x13fc600 <Sapply>, numargs=2,
      args=0xa019048) at eval.c:3265
  #10 0x012aa6a8 in exec_byte_code (fun=XIL(0xa00000001033a090),
      args_template=128, nargs=1, args=0xbfdcd0) at bytecode.c:805
  #11 0x0123e30c in funcall_lambda (fun=XIL(0xa00000001033a090), nargs=1,
      arg_vector=0xbfdcd0) at eval.c:3333
  #12 0x0123d3d2 in funcall_general (fun=XIL(0xa00000001033a090), numargs=1,
      args=0xbfdcd0) at eval.c:3125
  #13 0x0123d6fb in Ffuncall (nargs=2, args=0xbfdcc8) at eval.c:3174
  #14 0x012386f0 in internal_condition_case_n (bfun=0x123d5af <Ffuncall>,
      nargs=2, args=0xbfdcc8, handlers=XIL(0x30),
      hfun=0x10151d3 <dsafe_eval_handler>) at eval.c:1770
  #15 0x01015307 in dsafe__call (inhibit_quit=true, f=0x123d5af <Ffuncall>,
      nargs=2, args=0xbfdcc8) at xdisp.c:3102
  #16 0x0103711d in prepare_menu_bars () at xdisp.c:14054
  #17 0x0103f95e in redisplay_internal () at xdisp.c:17283
  #18 0x0103e65d in redisplay () at xdisp.c:16844
  #19 0x011456ef in read_char (commandflag=1, map=XIL(0xc00000001711a630),
      prev_event=XIL(0), used_mouse_menu=0xbff31f, end_time=0x0)
      at keyboard.c:2672
  #20 0x0115ee45 in read_key_sequence (keybuf=0xbff5f8, prompt=XIL(0),
      dont_downcase_last=false, can_return_switch_frame=true,
      fix_current_buffer=true, prevent_redisplay=false,
      disable_text_conversion_p=false) at keyboard.c:11146
  #21 0x011412ca in command_loop_1 () at keyboard.c:1424
  #22 0x01238416 in internal_condition_case (bfun=0x1140c68 <command_loop_1>,
      handlers=XIL(0x90), hfun=0x113fcc1 <cmd_error>) at eval.c:1690
  #23 0x011406cd in command_loop_2 (handlers=XIL(0x90)) at keyboard.c:1163
  #24 0x012375a2 in internal_catch (tag=XIL(0x12a80),
      func=0x1140696 <command_loop_2>, arg=XIL(0x90)) at eval.c:1370
  #25 0x01140638 in command_loop () at keyboard.c:1141
  #26 0x0113f721 in recursive_edit_1 () at keyboard.c:749
  #27 0x0113f9bf in Frecursive_edit () at keyboard.c:832
  #28 0x0113a9aa in main (argc=2, argv=0xf02638) at emacs.c:2629

  Lisp Backtrace:
  "redisplay" (0xa019120)
  "follow-adjust-window" (0xa0190b8)
  "follow-post-command-hook" (0xa019080)
  "follow-pre-redisplay-function" (0xa019050)
  "apply" (0xa019048)
  0x1033a090 PVEC_CLOSURE
  "redisplay_internal (C function)" (0x0)

Then we catch this and throw to top-level:

      int redisplay_counter_before = redisplay_counter;
      /* Use Qt to ensure debugger does not run,
	 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 ();  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

Is it allowed to perform redisplay from pre-redisplay-function?  If
so, we need to refine the above test, or maybe disable it when we call
pre-redisplay-function.