[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;
}
- master 82626e62ab 08/14: Allow aborting redisplay stuck in 'parse-partial-sexp', (continued)
- master 82626e62ab 08/14: Allow aborting redisplay stuck in 'parse-partial-sexp', Eli Zaretskii, 2022/06/24
- master a82af5ae78 09/14: Don't count ticks too eagerly in syntax.c, Eli Zaretskii, 2022/06/24
- master 5a596bbed5 02/14: Fix a typo in a doc string., Eli Zaretskii, 2022/06/24
- master bd44f39d6d 04/14: Restart tick counting every command, Eli Zaretskii, 2022/06/24
- master 264472a507 05/14: Handle W = NULL in 'update_redisplay_ticks', Eli Zaretskii, 2022/06/24
- master b89b23079c 12/14: Show warnings about aborted redisplay, Eli Zaretskii, 2022/06/24
- master 287e714fe0 11/14: Update redisplay ticks for more operations, and misc modifications, Eli Zaretskii, 2022/06/24
- master 94e1185369 06/14: Support callers which sometimes run unrelated to display code, Eli Zaretskii, 2022/06/24
- master 94672c2936 07/14: Handle too long syntactic fontifications, Eli Zaretskii, 2022/06/24
- master 4b00bc47c7 10/14: Update documentation, Eli Zaretskii, 2022/06/24
- master 289b457cac 14/14: Merge branch 'abort-redisplay',
Eli Zaretskii <=
- master 6fcd8ca743 13/14: ; * etc/NEWS: Minor update of description of 'max-redisplay-ticks'., Eli Zaretskii, 2022/06/24