vim-patch:8.0.0647: syntax highlighting can make cause a freeze

Problem:    Syntax highlighting can make cause a freeze.
Solution:   Apply 'redrawtime' to syntax highlighting, per window.
06f1ed2f78
This commit is contained in:
Jan Edmund Lazo
2019-04-14 22:34:58 -04:00
parent eada8f5aaa
commit 07a182c6b5
7 changed files with 82 additions and 23 deletions

View File

@@ -4528,10 +4528,14 @@ A jump table for the options with a short description can be found at |Q_op|.
'redrawtime' 'rdt' number (default 2000) 'redrawtime' 'rdt' number (default 2000)
global global
Time in milliseconds for redrawing the display. Applies to Time in milliseconds for redrawing the display. Applies to
'hlsearch', 'inccommand' and |:match| highlighting. 'hlsearch', 'inccommand', |:match| highlighting and syntax
highlighting.
When redrawing takes more than this many milliseconds no further When redrawing takes more than this many milliseconds no further
matches will be highlighted. This is used to avoid that Vim hangs matches will be highlighted.
when using a very complicated pattern. For syntax highlighting the time applies per window. When over the
limit syntax highlighting is disabled until |CTRL-L| is used.
This is used to avoid that Vim hangs when using a very complicated
pattern.
*'regexpengine'* *'re'* *'regexpengine'* *'re'*
'regexpengine' 're' number (default 0) 'regexpengine' 're' number (default 0)

View File

@@ -391,6 +391,7 @@ typedef struct {
hashtab_T b_keywtab; /* syntax keywords hash table */ hashtab_T b_keywtab; /* syntax keywords hash table */
hashtab_T b_keywtab_ic; /* idem, ignore case */ hashtab_T b_keywtab_ic; /* idem, ignore case */
int b_syn_error; /* TRUE when error occurred in HL */ int b_syn_error; /* TRUE when error occurred in HL */
bool b_syn_slow; // true when 'redrawtime' reached
int b_syn_ic; /* ignore case for :syn cmds */ int b_syn_ic; /* ignore case for :syn cmds */
int b_syn_spell; /* SYNSPL_ values */ int b_syn_spell; /* SYNSPL_ values */
garray_T b_syn_patterns; /* table for syntax patterns */ garray_T b_syn_patterns; /* table for syntax patterns */

View File

@@ -4642,6 +4642,9 @@ static void nv_clear(cmdarg_T *cap)
if (!checkclearop(cap->oap)) { if (!checkclearop(cap->oap)) {
/* Clear all syntax states to force resyncing. */ /* Clear all syntax states to force resyncing. */
syn_stack_free_all(curwin->w_s); syn_stack_free_all(curwin->w_s);
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
wp->w_s->b_syn_slow = false;
}
redraw_later(CLEAR); redraw_later(CLEAR);
} }
} }

View File

@@ -5098,8 +5098,6 @@ static int regmatch(
printf("Premature EOL\n"); printf("Premature EOL\n");
#endif #endif
} }
if (status == RA_FAIL)
got_int = TRUE;
return status == RA_MATCH; return status == RA_MATCH;
} }
@@ -7226,7 +7224,7 @@ static void report_re_switch(char_u *pat)
/// @param nl /// @param nl
/// ///
/// @return TRUE if there is a match, FALSE if not. /// @return TRUE if there is a match, FALSE if not.
static int vim_regexec_both(regmatch_T *rmp, char_u *line, colnr_T col, bool nl) static int vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, bool nl)
{ {
regexec_T rex_save; regexec_T rex_save;
bool rex_in_use_save = rex_in_use; bool rex_in_use_save = rex_in_use;
@@ -7276,7 +7274,7 @@ int vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line,
colnr_T col) colnr_T col)
{ {
regmatch_T regmatch = {.regprog = *prog, .rm_ic = ignore_case}; regmatch_T regmatch = {.regprog = *prog, .rm_ic = ignore_case};
int r = vim_regexec_both(&regmatch, line, col, false); int r = vim_regexec_string(&regmatch, line, col, false);
*prog = regmatch.regprog; *prog = regmatch.regprog;
return r; return r;
} }
@@ -7285,7 +7283,7 @@ int vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line,
// Return TRUE if there is a match, FALSE if not. // Return TRUE if there is a match, FALSE if not.
int vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col) int vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col)
{ {
return vim_regexec_both(rmp, line, col, false); return vim_regexec_string(rmp, line, col, false);
} }
// Like vim_regexec(), but consider a "\n" in "line" to be a line break. // Like vim_regexec(), but consider a "\n" in "line" to be a line break.
@@ -7293,7 +7291,7 @@ int vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col)
// Return TRUE if there is a match, FALSE if not. // Return TRUE if there is a match, FALSE if not.
int vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col) int vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col)
{ {
return vim_regexec_both(rmp, line, col, true); return vim_regexec_string(rmp, line, col, true);
} }
/// Match a regexp against multiple lines. /// Match a regexp against multiple lines.

View File

@@ -1135,6 +1135,8 @@ static void win_update(win_T *wp)
/* reset got_int, otherwise regexp won't work */ /* reset got_int, otherwise regexp won't work */
save_got_int = got_int; save_got_int = got_int;
got_int = 0; got_int = 0;
// Set the time limit to 'redrawtime'.
proftime_T syntax_tm = profile_setlimit(p_rdt);
win_foldinfo.fi_level = 0; win_foldinfo.fi_level = 0;
/* /*
@@ -1362,7 +1364,7 @@ static void win_update(win_T *wp)
/* /*
* Display one line. * Display one line.
*/ */
row = win_line(wp, lnum, srow, wp->w_grid.Rows, mod_top == 0, false); row = win_line(wp, lnum, srow, wp->w_grid.Rows, mod_top == 0, false, &syntax_tm);
wp->w_lines[idx].wl_folded = FALSE; wp->w_lines[idx].wl_folded = FALSE;
wp->w_lines[idx].wl_lastlnum = lnum; wp->w_lines[idx].wl_lastlnum = lnum;
@@ -1393,7 +1395,7 @@ static void win_update(win_T *wp)
if (fold_count != 0) { if (fold_count != 0) {
fold_line(wp, fold_count, &win_foldinfo, lnum, row); fold_line(wp, fold_count, &win_foldinfo, lnum, row);
} else { } else {
(void)win_line(wp, lnum, srow, wp->w_grid.Rows, true, true); (void)win_line(wp, lnum, srow, wp->w_grid.Rows, true, true, &syntax_tm);
} }
} }
@@ -2041,7 +2043,8 @@ win_line (
int startrow, int startrow,
int endrow, int endrow,
bool nochange, // not updating for changed text bool nochange, // not updating for changed text
bool number_only // only update the number column bool number_only, // only update the number column
proftime_T *syntax_tm
) )
{ {
int c = 0; // init for GCC int c = 0; // init for GCC
@@ -2189,18 +2192,20 @@ win_line (
// To speed up the loop below, set extra_check when there is linebreak, // To speed up the loop below, set extra_check when there is linebreak,
// trailing white space and/or syntax processing to be done. // trailing white space and/or syntax processing to be done.
extra_check = wp->w_p_lbr; extra_check = wp->w_p_lbr;
if (syntax_present(wp) && !wp->w_s->b_syn_error) { if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow) {
// Prepare for syntax highlighting in this line. When there is an // Prepare for syntax highlighting in this line. When there is an
// error, stop syntax highlighting. // error, stop syntax highlighting.
save_did_emsg = did_emsg; save_did_emsg = did_emsg;
did_emsg = false; did_emsg = false;
syntax_start(wp, lnum); syntax_start(wp, lnum, syntax_tm);
if (did_emsg) { if (did_emsg) {
wp->w_s->b_syn_error = true; wp->w_s->b_syn_error = true;
} else { } else {
did_emsg = save_did_emsg; did_emsg = save_did_emsg;
has_syntax = true; if (!wp->w_s->b_syn_slow) {
extra_check = true; has_syntax = true;
extra_check = true;
}
} }
} }
@@ -2540,8 +2545,9 @@ win_line (
wp->w_cursor = pos; wp->w_cursor = pos;
/* Need to restart syntax highlighting for this line. */ /* Need to restart syntax highlighting for this line. */
if (has_syntax) if (has_syntax) {
syntax_start(wp, lnum); syntax_start(wp, lnum, syntax_tm);
}
} }
} }
@@ -2587,6 +2593,9 @@ win_line (
} }
next_search_hl(wp, shl, lnum, (colnr_T)v, next_search_hl(wp, shl, lnum, (colnr_T)v,
shl == &search_hl ? NULL : cur); shl == &search_hl ? NULL : cur);
if (wp->w_s->b_syn_slow) {
has_syntax = false;
}
// Need to get the line again, a multi-line regexp may have made it // Need to get the line again, a multi-line regexp may have made it
// invalid. // invalid.

View File

@@ -359,6 +359,7 @@ static reg_extmatch_T *next_match_extmatch = NULL;
static win_T *syn_win; /* current window for highlighting */ static win_T *syn_win; /* current window for highlighting */
static buf_T *syn_buf; /* current buffer for highlighting */ static buf_T *syn_buf; /* current buffer for highlighting */
static synblock_T *syn_block; /* current buffer for highlighting */ static synblock_T *syn_block; /* current buffer for highlighting */
static proftime_T *syn_tm;
static linenr_T current_lnum = 0; /* lnum of current state */ static linenr_T current_lnum = 0; /* lnum of current state */
static colnr_T current_col = 0; /* column of current state */ static colnr_T current_col = 0; /* column of current state */
static int current_state_stored = 0; /* TRUE if stored current state static int current_state_stored = 0; /* TRUE if stored current state
@@ -384,7 +385,7 @@ static int syn_time_on = FALSE;
* it. Careful: curbuf and curwin are likely to point to another buffer and * it. Careful: curbuf and curwin are likely to point to another buffer and
* window. * window.
*/ */
void syntax_start(win_T *wp, linenr_T lnum) void syntax_start(win_T *wp, linenr_T lnum, proftime_T *syntax_tm)
{ {
synstate_T *p; synstate_T *p;
synstate_T *last_valid = NULL; synstate_T *last_valid = NULL;
@@ -411,6 +412,7 @@ void syntax_start(win_T *wp, linenr_T lnum)
} }
changedtick = buf_get_changedtick(syn_buf); changedtick = buf_get_changedtick(syn_buf);
syn_win = wp; syn_win = wp;
syn_tm = syntax_tm;
/* /*
* Allocate syntax stack when needed. * Allocate syntax stack when needed.
@@ -2887,6 +2889,7 @@ static char_u *syn_getcurline(void)
static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T *st) static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T *st)
{ {
int r; int r;
int timed_out = 0;
proftime_T pt; proftime_T pt;
const int l_syn_time_on = syn_time_on; const int l_syn_time_on = syn_time_on;
@@ -2902,7 +2905,8 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T
} }
rmp->rmm_maxcol = syn_buf->b_p_smc; rmp->rmm_maxcol = syn_buf->b_p_smc;
r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col, NULL, NULL); r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col,
syn_tm, &timed_out);
if (l_syn_time_on) { if (l_syn_time_on) {
pt = profile_end(pt); pt = profile_end(pt);
@@ -2914,6 +2918,9 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T
if (r > 0) if (r > 0)
++st->match; ++st->match;
} }
if (timed_out) {
syn_win->w_s->b_syn_slow = true;
}
if (r > 0) { if (r > 0) {
rmp->startpos[0].lnum += lnum; rmp->startpos[0].lnum += lnum;
@@ -3144,6 +3151,7 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing)
void syntax_clear(synblock_T *block) void syntax_clear(synblock_T *block)
{ {
block->b_syn_error = false; // clear previous error block->b_syn_error = false; // clear previous error
block->b_syn_slow = false; // clear previous timeout
block->b_syn_ic = false; // Use case, by default block->b_syn_ic = false; // Use case, by default
block->b_syn_spell = SYNSPL_DEFAULT; // default spell checking block->b_syn_spell = SYNSPL_DEFAULT; // default spell checking
block->b_syn_containedin = false; block->b_syn_containedin = false;
@@ -5682,7 +5690,7 @@ int syn_get_id(
// When the position is not after the current position and in the same // When the position is not after the current position and in the same
// line of the same buffer, need to restart parsing. // line of the same buffer, need to restart parsing.
if (wp->w_buffer != syn_buf || lnum != current_lnum || col < current_col) { if (wp->w_buffer != syn_buf || lnum != current_lnum || col < current_col) {
syntax_start(wp, lnum); syntax_start(wp, lnum, NULL);
} else if (col > current_col) { } else if (col > current_col) {
// next_match may not be correct when moving around, e.g. with the // next_match may not be correct when moving around, e.g. with the
// "skip" expression in searchpair() // "skip" expression in searchpair()
@@ -5757,8 +5765,10 @@ int syn_get_foldlevel(win_T *wp, long lnum)
int level = 0; int level = 0;
/* Return quickly when there are no fold items at all. */ /* Return quickly when there are no fold items at all. */
if (wp->w_s->b_syn_folditems != 0) { if (wp->w_s->b_syn_folditems != 0
syntax_start(wp, lnum); && !wp->w_s->b_syn_error
&& !wp->w_s->b_syn_slow) {
syntax_start(wp, lnum, NULL);
for (int i = 0; i < current_state.ga_len; ++i) { for (int i = 0; i < current_state.ga_len; ++i) {
if (CUR_STATE(i).si_flags & HL_FOLD) { if (CUR_STATE(i).si_flags & HL_FOLD) {

View File

@@ -503,3 +503,37 @@ func Test_syn_wrong_z_one()
" call test_override("ALL", 0) " call test_override("ALL", 0)
bwipe! bwipe!
endfunc endfunc
func Test_syntax_hangs()
if !has('reltime') || !has('float') || !has('syntax')
return
endif
" This pattern takes a long time to match, it should timeout.
new
call setline(1, ['aaa', repeat('abc ', 1000), 'ccc'])
let start = reltime()
set nolazyredraw redrawtime=101
syn match Error /\%#=1a*.*X\@<=b*/
redraw
let elapsed = reltimefloat(reltime(start))
call assert_true(elapsed > 0.1)
call assert_true(elapsed < 1.0)
" second time syntax HL is disabled
let start = reltime()
redraw
let elapsed = reltimefloat(reltime(start))
call assert_true(elapsed < 0.1)
" after CTRL-L the timeout flag is reset
let start = reltime()
exe "normal \<C-L>"
redraw
let elapsed = reltimefloat(reltime(start))
call assert_true(elapsed > 0.1)
call assert_true(elapsed < 1.0)
set redrawtime&
bwipe!
endfunc