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'.
This commit is contained in:
Eli Zaretskii 2022-06-24 10:44:44 +03:00
commit 289b457cac
12 changed files with 331 additions and 67 deletions

View file

@ -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

View file

@ -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

View file

@ -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
+++

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -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

View file

@ -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. */
}

View file

@ -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

View file

@ -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
{

View file

@ -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;
}