summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Zaretskii <eliz@gnu.org>2022-06-24 10:44:44 +0300
committerEli Zaretskii <eliz@gnu.org>2022-06-24 10:44:44 +0300
commit289b457cac1439ac5f9bb6ce1143d91b8d52da91 (patch)
treefd527d18b0bb54a9af6ff4dfd15574c23efab11c
parentfbb703f60aa9bbe3a0c60ee6e52d60d58126999f (diff)
parent6fcd8ca743c35566e9216fd0681914fde05761b3 (diff)
downloademacs-289b457cac1439ac5f9bb6ce1143d91b8d52da91.tar.gz
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'.
-rw-r--r--doc/emacs/emacs.texi2
-rw-r--r--doc/emacs/trouble.texi139
-rw-r--r--etc/NEWS8
-rw-r--r--src/bidi.c42
-rw-r--r--src/dispextern.h4
-rw-r--r--src/dispnew.c15
-rw-r--r--src/indent.c2
-rw-r--r--src/keyboard.c7
-rw-r--r--src/regex-emacs.c30
-rw-r--r--src/syntax.c9
-rw-r--r--src/window.c16
-rw-r--r--src/xdisp.c124
12 files changed, 331 insertions, 67 deletions
diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi
index ad0fa5f0cd0..5e72699bbe8 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 8da96de1cb4..5dc9fe00680 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 6c04ae164cb..7ef71092741 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 4d2c74b17cd..267b62fb0bc 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 c7399ca2998..8bcd13dbb6d 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 3bd2e0e96c3..53a47c4b2f2 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 c071b43ab4c..c3d78518c43 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 6bc2afd40ac..7fb7afca87a 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 8662fe8d6d0..4d87418eeaf 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 f9022d18d26..c13a8179ee4 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 ad7a85cf550..ad03a02758e 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 90809ac3ab7..2e3711a20d8 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;
}