diff options
Diffstat (limited to 'src')
53 files changed, 1644 insertions, 565 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 9cd178eaeb..3613a8f8bc 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -380,8 +380,6 @@ void nvim_buf_set_lines(uint64_t channel_id, } } - win_T *save_curwin = NULL; - tabpage_T *save_curtab = NULL; size_t new_len = replacement.size; size_t old_len = (size_t)(end - start); ptrdiff_t extra = 0; // lines added to text, can be negative @@ -397,8 +395,8 @@ void nvim_buf_set_lines(uint64_t channel_id, } try_start(); - bufref_T save_curbuf = { NULL, 0, 0 }; - switch_to_win_for_buf(buf, &save_curwin, &save_curtab, &save_curbuf); + aco_save_T aco; + aucmd_prepbuf(&aco, (buf_T *)buf); if (u_save((linenr_T)(start - 1), (linenr_T)end) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to save undo information"); @@ -465,27 +463,21 @@ void nvim_buf_set_lines(uint64_t channel_id, // changed range, and move any in the remainder of the buffer. // Only adjust marks if we managed to switch to a window that holds // the buffer, otherwise line numbers will be invalid. - if (save_curbuf.br_buf == NULL) { - mark_adjust((linenr_T)start, - (linenr_T)(end - 1), - MAXLNUM, - (long)extra, - false); - } + mark_adjust((linenr_T)start, + (linenr_T)(end - 1), + MAXLNUM, + (long)extra, + false); changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true); - if (save_curbuf.br_buf == NULL) { - fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra); - } - end: for (size_t i = 0; i < new_len; i++) { xfree(lines[i]); } xfree(lines); - restore_win_for_buf(save_curwin, save_curtab, &save_curbuf); + aucmd_restbuf(&aco); try_end(err); } @@ -1109,28 +1101,6 @@ free_exit: return 0; } -// Check if deleting lines made the cursor position invalid. -// Changed the lines from "lo" to "hi" and added "extra" lines (negative if -// deleted). -static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra) -{ - if (curwin->w_cursor.lnum >= lo) { - // Adjust the cursor position if it's in/after the changed - // lines. - if (curwin->w_cursor.lnum >= hi) { - curwin->w_cursor.lnum += extra; - check_cursor_col(); - } else if (extra < 0) { - curwin->w_cursor.lnum = lo; - check_cursor(); - } else { - check_cursor_col(); - } - changed_cline_bef_curs(); - } - invalidate_botline(); -} - // Normalizes 0-based indexes to buffer line numbers static int64_t normalize_index(buf_T *buf, int64_t index, bool *oob) { diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 94d1697083..a773234ea0 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -15,6 +15,7 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/api/buffer.h" +#include "nvim/api/window.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/lua/executor.h" @@ -31,6 +32,7 @@ #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/fileio.h" #include "nvim/option.h" #include "nvim/state.h" #include "nvim/syntax.h" @@ -978,11 +980,12 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) return 0; } if (scratch) { - WITH_BUFFER(buf, { - set_option_value("bh", 0L, "hide", OPT_LOCAL); - set_option_value("bt", 0L, "nofile", OPT_LOCAL); - set_option_value("swf", 0L, NULL, OPT_LOCAL); - }); + aco_save_T aco; + aucmd_prepbuf(&aco, buf); + set_option_value("bh", 0L, "hide", OPT_LOCAL); + set_option_value("bt", 0L, "nofile", OPT_LOCAL); + set_option_value("swf", 0L, NULL, OPT_LOCAL); + aucmd_restbuf(&aco); } return buf->b_fnum; } @@ -995,6 +998,8 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) /// GUI with the |ui-multigrid| extension. External windows are only supported /// with multigrid GUIs, and are displayed as separate top-level windows. /// +/// For a general overview of floats, see |api-floatwin|. +/// /// Exactly one of `external` and `relative` must be specified. /// /// @param buffer handle of buffer to be displayed in the window @@ -1047,7 +1052,6 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary options, Error *err) FUNC_API_SINCE(6) { - win_T *old = curwin; FloatConfig config = FLOAT_CONFIG_INIT; if (!parse_float_config(options, &config, false, err)) { return 0; @@ -1056,11 +1060,11 @@ Window nvim_open_win(Buffer buffer, Boolean enter, if (!wp) { return 0; } - if (buffer > 0) { - nvim_set_current_buf(buffer, err); + if (enter) { + win_enter(wp, false); } - if (!enter) { - win_enter(old, false); + if (buffer > 0) { + nvim_win_set_buf(wp->handle, buffer, err); } return wp->handle; } diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 6e4e7afeb2..8cb4e32815 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -2446,7 +2446,7 @@ void get_winopts(buf_T *buf) */ pos_T *buflist_findfpos(buf_T *buf) { - static pos_T no_position = INIT_POS_T(1, 0, 0); + static pos_T no_position = { 1, 0, 0 }; wininfo_T *wip = find_wininfo(buf, FALSE); return (wip == NULL) ? &no_position : &(wip->wi_fpos); diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index 79bed049ea..ee3fda5f6d 100644 --- a/src/nvim/buffer.h +++ b/src/nvim/buffer.h @@ -63,35 +63,6 @@ enum bfa_values { # include "buffer.h.generated.h" #endif -// Find a window that contains "buf" and switch to it. -// If there is no such window, use the current window and change "curbuf". -// Caller must initialize save_curbuf to NULL. -// restore_win_for_buf() MUST be called later! -static inline void switch_to_win_for_buf(buf_T *buf, - win_T **save_curwinp, - tabpage_T **save_curtabp, - bufref_T *save_curbuf) -{ - win_T *wp; - tabpage_T *tp; - - if (!find_win_for_buf(buf, &wp, &tp) - || switch_win(save_curwinp, save_curtabp, wp, tp, true) == FAIL) { - switch_buffer(save_curbuf, buf); - } -} - -static inline void restore_win_for_buf(win_T *save_curwin, - tabpage_T *save_curtab, - bufref_T *save_curbuf) -{ - if (save_curbuf->br_buf == NULL) { - restore_win(save_curwin, save_curtab, true); - } else { - restore_buffer(save_curbuf); - } -} - static inline void buf_set_changedtick(buf_T *const buf, const varnumber_T changedtick) REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE; @@ -103,6 +74,8 @@ static inline void buf_set_changedtick(buf_T *const buf, static inline void buf_set_changedtick(buf_T *const buf, const varnumber_T changedtick) { + typval_T old_val = buf->changedtick_di.di_tv; + #ifndef NDEBUG dictitem_T *const changedtick_di = tv_dict_find( buf->b_vars, S_LEN("changedtick")); @@ -116,6 +89,13 @@ static inline void buf_set_changedtick(buf_T *const buf, assert(changedtick_di == (dictitem_T *)&buf->changedtick_di); #endif buf->changedtick_di.di_tv.vval.v_number = changedtick; + + if (tv_dict_is_watched(buf->b_vars)) { + tv_dict_watcher_notify(buf->b_vars, + (char *)buf->changedtick_di.di_key, + &buf->changedtick_di.di_tv, + &old_val); + } } static inline varnumber_T buf_get_changedtick(const buf_T *const buf) @@ -145,15 +125,4 @@ static inline void buf_inc_changedtick(buf_T *const buf) buf_set_changedtick(buf, buf_get_changedtick(buf) + 1); } -#define WITH_BUFFER(b, code) \ - do { \ - win_T *save_curwin = NULL; \ - tabpage_T *save_curtab = NULL; \ - bufref_T save_curbuf = { NULL, 0, 0 }; \ - switch_to_win_for_buf(b, &save_curwin, &save_curtab, &save_curbuf); \ - code; \ - restore_win_for_buf(save_curwin, save_curtab, &save_curbuf); \ - } while (0) - - #endif // NVIM_BUFFER_H diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 6f0468dbea..f6b5a01915 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3924,10 +3924,11 @@ static int ins_compl_get_exp(pos_T *ini) compl_direction, compl_pattern); } else found_new_match = searchit(NULL, ins_buf, pos, - compl_direction, - compl_pattern, 1L, SEARCH_KEEP + SEARCH_NFMSG, - RE_LAST, (linenr_T)0, NULL); - --msg_silent; + compl_direction, + compl_pattern, 1L, + SEARCH_KEEP + SEARCH_NFMSG, + RE_LAST, (linenr_T)0, NULL, NULL); + msg_silent--; if (!compl_started || set_match_pos) { /* set "compl_started" even on fail */ compl_started = TRUE; @@ -5508,16 +5509,33 @@ internal_format ( /* remember position of blank just before text */ end_col = curwin->w_cursor.col; - /* find start of sequence of blanks */ + // find start of sequence of blanks + int wcc = 0; // counter for whitespace chars while (curwin->w_cursor.col > 0 && WHITECHAR(cc)) { dec_cursor(); cc = gchar_cursor(); + + // Increment count of how many whitespace chars in this + // group; we only need to know if it's more than one. + if (wcc < 2) { + wcc++; + } } - if (curwin->w_cursor.col == 0 && WHITECHAR(cc)) - break; /* only spaces in front of text */ - /* Don't break until after the comment leader */ - if (curwin->w_cursor.col < leader_len) + if (curwin->w_cursor.col == 0 && WHITECHAR(cc)) { + break; // only spaces in front of text + } + + // Don't break after a period when 'formatoptions' has 'p' and + // there are less than two spaces. + if (has_format_option(FO_PERIOD_ABBR) && cc == '.' && wcc < 2) { + continue; + } + + // Don't break until after the comment leader + if (curwin->w_cursor.col < leader_len) { break; + } + if (has_format_option(FO_ONE_LETTER)) { /* do not break after one-letter words */ if (curwin->w_cursor.col == 0) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index d18884ff07..5ef2a8772e 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -10086,10 +10086,23 @@ static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos) tv_list_append_number( l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0); if (getcurpos) { + const int save_set_curswant = curwin->w_set_curswant; + const colnr_T save_curswant = curwin->w_curswant; + const colnr_T save_virtcol = curwin->w_virtcol; + update_curswant(); tv_list_append_number(l, (curwin->w_curswant == MAXCOL ? (varnumber_T)MAXCOL : (varnumber_T)curwin->w_curswant + 1)); + + // Do not change "curswant", as it is unexpected that a get + // function has a side effect. + if (save_set_curswant) { + curwin->w_set_curswant = save_set_curswant; + curwin->w_curswant = save_curswant; + curwin->w_virtcol = save_virtcol; + curwin->w_valid &= ~VALID_VIRTCOL; + } } } @@ -10303,6 +10316,8 @@ static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) tv_dict_add_nr(dict, S_LEN("winid"), wp->handle); tv_dict_add_nr(dict, S_LEN("height"), wp->w_height); tv_dict_add_nr(dict, S_LEN("winrow"), wp->w_winrow); + tv_dict_add_nr(dict, S_LEN("topline"), wp->w_topline); + tv_dict_add_nr(dict, S_LEN("botline"), wp->w_botline - 1); tv_dict_add_nr(dict, S_LEN("width"), wp->w_width); tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum); tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol); @@ -13793,7 +13808,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) pos = save_cursor = curwin->w_cursor; subpatnum = searchit(curwin, curbuf, &pos, dir, (char_u *)pat, 1, - options, RE_SEARCH, (linenr_T)lnum_stop, &tm); + options, RE_SEARCH, (linenr_T)lnum_stop, &tm, NULL); if (subpatnum != FAIL) { if (flags & SP_SUBPAT) retval = subpatnum; @@ -14295,10 +14310,11 @@ do_searchpair( pat = pat3; for (;; ) { n = searchit(curwin, curbuf, &pos, dir, pat, 1L, - options, RE_SEARCH, lnum_stop, &tm); - if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) - /* didn't find it or found the first match again: FAIL */ + options, RE_SEARCH, lnum_stop, &tm, NULL); + if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) { + // didn't find it or found the first match again: FAIL break; + } if (firstpos.lnum == 0) firstpos = pos; @@ -20144,7 +20160,7 @@ void ex_function(exarg_T *eap) skip_until = vim_strsave((char_u *)"."); } - // Check for ":python <<EOF", ":lua <<EOF", etc. + // heredoc: Check for ":python <<EOF", ":lua <<EOF", etc. arg = skipwhite(skiptowhite(p)); if (arg[0] == '<' && arg[1] =='<' && ((p[0] == 'p' && p[1] == 'y' diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 2a5793f0d4..4356767cc9 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3432,7 +3432,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, || lnum <= curwin->w_botline); lnum++) { long nmatch = vim_regexec_multi(®match, curwin, curbuf, lnum, - (colnr_T)0, NULL); + (colnr_T)0, NULL, NULL); if (nmatch) { colnr_T copycol; colnr_T matchcol; @@ -3951,8 +3951,8 @@ skip: if (lastone || nmatch_tl > 0 || (nmatch = vim_regexec_multi(®match, curwin, - curbuf, sub_firstlnum, - matchcol, NULL)) == 0 + curbuf, sub_firstlnum, + matchcol, NULL, NULL)) == 0 || regmatch.startpos[0].lnum > 0) { if (new_start != NULL) { /* @@ -4016,7 +4016,7 @@ skip: } if (nmatch == -1 && !lastone) nmatch = vim_regexec_multi(®match, curwin, curbuf, - sub_firstlnum, matchcol, NULL); + sub_firstlnum, matchcol, NULL, NULL); /* * 5. break if there isn't another match in this line @@ -4314,7 +4314,7 @@ void ex_global(exarg_T *eap) if (global_busy) { lnum = curwin->w_cursor.lnum; match = vim_regexec_multi(®match, curwin, curbuf, lnum, - (colnr_T)0, NULL); + (colnr_T)0, NULL, NULL); if ((type == 'g' && match) || (type == 'v' && !match)) { global_exe_one(cmd, lnum); } @@ -4323,7 +4323,7 @@ void ex_global(exarg_T *eap) for (lnum = eap->line1; lnum <= eap->line2 && !got_int; lnum++) { // a match on this line? match = vim_regexec_multi(®match, curwin, curbuf, lnum, - (colnr_T)0, NULL); + (colnr_T)0, NULL, NULL); if ((type == 'g' && match) || (type == 'v' && !match)) { ml_setmarked(lnum); ndone++; diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 0ecc389699..4684a1b31d 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -1152,6 +1152,21 @@ static void script_dump_profile(FILE *fd) if (vim_fgets(IObuff, IOSIZE, sfd)) { break; } + // When a line has been truncated, append NL, taking care + // of multi-byte characters . + if (IObuff[IOSIZE - 2] != NUL && IObuff[IOSIZE - 2] != NL) { + int n = IOSIZE - 2; + + // Move to the first byte of this char. + // utf_head_off() doesn't work, because it checks + // for a truncated character. + while (n > 0 && (IObuff[n] & 0xc0) == 0x80) { + n--; + } + + IObuff[n] = NL; + IObuff[n + 1] = NUL; + } if (i < si->sn_prl_ga.ga_len && (pp = &PRL_ITEM(si, i))->snp_count > 0) { fprintf(fd, "%5d ", pp->snp_count); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 6b39ad8e87..5b9b4fed12 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -3699,7 +3699,7 @@ static linenr_T get_address(exarg_T *eap, } searchcmdlen = 0; if (!do_search(NULL, c, cmd, 1L, - SEARCH_HIS | SEARCH_MSG, NULL)) { + SEARCH_HIS | SEARCH_MSG, NULL, NULL)) { curwin->w_cursor = pos; cmd = NULL; goto error; @@ -3737,7 +3737,7 @@ static linenr_T get_address(exarg_T *eap, if (searchit(curwin, curbuf, &pos, *cmd == '?' ? BACKWARD : FORWARD, (char_u *)"", 1L, SEARCH_MSG, - i, (linenr_T)0, NULL) != FAIL) + i, (linenr_T)0, NULL, NULL) != FAIL) lnum = pos.lnum; else { cmd = NULL; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index a50e18efce..8e6fc5ad4f 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1061,7 +1061,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match) s->i = searchit(curwin, curbuf, &t, next_match ? FORWARD : BACKWARD, pat, s->count, search_flags, - RE_SEARCH, 0, NULL); + RE_SEARCH, 0, NULL, NULL); emsg_off--; ui_busy_stop(); if (s->i) { @@ -1847,7 +1847,7 @@ static int command_line_changed(CommandLineState *s) } s->i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count, search_flags, - &tm); + &tm, NULL); emsg_off--; // if interrupted while searching, behave like it failed if (got_int) { diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index b9de46efc8..7be4107c94 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -99,8 +99,9 @@ // defined and will have to be executed. // typedef struct AutoCmd { - char_u *cmd; // The command to be executed (NULL - // when command has been removed) + char_u *cmd; // Command to be executed (NULL when + // command has been removed) + bool once; // "One shot": removed after execution char nested; // If autocommands nest here char last; // last command in list scid_T scriptID; // script ID where defined @@ -121,20 +122,20 @@ typedef struct AutoPat { char last; // last pattern for apply_autocmds() } AutoPat; -/* - * struct used to keep status while executing autocommands for an event. - */ +/// +/// Struct used to keep status while executing autocommands for an event. +/// typedef struct AutoPatCmd { - AutoPat *curpat; /* next AutoPat to examine */ - AutoCmd *nextcmd; /* next AutoCmd to execute */ - int group; /* group being used */ - char_u *fname; /* fname to match with */ - char_u *sfname; /* sfname to match with */ - char_u *tail; /* tail of fname */ - event_T event; /* current event */ - int arg_bufnr; /* initially equal to <abuf>, set to zero when - buf is deleted */ - struct AutoPatCmd *next; /* chain of active apc-s for auto-invalidation*/ + AutoPat *curpat; // next AutoPat to examine + AutoCmd *nextcmd; // next AutoCmd to execute + int group; // group being used + char_u *fname; // fname to match with + char_u *sfname; // sfname to match with + char_u *tail; // tail of fname + event_T event; // current event + int arg_bufnr; // initially equal to <abuf>, set to zero when + // buf is deleted + struct AutoPatCmd *next; // chain of active apc-s for auto-invalidation } AutoPatCmd; #define AUGROUP_DEFAULT -1 /* default autocmd group */ @@ -5563,64 +5564,75 @@ static void show_autocmd(AutoPat *ap, event_T event) } } -/* - * Mark an autocommand pattern for deletion. - */ +// Mark an autocommand handler for deletion. static void au_remove_pat(AutoPat *ap) { xfree(ap->pat); ap->pat = NULL; ap->buflocal_nr = -1; - au_need_clean = TRUE; + au_need_clean = true; } -/* - * Mark all commands for a pattern for deletion. - */ +// Mark all commands for a pattern for deletion. static void au_remove_cmds(AutoPat *ap) { - AutoCmd *ac; - - for (ac = ap->cmds; ac != NULL; ac = ac->next) { + for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) { xfree(ac->cmd); ac->cmd = NULL; } - au_need_clean = TRUE; + au_need_clean = true; } -/* - * Cleanup autocommands and patterns that have been deleted. - * This is only done when not executing autocommands. - */ +// Delete one command from an autocmd pattern. +static void au_del_cmd(AutoCmd *ac) +{ + xfree(ac->cmd); + ac->cmd = NULL; + au_need_clean = true; +} + +/// Cleanup autocommands and patterns that have been deleted. +/// This is only done when not executing autocommands. static void au_cleanup(void) { AutoPat *ap, **prev_ap; AutoCmd *ac, **prev_ac; event_T event; - if (autocmd_busy || !au_need_clean) + if (autocmd_busy || !au_need_clean) { return; + } - /* loop over all events */ + // Loop over all events. for (event = (event_T)0; (int)event < (int)NUM_EVENTS; event = (event_T)((int)event + 1)) { - /* loop over all autocommand patterns */ + // Loop over all autocommand patterns. prev_ap = &(first_autopat[(int)event]); for (ap = *prev_ap; ap != NULL; ap = *prev_ap) { - /* loop over all commands for this pattern */ + // Loop over all commands for this pattern. prev_ac = &(ap->cmds); + bool has_cmd = false; + for (ac = *prev_ac; ac != NULL; ac = *prev_ac) { - /* remove the command if the pattern is to be deleted or when - * the command has been marked for deletion */ + // Remove the command if the pattern is to be deleted or when + // the command has been marked for deletion. if (ap->pat == NULL || ac->cmd == NULL) { *prev_ac = ac->next; xfree(ac->cmd); xfree(ac); - } else + } else { + has_cmd = true; prev_ac = &(ac->next); + } + } + + if (ap->pat != NULL && !has_cmd) { + // Pattern was not marked for deletion, but all of its commands were. + // So mark the pattern for deletion. + au_remove_pat(ap); } - /* remove the pattern if it has been marked for deletion */ + // Remove the pattern if it has been marked for deletion. if (ap->pat == NULL) { if (ap->next == NULL) { if (prev_ap == &(first_autopat[(int)event])) { @@ -5634,12 +5646,13 @@ static void au_cleanup(void) *prev_ap = ap->next; vim_regfree(ap->reg_prog); xfree(ap); - } else + } else { prev_ap = &(ap->next); + } } } - au_need_clean = FALSE; + au_need_clean = false; } /* @@ -5674,18 +5687,18 @@ void aubuflocal_remove(buf_T *buf) au_cleanup(); } -/* - * Add an autocmd group name. - * Return it's ID. Returns AUGROUP_ERROR (< 0) for error. - */ +// Add an autocmd group name. +// Return its ID. Returns AUGROUP_ERROR (< 0) for error. static int au_new_group(char_u *name) { int i = au_find_group(name); - if (i == AUGROUP_ERROR) { /* the group doesn't exist yet, add it */ - /* First try using a free entry. */ - for (i = 0; i < augroups.ga_len; ++i) - if (AUGROUP_NAME(i) == NULL) + if (i == AUGROUP_ERROR) { // the group doesn't exist yet, add it. + // First try using a free entry. + for (i = 0; i < augroups.ga_len; i++) { + if (AUGROUP_NAME(i) == NULL) { break; + } + } if (i == augroups.ga_len) { ga_grow(&augroups, 1); } @@ -5701,9 +5714,7 @@ static int au_new_group(char_u *name) static void au_del_group(char_u *name) { - int i; - - i = au_find_group(name); + int i = au_find_group(name); if (i == AUGROUP_ERROR) { // the group doesn't exist EMSG2(_("E367: No such group: \"%s\""), name); } else if (i == current_augroup) { @@ -5760,23 +5771,22 @@ bool au_has_group(const char_u *name) return au_find_group(name) != AUGROUP_ERROR; } -/* - * ":augroup {name}". - */ +/// ":augroup {name}". void do_augroup(char_u *arg, int del_group) { if (del_group) { - if (*arg == NUL) + if (*arg == NUL) { EMSG(_(e_argreq)); - else + } else { au_del_group(arg); - } else if (STRICMP(arg, "end") == 0) /* ":aug end": back to group 0 */ + } + } else if (STRICMP(arg, "end") == 0) { // ":aug end": back to group 0 current_augroup = AUGROUP_DEFAULT; - else if (*arg) { /* ":aug xxx": switch to group xxx */ + } else if (*arg) { // ":aug xxx": switch to group xxx int i = au_new_group(arg); if (i != AUGROUP_ERROR) current_augroup = i; - } else { /* ":aug": list the group names */ + } else { // ":aug": list the group names msg_start(); for (int i = 0; i < augroups.ga_len; ++i) { if (AUGROUP_NAME(i) != NULL) { @@ -5957,38 +5967,38 @@ void au_event_restore(char_u *old_ei) } } -/* - * do_autocmd() -- implements the :autocmd command. Can be used in the - * following ways: - * - * :autocmd <event> <pat> <cmd> Add <cmd> to the list of commands that - * will be automatically executed for <event> - * when editing a file matching <pat>, in - * the current group. - * :autocmd <event> <pat> Show the autocommands associated with - * <event> and <pat>. - * :autocmd <event> Show the autocommands associated with - * <event>. - * :autocmd Show all autocommands. - * :autocmd! <event> <pat> <cmd> Remove all autocommands associated with - * <event> and <pat>, and add the command - * <cmd>, for the current group. - * :autocmd! <event> <pat> Remove all autocommands associated with - * <event> and <pat> for the current group. - * :autocmd! <event> Remove all autocommands associated with - * <event> for the current group. - * :autocmd! Remove ALL autocommands for the current - * group. - * - * Multiple events and patterns may be given separated by commas. Here are - * some examples: - * :autocmd bufread,bufenter *.c,*.h set tw=0 smartindent noic - * :autocmd bufleave * set tw=79 nosmartindent ic infercase - * - * :autocmd * *.c show all autocommands for *.c files. - * - * Mostly a {group} argument can optionally appear before <event>. - */ +// Implements :autocmd. +// Defines an autocmd (does not execute; cf. apply_autocmds_group). +// +// Can be used in the following ways: +// +// :autocmd <event> <pat> <cmd> Add <cmd> to the list of commands that +// will be automatically executed for <event> +// when editing a file matching <pat>, in +// the current group. +// :autocmd <event> <pat> Show the autocommands associated with +// <event> and <pat>. +// :autocmd <event> Show the autocommands associated with +// <event>. +// :autocmd Show all autocommands. +// :autocmd! <event> <pat> <cmd> Remove all autocommands associated with +// <event> and <pat>, and add the command +// <cmd>, for the current group. +// :autocmd! <event> <pat> Remove all autocommands associated with +// <event> and <pat> for the current group. +// :autocmd! <event> Remove all autocommands associated with +// <event> for the current group. +// :autocmd! Remove ALL autocommands for the current +// group. +// +// Multiple events and patterns may be given separated by commas. Here are +// some examples: +// :autocmd bufread,bufenter *.c,*.h set tw=0 smartindent noic +// :autocmd bufleave * set tw=79 nosmartindent ic infercase +// +// :autocmd * *.c show all autocommands for *.c files. +// +// Mostly a {group} argument can optionally appear before <event>. void do_autocmd(char_u *arg_in, int forceit) { char_u *arg = arg_in; @@ -5997,6 +6007,7 @@ void do_autocmd(char_u *arg_in, int forceit) char_u *cmd; int need_free = false; int nested = false; + bool once = false; int group; if (*arg == '|') { @@ -6046,12 +6057,23 @@ void do_autocmd(char_u *arg_in, int forceit) } } - // Check for "nested" flag. cmd = skipwhite(cmd); - if (*cmd != NUL && STRNCMP(cmd, "nested", 6) == 0 - && ascii_iswhite(cmd[6])) { - nested = true; - cmd = skipwhite(cmd + 6); + for (size_t i = 0; i < 2; i++) { + if (*cmd != NUL) { + // Check for "++once" flag. + if (!once && STRNCMP(cmd, "++once", 6) == 0 && ascii_iswhite(cmd[6])) { + once = true; + cmd = skipwhite(cmd + 6); + } + // Check for "++nested" flag. + if (!nested + && ((STRNCMP(cmd, "++nested", 8) == 0 && ascii_iswhite(cmd[8])) + // Deprecated form (without "++"). + || (STRNCMP(cmd, "nested", 6) == 0 && ascii_iswhite(cmd[6])))) { + nested = true; + cmd = skipwhite(cmd + ('+' == cmd[0] ? 8 : 6)); + } + } } // Find the start of the commands. @@ -6081,7 +6103,8 @@ void do_autocmd(char_u *arg_in, int forceit) if (*arg == '*' || *arg == NUL || *arg == '|') { for (event_T event = (event_T)0; (int)event < (int)NUM_EVENTS; event = (event_T)((int)event + 1)) { - if (do_autocmd_event(event, pat, nested, cmd, forceit, group) == FAIL) { + if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group) + == FAIL) { break; } } @@ -6089,7 +6112,8 @@ void do_autocmd(char_u *arg_in, int forceit) while (*arg && *arg != '|' && !ascii_iswhite(*arg)) { event_T event = event_name2nr(arg, &arg); assert(event < NUM_EVENTS); - if (do_autocmd_event(event, pat, nested, cmd, forceit, group) == FAIL) { + if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group) + == FAIL) { break; } } @@ -6127,14 +6151,15 @@ static int au_get_grouparg(char_u **argp) return group; } -/* - * do_autocmd() for one event. - * If *pat == NUL do for all patterns. - * If *cmd == NUL show entries. - * If forceit == TRUE delete entries. - * If group is not AUGROUP_ALL, only use this group. - */ -static int do_autocmd_event(event_T event, char_u *pat, int nested, char_u *cmd, int forceit, int group) +// do_autocmd() for one event. +// Defines an autocmd (does not execute; cf. apply_autocmds_group). +// +// If *pat == NUL: do for all patterns. +// If *cmd == NUL: show entries. +// If forceit == TRUE: delete entries. +// If group is not AUGROUP_ALL: only use this group. +static int do_autocmd_event(event_T event, char_u *pat, bool once, int nested, + char_u *cmd, int forceit, int group) { AutoPat *ap; AutoPat **prev_ap; @@ -6333,6 +6358,7 @@ static int do_autocmd_event(event_T event, char_u *pat, int nested, char_u *cmd, ac->scriptID = current_SID; ac->next = NULL; *prev_ac = ac; + ac->once = once; ac->nested = nested; } } @@ -6996,7 +7022,7 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, patcmd.event = event; patcmd.arg_bufnr = autocmd_bufnr; patcmd.next = NULL; - auto_next_pat(&patcmd, FALSE); + auto_next_pat(&patcmd, false); /* found one, start executing the autocommands */ if (patcmd.curpat != NULL) { @@ -7020,8 +7046,11 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, } ap->last = true; check_lnums(true); // make sure cursor and topline are valid + + // Execute the autocmd. The `getnextac` callback handles iteration. do_cmdline(NULL, getnextac, (void *)&patcmd, DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); + if (eap != NULL) { (void)set_cmdarg(NULL, save_cmdarg); set_vim_var_nr(VV_CMDBANG, save_cmdbang); @@ -7233,12 +7262,18 @@ char_u *getnextac(int c, void *cookie, int indent) verbose_leave_scroll(); } retval = vim_strsave(ac->cmd); + // Remove one-shot ("once") autocmd in anticipation of its execution. + if (ac->once) { + au_del_cmd(ac); + } autocmd_nested = ac->nested; current_SID = ac->scriptID; - if (ac->last) + if (ac->last) { acp->nextcmd = NULL; - else + } else { acp->nextcmd = ac->next; + } + return retval; } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 52c5d65512..004b3252da 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -602,7 +602,7 @@ EXTERN bool can_si INIT(= false); EXTERN bool can_si_back INIT(= false); // w_cursor before formatting text. -EXTERN pos_T saved_cursor INIT(= INIT_POS_T(0, 0, 0)); +EXTERN pos_T saved_cursor INIT(= { 0, 0, 0 }); /* * Stuff for insert mode. @@ -789,7 +789,7 @@ EXTERN int autocmd_bufnr INIT(= 0); // fnum for <abuf> on cmdline EXTERN char_u *autocmd_match INIT(= NULL); // name for <amatch> on cmdline EXTERN int did_cursorhold INIT(= false); // set when CursorHold t'gerd // for CursorMoved event -EXTERN pos_T last_cursormoved INIT(= INIT_POS_T(0, 0, 0)); +EXTERN pos_T last_cursormoved INIT(= { 0, 0, 0 }); EXTERN int postponed_split INIT(= 0); /* for CTRL-W CTRL-] command */ EXTERN int postponed_split_flags INIT(= 0); /* args for win_split() */ diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 4c5fca6d39..3ba02be32d 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -160,14 +160,19 @@ void update_window_hl(win_T *wp, bool invalid) wp->w_hl_needs_update = false; // determine window specific background set in 'winhighlight' + bool float_win = wp->w_floating && !wp->w_float_config.external; if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] > 0) { wp->w_hl_attr_normal = hl_get_ui_attr(HLF_INACTIVE, wp->w_hl_ids[HLF_INACTIVE], true); + } else if (float_win && wp->w_hl_ids[HLF_NFLOAT] > 0) { + wp->w_hl_attr_normal = hl_get_ui_attr(HLF_NFLOAT, + wp->w_hl_ids[HLF_NFLOAT], true); } else if (wp->w_hl_id_normal > 0) { wp->w_hl_attr_normal = hl_get_ui_attr(-1, wp->w_hl_id_normal, true); } else { - wp->w_hl_attr_normal = 0; + wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0; } + if (wp != curwin) { wp->w_hl_attr_normal = hl_combine_attr(HL_ATTR(HLF_INACTIVE), wp->w_hl_attr_normal); diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 1da33bfea5..746d2c2dfc 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -90,6 +90,7 @@ typedef enum { , HLF_0 // Whitespace , HLF_INACTIVE // NormalNC: Normal text in non-current windows , HLF_MSGSEP // message separator line + , HLF_NFLOAT // Floating window , HLF_COUNT // MUST be the last one } hlf_T; @@ -142,6 +143,7 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_0] = "Whitespace", [HLF_INACTIVE] = "NormalNC", [HLF_MSGSEP] = "MsgSeparator", + [HLF_NFLOAT] = "NormalFloat", }); diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 05f78c76bc..af404c354b 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -924,7 +924,7 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, int i; int fnum = curbuf->b_fnum; linenr_T *lp; - static pos_T initpos = INIT_POS_T(1, 0, 0); + static pos_T initpos = { 1, 0, 0 }; if (line2 < line1 && amount_after == 0L) /* nothing to do */ return; @@ -1069,19 +1069,24 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, { \ posp->lnum += lnum_amount; \ assert(col_amount > INT_MIN && col_amount <= INT_MAX); \ - if (col_amount < 0 && posp->col <= (colnr_T)-col_amount) \ + if (col_amount < 0 && posp->col <= (colnr_T)-col_amount) { \ posp->col = 0; \ - else \ + } else if (posp->col < spaces_removed) { \ + posp->col = (int)col_amount + spaces_removed; \ + } else { \ posp->col += (colnr_T)col_amount; \ + } \ } \ } -/* - * Adjust marks in line "lnum" at column "mincol" and further: add - * "lnum_amount" to the line number and add "col_amount" to the column - * position. - */ -void mark_col_adjust(linenr_T lnum, colnr_T mincol, long lnum_amount, long col_amount) +// Adjust marks in line "lnum" at column "mincol" and further: add +// "lnum_amount" to the line number and add "col_amount" to the column +// position. +// "spaces_removed" is the number of spaces that were removed, matters when the +// cursor is inside them. +void mark_col_adjust( + linenr_T lnum, colnr_T mincol, long lnum_amount, long col_amount, + int spaces_removed) { int i; int fnum = curbuf->b_fnum; diff --git a/src/nvim/menu.c b/src/nvim/menu.c index aea297fce2..523bb20738 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -171,7 +171,7 @@ ex_menu(exarg_T *eap) if (enable != kNone) { // Change sensitivity of the menu. // For the PopUp menu, remove a menu for each mode separately. - // Careful: menu_nable_recurse() changes menu_path. + // Careful: menu_enable_recurse() changes menu_path. if (STRCMP(menu_path, "*") == 0) { // meaning: do all menus menu_path = (char_u *)""; } @@ -180,11 +180,11 @@ ex_menu(exarg_T *eap) for (i = 0; i < MENU_INDEX_TIP; ++i) if (modes & (1 << i)) { p = popup_mode_name(menu_path, i); - menu_nable_recurse(root_menu, p, MENU_ALL_MODES, enable); + menu_enable_recurse(root_menu, p, MENU_ALL_MODES, enable); xfree(p); } } - menu_nable_recurse(root_menu, menu_path, modes, enable); + menu_enable_recurse(root_menu, menu_path, modes, enable); } else if (unmenu) { /* * Delete menu(s). @@ -485,7 +485,10 @@ erret: * Set the (sub)menu with the given name to enabled or disabled. * Called recursively. */ -static int menu_nable_recurse(vimmenu_T *menu, char_u *name, int modes, int enable) +static int menu_enable_recurse(vimmenu_T *menu, + char_u *name, + int modes, + int enable) { char_u *p; @@ -503,13 +506,14 @@ static int menu_nable_recurse(vimmenu_T *menu, char_u *name, int modes, int enab EMSG(_(e_notsubmenu)); return FAIL; } - if (menu_nable_recurse(menu->children, p, modes, enable) - == FAIL) + if (menu_enable_recurse(menu->children, p, modes, enable) == FAIL) { return FAIL; - } else if (enable) + } + } else if (enable) { menu->enabled |= modes; - else + } else { menu->enabled &= ~modes; + } /* * When name is empty, we are doing all menu items for the given diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index a8cfc2d700..64a4b8b0b4 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -848,10 +848,11 @@ open_line ( /* Move marks after the line break to the new line. */ if (flags & OPENLINE_MARKFIX) mark_col_adjust(curwin->w_cursor.lnum, - curwin->w_cursor.col + less_cols_off, - 1L, (long)-less_cols); - } else + curwin->w_cursor.col + less_cols_off, + 1L, (long)-less_cols, 0); + } else { changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); + } } /* diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 49eef72a05..f12abd362f 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -3782,9 +3782,10 @@ find_decl ( valid = false; (void)valid; // Avoid "dead assignment" warning. t = searchit(curwin, curbuf, &curwin->w_cursor, FORWARD, - pat, 1L, searchflags, RE_LAST, (linenr_T)0, NULL); - if (curwin->w_cursor.lnum >= old_pos.lnum) - t = false; /* match after start is failure too */ + pat, 1L, searchflags, RE_LAST, (linenr_T)0, NULL, NULL); + if (curwin->w_cursor.lnum >= old_pos.lnum) { + t = false; // match after start is failure too + } if (thisblock && t != false) { const int64_t maxtravel = old_pos.lnum - curwin->w_cursor.lnum + 1; @@ -5384,7 +5385,7 @@ static int normal_search( curwin->w_set_curswant = true; i = do_search(cap->oap, dir, pat, cap->count1, - opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, NULL); + opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, NULL, NULL); if (i == 0) { clearop(cap->oap); } else { @@ -5443,7 +5444,7 @@ static void nv_csearch(cmdarg_T *cap) */ static void nv_brackets(cmdarg_T *cap) { - pos_T new_pos = INIT_POS_T(0, 0, 0); + pos_T new_pos = { 0, 0, 0 }; pos_T prev_pos; pos_T *pos = NULL; /* init for GCC */ pos_T old_pos; /* cursor position before command */ diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 99dee939fc..02ec3aad31 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -2605,17 +2605,16 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) static bool recursive = false; if (recursive || !has_event(EVENT_TEXTYANKPOST)) { - // No autocommand was defined - // or we yanked from this autocommand. + // No autocommand was defined, or we yanked from this autocommand. return; } recursive = true; - // set v:event to a dictionary with information about the yank + // Set the v:event dictionary with information about the yank. dict_T *dict = get_vim_var_dict(VV_EVENT); - // the yanked text + // The yanked text contents. list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(list, (const char *)reg->y_array[i], -1); @@ -2623,17 +2622,21 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) tv_list_set_lock(list, VAR_FIXED); tv_dict_add_list(dict, S_LEN("regcontents"), list); - // the register type + // Register type. char buf[NUMBUFLEN+2]; format_reg_type(reg->y_type, reg->y_width, buf, ARRAY_SIZE(buf)); tv_dict_add_str(dict, S_LEN("regtype"), buf); - // name of requested register or the empty string for an unnamed operation. + // Name of requested register, or empty string for unnamed operation. buf[0] = (char)oap->regname; buf[1] = NUL; tv_dict_add_str(dict, S_LEN("regname"), buf); - // kind of operation (yank/delete/change) + // Motion type: inclusive or exclusive. + tv_dict_add_special(dict, S_LEN("inclusive"), + oap->inclusive ? kSpecialVarTrue : kSpecialVarFalse); + + // Kind of operation: yank, delete, change). buf[0] = (char)get_op_char(oap->op_type); buf[1] = NUL; tv_dict_add_str(dict, S_LEN("operator"), buf); @@ -3704,10 +3707,16 @@ int do_join(size_t count, cend -= spaces[t]; memset(cend, ' ', (size_t)(spaces[t])); } + + // If deleting more spaces than adding, the cursor moves no more than + // what is added if it is inside these spaces. + const int spaces_removed = (int)((curr - curr_start) - spaces[t]); + mark_col_adjust(curwin->w_cursor.lnum + t, (colnr_T)0, (linenr_T)-t, - (long)(cend - newp + spaces[t] - (curr - curr_start))); - if (t == 0) + (long)(cend - newp - spaces_removed), spaces_removed); + if (t == 0) { break; + } curr = curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t - 1)); if (remove_comments) curr += comments[t - 1]; @@ -4135,14 +4144,14 @@ format_lines ( if (next_leader_len > 0) { (void)del_bytes(next_leader_len, false, false); mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L, - (long)-next_leader_len); + (long)-next_leader_len, 0); } else if (second_indent > 0) { // the "leader" for FO_Q_SECOND int indent = (int)getwhitecols_curline(); if (indent > 0) { (void)del_bytes(indent, FALSE, FALSE); mark_col_adjust(curwin->w_cursor.lnum, - (colnr_T)0, 0L, (long)-indent); + (colnr_T)0, 0L, (long)-indent, 0); } } curwin->w_cursor.lnum--; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 6dc5e418fc..74047e7cef 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -74,13 +74,14 @@ #define FO_MBYTE_JOIN 'M' /* no space before/after multi-byte char */ #define FO_MBYTE_JOIN2 'B' /* no space between multi-byte chars */ #define FO_ONE_LETTER '1' -#define FO_WHITE_PAR 'w' /* trailing white space continues paragr. */ -#define FO_AUTO 'a' /* automatic formatting */ -#define FO_REMOVE_COMS 'j' /* remove comment leaders when joining lines */ +#define FO_WHITE_PAR 'w' // trailing white space continues paragr. +#define FO_AUTO 'a' // automatic formatting +#define FO_REMOVE_COMS 'j' // remove comment leaders when joining lines +#define FO_PERIOD_ABBR 'p' // don't break a single space after a period #define DFLT_FO_VI "vt" #define DFLT_FO_VIM "tcqj" -#define FO_ALL "tcroq2vlb1mMBn,awj" /* for do_set() */ +#define FO_ALL "tcroq2vlb1mMBn,awjp" // for do_set() // characters for the p_cpo option: #define CPO_ALTREAD 'a' // ":read" sets alternate file name diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 7f2ebeec2f..27db675c52 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -288,7 +288,11 @@ static bool is_executable(const char *name) // a directory. return (S_ISREG(mode)); #else - return (S_ISREG(mode) && (S_IXUSR & mode)); + int r = -1; + if (S_ISREG(mode)) { + RUN_UV_FS_FUNC(r, uv_fs_access, name, X_OK, NULL); + } + return (r == 0); #endif } diff --git a/src/nvim/pos.h b/src/nvim/pos.h index 0a2afd5847..47d253e083 100644 --- a/src/nvim/pos.h +++ b/src/nvim/pos.h @@ -24,7 +24,6 @@ typedef struct { colnr_T coladd; } pos_T; -# define INIT_POS_T(l, c, ca) {l, c, ca} /* * Same, but without coladd. diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index f0c37c0e38..ee1cf2182f 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2128,8 +2128,9 @@ win_found: save_cursor = curwin->w_cursor; curwin->w_cursor.lnum = 0; if (!do_search(NULL, '/', qf_ptr->qf_pattern, (long)1, - SEARCH_KEEP, NULL)) + SEARCH_KEEP, NULL, NULL)) { curwin->w_cursor = save_cursor; + } } if ((fdo_flags & FDO_QUICKFIX) && old_KeyTyped) @@ -3758,7 +3759,7 @@ void ex_vimgrep(exarg_T *eap) ++lnum) { col = 0; while (vim_regexec_multi(®match, curwin, buf, lnum, - col, NULL) > 0) { + col, NULL, NULL) > 0) { // Pass the buffer number so that it gets used even for a // dummy buffer, unless duplicate_name is set, then the // buffer will be wiped out below. diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 3b3ca29dad..ab1a7c7b3e 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -1210,6 +1210,31 @@ char_u *skip_regexp(char_u *startp, int dirc, int magic, char_u **newp) return p; } +/// Return TRUE if the back reference is legal. We must have seen the close +/// brace. +/// TODO(vim): Should also check that we don't refer to something repeated +/// (+*=): what instance of the repetition should we match? +static int seen_endbrace(int refnum) +{ + if (!had_endbrace[refnum]) { + char_u *p; + + // Trick: check if "@<=" or "@<!" follows, in which case + // the \1 can appear before the referenced match. + for (p = regparse; *p != NUL; p++) { + if (p[0] == '@' && p[1] == '<' && (p[2] == '!' || p[2] == '=')) { + break; + } + } + + if (*p == NUL) { + EMSG(_("E65: Illegal back reference")); + rc_did_emsg = true; + return false; + } + } + return TRUE; +} /* * bt_regcomp() - compile a regular expression into internal code for the @@ -1928,22 +1953,8 @@ static char_u *regatom(int *flagp) int refnum; refnum = c - Magic('0'); - /* - * Check if the back reference is legal. We must have seen the - * close brace. - * TODO: Should also check that we don't refer to something - * that is repeated (+*=): what instance of the repetition - * should we match? - */ - if (!had_endbrace[refnum]) { - /* Trick: check if "@<=" or "@<!" follows, in which case - * the \1 can appear before the referenced match. */ - for (p = regparse; *p != NUL; ++p) - if (p[0] == '@' && p[1] == '<' - && (p[2] == '!' || p[2] == '=')) - break; - if (*p == NUL) - EMSG_RET_NULL(_("E65: Illegal back reference")); + if (!seen_endbrace(refnum)) { + return NULL; } ret = regnode(BACKREF + refnum); } @@ -3297,7 +3308,7 @@ bt_regexec_nl ( rex.reg_icombine = false; rex.reg_maxcol = 0; - long r = bt_regexec_both(line, col, NULL); + long r = bt_regexec_both(line, col, NULL, NULL); assert(r <= INT_MAX); return (int)r; } @@ -3357,7 +3368,8 @@ static inline char_u *cstrchr(const char_u *const s, const int c) /// @return zero if there is no match and number of lines contained in the match /// otherwise. static long bt_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, - linenr_T lnum, colnr_T col, proftime_T *tm) + linenr_T lnum, colnr_T col, + proftime_T *tm, int *timed_out) { rex.reg_match = NULL; rex.reg_mmatch = rmp; @@ -3370,18 +3382,16 @@ static long bt_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, rex.reg_icombine = false; rex.reg_maxcol = rmp->rmm_maxcol; - return bt_regexec_both(NULL, col, tm); + return bt_regexec_both(NULL, col, tm, timed_out); } -/* - * Match a regexp against a string ("line" points to the string) or multiple - * lines ("line" is NULL, use reg_getline()). - * Returns 0 for failure, number of lines contained in the match otherwise. - */ +/// Match a regexp against a string ("line" points to the string) or multiple +/// lines ("line" is NULL, use reg_getline()). +/// @return 0 for failure, or number of lines contained in the match. static long bt_regexec_both(char_u *line, - colnr_T col, /* column to start looking for match */ - proftime_T *tm /* timeout limit or NULL */ - ) + colnr_T col, // column to start search + proftime_T *tm, // timeout limit or NULL + int *timed_out) // flag set on timeout or NULL { bt_regprog_T *prog; char_u *s; @@ -3483,7 +3493,7 @@ static long bt_regexec_both(char_u *line, && (utf_fold(prog->regstart) == utf_fold(c) || (c < 255 && prog->regstart < 255 && mb_tolower(prog->regstart) == mb_tolower(c))))) { - retval = regtry(prog, col); + retval = regtry(prog, col, tm, timed_out); } else { retval = 0; } @@ -3507,9 +3517,10 @@ static long bt_regexec_both(char_u *line, break; } - retval = regtry(prog, col); - if (retval > 0) + retval = regtry(prog, col, tm, timed_out); + if (retval > 0) { break; + } /* if not currently on the first line, get it again */ if (reglnum != 0) { @@ -3525,8 +3536,12 @@ static long bt_regexec_both(char_u *line, /* Check for timeout once in a twenty times to avoid overhead. */ if (tm != NULL && ++tm_count == 20) { tm_count = 0; - if (profile_passed_limit(*tm)) + if (profile_passed_limit(*tm)) { + if (timed_out != NULL) { + *timed_out = true; + } break; + } } } } @@ -3582,11 +3597,12 @@ void unref_extmatch(reg_extmatch_T *em) } } -/* - * regtry - try match of "prog" with at regline["col"]. - * Returns 0 for failure, number of lines contained in the match otherwise. - */ -static long regtry(bt_regprog_T *prog, colnr_T col) +/// Try match of "prog" with at regline["col"]. +/// @returns 0 for failure, or number of lines contained in the match. +static long regtry(bt_regprog_T *prog, + colnr_T col, + proftime_T *tm, // timeout limit or NULL + int *timed_out) // flag set on timeout or NULL { reginput = regline + col; need_clear_subexpr = TRUE; @@ -3594,8 +3610,9 @@ static long regtry(bt_regprog_T *prog, colnr_T col) if (prog->reghasz == REX_SET) need_clear_zsubexpr = TRUE; - if (regmatch(prog->program + 1) == 0) + if (regmatch(prog->program + 1, tm, timed_out) == 0) { return 0; + } cleanup_subexpr(); if (REG_MULTI) { @@ -3736,24 +3753,23 @@ static int reg_match_visual(void) static long bl_minval; static long bl_maxval; -/* - * regmatch - main matching routine - * - * Conceptually the strategy is simple: Check to see whether the current node - * matches, push an item onto the regstack and loop to see whether the rest - * matches, and then act accordingly. In practice we make some effort to - * avoid using the regstack, in particular by going through "ordinary" nodes - * (that don't need to know whether the rest of the match failed) by a nested - * loop. - * - * Returns TRUE when there is a match. Leaves reginput and reglnum just after - * the last matched character. - * Returns FALSE when there is no match. Leaves reginput and reglnum in an - * undefined state! - */ -static int -regmatch ( - char_u *scan /* Current node. */ +/// Main matching routine +/// +/// Conceptually the strategy is simple: Check to see whether the current node +/// matches, push an item onto the regstack and loop to see whether the rest +/// matches, and then act accordingly. In practice we make some effort to +/// avoid using the regstack, in particular by going through "ordinary" nodes +/// (that don't need to know whether the rest of the match failed) by a nested +/// loop. +/// +/// Returns TRUE when there is a match. Leaves reginput and reglnum just after +/// the last matched character. +/// Returns FALSE when there is no match. Leaves reginput and reglnum in an +/// undefined state! +static int regmatch( + char_u *scan, // Current node. + proftime_T *tm, // timeout limit or NULL + int *timed_out // flag set on timeout or NULL ) { char_u *next; /* Next node. */ @@ -3761,15 +3777,16 @@ regmatch ( int c; regitem_T *rp; int no; - int status; /* one of the RA_ values: */ -#define RA_FAIL 1 /* something failed, abort */ -#define RA_CONT 2 /* continue in inner loop */ -#define RA_BREAK 3 /* break inner loop */ -#define RA_MATCH 4 /* successful match */ -#define RA_NOMATCH 5 /* didn't match */ - - /* Make "regstack" and "backpos" empty. They are allocated and freed in - * bt_regexec_both() to reduce malloc()/free() calls. */ + int status; // one of the RA_ values: + int tm_count = 0; +#define RA_FAIL 1 // something failed, abort +#define RA_CONT 2 // continue in inner loop +#define RA_BREAK 3 // break inner loop +#define RA_MATCH 4 // successful match +#define RA_NOMATCH 5 // didn't match + + // Make "regstack" and "backpos" empty. They are allocated and freed in + // bt_regexec_both() to reduce malloc()/free() calls. regstack.ga_len = 0; backpos.ga_len = 0; @@ -3797,6 +3814,17 @@ regmatch ( status = RA_FAIL; break; } + // Check for timeout once in a 100 times to avoid overhead. + if (tm != NULL && ++tm_count == 100) { + tm_count = 0; + if (profile_passed_limit(*tm)) { + if (timed_out != NULL) { + *timed_out = true; + } + status = RA_FAIL; + break; + } + } status = RA_CONT; #ifdef REGEXP_DEBUG @@ -7116,6 +7144,8 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags) } bt_regengine.expr = expr; nfa_regengine.expr = expr; + // reg_iswordc() uses rex.reg_buf + rex.reg_buf = curbuf; // // First try the NFA engine, unless backtracking was requested. @@ -7273,12 +7303,13 @@ int vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col) /// Return zero if there is no match. Return number of lines contained in the /// match otherwise. long vim_regexec_multi( - regmmatch_T *rmp, - win_T *win, /* window in which to search or NULL */ - buf_T *buf, /* buffer in which to search */ - linenr_T lnum, /* nr of line to start looking for match */ - colnr_T col, /* column to start looking for match */ - proftime_T *tm /* timeout limit or NULL */ + regmmatch_T *rmp, + win_T *win, // window in which to search or NULL + buf_T *buf, // buffer in which to search + linenr_T lnum, // nr of line to start looking for match + colnr_T col, // column to start looking for match + proftime_T *tm, // timeout limit or NULL + int *timed_out // flag is set when timeout limit reached ) { regexec_T rex_save; @@ -7291,7 +7322,7 @@ long vim_regexec_multi( rex_in_use = true; int result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col, - tm); + tm, timed_out); // NFA engine aborted because it's very slow, use backtracking engine instead. if (rmp->regprog->re_engine == AUTOMATIC_ENGINE @@ -7311,7 +7342,7 @@ long vim_regexec_multi( if (rmp->regprog != NULL) { result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col, - tm); + tm, timed_out); } xfree(pat); diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h index b5d56e07fc..298d82fc6b 100644 --- a/src/nvim/regexp_defs.h +++ b/src/nvim/regexp_defs.h @@ -156,11 +156,11 @@ struct reg_extmatch { }; struct regengine { - regprog_T *(*regcomp)(char_u*, int); + regprog_T *(*regcomp)(char_u *, int); void (*regfree)(regprog_T *); - int (*regexec_nl)(regmatch_T*, char_u*, colnr_T, bool); - long (*regexec_multi)(regmmatch_T*, win_T*, buf_T*, linenr_T, colnr_T, - proftime_T*); + int (*regexec_nl)(regmatch_T *, char_u *, colnr_T, bool); + long (*regexec_multi)(regmmatch_T *, win_T *, buf_T *, linenr_T, colnr_T, + proftime_T *, int *); char_u *expr; }; diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 95030974d8..b935b44291 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -1338,8 +1338,15 @@ static int nfa_regatom(void) case Magic('7'): case Magic('8'): case Magic('9'): - EMIT(NFA_BACKREF1 + (no_Magic(c) - '1')); - nfa_has_backref = TRUE; + { + int refnum = no_Magic(c) - '1'; + + if (!seen_endbrace(refnum + 1)) { + return FAIL; + } + EMIT(NFA_BACKREF1 + refnum); + nfa_has_backref = true; + } break; case Magic('z'): @@ -3568,6 +3575,7 @@ static char *pim_info(nfa_pim_T *pim) // Used during execution: whether a match has been found. static int nfa_match; static proftime_T *nfa_time_limit; +static int *nfa_timed_out; static int nfa_time_count; // Copy postponed invisible match info from "from" to "to". @@ -4574,7 +4582,9 @@ static bool nfa_re_num_cmp(uintmax_t val, int op, uintmax_t pos) * "pim" is NULL or contains info about a Postponed Invisible Match (start * position). */ -static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T *prog, regsubs_T *submatch, regsubs_T *m, int **listids) +static int recursive_regmatch( + nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T *prog, + regsubs_T *submatch, regsubs_T *m, int **listids, int *listids_len) { int save_reginput_col = (int)(reginput - regline); int save_reglnum = reglnum; @@ -4657,8 +4667,10 @@ static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T if (nfa_ll_index == 1) { /* Already calling nfa_regmatch() recursively. Save the lastlist[1] * values and clear them. */ - if (*listids == NULL) { + if (*listids == NULL || *listids_len < nstate) { + xfree(*listids); *listids = xmalloc(sizeof(**listids) * nstate); + *listids_len = nstate; } nfa_save_listids(prog, *listids); need_restore = TRUE; @@ -4939,6 +4951,17 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) #undef PTR2LEN } +static int nfa_did_time_out(void) +{ + if (nfa_time_limit != NULL && profile_passed_limit(*nfa_time_limit)) { + if (nfa_timed_out != NULL) { + *nfa_timed_out = true; + } + return true; + } + return false; +} + /// Main matching routine. /// /// Run NFA to determine whether it matches reginput. @@ -4960,6 +4983,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, nfa_list_T *thislist; nfa_list_T *nextlist; int *listids = NULL; + int listids_len = 0; nfa_state_T *add_state; bool add_here; int add_count; @@ -4982,7 +5006,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, #endif return false; } - if (nfa_time_limit != NULL && profile_passed_limit(*nfa_time_limit)) { + if (nfa_did_time_out()) { #ifdef NFA_REGEXP_DEBUG_LOG fclose(debug); #endif @@ -5095,8 +5119,20 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, if (thislist->n == 0) break; - /* compute nextlist */ - for (listidx = 0; listidx < thislist->n; ++listidx) { + // compute nextlist + for (listidx = 0; listidx < thislist->n; listidx++) { + // If the list gets very long there probably is something wrong. + // At least allow interrupting with CTRL-C. + fast_breakcheck(); + if (got_int) { + break; + } + if (nfa_time_limit != NULL && ++nfa_time_count == 20) { + nfa_time_count = 0; + if (nfa_did_time_out()) { + break; + } + } t = &thislist->t[listidx]; #ifdef NFA_REGEXP_DEBUG_LOG @@ -5240,7 +5276,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // First try matching the invisible match, then what // follows. result = recursive_regmatch(t->state, NULL, prog, submatch, m, - &listids); + &listids, &listids_len); if (result == NFA_TOO_EXPENSIVE) { nfa_match = result; goto theend; @@ -5341,7 +5377,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // First try matching the pattern. result = recursive_regmatch(t->state, NULL, prog, submatch, m, - &listids); + &listids, &listids_len); if (result == NFA_TOO_EXPENSIVE) { nfa_match = result; goto theend; @@ -6048,8 +6084,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, fprintf(log_fd, "Postponed recursive nfa_regmatch()\n"); fprintf(log_fd, "\n"); #endif - result = recursive_regmatch(pim->state, pim, - prog, submatch, m, &listids); + result = recursive_regmatch(pim->state, pim, prog, submatch, m, + &listids, &listids_len); pim->result = result ? NFA_PIM_MATCH : NFA_PIM_NOMATCH; // for \@! and \@<! it is a match when the result is // FALSE @@ -6218,7 +6254,7 @@ nextchar: // Check for timeout once every twenty times to avoid overhead. if (nfa_time_limit != NULL && ++nfa_time_count == 20) { nfa_time_count = 0; - if (profile_passed_limit(*nfa_time_limit)) { + if (nfa_did_time_out()) { break; } } @@ -6245,7 +6281,10 @@ theend: // Try match of "prog" with at regline["col"]. // Returns <= 0 for failure, number of lines contained in the match otherwise. -static long nfa_regtry(nfa_regprog_T *prog, colnr_T col, proftime_T *tm) +static long nfa_regtry(nfa_regprog_T *prog, + colnr_T col, + proftime_T *tm, // timeout limit or NULL + int *timed_out) // flag set on timeout or NULL { int i; regsubs_T subs, m; @@ -6256,6 +6295,7 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col, proftime_T *tm) reginput = regline + col; nfa_time_limit = tm; + nfa_timed_out = timed_out; nfa_time_count = 0; #ifdef REGEXP_DEBUG @@ -6364,10 +6404,12 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col, proftime_T *tm) /// @param line String in which to search or NULL /// @param startcol Column to start looking for match /// @param tm Timeout limit or NULL +/// @param timed_out Flag set on timeout or NULL /// /// @return <= 0 if there is no match and number of lines contained in the /// match otherwise. -static long nfa_regexec_both(char_u *line, colnr_T startcol, proftime_T *tm) +static long nfa_regexec_both(char_u *line, colnr_T startcol, + proftime_T *tm, int *timed_out) { nfa_regprog_T *prog; long retval = 0L; @@ -6449,7 +6491,7 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, proftime_T *tm) prog->state[i].lastlist[1] = 0; } - retval = nfa_regtry(prog, col, tm); + retval = nfa_regtry(prog, col, tm, timed_out); nfa_regengine.expr = NULL; @@ -6596,7 +6638,7 @@ nfa_regexec_nl ( rex.reg_ic = rmp->rm_ic; rex.reg_icombine = false; rex.reg_maxcol = 0; - return nfa_regexec_both(line, col, NULL); + return nfa_regexec_both(line, col, NULL, NULL); } /// Matches a regexp against multiple lines. @@ -6608,6 +6650,7 @@ nfa_regexec_nl ( /// @param lnum Number of line to start looking for match /// @param col Column to start looking for match /// @param tm Timeout limit or NULL +/// @param timed_out Flag set on timeout or NULL /// /// @return <= 0 if there is no match and number of lines contained in the match /// otherwise. @@ -6634,7 +6677,8 @@ nfa_regexec_nl ( /// @par /// FIXME if this behavior is not compatible. static long nfa_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, - linenr_T lnum, colnr_T col, proftime_T *tm) + linenr_T lnum, colnr_T col, + proftime_T *tm, int *timed_out) { rex.reg_match = NULL; rex.reg_mmatch = rmp; @@ -6647,5 +6691,5 @@ static long nfa_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, rex.reg_icombine = false; rex.reg_maxcol = rmp->rmm_maxcol; - return nfa_regexec_both(NULL, col, tm); + return nfa_regexec_both(NULL, col, tm, timed_out); } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 08bb4e4a52..5255fd2a51 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -5651,13 +5651,15 @@ next_search_hl ( && cur != NULL && shl == &cur->hl && cur->match.regprog == cur->hl.rm.regprog); + int timed_out = false; - nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol, &(shl->tm)); - /* Copy the regprog, in case it got freed and recompiled. */ + nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol, + &(shl->tm), &timed_out); + // Copy the regprog, in case it got freed and recompiled. if (regprog_is_copy) { cur->match.regprog = cur->hl.rm.regprog; } - if (called_emsg || got_int) { + if (called_emsg || got_int || timed_out) { // Error while handling regexp: stop using this regexp. if (shl == &search_hl) { // don't free regprog in the match list, it's a copy diff --git a/src/nvim/search.c b/src/nvim/search.c index f6b80d1b79..777ea07a21 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -523,9 +523,10 @@ int searchit( char_u *pat, long count, int options, - int pat_use, /* which pattern to use when "pat" is empty */ - linenr_T stop_lnum, /* stop after this line number when != 0 */ - proftime_T *tm /* timeout limit or NULL */ + int pat_use, // which pattern to use when "pat" is empty + linenr_T stop_lnum, // stop after this line number when != 0 + proftime_T *tm, // timeout limit or NULL + int *timed_out // set when timed out or NULL ) { int found; @@ -620,9 +621,9 @@ int searchit( // Look for a match somewhere in line "lnum". colnr_T col = at_first_line && (options & SEARCH_COL) ? pos->col : 0; nmatched = vim_regexec_multi(®match, win, buf, - lnum, col, tm); + lnum, col, tm, timed_out); // Abort searching on an error (e.g., out of stack). - if (called_emsg) { + if (called_emsg || (timed_out != NULL && *timed_out)) { break; } if (nmatched > 0) { @@ -686,8 +687,9 @@ int searchit( } if (ptr[matchcol] == NUL - || (nmatched = vim_regexec_multi(®match, win, buf, lnum, - matchcol, tm)) == 0) { + || (nmatched = vim_regexec_multi(®match, win, buf, + lnum, matchcol, tm, + timed_out)) == 0) { match_ok = false; break; } @@ -771,7 +773,7 @@ int searchit( if (ptr[matchcol] == NUL || (nmatched = vim_regexec_multi( ®match, win, buf, lnum + matchpos.lnum, matchcol, - tm)) == 0) { + tm, timed_out)) == 0) { // If the search timed out, we did find a match // but it might be the wrong one, so that's not // OK. @@ -855,30 +857,35 @@ int searchit( * twice. */ if (!p_ws || stop_lnum != 0 || got_int || called_emsg + || (timed_out != NULL && timed_out) || break_loop - || found || loop) + || found || loop) { break; - - /* - * If 'wrapscan' is set we continue at the other end of the file. - * If 'shortmess' does not contain 's', we give a message. - * This message is also remembered in keep_msg for when the screen - * is redrawn. The keep_msg is cleared whenever another message is - * written. - */ - if (dir == BACKWARD) /* start second loop at the other end */ + } + // + // If 'wrapscan' is set we continue at the other end of the file. + // If 'shortmess' does not contain 's', we give a message. + // This message is also remembered in keep_msg for when the screen + // is redrawn. The keep_msg is cleared whenever another message is + // written. + // + if (dir == BACKWARD) { // start second loop at the other end lnum = buf->b_ml.ml_line_count; - else + } else { lnum = 1; - if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG)) + } + if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG)) { give_warning((char_u *)_(dir == BACKWARD - ? top_bot_msg : bot_top_msg), true); + ? top_bot_msg : bot_top_msg), true); + } } if (got_int || called_emsg + || (timed_out != NULL && *timed_out) || break_loop - ) + ) { break; - } while (--count > 0 && found); /* stop after count matches or no match */ + } + } while (--count > 0 && found); // stop after count matches or no match vim_regfree(regmatch.regprog); @@ -965,7 +972,8 @@ int do_search( char_u *pat, long count, int options, - proftime_T *tm /* timeout limit or NULL */ + proftime_T *tm, // timeout limit or NULL + int *timed_out // flag set on timeout or NULL ) { pos_T pos; /* position of the last match */ @@ -1195,7 +1203,7 @@ int do_search( & (SEARCH_KEEP + SEARCH_PEEK + SEARCH_HIS + SEARCH_MSG + SEARCH_START + ((pat != NULL && *pat == ';') ? 0 : SEARCH_NOOF)))), - RE_LAST, (linenr_T)0, tm); + RE_LAST, (linenr_T)0, tm, timed_out); if (dircp != NULL) *dircp = dirc; /* restore second '/' or '?' for normal_cmd() */ @@ -3978,7 +3986,7 @@ current_search( result = searchit(curwin, curbuf, &pos, (dir ? FORWARD : BACKWARD), spats[last_idx].pat, i ? count : 1, - SEARCH_KEEP | flags, RE_SEARCH, 0, NULL); + SEARCH_KEEP | flags, RE_SEARCH, 0, NULL, NULL); /* First search may fail, but then start searching from the * beginning of the file (cursor might be on the search match) @@ -4025,7 +4033,7 @@ current_search( for (int i = 0; i < 2; i++) { result = searchit(curwin, curbuf, &pos, direction, spats[last_idx].pat, 0L, flags | SEARCH_KEEP, RE_SEARCH, - 0, NULL); + 0, NULL, NULL); // Search successfull, break out from the loop if (result) { break; @@ -4104,14 +4112,15 @@ static int is_one_char(char_u *pattern, bool move, pos_T *cur, flag = SEARCH_START; } if (searchit(curwin, curbuf, &pos, direction, pattern, 1, - SEARCH_KEEP + flag, RE_SEARCH, 0, NULL) != FAIL) { + SEARCH_KEEP + flag, RE_SEARCH, 0, NULL, NULL) != FAIL) { // Zero-width pattern should match somewhere, then we can check if // start and end are in the same position. called_emsg = false; do { regmatch.startpos[0].col++; nmatched = vim_regexec_multi(®match, curwin, curbuf, - pos.lnum, regmatch.startpos[0].col, NULL); + pos.lnum, regmatch.startpos[0].col, + NULL, NULL); if (!nmatched) { break; } diff --git a/src/nvim/spell.c b/src/nvim/spell.c index ff61c2e5de..ec4da88ea1 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -3048,9 +3048,10 @@ void ex_spellrepall(exarg_T *eap) sub_nlines = 0; curwin->w_cursor.lnum = 0; while (!got_int) { - if (do_search(NULL, '/', frompat, 1L, SEARCH_KEEP, NULL) == 0 - || u_save_cursor() == FAIL) + if (do_search(NULL, '/', frompat, 1L, SEARCH_KEEP, NULL, NULL) == 0 + || u_save_cursor() == FAIL) { break; + } // Only replace when the right word isn't there yet. This happens // when changing "etc" to "etc.". diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index b6b7dfff11..8c3ce823d3 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -2902,7 +2902,7 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T } rmp->rmm_maxcol = syn_buf->b_p_smc; - r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col, NULL); + r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col, NULL, NULL); if (l_syn_time_on) { pt = profile_end(pt); @@ -5956,6 +5956,7 @@ static const char *highlight_init_both[] = { "default link Substitute Search", "default link Whitespace NonText", "default link MsgSeparator StatusLine", + "default link NormalFloat Pmenu", NULL }; diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 50397d40e6..410b9dfcbd 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -109,7 +109,7 @@ static char_u *tagmatchname = NULL; /* name of last used tag */ * Tag for preview window is remembered separately, to avoid messing up the * normal tagstack. */ -static taggy_T ptag_entry = {NULL, {INIT_POS_T(0, 0, 0), 0, 0, NULL}, 0, 0}; +static taggy_T ptag_entry = { NULL, { { 0, 0, 0 }, 0, 0, NULL }, 0, 0 }; /* * Jump to tag; handling of tag commands and tag stack @@ -2508,9 +2508,9 @@ static int jumpto_tag( save_lnum = curwin->w_cursor.lnum; curwin->w_cursor.lnum = 0; /* start search before first line */ if (do_search(NULL, pbuf[0], pbuf + 1, (long)1, - search_options, NULL)) + search_options, NULL, NULL)) { retval = OK; - else { + } else { int found = 1; int cc; @@ -2519,23 +2519,22 @@ static int jumpto_tag( */ p_ic = TRUE; if (!do_search(NULL, pbuf[0], pbuf + 1, (long)1, - search_options, NULL)) { - /* - * Failed to find pattern, take a guess: "^func (" - */ + search_options, NULL, NULL)) { + // Failed to find pattern, take a guess: "^func (" found = 2; (void)test_for_static(&tagp); cc = *tagp.tagname_end; *tagp.tagname_end = NUL; - sprintf((char *)pbuf, "^%s\\s\\*(", tagp.tagname); + snprintf((char *)pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname); if (!do_search(NULL, '/', pbuf, (long)1, - search_options, NULL)) { - /* Guess again: "^char * \<func (" */ - sprintf((char *)pbuf, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(", - tagp.tagname); + search_options, NULL, NULL)) { + // Guess again: "^char * \<func (" + snprintf((char *)pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(", + tagp.tagname); if (!do_search(NULL, '/', pbuf, (long)1, - search_options, NULL)) + search_options, NULL, NULL)) { found = 0; + } } *tagp.tagname_end = cc; } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 8b4ad4d3af..ffe650f416 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -1112,11 +1112,15 @@ static void refresh_terminal(Terminal *term) return; } long ml_before = buf->b_ml.ml_line_count; - WITH_BUFFER(buf, { - refresh_size(term, buf); - refresh_scrollback(term, buf); - refresh_screen(term, buf); - }); + + // refresh_ functions assume the terminal buffer is current + aco_save_T aco; + aucmd_prepbuf(&aco, buf); + refresh_size(term, buf); + refresh_scrollback(term, buf); + refresh_screen(term, buf); + aucmd_restbuf(&aco); + long ml_added = buf->b_ml.ml_line_count - ml_before; adjust_topline(term, buf, ml_added); } diff --git a/src/nvim/testdir/test50.in b/src/nvim/testdir/test50.in deleted file mode 100644 index 392177b808..0000000000 --- a/src/nvim/testdir/test50.in +++ /dev/null @@ -1,89 +0,0 @@ -Test for shortpathname ':8' extension. -Only for use on Win32 systems! - -STARTTEST -:fun! TestIt(file, bits, expected) - let res=fnamemodify(a:file,a:bits) - if a:expected == '' - echo "'".a:file."'->(".a:bits.")->'".res."'" - else - if substitute(res,'/','\\', 'g') != substitute( a:expected, '/','\\', 'g') - echo "FAILED: '".a:file."'->(".a:bits.")->'".res."'" - echo "Expected: '".a:expected."'" - else - echo "OK" - endif - endif -endfun -:fun! MakeDir( dirname ) - "exe '!mkdir '.substitute(a:dirname,'/','\\','g') - call system('mkdir '.substitute(a:dirname,'/','\\','g')) -endfun -:fun! RMDir( dirname) - "exe '!rmdir '.substitute(a:dirname,'/','\\','g') - call system('rmdir '.substitute(a:dirname,'/','\\','g')) -endfun -:fun! MakeFile( filename) - "exe '!copy nul '.substitute(a:filename,'/','\\','g') - call system('copy nul '.substitute(a:filename,'/','\\','g')) -endfun -:fun! TestColonEight() - redir! >test.out - " This could change for CygWin to //cygdrive/c - let dir1='c:/x.x.y' - if filereadable(dir1) || isdirectory(dir1) - echo "FATAL: '".dir1."' exists, cannot run test" - return - endif - let file1=dir1.'/zz.y.txt' - let nofile1=dir1.'/z.y.txt' - let dir2=dir1.'/VimIsTheGreatestSinceSlicedBread' - let file2=dir2.'/z.txt' - let nofile2=dir2.'/zz.txt' - call MakeDir( dir1 ) - let resdir1 = substitute(fnamemodify(dir1, ':p:8'), '\\$', '', '') - if resdir1 !~ '\V\^c:/XX\x\x\x\x~1.Y\$' - echo "FATAL: unexpected short name: " . resdir1 - echo "INFO: please report your OS to vim-dev" - return - endif - let resfile1=resdir1.'/ZZY~1.TXT' - let resnofile1=resdir1.'/z.y.txt' - let resdir2=resdir1.'/VIMIST~1' - let resfile2=resdir2.'/z.txt' - let resnofile2=resdir2.'/zz.txt' - call MakeDir( dir2 ) - call MakeFile( file1 ) - call MakeFile( file2 ) - call TestIt(file1, ':p:8', resfile1) - call TestIt(nofile1, ':p:8', resnofile1) - call TestIt(file2, ':p:8', resfile2) - call TestIt(nofile2, ':p:8', resnofile2) - call TestIt(nofile2, ':p:8:h', fnamemodify(resnofile2,':h')) - exe 'cd '.dir1 - call TestIt(file1, ':.:8', strpart(resfile1,strlen(resdir1)+1)) - call TestIt(nofile1, ':.:8', strpart(resnofile1,strlen(resdir1)+1)) - call TestIt(file2, ':.:8', strpart(resfile2,strlen(resdir1)+1)) - call TestIt(nofile2, ':.:8', strpart(resnofile2,strlen(resdir1)+1)) - let $HOME=dir1 - call TestIt(file1, ':~:8', '~'.strpart(resfile1,strlen(resdir1))) - call TestIt(nofile1, ':~:8', '~'.strpart(resnofile1,strlen(resdir1))) - call TestIt(file2, ':~:8', '~'.strpart(resfile2,strlen(resdir1))) - call TestIt(nofile2, ':~:8', '~'.strpart(resnofile2,strlen(resdir1))) - cd c:/ - call delete( file2 ) - call delete( file1 ) - call RMDir( dir2 ) - call RMDir( dir1 ) - echo - redir END -endfun -:let dir = getcwd() -:call TestColonEight() -:exe "cd " . dir -:edit! test.out -:set ff=dos -:w -:qa! -ENDTEST - diff --git a/src/nvim/testdir/test50.ok b/src/nvim/testdir/test50.ok deleted file mode 100644 index 91ef1d6604..0000000000 --- a/src/nvim/testdir/test50.ok +++ /dev/null @@ -1,14 +0,0 @@ - -OK -OK -OK -OK -OK -OK -OK -OK -OK -OK -OK -OK -OK diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index f1fb8e67b9..7a6c9478ca 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -1161,23 +1161,23 @@ func Test_TextYankPost() norm "ayiw call assert_equal( - \{'regcontents': ['foo'], 'regname': 'a', 'operator': 'y', 'regtype': 'v'}, + \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': 'a', 'operator': 'y', 'regtype': 'v'}, \g:event) norm y_ call assert_equal( - \{'regcontents': ['foo'], 'regname': '', 'operator': 'y', 'regtype': 'V'}, + \{'regcontents': ['foo'], 'inclusive': v:false, 'regname': '', 'operator': 'y', 'regtype': 'V'}, \g:event) call feedkeys("\<C-V>y", 'x') call assert_equal( - \{'regcontents': ['f'], 'regname': '', 'operator': 'y', 'regtype': "\x161"}, + \{'regcontents': ['f'], 'inclusive': v:true, 'regname': '', 'operator': 'y', 'regtype': "\x161"}, \g:event) norm "xciwbar call assert_equal( - \{'regcontents': ['foo'], 'regname': 'x', 'operator': 'c', 'regtype': 'v'}, + \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': 'x', 'operator': 'c', 'regtype': 'v'}, \g:event) norm "bdiw call assert_equal( - \{'regcontents': ['bar'], 'regname': 'b', 'operator': 'd', 'regtype': 'v'}, + \{'regcontents': ['bar'], 'inclusive': v:true, 'regname': 'b', 'operator': 'd', 'regtype': 'v'}, \g:event) call assert_equal({}, v:event) diff --git a/src/nvim/testdir/test_findfile.vim b/src/nvim/testdir/test_findfile.vim index 78e51ed836..0bae161a8b 100644 --- a/src/nvim/testdir/test_findfile.vim +++ b/src/nvim/testdir/test_findfile.vim @@ -119,6 +119,14 @@ func Test_findfile() let &shellslash = save_shellslash endfunc +func Test_findfile_error() + call assert_fails('call findfile([])', 'E730:') + call assert_fails('call findfile("x", [])', 'E730:') + call assert_fails('call findfile("x", "", [])', 'E745:') + call assert_fails('call findfile("x", "**x")', 'E343:') + call assert_fails('call findfile("x", repeat("x", 5000))', 'E854:') +endfunc + " Test finddir({name} [, {path} [, {count}]]) func Test_finddir() let save_path = &path @@ -167,3 +175,11 @@ func Test_finddir() let &path = save_path let &shellslash = save_shellslash endfunc + +func Test_finddir_error() + call assert_fails('call finddir([])', 'E730:') + call assert_fails('call finddir("x", [])', 'E730:') + call assert_fails('call finddir("x", "", [])', 'E745:') + call assert_fails('call finddir("x", "**x")', 'E343:') + call assert_fails('call finddir("x", repeat("x", 5000))', 'E854:') +endfunc diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index bfe13d6b2d..824baffbc9 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -956,8 +956,8 @@ func Test_balloon_show() endfunc func Test_shellescape() - let save_shell = &shell - set shell=bash + let [save_shell, save_shellslash] = [&shell, &shellslash] + set shell=bash shellslash call assert_equal("'text'", shellescape('text')) call assert_equal("'te\"xt'", shellescape('te"xt')) call assert_equal("'te'\\''xt'", shellescape("te'xt")) @@ -971,13 +971,13 @@ func Test_shellescape() call assert_equal("'te\nxt'", shellescape("te\nxt")) call assert_equal("'te\\\nxt'", shellescape("te\nxt", 1)) - set shell=tcsh + set shell=tcsh shellslash call assert_equal("'te\\!xt'", shellescape("te!xt")) call assert_equal("'te\\\\!xt'", shellescape("te!xt", 1)) call assert_equal("'te\\\nxt'", shellescape("te\nxt")) call assert_equal("'te\\\\\nxt'", shellescape("te\nxt", 1)) - let &shell = save_shell + let [&shell, &shellslash] = [save_shell, save_shellslash] endfunc func Test_redo_in_nested_functions() diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim index c352379697..d233046d2b 100644 --- a/src/nvim/testdir/test_gf.vim +++ b/src/nvim/testdir/test_gf.vim @@ -9,6 +9,7 @@ func Test_gf_url() \ "third test for URL:\\\\machine.name\\vimtest2c and other text", \ "fourth test for URL:\\\\machine.name\\tmp\\vimtest2d, and other text", \ "fifth test for URL://machine.name/tmp?q=vim&opt=yes and other text", + \ "sixth test for URL://machine.name:1234?q=vim and other text", \ ]) call cursor(1,1) call search("^first") @@ -20,7 +21,7 @@ func Test_gf_url() if has("ebcdic") set isf=@,240-249,/,.,-,_,+,,,$,:,~,\ else - set isf=@,48-57,/,.,-,_,+,,,$,:,~,\ + set isf=@,48-57,/,.,-,_,+,,,$,~,\ endif call search("^third") call search("name") @@ -33,6 +34,10 @@ func Test_gf_url() call search("URL") call assert_equal("URL://machine.name/tmp?q=vim&opt=yes", expand("<cfile>")) + call search("^sixth") + call search("URL") + call assert_equal("URL://machine.name:1234?q=vim", expand("<cfile>")) + set isf&vim enew! endfunc diff --git a/src/nvim/testdir/test_hlsearch.vim b/src/nvim/testdir/test_hlsearch.vim index 1fc7b04f88..97f6ae7b51 100644 --- a/src/nvim/testdir/test_hlsearch.vim +++ b/src/nvim/testdir/test_hlsearch.vim @@ -32,6 +32,25 @@ function! Test_hlsearch() enew! endfunction +func Test_hlsearch_hangs() + if !has('reltime') || !has('float') + return + endif + + " This pattern takes a long time to match, it should timeout. + new + call setline(1, ['aaa', repeat('abc ', 100000), 'ccc']) + let start = reltime() + set hlsearch nolazyredraw redrawtime=101 + let @/ = '\%#=1a*.*X\@<=b*' + redraw + let elapsed = reltimefloat(reltime(start)) + call assert_true(elapsed > 0.1) + call assert_true(elapsed < 1.0) + set nohlsearch redrawtime& + bwipe! +endfunc + func Test_hlsearch_eol_highlight() new call append(1, repeat([''], 9)) diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim index 8858cd22b8..7fd115fd68 100644 --- a/src/nvim/testdir/test_marks.vim +++ b/src/nvim/testdir/test_marks.vim @@ -136,3 +136,44 @@ func Test_marks_cmd_multibyte() bwipe! endfunc + +func Test_delmarks() + new + norm mx + norm `x + delmarks x + call assert_fails('norm `x', 'E20:') + + " Deleting an already deleted mark should not fail. + delmarks x + + " Test deleting a range of marks. + norm ma + norm mb + norm mc + norm mz + delmarks b-z + norm `a + call assert_fails('norm `b', 'E20:') + call assert_fails('norm `c', 'E20:') + call assert_fails('norm `z', 'E20:') + call assert_fails('delmarks z-b', 'E475:') + + call assert_fails('delmarks', 'E471:') + call assert_fails('delmarks /', 'E475:') + + " Test delmarks! + norm mx + norm `x + delmarks! + call assert_fails('norm `x', 'E20:') + call assert_fails('delmarks! x', 'E474:') + + bwipe! +endfunc + +func Test_mark_error() + call assert_fails('mark', 'E471:') + call assert_fails('mark xx', 'E488:') + call assert_fails('mark _', 'E191:') +endfunc diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim index 4cbd800da5..8996e86b43 100644 --- a/src/nvim/testdir/test_profile.vim +++ b/src/nvim/testdir/test_profile.vim @@ -181,3 +181,44 @@ func Test_profile_errors() call assert_fails("profile pause", 'E750:') call assert_fails("profile continue", 'E750:') endfunc + +func Test_profile_truncate_mbyte() + if !has('multi_byte') || &enc !=# 'utf-8' + return + endif + + let lines = [ + \ 'scriptencoding utf-8', + \ 'func! Foo()', + \ ' return [', + \ ' \ "' . join(map(range(0x4E00, 0x4E00 + 340), 'nr2char(v:val)'), '') . '",', + \ ' \ "' . join(map(range(0x4F00, 0x4F00 + 340), 'nr2char(v:val)'), '') . '",', + \ ' \ ]', + \ 'endfunc', + \ 'call Foo()', + \ ] + + call writefile(lines, 'Xprofile_file.vim') + call system(v:progpath + \ . ' -es --cmd "set enc=utf-8"' + \ . ' -c "profile start Xprofile_file.log"' + \ . ' -c "profile file Xprofile_file.vim"' + \ . ' -c "so Xprofile_file.vim"' + \ . ' -c "qall!"') + call assert_equal(0, v:shell_error) + + split Xprofile_file.log + if &fenc != '' + call assert_equal('utf-8', &fenc) + endif + /func! Foo() + let lnum = line('.') + call assert_match('^\s*return \[$', getline(lnum + 1)) + call assert_match("\u4F52$", getline(lnum + 2)) + call assert_match("\u5052$", getline(lnum + 3)) + call assert_match('^\s*\\ \]$', getline(lnum + 4)) + bwipe! + + call delete('Xprofile_file.vim') + call delete('Xprofile_file.log') +endfunc diff --git a/src/nvim/testdir/test_regex_char_classes.vim b/src/nvim/testdir/test_regex_char_classes.vim index 2192b5e8fc..7873502943 100644 --- a/src/nvim/testdir/test_regex_char_classes.vim +++ b/src/nvim/testdir/test_regex_char_classes.vim @@ -1,5 +1,11 @@ " Tests for regexp with backslash and other special characters inside [] " Also test backslash for hex/octal numbered character. +" +if !has('multi_byte') + finish +endif + +scriptencoding utf-8 function RunSTest(value, calls, expected) new @@ -56,3 +62,237 @@ function Test_s_search() call RunSTest(" xyz", "s/~/bcd/", " bcd") call RunSTest(" bcdbcdbcd", "s/~\\+/BB/", " BB") endfunction + +" Test character classes in regexp using regexpengine 0, 1, 2. +func Test_regex_char_classes() + new + let save_enc = &encoding + set encoding=utf-8 + + let input = "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱" + + " Format is [cmd_to_run, expected_output] + let tests = [ + \ [':s/\%#=0\d//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1\d//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2\d//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0[0-9]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1[0-9]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2[0-9]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0\D//g', + \ "0123456789"], + \ [':s/\%#=1\D//g', + \ "0123456789"], + \ [':s/\%#=2\D//g', + \ "0123456789"], + \ [':s/\%#=0[^0-9]//g', + \ "0123456789"], + \ [':s/\%#=1[^0-9]//g', + \ "0123456789"], + \ [':s/\%#=2[^0-9]//g', + \ "0123456789"], + \ [':s/\%#=0\o//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1\o//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2\o//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0[0-7]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1[0-7]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2[0-7]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0\O//g', + \ "01234567"], + \ [':s/\%#=1\O//g', + \ "01234567"], + \ [':s/\%#=2\O//g', + \ "01234567"], + \ [':s/\%#=0[^0-7]//g', + \ "01234567"], + \ [':s/\%#=1[^0-7]//g', + \ "01234567"], + \ [':s/\%#=2[^0-7]//g', + \ "01234567"], + \ [':s/\%#=0\x//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1\x//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2\x//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0[0-9A-Fa-f]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1[0-9A-Fa-f]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2[0-9A-Fa-f]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0\X//g', + \ "0123456789ABCDEFabcdef"], + \ [':s/\%#=1\X//g', + \ "0123456789ABCDEFabcdef"], + \ [':s/\%#=2\X//g', + \ "0123456789ABCDEFabcdef"], + \ [':s/\%#=0[^0-9A-Fa-f]//g', + \ "0123456789ABCDEFabcdef"], + \ [':s/\%#=1[^0-9A-Fa-f]//g', + \ "0123456789ABCDEFabcdef"], + \ [':s/\%#=2[^0-9A-Fa-f]//g', + \ "0123456789ABCDEFabcdef"], + \ [':s/\%#=0\w//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1\w//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2\w//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0[0-9A-Za-z_]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1[0-9A-Za-z_]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2[0-9A-Za-z_]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0\W//g', + \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=1\W//g', + \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=2\W//g', + \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=0[^0-9A-Za-z_]//g', + \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=1[^0-9A-Za-z_]//g', + \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=2[^0-9A-Za-z_]//g', + \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=0\h//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1\h//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2\h//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0[A-Za-z_]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1[A-Za-z_]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2[A-Za-z_]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0\H//g', + \ "ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=1\H//g', + \ "ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=2\H//g', + \ "ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=0[^A-Za-z_]//g', + \ "ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=1[^A-Za-z_]//g', + \ "ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=2[^A-Za-z_]//g', + \ "ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=0\a//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1\a//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2\a//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0[A-Za-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1[A-Za-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2[A-Za-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0\A//g', + \ "ABCDEFGHIXYZabcdefghiwxyz"], + \ [':s/\%#=1\A//g', + \ "ABCDEFGHIXYZabcdefghiwxyz"], + \ [':s/\%#=2\A//g', + \ "ABCDEFGHIXYZabcdefghiwxyz"], + \ [':s/\%#=0[^A-Za-z]//g', + \ "ABCDEFGHIXYZabcdefghiwxyz"], + \ [':s/\%#=1[^A-Za-z]//g', + \ "ABCDEFGHIXYZabcdefghiwxyz"], + \ [':s/\%#=2[^A-Za-z]//g', + \ "ABCDEFGHIXYZabcdefghiwxyz"], + \ [':s/\%#=0\l//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1\l//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2\l//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0[a-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1[a-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2[a-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0\L//g', + \ "abcdefghiwxyz"], + \ [':s/\%#=1\L//g', + \ "abcdefghiwxyz"], + \ [':s/\%#=2\L//g', + \ "abcdefghiwxyz"], + \ [':s/\%#=0[^a-z]//g', + \ "abcdefghiwxyz"], + \ [':s/\%#=1[^a-z]//g', + \ "abcdefghiwxyz"], + \ [':s/\%#=2[^a-z]//g', + \ "abcdefghiwxyz"], + \ [':s/\%#=0\u//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1\u//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2\u//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0[A-Z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1[A-Z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2[A-Z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0\U//g', + \ "ABCDEFGHIXYZ"], + \ [':s/\%#=1\U//g', + \ "ABCDEFGHIXYZ"], + \ [':s/\%#=2\U//g', + \ "ABCDEFGHIXYZ"], + \ [':s/\%#=0[^A-Z]//g', + \ "ABCDEFGHIXYZ"], + \ [':s/\%#=1[^A-Z]//g', + \ "ABCDEFGHIXYZ"], + \ [':s/\%#=2[^A-Z]//g', + \ "ABCDEFGHIXYZ"], + \ [':s/\%#=0\%' . line('.') . 'l^\t...//g', + \ "!\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1\%' . line('.') . 'l^\t...//g', + \ "!\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2\%' . line('.') . 'l^\t...//g', + \ "!\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0[0-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=1[0-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=2[0-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009bΡ记娱"], + \ [':s/\%#=0[^0-z]//g', + \ "0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz"], + \ [':s/\%#=1[^0-z]//g', + \ "0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz"], + \ [':s/\%#=2[^0-z]//g', + \ "0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz"] + \] + + for [cmd, expected] in tests + call append(0, input) + call cursor(1, 1) + exe cmd + call assert_equal(expected, getline(1), cmd) + endfor + + let &encoding = save_enc + enew! + close +endfunc diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim index eb8f69ef15..0619e9c027 100644 --- a/src/nvim/testdir/test_regexp_latin.vim +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -47,3 +47,29 @@ func Test_get_equi_class() s/.*/[[. call assert_equal(1, search(getline(1))) endfunc + +func Test_rex_init() + set noincsearch + set re=1 + new + setlocal iskeyword=a-z + call setline(1, ['abc', 'ABC']) + call assert_equal(1, search('[[:keyword:]]')) + new + setlocal iskeyword=A-Z + call setline(1, ['abc', 'ABC']) + call assert_equal(2, search('[[:keyword:]]')) + bwipe! + bwipe! + set re=0 +endfunc + +func Test_backref() + new + call setline(1, ['one', 'two', 'three', 'four', 'five']) + call assert_equal(3, search('\%#=1\(e\)\1')) + call assert_equal(3, search('\%#=2\(e\)\1')) + call assert_fails('call search("\\%#=1\\(e\\1\\)")', 'E65:') + call assert_fails('call search("\\%#=2\\(e\\1\\)")', 'E65:') + bwipe! +endfunc diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index cf85bd58ac..351b119acd 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -223,7 +223,7 @@ func Test_statusline() set statusline=ab%(cd%q%)de call assert_match('^abde\s*$', s:get_statusline()) copen - call assert_match('^abcd\[Quickfix List\1]de\s*$', s:get_statusline()) + call assert_match('^abcd\[Quickfix List]de\s*$', s:get_statusline()) cclose " %#: Set highlight group. The name must follow and then a # again. diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index dbd26be089..d02454fbf0 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -322,6 +322,90 @@ func Test_sub_cmd_8() set titlestring& endfunc +" Test %s/\n// which is implemented as a special case to use a +" more efficient join rather than doing a regular substitution. +func Test_substitute_join() + new + + call setline(1, ["foo\tbar", "bar\<C-H>foo"]) + let a = execute('%s/\n//') + call assert_equal("", a) + call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$')) + call assert_equal('\n', histget("search", -1)) + + call setline(1, ["foo\tbar", "bar\<C-H>foo"]) + let a = execute('%s/\n//g') + call assert_equal("", a) + call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$')) + call assert_equal('\n', histget("search", -1)) + + call setline(1, ["foo\tbar", "bar\<C-H>foo"]) + let a = execute('%s/\n//p') + call assert_equal("\nfoo barbar^Hfoo", a) + call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$')) + call assert_equal('\n', histget("search", -1)) + + call setline(1, ["foo\tbar", "bar\<C-H>foo"]) + let a = execute('%s/\n//l') + call assert_equal("\nfoo^Ibarbar^Hfoo$", a) + call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$')) + call assert_equal('\n', histget("search", -1)) + + call setline(1, ["foo\tbar", "bar\<C-H>foo"]) + let a = execute('%s/\n//#') + call assert_equal("\n 1 foo barbar^Hfoo", a) + call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$')) + call assert_equal('\n', histget("search", -1)) + + bwipe! +endfunc + +func Test_substitute_count() + new + call setline(1, ['foo foo', 'foo foo', 'foo foo', 'foo foo', 'foo foo']) + 2 + + s/foo/bar/3 + call assert_equal(['foo foo', 'bar foo', 'bar foo', 'bar foo', 'foo foo'], + \ getline(1, '$')) + + call assert_fails('s/foo/bar/0', 'E939:') + + bwipe! +endfunc + +" Test substitute 'n' flag (report number of matches, do not substitute). +func Test_substitute_flag_n() + new + let lines = ['foo foo', 'foo foo', 'foo foo', 'foo foo', 'foo foo'] + call setline(1, lines) + + call assert_equal("\n3 matches on 3 lines", execute('2,4s/foo/bar/n')) + call assert_equal("\n6 matches on 3 lines", execute('2,4s/foo/bar/gn')) + + " c flag (confirm) should be ignored when using n flag. + call assert_equal("\n3 matches on 3 lines", execute('2,4s/foo/bar/nc')) + + " No substitution should have been done. + call assert_equal(lines, getline(1, '$')) + + bwipe! +endfunc + +func Test_substitute_errors() + new + call setline(1, 'foobar') + + call assert_fails('s/FOO/bar/', 'E486:') + call assert_fails('s/foo/bar/@', 'E488:') + call assert_fails('s/\(/bar/', 'E476:') + + setl nomodifiable + call assert_fails('s/foo/bar/', 'E21:') + + bwipe! +endfunc + " Test for *sub-replace-special* and *sub-replace-expression* on substitute(). func Test_sub_replace_1() " Run the tests with 'magic' on diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index 999566c6ac..13fb50b985 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -163,6 +163,329 @@ func Test_text_format() \ '# 1 xxxxx', \ '# foobar'], getline(1, 2)) + " Test the 'p' flag for 'formatoptions' + " First test without the flag: that it will break "Mr. Feynman" at the space + normal ggdG + setl tw=28 fo=tcq + call setline('.', 'Surely you''re joking, Mr. Feynman!') + normal gqq + call assert_equal([ + \ 'Surely you''re joking, Mr.', + \ 'Feynman!'], getline(1, 2)) + " Now test with the flag: that it will push the name with the title onto the + " next line + normal ggdG + setl fo+=p + call setline('.', 'Surely you''re joking, Mr. Feynman!') + normal gqq + call assert_equal([ + \ 'Surely you''re joking,', + \ 'Mr. Feynman!'], getline(1, 2)) + " Ensure that it will still break if two spaces are entered + normal ggdG + call setline('.', 'Surely you''re joking, Mr. Feynman!') + normal gqq + call assert_equal([ + \ 'Surely you''re joking, Mr.', + \ 'Feynman!'], getline(1, 2)) + setl ai& tw& fo& si& comments& enew! endfunc + +" Tests for :right, :center and :left on text with embedded TAB. +func Test_format_align() + enew! + set tw=65 + + " :left alignment + call append(0, [ + \ " test for :left", + \ " a a", + \ " fa a", + \ " dfa a", + \ " sdfa a", + \ " asdfa a", + \ " xasdfa a", + \ "asxxdfa a", + \ ]) + %left + call assert_equal([ + \ "test for :left", + \ "a a", + \ "fa a", + \ "dfa a", + \ "sdfa a", + \ "asdfa a", + \ "xasdfa a", + \ "asxxdfa a", + \ "" + \ ], getline(1, '$')) + enew! + + " :center alignment + call append(0, [ + \ " test for :center", + \ " a a", + \ " fa afd asdf", + \ " dfa a", + \ " sdfa afd asdf", + \ " asdfa a", + \ " xasdfa asdfasdfasdfasdfasdf", + \ "asxxdfa a" + \ ]) + %center + call assert_equal([ + \ " test for :center", + \ " a a", + \ " fa afd asdf", + \ " dfa a", + \ " sdfa afd asdf", + \ " asdfa a", + \ " xasdfa asdfasdfasdfasdfasdf", + \ " asxxdfa a", + \ "" + \ ], getline(1, '$')) + enew! + + " :right alignment + call append(0, [ + \ " test for :right", + \ " a a", + \ " fa a", + \ " dfa a", + \ " sdfa a", + \ " asdfa a", + \ " xasdfa a", + \ " asxxdfa a", + \ " asxa;ofa a", + \ " asdfaqwer a", + \ " a ax", + \ " fa ax", + \ " dfa ax", + \ " sdfa ax", + \ " asdfa ax", + \ " xasdfa ax", + \ " asxxdfa ax", + \ " asxa;ofa ax", + \ " asdfaqwer ax", + \ " a axx", + \ " fa axx", + \ " dfa axx", + \ " sdfa axx", + \ " asdfa axx", + \ " xasdfa axx", + \ " asxxdfa axx", + \ " asxa;ofa axx", + \ " asdfaqwer axx", + \ " a axxx", + \ " fa axxx", + \ " dfa axxx", + \ " sdfa axxx", + \ " asdfa axxx", + \ " xasdfa axxx", + \ " asxxdfa axxx", + \ " asxa;ofa axxx", + \ " asdfaqwer axxx", + \ " a axxxo", + \ " fa axxxo", + \ " dfa axxxo", + \ " sdfa axxxo", + \ " asdfa axxxo", + \ " xasdfa axxxo", + \ " asxxdfa axxxo", + \ " asxa;ofa axxxo", + \ " asdfaqwer axxxo", + \ " a axxxoi", + \ " fa axxxoi", + \ " dfa axxxoi", + \ " sdfa axxxoi", + \ " asdfa axxxoi", + \ " xasdfa axxxoi", + \ " asxxdfa axxxoi", + \ " asxa;ofa axxxoi", + \ " asdfaqwer axxxoi", + \ " a axxxoik", + \ " fa axxxoik", + \ " dfa axxxoik", + \ " sdfa axxxoik", + \ " asdfa axxxoik", + \ " xasdfa axxxoik", + \ " asxxdfa axxxoik", + \ " asxa;ofa axxxoik", + \ " asdfaqwer axxxoik", + \ " a axxxoike", + \ " fa axxxoike", + \ " dfa axxxoike", + \ " sdfa axxxoike", + \ " asdfa axxxoike", + \ " xasdfa axxxoike", + \ " asxxdfa axxxoike", + \ " asxa;ofa axxxoike", + \ " asdfaqwer axxxoike", + \ " a axxxoikey", + \ " fa axxxoikey", + \ " dfa axxxoikey", + \ " sdfa axxxoikey", + \ " asdfa axxxoikey", + \ " xasdfa axxxoikey", + \ " asxxdfa axxxoikey", + \ " asxa;ofa axxxoikey", + \ " asdfaqwer axxxoikey", + \ ]) + %right + call assert_equal([ + \ "\t\t\t\t test for :right", + \ "\t\t\t\t a a", + \ "\t\t\t\t fa a", + \ "\t\t\t\t dfa a", + \ "\t\t\t\t sdfa a", + \ "\t\t\t\t asdfa a", + \ "\t\t\t\t xasdfa a", + \ "\t\t\t\t asxxdfa a", + \ "\t\t\t\t asxa;ofa a", + \ "\t\t\t\t asdfaqwer a", + \ "\t\t\t\t a ax", + \ "\t\t\t\t fa ax", + \ "\t\t\t\t dfa ax", + \ "\t\t\t\t sdfa ax", + \ "\t\t\t\t asdfa ax", + \ "\t\t\t\t xasdfa ax", + \ "\t\t\t\t asxxdfa ax", + \ "\t\t\t\t asxa;ofa ax", + \ "\t\t\t\t asdfaqwer ax", + \ "\t\t\t\t a axx", + \ "\t\t\t\t fa axx", + \ "\t\t\t\t dfa axx", + \ "\t\t\t\t sdfa axx", + \ "\t\t\t\t asdfa axx", + \ "\t\t\t\t xasdfa axx", + \ "\t\t\t\t asxxdfa axx", + \ "\t\t\t\t asxa;ofa axx", + \ "\t\t\t\t asdfaqwer axx", + \ "\t\t\t\t a axxx", + \ "\t\t\t\t fa axxx", + \ "\t\t\t\t dfa axxx", + \ "\t\t\t\t sdfa axxx", + \ "\t\t\t\t asdfa axxx", + \ "\t\t\t\t xasdfa axxx", + \ "\t\t\t\t asxxdfa axxx", + \ "\t\t\t\t asxa;ofa axxx", + \ "\t\t\t\t asdfaqwer axxx", + \ "\t\t\t\t a axxxo", + \ "\t\t\t\t fa axxxo", + \ "\t\t\t\t dfa axxxo", + \ "\t\t\t\t sdfa axxxo", + \ "\t\t\t\t asdfa axxxo", + \ "\t\t\t\t xasdfa axxxo", + \ "\t\t\t\t asxxdfa axxxo", + \ "\t\t\t\t asxa;ofa axxxo", + \ "\t\t\t\t asdfaqwer axxxo", + \ "\t\t\t\t a axxxoi", + \ "\t\t\t\t fa axxxoi", + \ "\t\t\t\t dfa axxxoi", + \ "\t\t\t\t sdfa axxxoi", + \ "\t\t\t\t asdfa axxxoi", + \ "\t\t\t\t xasdfa axxxoi", + \ "\t\t\t\t asxxdfa axxxoi", + \ "\t\t\t\t asxa;ofa axxxoi", + \ "\t\t\t\t asdfaqwer axxxoi", + \ "\t\t\t\t a axxxoik", + \ "\t\t\t\t fa axxxoik", + \ "\t\t\t\t dfa axxxoik", + \ "\t\t\t\t sdfa axxxoik", + \ "\t\t\t\t asdfa axxxoik", + \ "\t\t\t\t xasdfa axxxoik", + \ "\t\t\t\t asxxdfa axxxoik", + \ "\t\t\t\t asxa;ofa axxxoik", + \ "\t\t\t\t asdfaqwer axxxoik", + \ "\t\t\t\t a axxxoike", + \ "\t\t\t\t fa axxxoike", + \ "\t\t\t\t dfa axxxoike", + \ "\t\t\t\t sdfa axxxoike", + \ "\t\t\t\t asdfa axxxoike", + \ "\t\t\t\t xasdfa axxxoike", + \ "\t\t\t\t asxxdfa axxxoike", + \ "\t\t\t\t asxa;ofa axxxoike", + \ "\t\t\t\t asdfaqwer axxxoike", + \ "\t\t\t\t a axxxoikey", + \ "\t\t\t\t fa axxxoikey", + \ "\t\t\t\t dfa axxxoikey", + \ "\t\t\t\t sdfa axxxoikey", + \ "\t\t\t\t asdfa axxxoikey", + \ "\t\t\t\t xasdfa axxxoikey", + \ "\t\t\t\t asxxdfa axxxoikey", + \ "\t\t\t\t asxa;ofa axxxoikey", + \ "\t\t\t\t asdfaqwer axxxoikey", + \ "" + \ ], getline(1, '$')) + enew! + + set tw& +endfunc + +" Test formatting a paragraph. +func Test_format_para() + enew! + set fo+=tcroql tw=72 + + call append(0, [ + \ "xxxxx xx xxxxxx ", + \ "xxxxxxx xxxxxxxxx xxx xxxx xxxxx xxxxx xxx xx", + \ "xxxxxxxxxxxxxxxxxx xxxxx xxxx, xxxx xxxx xxxx xxxx xxx xx xx", + \ "xx xxxxxxx. xxxx xxxx.", + \ "", + \ "> xx xx, xxxx xxxx xxx xxxx xxx xxxxx xxx xxx xxxxxxx xxx xxxxx", + \ "> xxxxxx xxxxxxx: xxxx xxxxxxx, xx xxxxxx xxxx xxxxxxxxxx" + \ ]) + exe "normal /xxxxxxxx$\<CR>" + normal 0gq6kk + call assert_equal([ + \ "xxxxx xx xxxxxx xxxxxxx xxxxxxxxx xxx xxxx xxxxx xxxxx xxx xx", + \ "xxxxxxxxxxxxxxxxxx xxxxx xxxx, xxxx xxxx xxxx xxxx xxx xx xx xx xxxxxxx.", + \ "xxxx xxxx.", + \ "", + \ "> xx xx, xxxx xxxx xxx xxxx xxx xxxxx xxx xxx xxxxxxx xxx xxxxx xxxxxx", + \ "> xxxxxxx: xxxx xxxxxxx, xx xxxxxx xxxx xxxxxxxxxx", + \ "" + \ ], getline(1, '$')) + + set fo& tw& + enew! +endfunc + +" Test undo after ":%s" and formatting. +func Test_format_undo() + enew! + map gg :.,.+2s/^/x/<CR>kk:set tw=3<CR>gqq + + call append(0, [ + \ "aa aa aa aa", + \ "bb bb bb bb", + \ "cc cc cc cc" + \ ]) + " undo/redo here to make the next undo only work on the following changes + exe "normal i\<C-G>u" + call cursor(1,1) + normal ggu + call assert_equal([ + \ "aa aa aa aa", + \ "bb bb bb bb", + \ "cc cc cc cc", + \ "" + \ ], getline(1, '$')) + + unmap gg + set tw& + enew! +endfunc + +func Test_format_list_auto() + new + call setline(1, ['1. abc', '2. def', '3. ghi']) + set fo=tan ai bs=2 + call feedkeys("3G0lli\<BS>\<BS>x\<Esc>", 'tx') + call assert_equal('2. defx ghi', getline(2)) + bwipe! + set fo& ai& bs& +endfunc diff --git a/src/nvim/testdir/test_true_false.vim b/src/nvim/testdir/test_true_false.vim index 84aca737ac..4a5d47471d 100644 --- a/src/nvim/testdir/test_true_false.vim +++ b/src/nvim/testdir/test_true_false.vim @@ -57,6 +57,9 @@ endfunc " Test using TRUE or FALSE values for an argument. func Test_true_false_arg() + let shellslash = &shellslash + let wildignore = &wildignore + set shellslash call Try_arg_true_false('count(["a", "A"], "a", %v%)', 1, 2) set wildignore=*.swp @@ -110,6 +113,8 @@ func Test_true_false_arg() let here_id = synID(1, 3, 0) call Try_arg_true_false('synID(1, 3, %v%)', here_id, brackets_id) bwipe! + let &wildignore = wildignore + let &shellslash = shellslash endfunc function Try_arg_non_zero(expr, false_val, true_val) diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 4a143d665d..74afc72f03 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -1,8 +1,4 @@ " Tests for various Visual mode. -if !has('visual') - finish -endif - func Test_block_shift_multibyte() " Uses double-wide character. @@ -278,9 +274,46 @@ func Test_visual_mode_reset() set belloff& endfunc +func Test_Visual_word_textobject() + new + call setline(1, ['First sentence. Second sentence.']) + + " When start and end of visual area are identical, 'aw' or 'iw' select + " the whole word. + norm! 1go2fcvawy + call assert_equal('Second ', @") + norm! 1go2fcviwy + call assert_equal('Second', @") + + " When start and end of visual area are not identical, 'aw' or 'iw' + " extend the word in direction of the end of the visual area. + norm! 1go2fcvlawy + call assert_equal('cond ', @") + norm! gv2awy + call assert_equal('cond sentence.', @") + + norm! 1go2fcvliwy + call assert_equal('cond', @") + norm! gv2iwy + call assert_equal('cond sentence', @") + + " Extend visual area in opposite direction. + norm! 1go2fcvhawy + call assert_equal(' Sec', @") + norm! gv2awy + call assert_equal(' sentence. Sec', @") + + norm! 1go2fcvhiwy + call assert_equal('Sec', @") + norm! gv2iwy + call assert_equal('. Sec', @") + + bwipe! +endfunc + func Test_Visual_sentence_textobject() new - call setline(1, ['First sentence. Second sentence. Third', 'sentence. Fouth sentence']) + call setline(1, ['First sentence. Second sentence. Third', 'sentence. Fourth sentence']) " When start and end of visual area are identical, 'as' or 'is' select " the whole sentence. @@ -318,3 +351,63 @@ func Test_Visual_sentence_textobject() bwipe! endfunc + +func Test_curswant_not_changed() + new + call setline(1, ['one', 'two']) + au InsertLeave * call getcurpos() + call feedkeys("gg0\<C-V>jI123 \<Esc>j", 'xt') + call assert_equal([0, 2, 1, 0, 1], getcurpos()) + + bwipe! + au! InsertLeave +endfunc + +func Test_Visual_paragraph_textobject() + new + call setline(1, ['First line.', + \ '', + \ 'Second line.', + \ 'Third line.', + \ 'Fourth line.', + \ 'Fifth line.', + \ '', + \ 'Sixth line.']) + + " When start and end of visual area are identical, 'ap' or 'ip' select + " the whole paragraph. + norm! 4ggvapy + call assert_equal("Second line.\nThird line.\nFourth line.\nFifth line.\n\n", @") + norm! 4ggvipy + call assert_equal("Second line.\nThird line.\nFourth line.\nFifth line.\n", @") + + " When start and end of visual area are not identical, 'ap' or 'ip' + " extend the sentence in direction of the end of the visual area. + " FIXME: actually, it is not sufficient to have different start and + " end of visual selection, the start line and end line have to differ, + " which is not consistent with the documentation. + norm! 4ggVjapy + call assert_equal("Third line.\nFourth line.\nFifth line.\n\n", @") + norm! gvapy + call assert_equal("Third line.\nFourth line.\nFifth line.\n\nSixth line.\n", @") + norm! 4ggVjipy + call assert_equal("Third line.\nFourth line.\nFifth line.\n", @") + norm! gvipy + call assert_equal("Third line.\nFourth line.\nFifth line.\n\n", @") + norm! gvipy + call assert_equal("Third line.\nFourth line.\nFifth line.\n\nSixth line.\n", @") + + " Extend visual area in opposite direction. + norm! 5ggVkapy + call assert_equal("\nSecond line.\nThird line.\nFourth line.\n", @") + norm! gvapy + call assert_equal("First line.\n\nSecond line.\nThird line.\nFourth line.\n", @") + norm! 5ggVkipy + call assert_equal("Second line.\nThird line.\nFourth line.\n", @") + norma gvipy + call assert_equal("\nSecond line.\nThird line.\nFourth line.\n", @") + norm! gvipy + call assert_equal("First line.\n\nSecond line.\nThird line.\nFourth line.\n", @") + + bwipe! +endfunc diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index b3ab6957dc..57fb36abb8 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -518,4 +518,132 @@ func Test_winrestcmd() only endfunc +func Test_relative_cursor_position_in_one_line_window() + new + only + call setline(1, range(1, 10000)) + normal 50% + let lnum = getcurpos()[1] + split + split + " make third window take as many lines as possible, other windows will + " become one line + 3wincmd w + for i in range(1, &lines - 6) + wincmd + + redraw! + endfor + + " first and second window should show cursor line + let wininfo = getwininfo() + call assert_equal(lnum, wininfo[0].topline) + call assert_equal(lnum, wininfo[1].topline) + + only! + bwipe! +endfunc + +func Test_relative_cursor_position_after_move_and_resize() + let so_save = &so + set so=0 + enew + call setline(1, range(1, 10000)) + normal 50% + split + 1wincmd w + " Move cursor to first line in window + normal H + redraw! + " Reduce window height to two lines + let height = winheight(0) + while winheight(0) > 2 + wincmd - + redraw! + endwhile + " move cursor to second/last line in window + normal j + " restore previous height + while winheight(0) < height + wincmd + + redraw! + endwhile + " make window two lines again + while winheight(0) > 2 + wincmd - + redraw! + endwhile + + " cursor should be at bottom line + let info = getwininfo(win_getid())[0] + call assert_equal(info.topline + 1, getcurpos()[1]) + + only! + bwipe! + let &so = so_save +endfunc + +func Test_relative_cursor_position_after_resize() + let so_save = &so + set so=0 + enew + call setline(1, range(1, 10000)) + normal 50% + split + 1wincmd w + let winid1 = win_getid() + let info = getwininfo(winid1)[0] + " Move cursor to second line in window + exe "normal " . (info.topline + 1) . "G" + redraw! + let lnum = getcurpos()[1] + + " Make the window only two lines high, cursor should end up in top line + 2wincmd w + exe (info.height - 2) . "wincmd +" + redraw! + let info = getwininfo(winid1)[0] + call assert_equal(lnum, info.topline) + + only! + bwipe! + let &so = so_save +endfunc + +func Test_relative_cursor_second_line_after_resize() + let so_save = &so + set so=0 + enew + call setline(1, range(1, 10000)) + normal 50% + split + 1wincmd w + let winid1 = win_getid() + let info = getwininfo(winid1)[0] + + " Make the window only two lines high + 2wincmd _ + + " Move cursor to second line in window + normal H + normal j + + " Make window size bigger, then back to 2 lines + for i in range(1, 10) + wincmd + + redraw! + endfor + for i in range(1, 10) + wincmd - + redraw! + endfor + + " cursor should end up in bottom line + let info = getwininfo(winid1)[0] + call assert_equal(info.topline + 1, getcurpos()[1]) + + only! + bwipe! + let &so = so_save +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 3eb88366d6..0bdda17299 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -10,6 +10,7 @@ #include "nvim/charset.h" #include "nvim/main.h" #include "nvim/aucmd.h" +#include "nvim/ex_docmd.h" #include "nvim/option.h" #include "nvim/os/os.h" #include "nvim/os/input.h" @@ -357,15 +358,17 @@ static bool handle_forced_escape(TermInput *input) static void set_bg_deferred(void **argv) { char *bgvalue = argv[0]; - if (starting) { - // Wait until after startup, so OptionSet is triggered. - loop_schedule(&main_loop, event_create(set_bg_deferred, 1, bgvalue)); - return; - } if (!option_was_set("bg") && !strequal((char *)p_bg, bgvalue)) { // Value differs, apply it. - set_option_value("bg", 0L, bgvalue, 0); - reset_option_was_set("bg"); + if (starting) { + // Wait until after startup, so OptionSet is triggered. + do_cmdline_cmd((bgvalue[0] == 'l') + ? "autocmd VimEnter * ++once ++nested set bg=light" + : "autocmd VimEnter * ++once ++nested set bg=dark"); + } else { + set_option_value("bg", 0L, bgvalue, 0); + reset_option_was_set("bg"); + } } } @@ -424,7 +427,8 @@ static bool handle_background_color(TermInput *input) double luminance = (0.299 * r) + (0.587 * g) + (0.114 * b); // CCIR 601 char *bgvalue = luminance < 0.5 ? "dark" : "light"; DLOG("bg response: %s", bgvalue); - loop_schedule(&main_loop, event_create(set_bg_deferred, 1, bgvalue)); + loop_schedule_deferred(&main_loop, + event_create(set_bg_deferred, 1, bgvalue)); } else { DLOG("failed to parse bg response"); } diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index e20cf15a79..3e7a3b1ba1 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -2000,7 +2000,7 @@ static const char *tui_tk_ti_getstr(const char *name, const char *value, } else if (strequal(name, "key_dc")) { DLOG("libtermkey:kdch1=%s", value); // Vim: "If <BS> and <DEL> are now the same, redefine <DEL>." - if (value != NULL && strequal(stty_erase, value)) { + if (value != NULL && value != (char *)-1 && strequal(stty_erase, value)) { return stty_erase[0] == DEL ? CTRL_H_STR : DEL_STR; } } else if (strequal(name, "key_mouse")) { diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 9ad3f851ad..50432dd119 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -158,6 +158,7 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width, if (insert_at < kv_size(layers)-1) { for (size_t i = kv_size(layers)-1; i > insert_at; i--) { kv_A(layers, i) = kv_A(layers, i-1); + kv_A(layers, i)->comp_index = i; } kv_A(layers, insert_at) = grid; } diff --git a/src/nvim/window.c b/src/nvim/window.c index 18fad76a95..edb5b06a2e 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -541,9 +541,7 @@ static void cmd_with_count(char *cmd, char_u *bufp, size_t bufsize, win_T *win_new_float(win_T *wp, int width, int height, FloatConfig config, Error *err) { - bool new = false; if (wp == NULL) { - new = true; wp = win_alloc(lastwin_nofloating(), false); win_init(wp, curwin, 0); } else { @@ -569,12 +567,13 @@ win_T *win_new_float(win_T *wp, int width, int height, FloatConfig config, wp->w_floating = 1; wp->w_status_height = 0; wp->w_vsep_width = 0; + + // TODO(bfredl): use set_option_to() after merging #9110 ? + wp->w_p_nu = false; + wp->w_allbuf_opt.wo_nu = false; win_config_float(wp, width, height, config); wp->w_pos_changed = true; redraw_win_later(wp, VALID); - if (new) { - win_enter(wp, false); - } return wp; } @@ -591,6 +590,7 @@ void win_config_float(win_T *wp, int width, int height, config.window = curwin->handle; } + bool change_external = config.external != wp->w_float_config.external; wp->w_float_config = config; if (!ui_has(kUIMultigrid)) { @@ -601,6 +601,10 @@ void win_config_float(win_T *wp, int width, int height, win_set_inner_size(wp); must_redraw = MAX(must_redraw, VALID); wp->w_pos_changed = true; + if (change_external) { + wp->w_hl_needs_update = true; + redraw_win_later(wp, NOT_VALID); + } } static void ui_ext_win_position(win_T *wp) @@ -806,12 +810,12 @@ bool parse_float_config(Dictionary config, FloatConfig *out, bool reconf, api_set_error(err, kErrorTypeValidation, "Only one of 'relative' and 'external' should be used"); return false; - } else if (has_relative) { - out->external = false; } else if (!reconf && !has_relative && !has_external) { api_set_error(err, kErrorTypeValidation, "One of 'relative' and 'external' must be used"); return false; + } else if (has_relative) { + out->external = false; } if (out->external && !ui_has(kUIMultigrid)) { @@ -5332,7 +5336,10 @@ void win_drag_vsep_line(win_T *dragwin, int offset) void set_fraction(win_T *wp) { if (wp->w_height_inner > 1) { - wp->w_fraction = ((long)wp->w_wrow * FRACTION_MULT + wp->w_height_inner / 2) + // When cursor is in the first line the percentage is computed as if + // it's halfway that line. Thus with two lines it is 25%, with three + // lines 17%, etc. Similarly for the last line: 75%, 83%, etc. + wp->w_fraction = ((long)wp->w_wrow * FRACTION_MULT + FRACTION_MULT / 2) / (long)wp->w_height_inner; } } @@ -5364,8 +5371,8 @@ void scroll_to_fraction(win_T *wp, int prev_height) int sline, line_size; int height = wp->w_height_inner; - /* Don't change w_topline when height is zero. Don't set w_topline when - * 'scrollbind' is set and this isn't the current window. */ + // Don't change w_topline when height is zero. Don't set w_topline when + // 'scrollbind' is set and this isn't the current window. if (height > 0 && (!wp->w_p_scb || wp == curwin) ) { @@ -5376,8 +5383,7 @@ void scroll_to_fraction(win_T *wp, int prev_height) lnum = wp->w_cursor.lnum; if (lnum < 1) /* can happen when starting up */ lnum = 1; - wp->w_wrow = ((long)wp->w_fraction * (long)height - 1L + FRACTION_MULT / 2) - / FRACTION_MULT; + wp->w_wrow = ((long)wp->w_fraction * (long)height - 1L) / FRACTION_MULT; line_size = plines_win_col(wp, lnum, (long)(wp->w_cursor.col)) - 1; sline = wp->w_wrow - line_size; @@ -5408,7 +5414,6 @@ void scroll_to_fraction(win_T *wp, int prev_height) wp->w_wrow--; } } - set_topline(wp, lnum); } else if (sline > 0) { while (sline > 0 && lnum > 1) { (void)hasFoldingWin(wp, lnum, &lnum, NULL, true, NULL); @@ -5437,12 +5442,12 @@ void scroll_to_fraction(win_T *wp, int prev_height) lnum++; wp->w_wrow -= line_size + sline; } else if (sline > 0) { - /* First line of file reached, use that as topline. */ + // First line of file reached, use that as topline. lnum = 1; wp->w_wrow -= sline; } - set_topline(wp, lnum); } + set_topline(wp, lnum); } if (wp == curwin) { @@ -5546,10 +5551,11 @@ void command_height(void) * p_ch was changed in another tab page. */ curtab->tp_ch_used = p_ch; - /* Find bottom frame with width of screen. */ - frp = lastwin->w_frame; - while (frp->fr_width != Columns && frp->fr_parent != NULL) + // Find bottom frame with width of screen. + frp = lastwin_nofloating()->w_frame; + while (frp->fr_width != Columns && frp->fr_parent != NULL) { frp = frp->fr_parent; + } /* Avoid changing the height of a window with 'winfixheight' set. */ while (frp->fr_prev != NULL && frp->fr_layout == FR_LEAF @@ -5708,9 +5714,9 @@ file_name_in_line ( len = 0; while (vim_isfilec(ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ') || ((options & FNAME_HYP) && path_is_url((char *)ptr + len)) - || (is_url && vim_strchr((char_u *)"?&=", ptr[len]) != NULL)) { - // After type:// we also include ?, & and = as valid characters, so that - // http://google.com?q=this&that=ok works. + || (is_url && vim_strchr((char_u *)":?&=", ptr[len]) != NULL)) { + // After type:// we also include :, ?, & and = as valid characters, so that + // http://google.com:8080?q=this&that=ok works. if ((ptr[len] >= 'A' && ptr[len] <= 'Z') || (ptr[len] >= 'a' && ptr[len] <= 'z')) { if (in_type && path_is_url((char *)ptr + len + 1)) { |