diff options
author | Eli Zaretskii <eliz@gnu.org> | 2022-06-24 10:44:44 +0300 |
---|---|---|
committer | Eli Zaretskii <eliz@gnu.org> | 2022-06-24 10:44:44 +0300 |
commit | 289b457cac1439ac5f9bb6ce1143d91b8d52da91 (patch) | |
tree | fd527d18b0bb54a9af6ff4dfd15574c23efab11c | |
parent | fbb703f60aa9bbe3a0c60ee6e52d60d58126999f (diff) | |
parent | 6fcd8ca743c35566e9216fd0681914fde05761b3 (diff) | |
download | emacs-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.texi | 2 | ||||
-rw-r--r-- | doc/emacs/trouble.texi | 139 | ||||
-rw-r--r-- | etc/NEWS | 8 | ||||
-rw-r--r-- | src/bidi.c | 42 | ||||
-rw-r--r-- | src/dispextern.h | 4 | ||||
-rw-r--r-- | src/dispnew.c | 15 | ||||
-rw-r--r-- | src/indent.c | 2 | ||||
-rw-r--r-- | src/keyboard.c | 7 | ||||
-rw-r--r-- | src/regex-emacs.c | 30 | ||||
-rw-r--r-- | src/syntax.c | 9 | ||||
-rw-r--r-- | src/window.c | 16 | ||||
-rw-r--r-- | src/xdisp.c | 124 |
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 @@ -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; } |