emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

master 289b457cac 14/14: Merge branch 'abort-redisplay'


From: Eli Zaretskii
Subject: master 289b457cac 14/14: Merge branch 'abort-redisplay'
Date: Fri, 24 Jun 2022 03:54:19 -0400 (EDT)

branch: master
commit 289b457cac1439ac5f9bb6ce1143d91b8d52da91
Merge: fbb703f60a 6fcd8ca743
Author: Eli Zaretskii <eliz@gnu.org>
Commit: Eli Zaretskii <eliz@gnu.org>

    Merge branch 'abort-redisplay'
    
    This allows abandoning the redisplay of a window
    that takes too long to complete.  Bug#45898
    * src/xdisp.c (update_redisplay_ticks): New function.
    (init_iterator, set_iterator_to_next): Call
    'update_redisplay_ticks'.
    (syms_of_xdisp) <max_redisplay_ticks>: New variable.
    <list_of_error>: Remove 'void-variable': it is no longer needed,
    since 'calc_pixel_width_or_height' can no longer signal a
    void-variable error, and it gets in the way of aborting
    redisplay via 'redisplay_window_error'.
    * src/keyboard.c (command_loop_1): Reinitialize the tick count
    before executing each command in the loop.
    * src/syntax.c (scan_sexps_forward): Call 'update_redisplay_ticks'
    after finishing the loop.
    * src/dispnew.c (make_current): Make sure enabled rows of the
    current matrix have a valid hash, even if redisplay of a window
    was aborted due to slowness.  This avoids assertion violations in
    'scrolling_window' due to the wrong hash value.
    * src/xdisp.c (display_working_on_window_p): New global variable.
    (unwind_display_working_on_window): New function.
    * src/keyboard.c (command_loop_1): Reset
    'display_working_on_window_p' before and after executing commands.
    * src/window.c (Frecenter, window_scroll, displayed_window_lines):
    * src/indent.c (Fvertical_motion): Set
    'display_working_on_window_p' before calling 'start_display'.
    * src/syntax.c (scan_sexps_forward): Call 'update_redisplay_ticks'
    after finishing the loop.
    * src/regex-emacs.c (re_match_2_internal):
    * src/bidi.c (bidi_find_bracket_pairs, bidi_fetch_char)
    (bidi_paragraph_init, bidi_find_other_level_edge): Update the
    redisplay tick count as appropriate, when moving the iterator by
    one character position actually requires to examine many more
    positions.
    * src/xdisp.c (redisplay_window_error): Show messages about
    aborted redisplay of a window as delayed-warnings.
    
    * doc/emacs/trouble.texi (DEL Does Not Delete): Move to the end of
    the chapter.  This issue is no longer frequent or important as it
    was back in Emacs 20 days.
    (Long Lines): Document 'max-redisplay-ticks'.
    * doc/emacs/emacs.texi (Top): Update the detailed menu.
    
    * etc/NEWS: Announce 'max-redisplay-ticks'.
---
 doc/emacs/emacs.texi   |   2 +-
 doc/emacs/trouble.texi | 139 ++++++++++++++++++++++++++++---------------------
 etc/NEWS               |   8 +++
 src/bidi.c             |  42 ++++++++++++++-
 src/dispextern.h       |   4 ++
 src/dispnew.c          |  15 +++++-
 src/indent.c           |   2 +
 src/keyboard.c         |   7 +++
 src/regex-emacs.c      |  30 +++++++++++
 src/syntax.c           |   9 ++++
 src/window.c           |  16 +++++-
 src/xdisp.c            | 124 +++++++++++++++++++++++++++++++++++++++++--
 12 files changed, 331 insertions(+), 67 deletions(-)

diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi
index ad0fa5f0cd..5e72699bbe 100644
--- a/doc/emacs/emacs.texi
+++ b/doc/emacs/emacs.texi
@@ -1183,7 +1183,6 @@ The Emacs Initialization File
 
 Dealing with Emacs Trouble
 
-* DEL Does Not Delete:: What to do if @key{DEL} doesn't delete.
 * Stuck Recursive::     '[...]' in mode line around the parentheses.
 * Screen Garbled::      Garbage on the screen.
 * Text Garbled::        Garbage in the text.
@@ -1192,6 +1191,7 @@ Dealing with Emacs Trouble
 * After a Crash::       Recovering editing in an Emacs session that crashed.
 * Emergency Escape::    What to do if Emacs stops responding.
 * Long Lines::          Mitigating slowness due to extremely long lines.
+* DEL Does Not Delete:: What to do if @key{DEL} doesn't delete.
 
 Reporting Bugs
 
diff --git a/doc/emacs/trouble.texi b/doc/emacs/trouble.texi
index 8da96de1cb..5dc9fe0068 100644
--- a/doc/emacs/trouble.texi
+++ b/doc/emacs/trouble.texi
@@ -151,7 +151,6 @@ garbled displays, running out of memory, and crashes and 
hangs.
 Emacs.
 
 @menu
-* DEL Does Not Delete::   What to do if @key{DEL} doesn't delete.
 * Stuck Recursive::       '[...]' in mode line around the parentheses.
 * Screen Garbled::        Garbage on the screen.
 * Text Garbled::          Garbage in the text.
@@ -160,65 +159,9 @@ Emacs.
 * After a Crash::         Recovering editing in an Emacs session that crashed.
 * Emergency Escape::      What to do if Emacs stops responding.
 * Long Lines::            Mitigating slowness due to extremely long lines.
+* DEL Does Not Delete::   What to do if @key{DEL} doesn't delete.
 @end menu
 
-@node DEL Does Not Delete
-@subsection If @key{DEL} Fails to Delete
-@cindex @key{DEL} vs @key{BACKSPACE}
-@cindex @key{BACKSPACE} vs @key{DEL}
-@cindex @key{DEL} does not delete
-
-  Every keyboard has a large key, usually labeled @key{BACKSPACE},
-which is ordinarily used to erase the last character that you typed.
-In Emacs, this key is supposed to be equivalent to @key{DEL}.
-
-  When Emacs starts up on a graphical display, it determines
-automatically which key should be @key{DEL}.  In some unusual cases,
-Emacs gets the wrong information from the system, and @key{BACKSPACE}
-ends up deleting forwards instead of backwards.
-
-  Some keyboards also have a @key{Delete} key, which is ordinarily
-used to delete forwards.  If this key deletes backward in Emacs, that
-too suggests Emacs got the wrong information---but in the opposite
-sense.
-
-  On a text terminal, if you find that @key{BACKSPACE} prompts for a
-Help command, like @kbd{Control-h}, instead of deleting a character,
-it means that key is actually sending the @samp{BS} character.  Emacs
-ought to be treating @key{BS} as @key{DEL}, but it isn't.
-
-@findex normal-erase-is-backspace-mode
-  In all of those cases, the immediate remedy is the same: use the
-command @kbd{M-x normal-erase-is-backspace-mode}.  This toggles
-between the two modes that Emacs supports for handling @key{DEL}, so
-if Emacs starts in the wrong mode, this should switch to the right
-mode.  On a text terminal, if you want to ask for help when @key{BS}
-is treated as @key{DEL}, use @key{F1} instead of @kbd{C-h}; @kbd{C-?}
-may also work, if it sends character code 127.
-
-  To fix the problem in every Emacs session, put one of the following
-lines into your initialization file (@pxref{Init File}).  For the
-first case above, where @key{BACKSPACE} deletes forwards instead of
-backwards, use this line to make @key{BACKSPACE} act as @key{DEL}:
-
-@lisp
-(normal-erase-is-backspace-mode 0)
-@end lisp
-
-@noindent
-For the other two cases, use this line:
-
-@lisp
-(normal-erase-is-backspace-mode 1)
-@end lisp
-
-@vindex normal-erase-is-backspace
-  Another way to fix the problem for every Emacs session is to
-customize the variable @code{normal-erase-is-backspace}: the value
-@code{t} specifies the mode where @key{BS} or @key{BACKSPACE} is
-@key{DEL}, and @code{nil} specifies the other mode.  @xref{Easy
-Customization}.
-
 @node Stuck Recursive
 @subsection Recursive Editing Levels
 @cindex stuck in recursive editing
@@ -525,6 +468,86 @@ be substantial.
   Use @kbd{M-x so-long-commentary} to view the documentation for this
 library and learn more about how to enable and configure it.
 
+@vindex max-redisplay-ticks
+  If even @code{so-long-mode} doesn't help making Emacs responsive
+enough, or if you'd rather not disable the display-related features
+that @code{so-long-mode} turns off, you can instead customize the
+variable @code{max-redisplay-ticks} to a non-zero value.  Then Emacs
+will abort redisplay of a window and commands, like @kbd{C-n} and
+@kbd{M-v}, which use the display code to do their job, if processing a
+window needs more low-level display operations than the value of this
+variable.  The display of the offending window will then remain
+outdated, and possibly incomplete, on the screen, but Emacs should
+otherwise be responsive, and you could then switch to another buffer,
+or kill the problematic buffer, or turn on @code{so-long-mode} or
+@code{sol-long-minor-mode} in that buffer.  When the display of a
+window is aborted due to this reason, the buffer shown in that window
+will not have any of its windows redisplayed until the buffer is
+modified or until you type @kbd{C-l} (@pxref{Recentering}) in one of
+that buffer's windows.
+
+  If you decide to customize this variable to a non-zero value, we
+recommend to use a value between 100,000 and 1,000,000, depending on
+your patience and the speed of your system.  The default value is
+zero, which disables this feature.
+
+@node DEL Does Not Delete
+@subsection If @key{DEL} Fails to Delete
+@cindex @key{DEL} vs @key{BACKSPACE}
+@cindex @key{BACKSPACE} vs @key{DEL}
+@cindex @key{DEL} does not delete
+
+  Every keyboard has a large key, usually labeled @key{BACKSPACE},
+which is ordinarily used to erase the last character that you typed.
+In Emacs, this key is supposed to be equivalent to @key{DEL}.
+
+  When Emacs starts up on a graphical display, it determines
+automatically which key should be @key{DEL}.  In some unusual cases,
+Emacs gets the wrong information from the system, and @key{BACKSPACE}
+ends up deleting forwards instead of backwards.
+
+  Some keyboards also have a @key{Delete} key, which is ordinarily
+used to delete forwards.  If this key deletes backward in Emacs, that
+too suggests Emacs got the wrong information---but in the opposite
+sense.
+
+  On a text terminal, if you find that @key{BACKSPACE} prompts for a
+Help command, like @kbd{Control-h}, instead of deleting a character,
+it means that key is actually sending the @samp{BS} character.  Emacs
+ought to be treating @key{BS} as @key{DEL}, but it isn't.
+
+@findex normal-erase-is-backspace-mode
+  In all of those cases, the immediate remedy is the same: use the
+command @kbd{M-x normal-erase-is-backspace-mode}.  This toggles
+between the two modes that Emacs supports for handling @key{DEL}, so
+if Emacs starts in the wrong mode, this should switch to the right
+mode.  On a text terminal, if you want to ask for help when @key{BS}
+is treated as @key{DEL}, use @key{F1} instead of @kbd{C-h}; @kbd{C-?}
+may also work, if it sends character code 127.
+
+  To fix the problem in every Emacs session, put one of the following
+lines into your initialization file (@pxref{Init File}).  For the
+first case above, where @key{BACKSPACE} deletes forwards instead of
+backwards, use this line to make @key{BACKSPACE} act as @key{DEL}:
+
+@lisp
+(normal-erase-is-backspace-mode 0)
+@end lisp
+
+@noindent
+For the other two cases, use this line:
+
+@lisp
+(normal-erase-is-backspace-mode 1)
+@end lisp
+
+@vindex normal-erase-is-backspace
+  Another way to fix the problem for every Emacs session is to
+customize the variable @code{normal-erase-is-backspace}: the value
+@code{t} specifies the mode where @key{BS} or @key{BACKSPACE} is
+@key{DEL}, and @code{nil} specifies the other mode.  @xref{Easy
+Customization}.
+
 @node Bugs
 @section Reporting Bugs
 
diff --git a/etc/NEWS b/etc/NEWS
index 6c04ae164c..7ef7109274 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -788,6 +788,14 @@ available options can be restored by enabling this option.
 Use it if you want Imenu to forget the buffer's index alist and
 recreate it anew next time 'imenu' is invoked.
 
++++
+** Emacs is now capable of abandoning a window's redisplay that takes too long.
+This is controlled by the new variable 'max-redisplay-ticks'.  If that
+variable is set to a non-zero value, display of a window will be
+aborted after that many low-level redisplay operations, thus
+preventing Emacs from becoming wedged when visiting files with very
+long lines.
+
 * Editing Changes in Emacs 29.1
 
 +++
diff --git a/src/bidi.c b/src/bidi.c
index 4d2c74b17c..267b62fb0b 100644
--- a/src/bidi.c
+++ b/src/bidi.c
@@ -1277,6 +1277,12 @@ bidi_fetch_char (ptrdiff_t charpos, ptrdiff_t bytepos, 
ptrdiff_t *disp_pos,
       SET_TEXT_POS (pos, charpos, bytepos);
       *disp_pos = compute_display_string_pos (&pos, string, w, frame_window_p,
                                              disp_prop);
+      /* The factor of 100 below is a heuristic that needs to be
+        tuned.  It means we consider 100 buffer positions examined by
+        the above call roughly equivalent to the display engine
+        iterating over a single buffer position.  */
+      if (*disp_pos > charpos)
+       update_redisplay_ticks ((*disp_pos - charpos) / 100 + 1, w);
     }
 
   /* Fetch the character at BYTEPOS.  */
@@ -1385,6 +1391,8 @@ bidi_fetch_char (ptrdiff_t charpos, ptrdiff_t bytepos, 
ptrdiff_t *disp_pos,
       SET_TEXT_POS (pos, charpos + *nchars, bytepos + *ch_len);
       *disp_pos = compute_display_string_pos (&pos, string, w, frame_window_p,
                                              disp_prop);
+      if (*disp_pos > charpos + *nchars)
+       update_redisplay_ticks ((*disp_pos - charpos - *nchars) / 100 + 1, w);
     }
 
   return ch;
@@ -1583,6 +1591,9 @@ bidi_find_paragraph_start (ptrdiff_t pos, ptrdiff_t 
pos_byte)
   return pos_byte;
 }
 
+/* This tracks how far we needed to search for first strong character.  */
+static ptrdiff_t nsearch_for_strong;
+
 /* On a 3.4 GHz machine, searching forward for a strong directional
    character in a long paragraph full of weaks or neutrals takes about
    1 ms for each 20K characters.  The number below limits each call to
@@ -1652,6 +1663,8 @@ find_first_strong_char (ptrdiff_t pos, ptrdiff_t bytepos, 
ptrdiff_t end,
       pos += *nchars;
       bytepos += *ch_len;
     }
+
+  nsearch_for_strong += pos - pos1;
   return type;
 }
 
@@ -1681,6 +1694,9 @@ bidi_paragraph_init (bidi_dir_t dir, struct bidi_it 
*bidi_it, bool no_default_p)
      calls to BYTE_TO_CHAR and its ilk.  */
   ptrdiff_t begbyte = string_p ? 0 : BEGV_BYTE;
   ptrdiff_t end = string_p ? bidi_it->string.schars : ZV;
+  ptrdiff_t pos = bidi_it->charpos;
+
+  nsearch_for_strong = 0;
 
   /* Special case for an empty buffer. */
   if (bytepos == begbyte && bidi_it->charpos == end)
@@ -1702,7 +1718,7 @@ bidi_paragraph_init (bidi_dir_t dir, struct bidi_it 
*bidi_it, bool no_default_p)
   else if (dir == NEUTRAL_DIR) /* P2 */
     {
       ptrdiff_t ch_len, nchars;
-      ptrdiff_t pos, disp_pos = -1;
+      ptrdiff_t disp_pos = -1;
       int disp_prop = 0;
       bidi_type_t type;
       const unsigned char *s;
@@ -1800,6 +1816,14 @@ bidi_paragraph_init (bidi_dir_t dir, struct bidi_it 
*bidi_it, bool no_default_p)
     bidi_it->level_stack[0].level = 0;
 
   bidi_line_init (bidi_it);
+
+  /* The factor of 50 below is a heuristic that needs to be tuned.  It
+     means we consider 50 buffer positions examined by this function
+     roughly equivalent to the display engine iterating over a single
+     buffer position.  */
+  ptrdiff_t nexamined = bidi_it->charpos - pos + nsearch_for_strong;
+  if (nexamined > 0)
+    update_redisplay_ticks (nexamined / 50, bidi_it->w);
 }
 
 
@@ -2566,6 +2590,7 @@ bidi_find_bracket_pairs (struct bidi_it *bidi_it)
   bidi_bracket_type_t btype;
   bidi_type_t type = bidi_it->type;
   bool retval = false;
+  ptrdiff_t n = 0;
 
   /* When scanning backwards, we don't expect any unresolved bidi
      bracket characters.  */
@@ -2695,6 +2720,7 @@ bidi_find_bracket_pairs (struct bidi_it *bidi_it)
            }
          old_sidx = bidi_it->stack_idx;
          type = bidi_resolve_weak (bidi_it);
+         n++;
          /* Skip level runs excluded from this isolating run sequence.  */
          new_sidx = bidi_it->stack_idx;
          if (bidi_it->level_stack[new_sidx].level > current_level
@@ -2718,6 +2744,7 @@ bidi_find_bracket_pairs (struct bidi_it *bidi_it)
                      goto give_up;
                    }
                  type = bidi_resolve_weak (bidi_it);
+                 n++;
                }
            }
          if (type == NEUTRAL_B
@@ -2794,6 +2821,12 @@ bidi_find_bracket_pairs (struct bidi_it *bidi_it)
     }
 
  give_up:
+  /* The factor of 20 below is a heuristic that needs to be tuned.  It
+     means we consider 20 buffer positions examined by this function
+     roughly equivalent to the display engine iterating over a single
+     buffer position.  */
+  if (n > 0)
+    update_redisplay_ticks (n / 20 + 1, bidi_it->w);
   return retval;
 }
 
@@ -3363,6 +3396,7 @@ bidi_find_other_level_edge (struct bidi_it *bidi_it, int 
level, bool end_flag)
   else
     {
       int new_level;
+      ptrdiff_t pos0 = bidi_it->charpos;
 
       /* If we are at end of level, its edges must be cached.  */
       if (end_flag)
@@ -3398,6 +3432,12 @@ bidi_find_other_level_edge (struct bidi_it *bidi_it, int 
level, bool end_flag)
            bidi_cache_iterator_state (bidi_it, 1, 1);
          }
       } while (new_level >= level);
+      /* The factor of 50 below is a heuristic that needs to be
+        tuned.  It means we consider 50 buffer positions examined by
+        the above call roughly equivalent to the display engine
+        iterating over a single buffer position.  */
+      if (bidi_it->charpos > pos0)
+       update_redisplay_ticks ((bidi_it->charpos - pos0) / 50 + 1, bidi_it->w);
     }
 }
 
diff --git a/src/dispextern.h b/src/dispextern.h
index c7399ca299..8bcd13dbb6 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -3407,6 +3407,8 @@ 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 bool display_working_on_window_p;
+extern void unwind_display_working_on_window (void);
 extern bool help_echo_showing_p;
 extern Lisp_Object help_echo_string, help_echo_window;
 extern Lisp_Object help_echo_object, previous_help_echo_string;
@@ -3505,6 +3507,8 @@ extern unsigned row_hash (struct glyph_row *);
 
 extern bool buffer_flipping_blocked_p (void);
 
+extern void update_redisplay_ticks (int, struct window *);
+
 /* Defined in image.c */
 
 #ifdef HAVE_WINDOW_SYSTEM
diff --git a/src/dispnew.c b/src/dispnew.c
index 3bd2e0e96c..53a47c4b2f 100644
--- a/src/dispnew.c
+++ b/src/dispnew.c
@@ -2732,12 +2732,25 @@ set_frame_matrix_frame (struct frame *f)
    operations in window matrices of frame_matrix_frame.  */
 
 static void
-make_current (struct glyph_matrix *desired_matrix, struct glyph_matrix 
*current_matrix, int row)
+make_current (struct glyph_matrix *desired_matrix,
+             struct glyph_matrix *current_matrix, int row)
 {
   struct glyph_row *current_row = MATRIX_ROW (current_matrix, row);
   struct glyph_row *desired_row = MATRIX_ROW (desired_matrix, row);
   bool mouse_face_p = current_row->mouse_face_p;
 
+  /* If we aborted redisplay of this window, a row in the desired
+     matrix might not have its hash computed.  But update_window
+     relies on each row having its correct hash, so do it here if
+     needed.  */
+  if (!desired_row->hash
+      /* A glyph row that is not completely empty is unlikely to have
+        a zero hash value.  */
+      && !(!desired_row->used[0]
+          && !desired_row->used[1]
+          && !desired_row->used[2]))
+    desired_row->hash = row_hash (desired_row);
+
   /* Do current_row = desired_row.  This exchanges glyph pointers
      between both rows, and does a structure assignment otherwise.  */
   assign_row (current_row, desired_row);
diff --git a/src/indent.c b/src/indent.c
index c071b43ab4..c3d78518c4 100644
--- a/src/indent.c
+++ b/src/indent.c
@@ -2177,6 +2177,8 @@ whether or not it is currently displayed in some window.  
*/)
        line_number_display_width (w, &lnum_width, &lnum_pixel_width);
       SET_TEXT_POS (pt, PT, PT_BYTE);
       itdata = bidi_shelve_cache ();
+      record_unwind_protect_void (unwind_display_working_on_window);
+      display_working_on_window_p = true;
       start_display (&it, w, pt);
       it.lnum_width = lnum_width;
       first_x = it.first_visible_x;
diff --git a/src/keyboard.c b/src/keyboard.c
index 6bc2afd40a..7fb7afca87 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -1501,7 +1501,14 @@ command_loop_1 (void)
             point_before_last_command_or_undo = PT;
             buffer_before_last_command_or_undo = current_buffer;
 
+           /* Restart our counting of redisplay ticks before
+              executing the command, so that we don't blame the new
+              command for the sins of the previous one.  */
+           update_redisplay_ticks (0, NULL);
+           display_working_on_window_p = false;
+
             call1 (Qcommand_execute, Vthis_command);
+           display_working_on_window_p = false;
 
 #ifdef HAVE_WINDOW_SYSTEM
          /* Do not check display_hourglass_p here, because
diff --git a/src/regex-emacs.c b/src/regex-emacs.c
index 8662fe8d6d..4d87418eea 100644
--- a/src/regex-emacs.c
+++ b/src/regex-emacs.c
@@ -33,6 +33,7 @@
 #include "buffer.h"
 #include "syntax.h"
 #include "category.h"
+#include "dispextern.h"
 
 /* Maximum number of duplicates an interval can allow.  Some systems
    define this in other header files, but we want our value, so remove
@@ -3953,6 +3954,9 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
      and need to test it, it's not garbage.  */
   re_char *match_end = NULL;
 
+  /* This keeps track of how many buffer/string positions we examined.  */
+  ptrdiff_t nchars = 0;
+
 #ifdef DEBUG_COMPILES_ARGUMENTS
   /* Counts the total number of registers pushed.  */
   ptrdiff_t num_regs_pushed = 0;
@@ -4209,6 +4213,12 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
 
          unbind_to (count, Qnil);
          SAFE_FREE ();
+         /* The factor of 50 below is a heuristic that needs to be tuned.  It
+            means we consider 50 buffer positions examined by this function
+            roughly equivalent to the display engine iterating over a single
+            buffer position.  */
+         if (nchars > 0)
+           update_redisplay_ticks (nchars / 50 + 1, NULL);
          return dcnt;
        }
 
@@ -4261,6 +4271,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
                p += pat_charlen;
                d += buf_charlen;
                mcnt -= pat_charlen;
+               nchars++;
              }
            while (mcnt > 0);
          else
@@ -4298,6 +4309,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
                p += pat_charlen;
                d++;
                mcnt -= pat_charlen;
+               nchars++;
              }
            while (mcnt > 0);
 
@@ -4321,6 +4333,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
 
            DEBUG_PRINT ("  Matched \"%d\".\n", *d);
            d += buf_charlen;
+           nchars++;
          }
          break;
 
@@ -4373,6 +4386,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
              goto fail;
 
            d += len;
+           nchars++;
          }
          break;
 
@@ -4492,6 +4506,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
                    goto fail;
                  }
                d += dcnt, d2 += dcnt;
+               nchars++;
              }
          }
          break;
@@ -4773,10 +4788,12 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
                 ptrdiff_t charpos = SYNTAX_TABLE_BYTE_TO_CHAR (offset) - 1;
                UPDATE_SYNTAX_TABLE (charpos);
                GET_CHAR_BEFORE_2 (c1, d, string1, end1, string2, end2);
+               nchars++;
                s1 = SYNTAX (c1);
                UPDATE_SYNTAX_TABLE_FORWARD (charpos + 1);
                PREFETCH_NOLIMIT ();
                GET_CHAR_AFTER (c2, d, dummy);
+               nchars++;
                s2 = SYNTAX (c2);
 
                if (/* Case 2: Only one of S1 and S2 is Sword.  */
@@ -4812,6 +4829,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
              UPDATE_SYNTAX_TABLE (charpos);
              PREFETCH ();
              GET_CHAR_AFTER (c2, d, dummy);
+             nchars++;
              s2 = SYNTAX (c2);
 
              /* Case 2: S2 is not Sword. */
@@ -4822,6 +4840,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
              if (!AT_STRINGS_BEG (d))
                {
                  GET_CHAR_BEFORE_2 (c1, d, string1, end1, string2, end2);
+                 nchars++;
                  UPDATE_SYNTAX_TABLE_BACKWARD (charpos - 1);
                  s1 = SYNTAX (c1);
 
@@ -4852,6 +4871,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
               ptrdiff_t charpos = SYNTAX_TABLE_BYTE_TO_CHAR (offset) - 1;
              UPDATE_SYNTAX_TABLE (charpos);
              GET_CHAR_BEFORE_2 (c1, d, string1, end1, string2, end2);
+             nchars++;
              s1 = SYNTAX (c1);
 
              /* Case 2: S1 is not Sword.  */
@@ -4863,6 +4883,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
                {
                  PREFETCH_NOLIMIT ();
                  GET_CHAR_AFTER (c2, d, dummy);
+                 nchars++;
                   UPDATE_SYNTAX_TABLE_FORWARD (charpos + 1);
                  s2 = SYNTAX (c2);
 
@@ -4893,6 +4914,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
              UPDATE_SYNTAX_TABLE (charpos);
              PREFETCH ();
              c2 = RE_STRING_CHAR (d, target_multibyte);
+             nchars++;
              s2 = SYNTAX (c2);
 
              /* Case 2: S2 is neither Sword nor Ssymbol. */
@@ -4903,6 +4925,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
              if (!AT_STRINGS_BEG (d))
                {
                  GET_CHAR_BEFORE_2 (c1, d, string1, end1, string2, end2);
+                 nchars++;
                  UPDATE_SYNTAX_TABLE_BACKWARD (charpos - 1);
                  s1 = SYNTAX (c1);
 
@@ -4931,6 +4954,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
               ptrdiff_t charpos = SYNTAX_TABLE_BYTE_TO_CHAR (offset) - 1;
              UPDATE_SYNTAX_TABLE (charpos);
              GET_CHAR_BEFORE_2 (c1, d, string1, end1, string2, end2);
+             nchars++;
              s1 = SYNTAX (c1);
 
              /* Case 2: S1 is neither Ssymbol nor Sword.  */
@@ -4942,6 +4966,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
                {
                  PREFETCH_NOLIMIT ();
                  c2 = RE_STRING_CHAR (d, target_multibyte);
+                 nchars++;
                  UPDATE_SYNTAX_TABLE_FORWARD (charpos + 1);
                  s2 = SYNTAX (c2);
 
@@ -4973,6 +4998,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
              if ((SYNTAX (c) != (enum syntaxcode) mcnt) ^ not)
                goto fail;
              d += len;
+             nchars++;
            }
          }
          break;
@@ -4999,6 +5025,7 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
              if ((!CHAR_HAS_CATEGORY (c, mcnt)) ^ not)
                goto fail;
              d += len;
+             nchars++;
            }
          }
          break;
@@ -5060,6 +5087,9 @@ re_match_2_internal (struct re_pattern_buffer *bufp,
   unbind_to (count, Qnil);
   SAFE_FREE ();
 
+  if (nchars > 0)
+    update_redisplay_ticks (nchars / 50 + 1, NULL);
+
   return -1;                           /* Failure to match.  */
 }
 
diff --git a/src/syntax.c b/src/syntax.c
index f9022d18d2..c13a8179ee 100644
--- a/src/syntax.c
+++ b/src/syntax.c
@@ -20,6 +20,7 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 #include <config.h>
 
 #include "lisp.h"
+#include "dispextern.h"
 #include "character.h"
 #include "buffer.h"
 #include "regex-emacs.h"
@@ -3195,6 +3196,7 @@ scan_sexps_forward (struct lisp_parse_state *state,
   ptrdiff_t out_bytepos, out_charpos;
   int temp;
   unsigned short int quit_count = 0;
+  ptrdiff_t started_from = from;
 
   prev_from = from;
   prev_from_byte = from_byte;
@@ -3474,6 +3476,13 @@ do { prev_from = from;                           \
                                 state->levelstarts);
   state->prev_syntax = (SYNTAX_FLAGS_COMSTARTEND_FIRST (prev_from_syntax)
                         || state->quoted) ? prev_from_syntax : Smax;
+
+  /* The factor of 10 below is a heuristic that needs to be tuned.  It
+     means we consider 10 buffer positions examined by this function
+     roughly equivalent to the display engine iterating over a single
+     buffer position.  */
+  if (from > started_from)
+    update_redisplay_ticks ((from - started_from) / 10 + 1, NULL);
 }
 
 /* Convert a (lisp) parse state to the internal form used in
diff --git a/src/window.c b/src/window.c
index ad7a85cf55..ad03a02758 100644
--- a/src/window.c
+++ b/src/window.c
@@ -5568,7 +5568,11 @@ window_scroll (Lisp_Object window, EMACS_INT n, bool 
whole, bool noerror)
   /* On GUI frames, use the pixel-based version which is much slower
      than the line-based one but can handle varying line heights.  */
   if (FRAME_WINDOW_P (XFRAME (XWINDOW (window)->frame)))
-    window_scroll_pixel_based (window, n, whole, noerror);
+    {
+      record_unwind_protect_void (unwind_display_working_on_window);
+      display_working_on_window_p = true;
+      window_scroll_pixel_based (window, n, whole, noerror);
+    }
   else
     window_scroll_line_based (window, n, whole, noerror);
 
@@ -6496,9 +6500,14 @@ displayed_window_lines (struct window *w)
   CLIP_TEXT_POS_FROM_MARKER (start, w->start);
 
   itdata = bidi_shelve_cache ();
+
+  specpdl_ref count = SPECPDL_INDEX ();
+  record_unwind_protect_void (unwind_display_working_on_window);
+  display_working_on_window_p = true;
   start_display (&it, w, start);
   move_it_vertically (&it, height);
   bottom_y = line_bottom_y (&it);
+  unbind_to (count, Qnil);
   bidi_unshelve_cache (itdata, false);
 
   /* Add in empty lines at the bottom of the window.  */
@@ -6592,6 +6601,10 @@ and redisplay normally--don't erase and redraw the 
frame.  */)
      data structures might not be set up yet then.  */
   if (!FRAME_INITIAL_P (XFRAME (w->frame)))
     {
+      specpdl_ref count = SPECPDL_INDEX ();
+
+      record_unwind_protect_void (unwind_display_working_on_window);
+      display_working_on_window_p = true;
       if (center_p)
        {
          struct it it;
@@ -6708,6 +6721,7 @@ and redisplay normally--don't erase and redraw the frame. 
 */)
 
          bidi_unshelve_cache (itdata, false);
        }
+      unbind_to (count, Qnil);
     }
   else
     {
diff --git a/src/xdisp.c b/src/xdisp.c
index 90809ac3ab..2e3711a20d 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -1030,6 +1030,15 @@ static struct glyph_slice null_glyph_slice = { 0, 0, 0, 
0 };
 
 bool redisplaying_p;
 
+/* True while some display-engine code is working on layout of some
+   window.
+
+   WARNING: Use sparingly, preferably only in top level of commands
+   and important functions, because using it in nested calls might
+   reset the flag when the inner call returns, behind the back of
+   the callers.  */
+bool display_working_on_window_p;
+
 /* If a string, XTread_socket generates an event to display that string.
    (The display is done in read_char.)  */
 
@@ -3222,6 +3231,8 @@ init_iterator (struct it *it, struct window *w,
 
   it->cmp_it.id = -1;
 
+  update_redisplay_ticks (0, w);
+
   /* Extra space between lines (on window systems only).  */
   if (base_face_id == DEFAULT_FACE_ID
       && FRAME_WINDOW_P (it->f))
@@ -8175,6 +8186,8 @@ void
 set_iterator_to_next (struct it *it, bool reseat_p)
 {
 
+  update_redisplay_ticks (1, it->w);
+
   switch (it->method)
     {
     case GET_FROM_BUFFER:
@@ -10957,6 +10970,7 @@ window_text_pixel_size (Lisp_Object window, Lisp_Object 
from, Lisp_Object to,
     max_y = XFIXNUM (y_limit);
 
   itdata = bidi_shelve_cache ();
+
   start_display (&it, w, startp);
 
   int start_y = it.current_y;
@@ -16724,9 +16738,14 @@ redisplay_internal (void)
                                 list_of_error,
                                 redisplay_window_error);
       if (update_miniwindow_p)
-       internal_condition_case_1 (redisplay_window_1,
-                                  FRAME_MINIBUF_WINDOW (sf), list_of_error,
-                                  redisplay_window_error);
+       {
+         Lisp_Object mini_window = FRAME_MINIBUF_WINDOW (sf);
+
+         displayed_buffer = XBUFFER (XWINDOW (mini_window)->contents);
+         internal_condition_case_1 (redisplay_window_1, mini_window,
+                                    list_of_error,
+                                    redisplay_window_error);
+       }
 
       /* Compare desired and current matrices, perform output.  */
 
@@ -16961,6 +16980,13 @@ unwind_redisplay (void)
   unblock_buffer_flips ();
 }
 
+/* Function registered with record_unwind_protect before calling
+   start_display outside of redisplay_internal.  */
+void
+unwind_display_working_on_window (void)
+{
+  display_working_on_window_p = false;
+}
 
 /* Mark the display of leaf window W as accurate or inaccurate.
    If ACCURATE_P, mark display of W as accurate.
@@ -17135,9 +17161,19 @@ redisplay_windows (Lisp_Object window)
 }
 
 static Lisp_Object
-redisplay_window_error (Lisp_Object ignore)
+redisplay_window_error (Lisp_Object error_data)
 {
   displayed_buffer->display_error_modiff = BUF_MODIFF (displayed_buffer);
+
+  /* When in redisplay, the error is captured and not shown.  Arrange
+     for it to be shown later.  */
+  if (max_redisplay_ticks > 0
+      && CONSP (error_data)
+      && EQ (XCAR (error_data), Qerror)
+      && STRINGP (XCAR (XCDR (error_data))))
+    Vdelayed_warnings_list = Fcons (list2 (XCAR (error_data),
+                                          XCAR (XCDR (error_data))),
+                                   Vdelayed_warnings_list);
   return Qnil;
 }
 
@@ -17156,6 +17192,68 @@ redisplay_window_1 (Lisp_Object window)
     redisplay_window (window, true);
   return Qnil;
 }
+
+
+/***********************************************************************
+                     Aborting runaway redisplay
+ ***********************************************************************/
+
+/* Update the redisplay-tick count for window W, and signal an error
+   if the tick count is above some threshold, indicating that
+   redisplay of the window takes "too long".
+
+   TICKS is the amount of ticks to add to the W's current count; zero
+   means to initialize the tick count to zero.
+
+   W can be NULL if TICKS is zero: that means unconditionally
+   re-initialize the current tick count to zero.
+
+   W can also be NULL if the caller doesn't know which window is being
+   processed by the display code.  In that case, if TICKS is non-zero,
+   we assume it's the last window that shows the current buffer.  */
+void
+update_redisplay_ticks (int ticks, struct window *w)
+{
+  /* This keeps track of the window on which redisplay is working.  */
+  static struct window *cwindow;
+  static EMACS_INT window_ticks;
+
+  /* We only initialize the count if this is a different window or
+     NULL.  Otherwise, this is a call from init_iterator for the same
+     window we tracked before, and we should keep the count.  */
+  if (!ticks && w != cwindow)
+    {
+      cwindow = w;
+      window_ticks = 0;
+    }
+  /* Some callers can be run in contexts unrelated to display code, so
+     don't abort them and don't update the tick count in those cases.  */
+  if ((!w && !redisplaying_p && !display_working_on_window_p)
+      /* We never disable redisplay of a mini-window, since that is
+        absolutely essential for communicating with Emacs.  */
+      || (w && MINI_WINDOW_P (w)))
+    return;
+
+  if (ticks > 0)
+    window_ticks += ticks;
+  if (max_redisplay_ticks > 0 && window_ticks > max_redisplay_ticks)
+    {
+      /* In addition to a buffer, this could be a window (for non-leaf
+        windows, not expected here) or nil (for pseudo-windows like
+        the one used for the native tool bar).  */
+      Lisp_Object contents = w ? w->contents : Qnil;
+      char *bufname =
+       NILP (contents)
+       ? SSDATA (BVAR (current_buffer, name))
+       : (BUFFERP (contents)
+          ? SSDATA (BVAR (XBUFFER (contents), name))
+          : (char *) "<unknown>");
+
+      windows_or_buffers_changed = 177;
+      error ("Window showing buffer %s takes too long to redisplay", bufname);
+    }
+}
+
 
 
 /* Set cursor position of W.  PT is assumed to be displayed in ROW.
@@ -35777,7 +35875,7 @@ be let-bound around code that needs to disable messages 
temporarily. */);
 
   DEFSYM (Qinhibit_free_realized_faces, "inhibit-free-realized-faces");
 
-  list_of_error = list1 (list2 (Qerror, Qvoid_variable));
+  list_of_error = list1 (Qerror);
   staticpro (&list_of_error);
 
   /* Values of those variables at last redisplay are stored as
@@ -36667,6 +36765,22 @@ and display the most important part of the minibuffer. 
  */);
 This makes it easier to edit character sequences that are
 composed on display.  */);
   composition_break_at_point = false;
+
+  DEFVAR_INT ("max-redisplay-ticks", max_redisplay_ticks,
+    doc: /* Maximum number of redisplay ticks before aborting redisplay of a 
window.
+
+This allows to abort the display of a window if the amount of low-level
+redisplay operations exceeds the value of this variable.  When display of
+a window is aborted due to this reason, the buffer shown in that window
+will not have its windows redisplayed until the buffer is modified or until
+you type \\[recenter-top-bottom] with one of its windows selected.
+You can also decide to kill the buffer and visit it in some
+other way, like under `so-long-mode' or literally.
+
+The default value is zero, which disables this feature.
+The recommended non-zero value is between 100000 and 1000000,
+depending on your patience and the speed of your system.  */);
+  max_redisplay_ticks = 0;
 }
 
 



reply via email to

[Prev in Thread] Current Thread [Next in Thread]