diff options
Diffstat (limited to 'src')
36 files changed, 3357 insertions, 261 deletions
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index f52989c74d..9e781f5dff 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -438,6 +438,17 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) /* Remember if we are closing the current buffer. Restore the number of * windows, so that autocommands in buf_freeall() don't get confused. */ bool is_curbuf = (buf == curbuf); + + // When closing the current buffer stop Visual mode before freeing + // anything. + if (is_curbuf && VIsual_active +#if defined(EXITFREE) + && !entered_free_all_mem +#endif + ) { + end_visual_mode(); + } + buf->b_nwindows = nwindows; buf_freeall(buf, (del_buf ? BFA_DEL : 0) + (wipe_buf ? BFA_WIPE : 0)); @@ -1075,6 +1086,11 @@ do_buffer ( } } + // When closing the current buffer stop Visual mode. + if (buf == curbuf && VIsual_active) { + end_visual_mode(); + } + /* * If deleting the last (listed) buffer, make it empty. * The last (listed) buffer cannot be unloaded. @@ -1667,6 +1683,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_inex); clear_string_option(&buf->b_p_inde); clear_string_option(&buf->b_p_indk); + clear_string_option(&buf->b_p_fp); clear_string_option(&buf->b_p_fex); clear_string_option(&buf->b_p_kp); clear_string_option(&buf->b_p_mps); diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 3e9767adde..fdd7d945c9 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -640,6 +640,7 @@ struct file_buffer { char_u *b_p_inde; ///< 'indentexpr' uint32_t b_p_inde_flags; ///< flags for 'indentexpr' char_u *b_p_indk; ///< 'indentkeys' + char_u *b_p_fp; ///< 'formatprg' char_u *b_p_fex; ///< 'formatexpr' uint32_t b_p_fex_flags; ///< flags for 'formatexpr' char_u *b_p_kp; ///< 'keywordprg' diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 3ba9da34f2..544bcf6ede 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -294,6 +294,26 @@ linenr_T get_cursor_rel_lnum(win_T *wp, linenr_T lnum) return (lnum < cursor) ? -retval : retval; } +// Make sure "pos.lnum" and "pos.col" are valid in "buf". +// This allows for the col to be on the NUL byte. +void check_pos(buf_T *buf, pos_T *pos) +{ + char_u *line; + colnr_T len; + + if (pos->lnum > buf->b_ml.ml_line_count) { + pos->lnum = buf->b_ml.ml_line_count; + } + + if (pos->col > 0) { + line = ml_get_buf(buf, pos->lnum, false); + len = (colnr_T)STRLEN(line); + if (pos->col > len) { + pos->col = len; + } + } +} + /* * Make sure curwin->w_cursor.lnum is valid. */ diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5de999f175..0872ed37ad 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -4196,7 +4196,7 @@ static int eval7( // string and free a string that isn't there. rettv->v_type = VAR_UNKNOWN; - // Skip '!' and '-' characters. They are handled later. + // Skip '!', '-' and '+' characters. They are handled later. start_leader = *arg; while (**arg == '!' || **arg == '-' || **arg == '+') { *arg = skipwhite(*arg + 1); @@ -23631,6 +23631,7 @@ void ex_oldfiles(exarg_T *eap) msg_outnum(++nr); MSG_PUTS(": "); msg_outtrans(get_tv_string(&li->li_tv)); + msg_clr_eos(); msg_putchar('\n'); ui_flush(); /* output one line at a time */ os_breakcheck(); diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 36f50ae7a2..9fc4ef2a02 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -2361,12 +2361,25 @@ int do_in_path(char_u *path, char_u *name, int flags, while (*rtp != NUL && ((flags & DIP_ALL) || !did_one)) { // Copy the path from 'runtimepath' to buf[]. copy_option_part(&rtp, buf, MAXPATHL, ","); + size_t buflen = STRLEN(buf); + + // Skip after or non-after directories. + if (flags & (DIP_NOAFTER | DIP_AFTER)) { + bool is_after = buflen >= 5 + && STRCMP(buf + buflen - 5, "after") == 0; + + if ((is_after && (flags & DIP_NOAFTER)) + || (!is_after && (flags & DIP_AFTER))) { + continue; + } + } + if (name == NULL) { (*callback)(buf, (void *)&cookie); if (!did_one) { did_one = (cookie == NULL); } - } else if (STRLEN(buf) + STRLEN(name) + 2 < MAXPATHL) { + } else if (buflen + STRLEN(name) + 2 < MAXPATHL) { add_pathsep((char *)buf); tail = buf + STRLEN(buf); @@ -2597,6 +2610,7 @@ static bool did_source_packages = false; // ":packloadall" // Find plugins in the package directories and source them. +// "eap" is NULL when invoked during startup. void ex_packloadall(exarg_T *eap) { if (!did_source_packages || (eap != NULL && eap->forceit)) { diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 1234f8e888..bd3b8c204a 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7325,6 +7325,7 @@ static void ex_at(exarg_T *eap) int prev_len = typebuf.tb_len; curwin->w_cursor.lnum = eap->line2; + check_cursor_col(); // Get the register name. No name means use the previous one. int c = *eap->arg; diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 9c64be6d0c..873c15ff4a 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -700,16 +700,9 @@ readfile ( wasempty = (curbuf->b_ml.ml_flags & ML_EMPTY); if (!recoverymode && !filtering && !(flags & READ_DUMMY)) { - /* - * Show the user that we are busy reading the input. Sometimes this - * may take a while. When reading from stdin another program may - * still be running, don't move the cursor to the last line, unless - * always using the GUI. - */ - if (read_stdin) { - mch_msg(_("Nvim: Reading from stdin...\n")); - } else if (!read_buffer) + if (!read_stdin && !read_buffer) { filemess(curbuf, sfname, (char_u *)"", 0); + } } msg_scroll = FALSE; /* overwrite the file message */ diff --git a/src/nvim/main.c b/src/nvim/main.c index d7baa7acfa..7b1c912f4b 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -326,6 +326,12 @@ int main(int argc, char **argv) do_cmdline_cmd("augroup END"); #undef PROTO + // Reset 'loadplugins' for "-u NONE" before "--cmd" arguments. + // Allows for setting 'loadplugins' there. + if (params.use_vimrc != NULL && strcmp(params.use_vimrc, "NONE") == 0) { + p_lpl = false; + } + /* Execute --cmd arguments. */ exe_pre_commands(¶ms); @@ -513,6 +519,12 @@ int main(int argc, char **argv) apply_autocmds(EVENT_VIMENTER, NULL, NULL, false, curbuf); TIME_MSG("VimEnter autocommands"); + // Adjust default register name for "unnamed" in 'clipboard'. Can only be + // done after the clipboard is available and all initial commands that may + // modify the 'clipboard' setting have run; i.e. just before entering the + // main loop. + set_reg_var(get_default_register_name()); + /* When a startup script or session file setup for diff'ing and * scrollbind, sync the scrollbind now. */ if (curwin->w_p_diff && curwin->w_p_scb) { @@ -1262,11 +1274,14 @@ static void set_window_layout(mparm_T *paramp) static void load_plugins(void) { if (p_lpl) { - source_runtime((char_u *)"plugin/**/*.vim", DIP_ALL); // NOLINT + source_runtime((char_u *)"plugin/**/*.vim", DIP_ALL | DIP_NOAFTER); // NOLINT TIME_MSG("loading plugins"); ex_packloadall(NULL); TIME_MSG("loading packages"); + + source_runtime((char_u *)"plugin/**/*.vim", DIP_ALL | DIP_AFTER); + TIME_MSG("loading after plugins"); } } @@ -1702,8 +1717,6 @@ static void source_startup_scripts(const mparm_T *const parmp) if (parmp->use_vimrc != NULL) { if (strcmp(parmp->use_vimrc, "NONE") == 0 || strcmp(parmp->use_vimrc, "NORC") == 0) { - if (parmp->use_vimrc[2] == 'N') - p_lpl = false; // don't load plugins either } else { if (do_source((char_u *)parmp->use_vimrc, FALSE, DOSO_NONE) != OK) EMSG2(_("E282: Cannot read from \"%s\""), parmp->use_vimrc); diff --git a/src/nvim/move.c b/src/nvim/move.c index 4c1b8a8411..4feabf624b 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -1878,8 +1878,10 @@ int onepage(int dir, long count) } foldAdjustCursor(); cursor_correct(); - if (retval == OK) + check_cursor_col(); + if (retval == OK) { beginline(BL_SOL | BL_FIX); + } curwin->w_valid &= ~(VALID_WCOL|VALID_WROW|VALID_VIRTCOL); /* diff --git a/src/nvim/normal.c b/src/nvim/normal.c index ee3c3f9f11..7188e13436 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1596,6 +1596,8 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) oap->start = curwin->w_cursor; } + // Just in case lines were deleted that make the position invalid. + check_pos(curwin->w_buffer, &oap->end); oap->line_count = oap->end.lnum - oap->start.lnum + 1; /* Set "virtual_op" before resetting VIsual_active. */ @@ -1899,12 +1901,13 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) break; case OP_FORMAT: - if (*curbuf->b_p_fex != NUL) - op_formatexpr(oap); /* use expression */ - else if (*p_fp != NUL) - op_colon(oap); /* use external command */ - else - op_format(oap, false); /* use internal function */ + if (*curbuf->b_p_fex != NUL) { + op_formatexpr(oap); // use expression + } else if (*p_fp != NUL || *curbuf->b_p_fp != NUL) { + op_colon(oap); // use external command + } else { + op_format(oap, false); // use internal function + } break; case OP_FORMAT2: @@ -1912,7 +1915,10 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) break; case OP_FUNCTION: - op_function(oap); /* call 'operatorfunc' */ + // Restore linebreak, so that when the user edits it looks as + // before. + curwin->w_p_lbr = lbr_saved; + op_function(oap); // call 'operatorfunc' break; case OP_INSERT: @@ -2059,10 +2065,13 @@ static void op_colon(oparg_T *oap) stuffReadbuff(get_equalprg()); stuffReadbuff((char_u *)"\n"); } else if (oap->op_type == OP_FORMAT) { - if (*p_fp == NUL) - stuffReadbuff((char_u *)"fmt"); - else + if (*curbuf->b_p_fp != NUL) { + stuffReadbuff(curbuf->b_p_fp); + } else if (*p_fp != NUL) { stuffReadbuff(p_fp); + } else { + stuffReadbuff((char_u *)"fmt"); + } stuffReadbuff((char_u *)"\n']"); } @@ -4757,13 +4766,16 @@ static void nv_ident(cmdarg_T *cap) } } - /* - * Now grab the chars in the identifier - */ - if (cmdchar == 'K' && !kp_ex) { - /* Escape the argument properly for a shell command */ + // Now grab the chars in the identifier + if (cmdchar == 'K') { ptr = vim_strnsave(ptr, n); - p = vim_strsave_shellescape(ptr, true, true); + if (kp_ex) { + // Escape the argument properly for an Ex command + p = vim_strsave_fnameescape(ptr, false); + } else { + // Escape the argument properly for a shell command + p = vim_strsave_shellescape(ptr, true, true); + } xfree(ptr); char *newbuf = xrealloc(buf, STRLEN(buf) + STRLEN(p) + 1); buf = newbuf; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 1e4d392754..c13b6f736a 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -3847,6 +3847,7 @@ fex_format ( int use_sandbox = was_set_insecurely((char_u *)"formatexpr", OPT_LOCAL); int r; + char_u *fex; /* * Set v:lnum to the first line number and v:count to the number of lines. @@ -3856,16 +3857,22 @@ fex_format ( set_vim_var_nr(VV_COUNT, (varnumber_T)count); set_vim_var_char(c); - /* - * Evaluate the function. - */ - if (use_sandbox) - ++sandbox; - r = eval_to_number(curbuf->b_p_fex); - if (use_sandbox) - --sandbox; + // Make a copy, the option could be changed while calling it. + fex = vim_strsave(curbuf->b_p_fex); + if (fex == NULL) { + return 0; + } + // Evaluate the function. + if (use_sandbox) { + sandbox++; + } + r = (int)eval_to_number(fex); + if (use_sandbox) { + sandbox--; + } set_vim_var_string(VV_CHAR, NULL, -1); + xfree(fex); return r; } diff --git a/src/nvim/option.c b/src/nvim/option.c index 2fae4aa848..2a17ca69d1 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2150,6 +2150,7 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_inex); check_string_option(&buf->b_p_inde); check_string_option(&buf->b_p_indk); + check_string_option(&buf->b_p_fp); check_string_option(&buf->b_p_fex); check_string_option(&buf->b_p_kp); check_string_option(&buf->b_p_mps); @@ -5255,6 +5256,9 @@ void unset_global_local_option(char *name, void *from) case PV_TSR: clear_string_option(&buf->b_p_tsr); break; + case PV_FP: + clear_string_option(&buf->b_p_fp); + break; case PV_EFM: clear_string_option(&buf->b_p_efm); break; @@ -5288,6 +5292,7 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) } if ((opt_flags & OPT_LOCAL) && ((int)p->indir & PV_BOTH)) { switch ((int)p->indir) { + case PV_FP: return (char_u *)&(curbuf->b_p_fp); case PV_EFM: return (char_u *)&(curbuf->b_p_efm); case PV_GP: return (char_u *)&(curbuf->b_p_gp); case PV_MP: return (char_u *)&(curbuf->b_p_mp); @@ -5346,6 +5351,8 @@ static char_u *get_varp(vimoption_T *p) ? (char_u *)&(curbuf->b_p_dict) : p->var; case PV_TSR: return *curbuf->b_p_tsr != NUL ? (char_u *)&(curbuf->b_p_tsr) : p->var; + case PV_FP: return *curbuf->b_p_fp != NUL + ? (char_u *)&(curbuf->b_p_fp) : p->var; case PV_EFM: return *curbuf->b_p_efm != NUL ? (char_u *)&(curbuf->b_p_efm) : p->var; case PV_GP: return *curbuf->b_p_gp != NUL @@ -5694,6 +5701,7 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_s.b_p_spl = vim_strsave(p_spl); buf->b_p_inde = vim_strsave(p_inde); buf->b_p_indk = vim_strsave(p_indk); + buf->b_p_fp = empty_option; buf->b_p_fex = vim_strsave(p_fex); buf->b_p_sua = vim_strsave(p_sua); buf->b_p_keymap = vim_strsave(p_keymap); diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 1fd6dc9c91..b171b23edb 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -713,6 +713,7 @@ enum { , BV_EP , BV_ET , BV_FENC + , BV_FP , BV_BEXPR , BV_FEX , BV_FF diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 1476fdda2c..853c2b52d7 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -948,7 +948,7 @@ return { }, { full_name='formatprg', abbreviation='fp', - type='string', scope={'global'}, + type='string', scope={'global', 'buffer'}, secure=true, vi_def=true, expand=true, diff --git a/src/nvim/search.c b/src/nvim/search.c index ba6c4e6548..75862e1136 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3028,7 +3028,8 @@ extend: ++curwin->w_cursor.col; VIsual = start_pos; VIsual_mode = 'v'; - redraw_curbuf_later(INVERTED); /* update the inversion */ + redraw_cmdline = true; // show mode later + redraw_curbuf_later(INVERTED); // update the inversion } else { /* include a newline after the sentence, if there is one */ if (incl(&curwin->w_cursor) == -1) diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 7768636ded..267832ed2d 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -753,6 +753,22 @@ int vim_snprintf(char *str, size_t str_m, const char *fmt, ...) return str_l; } +// Return the representation of infinity for printf() function: +// "-inf", "inf", "+inf", " inf", "-INF", "INF", "+INF" or " INF". +static const char *infinity_str(bool positive, char fmt_spec, + int force_sign, int space_for_positive) +{ + static const char *table[] = { + "-inf", "inf", "+inf", " inf", + "-INF", "INF", "+INF", " INF" + }; + int idx = positive * (1 + force_sign + force_sign * space_for_positive); + if (ASCII_ISUPPER(fmt_spec)) { + idx += 4; + } + return table[idx]; +} + /// Write formatted value to the string /// @@ -909,7 +925,6 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap, case 'D': fmt_spec = 'd'; length_modifier = 'l'; break; case 'U': fmt_spec = 'u'; length_modifier = 'l'; break; case 'O': fmt_spec = 'o'; length_modifier = 'l'; break; - case 'F': fmt_spec = 'f'; break; default: break; } @@ -1186,6 +1201,7 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap, } case 'f': + case 'F': case 'e': case 'E': case 'g': @@ -1201,36 +1217,51 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap, if (fmt_spec == 'g' || fmt_spec == 'G') { // can't use %g directly, cause it prints "1.0" as "1" if ((abs_f >= 0.001 && abs_f < 10000000.0) || abs_f == 0.0) { - fmt_spec = 'f'; + fmt_spec = ASCII_ISUPPER(fmt_spec) ? 'F' : 'f'; } else { fmt_spec = fmt_spec == 'g' ? 'e' : 'E'; } remove_trailing_zeroes = true; } - if (fmt_spec == 'f' && abs_f > 1.0e307) { - // avoid a buffer overflow - memmove(tmp, "inf", sizeof("inf")); - str_arg_l = sizeof("inf") - 1; + if (isinf(f) + || (strchr("fF", fmt_spec) != NULL && abs_f > 1.0e307)) { + xstrlcpy(tmp, infinity_str(f > 0.0, fmt_spec, + force_sign, space_for_positive), + sizeof(tmp)); + str_arg_l = strlen(tmp); + zero_padding = 0; + } else if (isnan(f)) { + // Not a number: nan or NAN + memmove(tmp, ASCII_ISUPPER(fmt_spec) ? "NAN" : "nan", 4); + str_arg_l = 3; + zero_padding = 0; } else { format[0] = '%'; - int l = 1; + size_t l = 1; + if (force_sign) { + format[l++] = space_for_positive ? ' ' : '+'; + } if (precision_specified) { size_t max_prec = TMP_LEN - 10; // make sure we don't get more digits than we have room for - if (fmt_spec == 'f' && abs_f > 1.0) { + if ((fmt_spec == 'f' || fmt_spec == 'F') && abs_f > 1.0) { max_prec -= (size_t)log10(abs_f); } if (precision > max_prec) { precision = max_prec; } - l += snprintf(format + 1, sizeof(format) - 1, ".%d", - (int)precision); + l += (size_t)snprintf(format + l, sizeof(format) - l, ".%d", + (int)precision); } - format[l] = fmt_spec == 'F' ? 'f' : fmt_spec; + + // Cast to char to avoid a conversion warning on Ubuntu 12.04. + format[l] = (char)(fmt_spec == 'F' ? 'f' : fmt_spec); format[l + 1] = NUL; - assert(l + 1 < (int)sizeof(format)); + + // Regular float number + assert(l + 1 < sizeof(format)); str_arg_l = (size_t)snprintf(tmp, sizeof(tmp), format, f); assert(str_arg_l < sizeof(tmp)); @@ -1239,7 +1270,7 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap, char *tp; // using %g or %G: remove superfluous zeroes - if (fmt_spec == 'f') { + if (fmt_spec == 'f' || fmt_spec == 'F') { tp = tmp + str_arg_l - 1; } else { tp = (char *)vim_strchr((char_u *)tmp, @@ -1281,6 +1312,12 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap, } } } + if (zero_padding && min_field_width > str_arg_l + && (tmp[0] == '-' || force_sign)) { + // Padding 0's should be inserted after the sign. + number_of_zeros_to_pad = min_field_width - str_arg_l; + zero_padding_insertion_ind = 1; + } str_arg = tmp; break; } diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 59f4c1e968..7bcaff662c 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -72,23 +72,18 @@ typedef struct { regmatch_T regmatch; /* regexp program, may be NULL */ } pat_T; -/* - * The matching tags are first stored in ga_match[]. In which one depends on - * the priority of the match. - * At the end, the matches from ga_match[] are concatenated, to make a list - * sorted on priority. - */ -#define MT_ST_CUR 0 /* static match in current file */ -#define MT_GL_CUR 1 /* global match in current file */ -#define MT_GL_OTH 2 /* global match in other file */ -#define MT_ST_OTH 3 /* static match in other file */ -#define MT_IC_ST_CUR 4 /* icase static match in current file */ -#define MT_IC_GL_CUR 5 /* icase global match in current file */ -#define MT_IC_GL_OTH 6 /* icase global match in other file */ -#define MT_IC_ST_OTH 7 /* icase static match in other file */ -#define MT_IC_OFF 4 /* add for icase match */ -#define MT_RE_OFF 8 /* add for regexp match */ -#define MT_MASK 7 /* mask for printing priority */ +// The matching tags are first stored in one of the hash tables. In +// which one depends on the priority of the match. +// ht_match[] is used to find duplicates, ga_match[] to keep them in sequence. +// At the end, the matches from ga_match[] are concatenated, to make a list +// sorted on priority. +#define MT_ST_CUR 0 // static match in current file +#define MT_GL_CUR 1 // global match in current file +#define MT_GL_OTH 2 // global match in other file +#define MT_ST_OTH 3 // static match in other file +#define MT_IC_OFF 4 // add for icase match +#define MT_RE_OFF 8 // add for regexp match +#define MT_MASK 7 // mask for printing priority #define MT_COUNT 16 static char *mt_names[MT_COUNT/2] = @@ -919,7 +914,8 @@ end_do_tag: /* Only store the new index when using the tagstack and it's valid. */ if (use_tagstack && tagstackidx <= curwin->w_tagstacklen) curwin->w_tagstackidx = tagstackidx; - postponed_split = 0; /* don't split next time */ + postponed_split = 0; // don't split next time + g_do_tagpreview = 0; // don't do tag preview next time return jumped_to_tag; } @@ -1060,6 +1056,7 @@ static void prepare_pats(pat_T *pats, int has_re) * TAG_REGEXP use "pat" as a regexp * TAG_NOIC don't always ignore case * TAG_KEEP_LANG keep language + * TAG_CSCOPE use cscope results for tags */ int find_tags ( @@ -1121,19 +1118,19 @@ find_tags ( int save_emsg_off; - struct match_found { - int len; /* nr of chars of match[] to be compared */ - char_u match[1]; /* actually longer */ - } *mfp, *mfp2; - garray_T ga_match[MT_COUNT]; - int match_count = 0; /* number of matches found */ + char_u *mfp; + garray_T ga_match[MT_COUNT]; // stores matches in sequence + hashtab_T ht_match[MT_COUNT]; // stores matches by key + hash_T hash = 0; + int match_count = 0; // number of matches found char_u **matches; int mtt; int help_save; int help_pri = 0; - char_u *help_lang_find = NULL; /* lang to be found */ - char_u help_lang[3]; /* lang of current tags file */ - char_u *saved_pat = NULL; /* copy of pat[] */ + char_u *help_lang_find = NULL; // lang to be found + char_u help_lang[3]; // lang of current tags file + char_u *saved_pat = NULL; // copy of pat[] + bool is_txt = false; pat_T orgpat; /* holds unconverted pattern info */ vimconv_T vimconv; @@ -1183,16 +1180,23 @@ find_tags ( */ lbuf = xmalloc(lbuf_size); tag_fname = xmalloc(MAXPATHL + 1); - for (mtt = 0; mtt < MT_COUNT; ++mtt) - ga_init(&ga_match[mtt], (int)sizeof(struct match_found *), 100); + for (mtt = 0; mtt < MT_COUNT; mtt++) { + ga_init(&ga_match[mtt], sizeof(char_u *), 100); + hash_init(&ht_match[mtt]); + } STRCPY(tag_fname, "from cscope"); /* for error messages */ /* * Initialize a few variables */ - if (help_only) /* want tags from help file */ - curbuf->b_help = true; /* will be restored later */ + if (help_only) { // want tags from help file + curbuf->b_help = true; // will be restored later + } else if (use_cscope) { + // Make sure we don't mix help and cscope, confuses Coverity. + help_only = false; + curbuf->b_help = false; + } orgpat.len = (int)STRLEN(pat); if (curbuf->b_help) { @@ -1231,6 +1235,14 @@ find_tags ( * When the tag file is case-fold sorted, it is either one or the other. * Only ignore case when TAG_NOIC not used or 'ignorecase' set. */ + // Set a flag if the file extension is .txt + if ((flags & TAG_KEEP_LANG) + && help_lang_find == NULL + && curbuf->b_fname != NULL + && (i = (int)STRLEN(curbuf->b_fname)) > 4 + && STRICMP(curbuf->b_fname + i - 4, ".txt") == 0) { + is_txt = true; + } orgpat.regmatch.rm_ic = ((p_ic || !noic) && (findall || orgpat.headlen == 0 || !p_tbs)); for (round = 1; round <= 2; ++round) { @@ -1246,13 +1258,19 @@ find_tags ( fp = NULL; // avoid GCC warning } else { if (curbuf->b_help) { - /* Prefer help tags according to 'helplang'. Put the - * two-letter language name in help_lang[]. */ - i = (int)STRLEN(tag_fname); - if (i > 3 && tag_fname[i - 3] == '-') - STRCPY(help_lang, tag_fname + i - 2); - else + // Keep en if the file extension is .txt + if (is_txt) { STRCPY(help_lang, "en"); + } else { + // Prefer help tags according to 'helplang'. Put the + // two-letter language name in help_lang[]. + i = (int)STRLEN(tag_fname); + if (i > 3 && tag_fname[i - 3] == '-') { + STRCPY(help_lang, tag_fname + i - 2); + } else { + STRCPY(help_lang, "en"); + } + } /* When searching for a specific language skip tags files * for other languages. */ @@ -1736,10 +1754,10 @@ parse_line: match_re = TRUE; } - /* - * If a match is found, add it to ga_match[]. - */ + // If a match is found, add it to ht_match[] and ga_match[]. if (match) { + int len = 0; + if (use_cscope) { /* Don't change the ordering, always use the same table. */ mtt = MT_GL_OTH; @@ -1774,116 +1792,109 @@ parse_line: mtt += MT_RE_OFF; } - /* - * Add the found match in ga_match[mtt], avoiding duplicates. - * Store the info we need later, which depends on the kind of - * tags we are dealing with. - */ - ga_grow(&ga_match[mtt], 1); - { - int len; - - if (help_only) { + // Add the found match in ht_match[mtt] and ga_match[mtt]. + // Store the info we need later, which depends on the kind of + // tags we are dealing with. + if (help_only) { # define ML_EXTRA 3 - /* - * Append the help-heuristic number after the - * tagname, for sorting it later. - */ - *tagp.tagname_end = NUL; - len = (int)(tagp.tagname_end - tagp.tagname); - mfp = xmalloc(sizeof(struct match_found) + len + 10 + ML_EXTRA); - /* "len" includes the language and the NUL, but - * not the priority. */ - mfp->len = len + ML_EXTRA + 1; -#define ML_HELP_LEN 6 - p = mfp->match; - STRCPY(p, tagp.tagname); - p[len] = '@'; - STRCPY(p + len + 1, help_lang); - sprintf((char *)p + len + 1 + ML_EXTRA, "%06d", - help_heuristic(tagp.tagname, - match_re ? matchoff : 0, !match_no_ic) - + help_pri - ); - - *tagp.tagname_end = TAB; - } else if (name_only) { - if (get_it_again) { - char_u *temp_end = tagp.command; - - if (*temp_end == '/') - while (*temp_end && *temp_end != '\r' - && *temp_end != '\n' - && *temp_end != '$') - temp_end++; - - if (tagp.command + 2 < temp_end) { - len = (int)(temp_end - tagp.command - 2); - mfp = xmalloc(sizeof(struct match_found) + len); - mfp->len = len + 1; /* include the NUL */ - p = mfp->match; - STRLCPY(p, tagp.command + 2, len + 1); - } else - mfp = NULL; - get_it_again = FALSE; + // Append the help-heuristic number after the tagname, for + // sorting it later. The heuristic is ignored for + // detecting duplicates. + // The format is {tagname}@{lang}NUL{heuristic}NUL + *tagp.tagname_end = NUL; + len = (int)(tagp.tagname_end - tagp.tagname); + mfp = xmalloc(sizeof(char_u) + len + 10 + ML_EXTRA + 1); + + p = mfp; + STRCPY(p, tagp.tagname); + p[len] = '@'; + STRCPY(p + len + 1, help_lang); + snprintf((char *)p + len + 1 + ML_EXTRA, 10, "%06d", + help_heuristic(tagp.tagname, + match_re ? matchoff : 0, !match_no_ic) + + help_pri); + + *tagp.tagname_end = TAB; + } else if (name_only) { + if (get_it_again) { + char_u *temp_end = tagp.command; + + if (*temp_end == '/') { + while (*temp_end && *temp_end != '\r' + && *temp_end != '\n' + && *temp_end != '$') { + temp_end++; + } + } + + if (tagp.command + 2 < temp_end) { + len = (int)(temp_end - tagp.command - 2); + mfp = xmalloc(len + 2); + STRLCPY(mfp, tagp.command + 2, len + 1); } else { - len = (int)(tagp.tagname_end - tagp.tagname); - mfp = xmalloc(sizeof(struct match_found) + len); - mfp->len = len + 1; /* include the NUL */ - p = mfp->match; - STRLCPY(p, tagp.tagname, len + 1); - - /* if wanted, re-read line to get long form too */ - if (State & INSERT) - get_it_again = p_sft; + mfp = NULL; } + get_it_again = false; } else { - /* Save the tag in a buffer. - * Emacs tag: <mtt><tag_fname><NUL><ebuf><NUL><lbuf> - * other tag: <mtt><tag_fname><NUL><NUL><lbuf> - * without Emacs tags: <mtt><tag_fname><NUL><lbuf> - */ - len = (int)STRLEN(tag_fname) - + (int)STRLEN(lbuf) + 3; - mfp = xmalloc(sizeof(struct match_found) + len); - mfp->len = len; - p = mfp->match; - p[0] = mtt; - STRCPY(p + 1, tag_fname); + len = (int)(tagp.tagname_end - tagp.tagname); + mfp = xmalloc(sizeof(char_u) + len + 1); + STRLCPY(mfp, tagp.tagname, len + 1); + + // if wanted, re-read line to get long form too + if (State & INSERT) { + get_it_again = p_sft; + } + } + } else { +#define TAG_SEP 0x01 + size_t tag_fname_len = STRLEN(tag_fname); + // Save the tag in a buffer. + // Use 0x01 to separate fields (Can't use NUL, because the + // hash key is terminated by NUL). + // Emacs tag: <mtt><tag_fname><NUL><ebuf><NUL><lbuf> + // other tag: <mtt><tag_fname><NUL><NUL><lbuf> + // without Emacs tags: <mtt><tag_fname><NUL><lbuf> + // Here <mtt> is the "mtt" value plus 1 to avoid NUL. + len = (int)tag_fname_len + (int)STRLEN(lbuf) + 3; + mfp = xmalloc(sizeof(char_u) + len + 1); + p = mfp; + p[0] = mtt + 1; + STRCPY(p + 1, tag_fname); #ifdef BACKSLASH_IN_FILENAME - /* Ignore differences in slashes, avoid adding - * both path/file and path\file. */ - slash_adjust(p + 1); + // Ignore differences in slashes, avoid adding + // both path/file and path\file. + slash_adjust(p + 1); #endif - s = p + 1 + STRLEN(tag_fname) + 1; - STRCPY(s, lbuf); - } + p[tag_fname_len + 1] = TAG_SEP; + s = p + 1 + tag_fname_len + 1; + STRCPY(s, lbuf); + } - if (mfp != NULL) { - /* - * Don't add identical matches. - * This can take a lot of time when finding many - * matches, check for CTRL-C now and then. - * Add all cscope tags, because they are all listed. - */ - if (use_cscope) - i = -1; - else - for (i = ga_match[mtt].ga_len; --i >= 0 && !got_int; ) { - mfp2 = ((struct match_found **) - (ga_match[mtt].ga_data))[i]; - if (mfp2->len == mfp->len - && memcmp(mfp2->match, mfp->match, - (size_t)mfp->len) == 0) - break; - fast_breakcheck(); - } - if (i < 0) { - ((struct match_found **)(ga_match[mtt].ga_data)) + if (mfp != NULL) { + hashitem_T *hi; + + // Don't add identical matches. + // Add all cscope tags, because they are all listed. + // "mfp" is used as a hash key, there is a NUL byte to end + // the part matters for comparing, more bytes may follow + // after it. E.g. help tags store the priority after the + // NUL. + if (use_cscope) { + hash++; + } else { + hash = hash_hash(mfp); + } + hi = hash_lookup(&ht_match[mtt], (const char *)mfp, + STRLEN(mfp), hash); + if (HASHITEM_EMPTY(hi)) { + hash_add_item(&ht_match[mtt], hi, mfp, hash); + ga_grow(&ga_match[mtt], 1); + ((char_u **)(ga_match[mtt].ga_data)) [ga_match[mtt].ga_len++] = mfp; - ++match_count; - } else - xfree(mfp); + match_count++; + } else { + // duplicate tag, drop it + xfree(mfp); } } } @@ -1958,21 +1969,29 @@ findtag_end: else matches = NULL; match_count = 0; - for (mtt = 0; mtt < MT_COUNT; ++mtt) { - for (int i = 0; i < ga_match[mtt].ga_len; ++i) { - mfp = ((struct match_found **)(ga_match[mtt].ga_data))[i]; - if (matches == NULL) + for (mtt = 0; mtt < MT_COUNT; mtt++) { + for (i = 0; i < ga_match[mtt].ga_len; i++) { + mfp = ((char_u **)(ga_match[mtt].ga_data))[i]; + if (matches == NULL) { xfree(mfp); - else { - /* To avoid allocating memory again we turn the struct - * match_found into a string. For help the priority was not - * included in the length. */ - memmove(mfp, mfp->match, - (size_t)(mfp->len + (help_only ? ML_HELP_LEN : 0))); + } else { + if (!name_only) { + // Change mtt back to zero-based. + *mfp = *mfp - 1; + + // change the TAG_SEP back to NUL + for (p = mfp + 1; *p != NUL; p++) { + if (*p == TAG_SEP) { + *p = NUL; + } + } + } matches[match_count++] = (char_u *)mfp; } } + ga_clear(&ga_match[mtt]); + hash_clear(&ht_match[mtt]); } *matchesp = matches; diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 9f9ecbc6c9..d090ace432 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -47,6 +47,7 @@ NEW_TESTS ?= \ test_match.res \ test_matchadd_conceal.res \ test_nested_function.res \ + test_normal.res \ test_quickfix.res \ test_signs.res \ test_syntax.res \ diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 5299fec7c2..b4eb9de506 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -68,10 +68,10 @@ let $HOME = '/does/not/exist' " Prepare for calling garbagecollect_for_testing(). let v:testing = 1 -" Align with vim defaults. +" Align Nvim defaults to Vim. set directory^=. -set nohidden set backspace= +set nohidden smarttab noautoindent noautoread complete-=i noruler noshowcmd function RunTheTest(test) echo 'Executing ' . a:test @@ -95,8 +95,17 @@ function RunTheTest(test) endif " Close any extra windows and make the current one not modified. - while winnr('$') > 1 + while 1 + let wincount = winnr('$') + if wincount == 1 + break + endif bwipe! + if wincount == winnr('$') + " Did not manage to close a window. + only! + break + endif endwhile set nomodified endfunc diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index 05257d566d..06f2199214 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -4,8 +4,9 @@ set noruler set noshowcmd set belloff= -" Make sure 'runtimepath' does not include $HOME. +" Make sure 'runtimepath' and 'packpath' does not include $HOME. set rtp=$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after +let &packpath = &rtp " Make sure $HOME does not get read or written. let $HOME = '/does/not/exist' diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim new file mode 100644 index 0000000000..784e4a0a02 --- /dev/null +++ b/src/nvim/testdir/shared.vim @@ -0,0 +1,214 @@ +" Functions shared by several tests. + +" Get the name of the Python executable. +" Also keeps it in s:python. +func PythonProg() + " This test requires the Python command to run the test server. + " This most likely only works on Unix and Windows. + if has('unix') + " We also need the job feature or the pkill command to make sure the server + " can be stopped. + if !(executable('python') && (has('job') || executable('pkill'))) + return '' + endif + let s:python = 'python' + elseif has('win32') + " Use Python Launcher for Windows (py.exe) if available. + if executable('py.exe') + let s:python = 'py.exe' + elseif executable('python.exe') + let s:python = 'python.exe' + else + return '' + endif + else + return '' + endif + return s:python +endfunc + +" Run "cmd". Returns the job if using a job. +func RunCommand(cmd) + let job = 0 + if has('job') + let job = job_start(a:cmd, {"stoponexit": "hup"}) + call job_setoptions(job, {"stoponexit": "kill"}) + elseif has('win32') + exe 'silent !start cmd /c start "test_channel" ' . a:cmd + else + exe 'silent !' . a:cmd . '&' + endif + return job +endfunc + +" Read the port number from the Xportnr file. +func GetPort() + let l = [] + for i in range(200) + try + let l = readfile("Xportnr") + catch + endtry + if len(l) >= 1 + break + endif + sleep 10m + endfor + call delete("Xportnr") + + if len(l) == 0 + " Can't make the connection, give up. + return 0 + endif + return l[0] +endfunc + +" Run a Python server for "cmd" and call "testfunc". +" Always kills the server before returning. +func RunServer(cmd, testfunc, args) + " The Python program writes the port number in Xportnr. + call delete("Xportnr") + + if len(a:args) == 1 + let arg = ' ' . a:args[0] + else + let arg = '' + endif + let pycmd = s:python . " " . a:cmd . arg + + try + let g:currentJob = RunCommand(pycmd) + + " Wait for up to 2 seconds for the port number to be there. + let port = GetPort() + if port == 0 + call assert_false(1, "Can't start " . a:cmd) + return + endif + + call call(function(a:testfunc), [port]) + catch + call assert_false(1, 'Caught exception: "' . v:exception . '" in ' . v:throwpoint) + finally + call s:kill_server(a:cmd) + endtry +endfunc + +func s:kill_server(cmd) + if has('job') + if exists('g:currentJob') + call job_stop(g:currentJob) + unlet g:currentJob + endif + elseif has('win32') + let cmd = substitute(a:cmd, ".py", '', '') + call system('taskkill /IM ' . s:python . ' /T /F /FI "WINDOWTITLE eq ' . cmd . '"') + else + call system("pkill -f " . a:cmd) + endif +endfunc + +" Wait for up to a second for "expr" to become true. +" Return time slept in milliseconds. With the +reltime feature this can be +" more than the actual waiting time. Without +reltime it can also be less. +func WaitFor(expr) + " using reltime() is more accurate, but not always available + if has('reltime') + let start = reltime() + else + let slept = 0 + endif + for i in range(100) + try + if eval(a:expr) + if has('reltime') + return float2nr(reltimefloat(reltime(start)) * 1000) + endif + return slept + endif + catch + endtry + if !has('reltime') + let slept += 10 + endif + sleep 10m + endfor + return 1000 +endfunc + +" Wait for up to a given milliseconds. +" With the +timers feature this waits for key-input by getchar(), Resume() +" feeds key-input and resumes process. Return time waited in milliseconds. +" Without +timers it uses simply :sleep. +func Standby(msec) + if has('timers') + let start = reltime() + let g:_standby_timer = timer_start(a:msec, function('s:feedkeys')) + call getchar() + return float2nr(reltimefloat(reltime(start)) * 1000) + else + execute 'sleep ' a:msec . 'm' + return a:msec + endif +endfunc + +func Resume() + if exists('g:_standby_timer') + call timer_stop(g:_standby_timer) + call s:feedkeys(0) + unlet g:_standby_timer + endif +endfunc + +func s:feedkeys(timer) + call feedkeys('x', 'nt') +endfunc + +" Get the command to run Vim, with -u NONE and --headless arguments. +" Returns an empty string on error. +func GetVimCommand() + let cmd = v:progpath + let cmd = substitute(cmd, '-u \f\+', '-u NONE', '') + if cmd !~ '-u NONE' + let cmd = cmd . ' -u NONE' + endif + let cmd .= ' --headless -i NONE' + let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '') + return cmd +endfunc + +" Run Vim, using the "vimcmd" file and "-u NORC". +" "before" is a list of Vim commands to be executed before loading plugins. +" "after" is a list of Vim commands to be executed after loading plugins. +" Plugins are not loaded, unless 'loadplugins' is set in "before". +" Return 1 if Vim could be executed. +func RunVim(before, after, arguments) + return RunVimPiped(a:before, a:after, a:arguments, '') +endfunc + +func RunVimPiped(before, after, arguments, pipecmd) + let $NVIM_LOG_FILE='Xnvim.log' + let cmd = GetVimCommand() + if cmd == '' + return 0 + endif + let args = '' + if len(a:before) > 0 + call writefile(a:before, 'Xbefore.vim') + let args .= ' --cmd "so Xbefore.vim"' + endif + if len(a:after) > 0 + call writefile(a:after, 'Xafter.vim') + let args .= ' -S Xafter.vim' + endif + + exe "silent !" . a:pipecmd . cmd . args . ' ' . a:arguments + + if len(a:before) > 0 + call delete('Xbefore.vim') + endif + if len(a:after) > 0 + call delete('Xafter.vim') + endif + return 1 +endfunc diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim index 95500853f2..6290680305 100644 --- a/src/nvim/testdir/test_digraph.vim +++ b/src/nvim/testdir/test_digraph.vim @@ -458,4 +458,4 @@ func! Test_digraph_cmndline() call assert_equal("€", s) endfunc -" vim: tabstop=2 shiftwidth=0 sts=-1 expandtab +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 7f7811dc7a..ccbdde8244 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -152,20 +152,52 @@ function Test_printf_misc() call assert_equal(' 123', printf('% d', 123)) call assert_equal('-123', printf('% d', -123)) + call assert_equal('123', printf('%2d', 123)) + call assert_equal(' 123', printf('%6d', 123)) + call assert_equal('000123', printf('%06d', 123)) + call assert_equal('+00123', printf('%+06d', 123)) + call assert_equal(' 00123', printf('% 06d', 123)) + call assert_equal(' +123', printf('%+6d', 123)) + call assert_equal(' 123', printf('% 6d', 123)) + call assert_equal(' -123', printf('% 6d', -123)) + + " Test left adjusted. + call assert_equal('123 ', printf('%-6d', 123)) + call assert_equal('+123 ', printf('%-+6d', 123)) + call assert_equal(' 123 ', printf('%- 6d', 123)) + call assert_equal('-123 ', printf('%- 6d', -123)) + + call assert_equal(' 00123', printf('%7.5d', 123)) + call assert_equal(' -00123', printf('%7.5d', -123)) + call assert_equal(' +00123', printf('%+7.5d', 123)) + " Precision field should not be used when combined with %0 + call assert_equal(' 00123', printf('%07.5d', 123)) + call assert_equal(' -00123', printf('%07.5d', -123)) + + call assert_equal(' 123', printf('%*d', 5, 123)) + call assert_equal('123 ', printf('%*d', -5, 123)) call assert_equal('00123', printf('%.*d', 5, 123)) call assert_equal(' 123', printf('% *d', 5, 123)) call assert_equal(' +123', printf('%+ *d', 5, 123)) - call assert_equal('123', printf('%2d', 123)) - call assert_equal(' 123', printf('%5d', 123)) - call assert_equal('00123', printf('%05d', 123)) - call assert_equal('123 ', printf('%-5d', 123)) + " Simple quote (thousand grouping char) is ignored. + call assert_equal('+00123456', printf("%+'09d", 123456)) + + " Unrecognized format specifier kept as-is. + call assert_equal('_123', printf("%_%d", 123)) + + " Test alternate forms. call assert_equal('0x7b', printf('%#x', 123)) call assert_equal('0X7B', printf('%#X', 123)) call assert_equal('0173', printf('%#o', 123)) call assert_equal('0173', printf('%#O', 123)) call assert_equal('abc', printf('%#s', 'abc')) call assert_equal('abc', printf('%#S', 'abc')) + call assert_equal(' 0173', printf('%#6o', 123)) + call assert_equal(' 00173', printf('%#6.5o', 123)) + call assert_equal(' 0173', printf('%#6.2o', 123)) + call assert_equal(' 0173', printf('%#6.2o', 123)) + call assert_equal('0173', printf('%#2.2o', 123)) call assert_equal(' 00123', printf('%6.5d', 123)) call assert_equal(' 0007b', printf('%6.5x', 123)) @@ -189,24 +221,104 @@ function Test_printf_misc() endfunc function Test_printf_float() + call assert_equal('1.000000', printf('%f', 1)) call assert_equal('1.230000', printf('%f', 1.23)) call assert_equal('1.230000', printf('%F', 1.23)) - call assert_equal('1.23', printf('%g', 1.23)) - call assert_equal('1.23', printf('%G', 1.23)) + call assert_equal('9999999.9', printf('%g', 9999999.9)) + call assert_equal('9999999.9', printf('%G', 9999999.9)) + call assert_equal('1.00000001e7', printf('%.8g', 10000000.1)) + call assert_equal('1.00000001E7', printf('%.8G', 10000000.1)) call assert_equal('1.230000e+00', printf('%e', 1.23)) call assert_equal('1.230000E+00', printf('%E', 1.23)) call assert_equal('1.200000e-02', printf('%e', 0.012)) call assert_equal('-1.200000e-02', printf('%e', -0.012)) - call assert_equal('1.2', printf('%.1f', 1.23)) - + call assert_equal('0.33', printf('%.2f', 1.0/3.0)) + call assert_equal(' 0.33', printf('%6.2f', 1.0/3.0)) + call assert_equal(' -0.33', printf('%6.2f', -1.0/3.0)) + call assert_equal('000.33', printf('%06.2f', 1.0/3.0)) + call assert_equal('-00.33', printf('%06.2f', -1.0/3.0)) + call assert_equal('-00.33', printf('%+06.2f', -1.0/3.0)) + call assert_equal('+00.33', printf('%+06.2f', 1.0/3.0)) + call assert_equal(' 00.33', printf('% 06.2f', 1.0/3.0)) + call assert_equal('000.33', printf('%06.2g', 1.0/3.0)) + call assert_equal('-00.33', printf('%06.2g', -1.0/3.0)) + call assert_equal('0.33', printf('%3.2f', 1.0/3.0)) + call assert_equal('003.33e-01', printf('%010.2e', 1.0/3.0)) + call assert_equal(' 03.33e-01', printf('% 010.2e', 1.0/3.0)) + call assert_equal('+03.33e-01', printf('%+010.2e', 1.0/3.0)) + call assert_equal('-03.33e-01', printf('%010.2e', -1.0/3.0)) + + " When precision is 0, the dot should be omitted. + call assert_equal(' 2', printf('%3.f', 7.0/3.0)) + call assert_equal(' 2', printf('%3.g', 7.0/3.0)) + call assert_equal(' 2e+00', printf('%7.e', 7.0/3.0)) + + " Float zero can be signed. + call assert_equal('+0.000000', printf('%+f', 0.0)) + call assert_equal('0.000000', printf('%f', 1.0/(1.0/0.0))) + call assert_equal('-0.000000', printf('%f', 1.0/(-1.0/0.0))) + call assert_equal('0.0', printf('%s', 1.0/(1.0/0.0))) + call assert_equal('-0.0', printf('%s', 1.0/(-1.0/0.0))) + call assert_equal('0.0', printf('%S', 1.0/(1.0/0.0))) + call assert_equal('-0.0', printf('%S', 1.0/(-1.0/0.0))) + + " Float infinity can be signed. call assert_equal('inf', printf('%f', 1.0/0.0)) - - " This prints inf but shouldn't it print -inf instead? - call assert_match('^-\?inf$', printf('%f', -1.0/0.0)) - - " This prints -nan but shouldn't it print nan instead? - call assert_match('^-\?nan$', printf('%f', sqrt(-1.0))) - call assert_match('^-\?nan$', printf('%f', 0.0/0.0)) + call assert_equal('-inf', printf('%f', -1.0/0.0)) + call assert_equal('inf', printf('%g', 1.0/0.0)) + call assert_equal('-inf', printf('%g', -1.0/0.0)) + call assert_equal('inf', printf('%e', 1.0/0.0)) + call assert_equal('-inf', printf('%e', -1.0/0.0)) + call assert_equal('INF', printf('%F', 1.0/0.0)) + call assert_equal('-INF', printf('%F', -1.0/0.0)) + call assert_equal('INF', printf('%E', 1.0/0.0)) + call assert_equal('-INF', printf('%E', -1.0/0.0)) + call assert_equal('INF', printf('%E', 1.0/0.0)) + call assert_equal('-INF', printf('%G', -1.0/0.0)) + call assert_equal('+inf', printf('%+f', 1.0/0.0)) + call assert_equal('-inf', printf('%+f', -1.0/0.0)) + call assert_equal(' inf', printf('% f', 1.0/0.0)) + call assert_equal(' inf', printf('%6f', 1.0/0.0)) + call assert_equal(' -inf', printf('%6f', -1.0/0.0)) + call assert_equal(' inf', printf('%6g', 1.0/0.0)) + call assert_equal(' -inf', printf('%6g', -1.0/0.0)) + call assert_equal(' +inf', printf('%+6f', 1.0/0.0)) + call assert_equal(' inf', printf('% 6f', 1.0/0.0)) + call assert_equal(' +inf', printf('%+06f', 1.0/0.0)) + call assert_equal('inf ', printf('%-6f', 1.0/0.0)) + call assert_equal('-inf ', printf('%-6f', -1.0/0.0)) + call assert_equal('+inf ', printf('%-+6f', 1.0/0.0)) + call assert_equal(' inf ', printf('%- 6f', 1.0/0.0)) + call assert_equal('-INF ', printf('%-6F', -1.0/0.0)) + call assert_equal('+INF ', printf('%-+6F', 1.0/0.0)) + call assert_equal(' INF ', printf('%- 6F', 1.0/0.0)) + call assert_equal('INF ', printf('%-6G', 1.0/0.0)) + call assert_equal('-INF ', printf('%-6G', -1.0/0.0)) + call assert_equal('INF ', printf('%-6E', 1.0/0.0)) + call assert_equal('-INF ', printf('%-6E', -1.0/0.0)) + call assert_equal("str2float('inf')", printf('%s', 1.0/0.0)) + call assert_equal("-str2float('inf')", printf('%s', -1.0/0.0)) + + " Float nan (not a number) has no sign. + call assert_equal('nan', printf('%f', sqrt(-1.0))) + call assert_equal('nan', printf('%f', 0.0/0.0)) + call assert_equal('nan', printf('%f', -0.0/0.0)) + call assert_equal('nan', printf('%g', 0.0/0.0)) + call assert_equal('nan', printf('%e', 0.0/0.0)) + call assert_equal('NAN', printf('%F', 0.0/0.0)) + call assert_equal('NAN', printf('%G', 0.0/0.0)) + call assert_equal('NAN', printf('%E', 0.0/0.0)) + call assert_equal('NAN', printf('%F', -0.0/0.0)) + call assert_equal('NAN', printf('%G', -0.0/0.0)) + call assert_equal('NAN', printf('%E', -0.0/0.0)) + call assert_equal(' nan', printf('%6f', 0.0/0.0)) + call assert_equal(' nan', printf('%06f', 0.0/0.0)) + call assert_equal('nan ', printf('%-6f', 0.0/0.0)) + call assert_equal('nan ', printf('%- 6f', 0.0/0.0)) + call assert_equal("str2float('nan')", printf('%s', 0.0/0.0)) + call assert_equal("str2float('nan')", printf('%s', -0.0/0.0)) + call assert_equal("str2float('nan')", printf('%S', 0.0/0.0)) + call assert_equal("str2float('nan')", printf('%S', -0.0/0.0)) call assert_fails('echo printf("%f", "a")', 'E807:') endfunc diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim index 3eca99bd99..7a5cdabaa3 100644 --- a/src/nvim/testdir/test_gn.vim +++ b/src/nvim/testdir/test_gn.vim @@ -90,4 +90,4 @@ func Test_gn_command() sil! %d _ endfu -" vim: tabstop=2 shiftwidth=0 expandtab +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim index cc1c81c7f6..1ca0f722cf 100644 --- a/src/nvim/testdir/test_help_tagjump.vim +++ b/src/nvim/testdir/test_help_tagjump.vim @@ -162,4 +162,36 @@ func Test_help_complete() endtry endfunc -" vim: et sw=2: +func Test_help_respect_current_file_lang() + try + let list = [] + call s:doc_config_setup() + + if has('multi_lang') + function s:check_help_file_ext(help_keyword, ext) + exec 'help ' . a:help_keyword + call assert_equal(a:ext, expand('%:e')) + call feedkeys("\<C-]>", 'tx') + call assert_equal(a:ext, expand('%:e')) + pop + helpclose + endfunc + + set rtp+=Xdir1/doc-ab + set rtp+=Xdir1/doc-ja + + set helplang=ab + call s:check_help_file_ext('test-char', 'abx') + call s:check_help_file_ext('test-char@ja', 'jax') + set helplang=ab,ja + call s:check_help_file_ext('test-char@ja', 'jax') + call s:check_help_file_ext('test-char@en', 'txt') + endif + catch + call assert_exception('X') + finally + call s:doc_config_teardown() + endtry +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_increment.vim b/src/nvim/testdir/test_increment.vim index e53b569716..8bfd95d810 100644 --- a/src/nvim/testdir/test_increment.vim +++ b/src/nvim/testdir/test_increment.vim @@ -757,4 +757,4 @@ func Test_normal_increment_03() endfunc -" vim: tabstop=2 shiftwidth=2 expandtab +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_increment_dbcs.vim b/src/nvim/testdir/test_increment_dbcs.vim index ee286a0a24..474a16feeb 100644 --- a/src/nvim/testdir/test_increment_dbcs.vim +++ b/src/nvim/testdir/test_increment_dbcs.vim @@ -26,4 +26,4 @@ func Test_increment_dbcs_1() call assert_equal([0, 1, 13, 0], getpos('.')) endfunc -" vim: shiftwidth=2 expandtab +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_match.vim b/src/nvim/testdir/test_match.vim index 7748dee87f..9398ef2f27 100644 --- a/src/nvim/testdir/test_match.vim +++ b/src/nvim/testdir/test_match.vim @@ -231,4 +231,4 @@ func Test_matchaddpos_using_negative_priority() set hlsearch& endfunc -" vim: et ts=2 sw=2 +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim new file mode 100644 index 0000000000..a22dca35cc --- /dev/null +++ b/src/nvim/testdir/test_normal.vim @@ -0,0 +1,2285 @@ +" Test for various Normal mode commands + +func! Setup_NewWindow() + 10new + call setline(1, range(1,100)) +endfunc + +func! MyFormatExpr() + " Adds '->$' at lines having numbers followed by trailing whitespace + for ln in range(v:lnum, v:lnum+v:count-1) + let line = getline(ln) + if getline(ln) =~# '\d\s\+$' + call setline(ln, substitute(line, '\s\+$', '', '') . '->$') + endif + endfor +endfunc + +func! CountSpaces(type, ...) + " for testing operatorfunc + " will count the number of spaces + " and return the result in g:a + let sel_save = &selection + let &selection = "inclusive" + let reg_save = @@ + + if a:0 " Invoked from Visual mode, use gv command. + silent exe "normal! gvy" + elseif a:type == 'line' + silent exe "normal! '[V']y" + else + silent exe "normal! `[v`]y" + endif + let g:a=strlen(substitute(@@, '[^ ]', '', 'g')) + let &selection = sel_save + let @@ = reg_save +endfunc + +func! OpfuncDummy(type, ...) + " for testing operatorfunc + let g:opt=&linebreak + + if a:0 " Invoked from Visual mode, use gv command. + silent exe "normal! gvy" + elseif a:type == 'line' + silent exe "normal! '[V']y" + else + silent exe "normal! `[v`]y" + endif + " Create a new dummy window + new + let g:bufnr=bufnr('%') +endfunc + +fun! Test_normal00_optrans() + new + call append(0, ['1 This is a simple test: abcd', '2 This is the second line', '3 this is the third line']) + 1 + exe "norm! Sfoobar\<esc>" + call assert_equal(['foobar', '2 This is the second line', '3 this is the third line', ''], getline(1,'$')) + 2 + exe "norm! $vbsone" + call assert_equal(['foobar', '2 This is the second one', '3 this is the third line', ''], getline(1,'$')) + norm! VS Second line here + call assert_equal(['foobar', ' Second line here', '3 this is the third line', ''], getline(1, '$')) + %d + call append(0, ['4 This is a simple test: abcd', '5 This is the second line', '6 this is the third line']) + call append(0, ['1 This is a simple test: abcd', '2 This is the second line', '3 this is the third line']) + + 1 + norm! 2D + call assert_equal(['3 this is the third line', '4 This is a simple test: abcd', '5 This is the second line', '6 this is the third line', ''], getline(1,'$')) + " Nvim: no "#" flag in 'cpoptions'. + " set cpo+=# + " norm! 4D + " call assert_equal(['', '4 This is a simple test: abcd', '5 This is the second line', '6 this is the third line', ''], getline(1,'$')) + + " clean up + set cpo-=# + bw! +endfunc + +func! Test_normal01_keymodel() + throw "skipped: Nvim regression: 'keymodel'" + call Setup_NewWindow() + " Test 1: depending on 'keymodel' <s-down> does something different + 50 + call feedkeys("V\<S-Up>y", 'tx') + call assert_equal(['47', '48', '49', '50'], getline("'<", "'>")) + set keymodel=startsel + 50 + call feedkeys("V\<S-Up>y", 'tx') + call assert_equal(['49', '50'], getline("'<", "'>")) + " Start visual mode when keymodel = startsel + 50 + call feedkeys("\<S-Up>y", 'tx') + call assert_equal(['49', '5'], getreg(0, 0, 1)) + " Do not start visual mode when keymodel= + set keymodel= + 50 + call feedkeys("\<S-Up>y$", 'tx') + call assert_equal(['42'], getreg(0, 0, 1)) + " Stop visual mode when keymodel=stopsel + set keymodel=stopsel + 50 + call feedkeys("Vkk\<Up>yy", 'tx') + call assert_equal(['47'], getreg(0, 0, 1)) + + set keymodel= + 50 + call feedkeys("Vkk\<Up>yy", 'tx') + call assert_equal(['47', '48', '49', '50'], getreg(0, 0, 1)) + + " clean up + bw! +endfunc + +func! Test_normal02_selectmode() + " some basic select mode tests + call Setup_NewWindow() + 50 + norm! gHy + call assert_equal('y51', getline('.')) + call setline(1, range(1,100)) + 50 + exe ":norm! V9jo\<c-g>y" + call assert_equal('y60', getline('.')) + " clean up + bw! +endfunc + +func! Test_normal02_selectmode2() + " some basic select mode tests + call Setup_NewWindow() + 50 + call feedkeys(":set im\n\<c-o>gHc\<c-o>:set noim\n", 'tx') + call assert_equal('c51', getline('.')) + " clean up + bw! +endfunc + +func! Test_normal03_join() + " basic join test + call Setup_NewWindow() + 50 + norm! VJ + call assert_equal('50 51', getline('.')) + $ + norm! J + call assert_equal('100', getline('.')) + $ + norm! V9-gJ + call assert_equal('919293949596979899100', getline('.')) + call setline(1, range(1,100)) + $ + :j 10 + call assert_equal('100', getline('.')) + " clean up + bw! +endfunc + +func! Test_normal04_filter() + " basic filter test + " only test on non windows platform + if has('win32') + return + endif + call Setup_NewWindow() + 1 + call feedkeys("!!sed -e 's/^/| /'\n", 'tx') + call assert_equal('| 1', getline('.')) + 90 + :sil :!echo one + call feedkeys('.', 'tx') + call assert_equal('| 90', getline('.')) + 95 + set cpo+=! + " 2 <CR>, 1: for executing the command, + " 2: clear hit-enter-prompt + call feedkeys("!!\n", 'tx') + call feedkeys(":!echo one\n\n", 'tx') + call feedkeys(".", 'tx') + call assert_equal('one', getline('.')) + set cpo-=! + bw! +endfunc + +func! Test_normal05_formatexpr() + " basic formatexpr test + call Setup_NewWindow() + %d_ + call setline(1, ['here: 1 ', '2', 'here: 3 ', '4', 'not here: ']) + 1 + set formatexpr=MyFormatExpr() + norm! gqG + call assert_equal(['here: 1->$', '2', 'here: 3->$', '4', 'not here: '], getline(1,'$')) + set formatexpr= + bw! +endfunc + +func Test_normal05_formatexpr_newbuf() + " Edit another buffer in the 'formatexpr' function + new + func! Format() + edit another + endfunc + set formatexpr=Format() + norm gqG + bw! + set formatexpr= +endfunc + +func Test_normal05_formatexpr_setopt() + " Change the 'formatexpr' value in the function + new + func! Format() + set formatexpr= + endfunc + set formatexpr=Format() + norm gqG + bw! + set formatexpr= +endfunc + +func! Test_normal06_formatprg() + " basic test for formatprg + " only test on non windows platform + if has('win32') + return + endif + + " uses sed to number non-empty lines + call writefile(['#!/bin/sh', 'sed ''/./=''|sed ''/./{', 'N', 's/\n/ /', '}'''], 'Xsed_format.sh') + call system('chmod +x ./Xsed_format.sh') + let text = ['a', '', 'c', '', ' ', 'd', 'e'] + let expected = ['1 a', '', '3 c', '', '5 ', '6 d', '7 e'] + + 10new + call setline(1, text) + set formatprg=./Xsed_format.sh + norm! gggqG + call assert_equal(expected, getline(1, '$')) + bw! + + 10new + call setline(1, text) + set formatprg=donothing + setlocal formatprg=./Xsed_format.sh + norm! gggqG + call assert_equal(expected, getline(1, '$')) + bw! + + " clean up + set formatprg= + setlocal formatprg= + call delete('Xsed_format.sh') +endfunc + +func! Test_normal07_internalfmt() + " basic test for internal formmatter to textwidth of 12 + let list=range(1,11) + call map(list, 'v:val." "') + 10new + call setline(1, list) + set tw=12 + norm! gggqG + call assert_equal(['1 2 3', '4 5 6', '7 8 9', '10 11 '], getline(1, '$')) + " clean up + set tw=0 + bw! +endfunc + +func! Test_normal08_fold() + " basic tests for foldopen/folddelete + if !has("folding") + return + endif + call Setup_NewWindow() + 50 + setl foldenable fdm=marker + " First fold + norm! V4jzf + " check that folds have been created + call assert_equal(['50/*{{{*/', '51', '52', '53', '54/*}}}*/'], getline(50,54)) + " Second fold + 46 + norm! V10jzf + " check that folds have been created + call assert_equal('46/*{{{*/', getline(46)) + call assert_equal('60/*}}}*/', getline(60)) + norm! k + call assert_equal('45', getline('.')) + norm! j + call assert_equal('46/*{{{*/', getline('.')) + norm! j + call assert_equal('61', getline('.')) + norm! k + " open a fold + norm! Vzo + norm! k + call assert_equal('45', getline('.')) + norm! j + call assert_equal('46/*{{{*/', getline('.')) + norm! j + call assert_equal('47', getline('.')) + norm! k + norm! zcVzO + call assert_equal('46/*{{{*/', getline('.')) + norm! j + call assert_equal('47', getline('.')) + norm! j + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49', getline('.')) + norm! j + call assert_equal('50/*{{{*/', getline('.')) + norm! j + call assert_equal('51', getline('.')) + " delete folds + :46 + " collapse fold + norm! V14jzC + " delete all folds recursively + norm! VzD + call assert_equal(['46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60'], getline(46,60)) + + " clean up + setl nofoldenable fdm=marker + bw! +endfunc + +func! Test_normal09_operatorfunc() + " Test operatorfunc + call Setup_NewWindow() + " Add some spaces for counting + 50,60s/$/ / + unlet! g:a + let g:a=0 + nmap <buffer><silent> ,, :set opfunc=CountSpaces<CR>g@ + vmap <buffer><silent> ,, :<C-U>call CountSpaces(visualmode(), 1)<CR> + 50 + norm V2j,, + call assert_equal(6, g:a) + norm V,, + call assert_equal(2, g:a) + norm ,,l + call assert_equal(0, g:a) + 50 + exe "norm 0\<c-v>10j2l,," + call assert_equal(11, g:a) + 50 + norm V10j,, + call assert_equal(22, g:a) + + " clean up + unmap <buffer> ,, + set opfunc= + unlet! g:a + bw! +endfunc + +func! Test_normal09a_operatorfunc() + " Test operatorfunc + call Setup_NewWindow() + " Add some spaces for counting + 50,60s/$/ / + unlet! g:opt + set linebreak + nmap <buffer><silent> ,, :set opfunc=OpfuncDummy<CR>g@ + 50 + norm ,,j + exe "bd!" g:bufnr + call assert_true(&linebreak) + call assert_equal(g:opt, &linebreak) + set nolinebreak + norm ,,j + exe "bd!" g:bufnr + call assert_false(&linebreak) + call assert_equal(g:opt, &linebreak) + + " clean up + unmap <buffer> ,, + set opfunc= + bw! + unlet! g:opt +endfunc + +func! Test_normal10_expand() + " Test for expand() + 10new + call setline(1, ['1', 'ifooar,,cbar']) + 2 + norm! $ + let a=expand('<cword>') + let b=expand('<cWORD>') + call assert_equal('cbar', a) + call assert_equal('ifooar,,cbar', b) + " clean up + bw! +endfunc + +func! Test_normal11_showcmd() + " test for 'showcmd' + 10new + exe "norm! ofoobar\<esc>" + call assert_equal(2, line('$')) + set showcmd + exe "norm! ofoobar2\<esc>" + call assert_equal(3, line('$')) + exe "norm! VAfoobar3\<esc>" + call assert_equal(3, line('$')) + exe "norm! 0d3\<del>2l" + call assert_equal('obar2foobar3', getline('.')) + bw! +endfunc + +func! Test_normal12_nv_error() + " Test for nv_error + 10new + call setline(1, range(1,5)) + " should not do anything, just beep + exe "norm! <c-k>" + call assert_equal(map(range(1,5), 'string(v:val)'), getline(1,'$')) + bw! +endfunc + +func! Test_normal13_help() + " Test for F1 + call assert_equal(1, winnr()) + call feedkeys("\<f1>", 'txi') + call assert_match('help\.txt', bufname('%')) + call assert_equal(2, winnr('$')) + bw! +endfunc + +func! Test_normal14_page() + " basic test for Ctrl-F and Ctrl-B + call Setup_NewWindow() + exe "norm! \<c-f>" + call assert_equal('9', getline('.')) + exe "norm! 2\<c-f>" + call assert_equal('25', getline('.')) + exe "norm! 2\<c-b>" + call assert_equal('18', getline('.')) + 1 + set scrolloff=5 + exe "norm! 2\<c-f>" + call assert_equal('21', getline('.')) + exe "norm! \<c-b>" + call assert_equal('13', getline('.')) + 1 + set scrolloff=99 + exe "norm! \<c-f>" + call assert_equal('13', getline('.')) + set scrolloff=0 + 100 + exe "norm! $\<c-b>" + call assert_equal('92', getline('.')) + call assert_equal([0, 92, 1, 0, 1], getcurpos()) + 100 + set nostartofline + exe "norm! $\<c-b>" + call assert_equal('92', getline('.')) + call assert_equal([0, 92, 2, 0, 2147483647], getcurpos()) + " cleanup + set startofline + bw! +endfunc + +func! Test_normal14_page_eol() + 10new + norm oxxxxxxx + exe "norm 2\<c-f>" + " check with valgrind that cursor is put back in column 1 + exe "norm 2\<c-b>" + bw! +endfunc + +func! Test_normal15_z_scroll_vert() + " basic test for z commands that scroll the window + call Setup_NewWindow() + 100 + norm! >> + " Test for z<cr> + exe "norm! z\<cr>" + call assert_equal(' 100', getline('.')) + call assert_equal(100, winsaveview()['topline']) + call assert_equal([0, 100, 2, 0, 9], getcurpos()) + + " Test for zt + 21 + norm! >>0zt + call assert_equal(' 21', getline('.')) + call assert_equal(21, winsaveview()['topline']) + call assert_equal([0, 21, 1, 0, 8], getcurpos()) + + " Test for zb + 30 + norm! >>$ztzb + call assert_equal(' 30', getline('.')) + call assert_equal(30, winsaveview()['topline']+winheight(0)-1) + call assert_equal([0, 30, 3, 0, 2147483647], getcurpos()) + + " Test for z- + 1 + 30 + norm! 0z- + call assert_equal(' 30', getline('.')) + call assert_equal(30, winsaveview()['topline']+winheight(0)-1) + call assert_equal([0, 30, 2, 0, 9], getcurpos()) + + " Test for z{height}<cr> + call assert_equal(10, winheight(0)) + exe "norm! z12\<cr>" + call assert_equal(12, winheight(0)) + exe "norm! z10\<cr>" + call assert_equal(10, winheight(0)) + + " Test for z. + 1 + 21 + norm! 0z. + call assert_equal(' 21', getline('.')) + call assert_equal(17, winsaveview()['topline']) + call assert_equal([0, 21, 2, 0, 9], getcurpos()) + + " Test for zz + 1 + 21 + norm! 0zz + call assert_equal(' 21', getline('.')) + call assert_equal(17, winsaveview()['topline']) + call assert_equal([0, 21, 1, 0, 8], getcurpos()) + + " Test for z+ + 11 + norm! zt + norm! z+ + call assert_equal(' 21', getline('.')) + call assert_equal(21, winsaveview()['topline']) + call assert_equal([0, 21, 2, 0, 9], getcurpos()) + + " Test for [count]z+ + 1 + norm! 21z+ + call assert_equal(' 21', getline('.')) + call assert_equal(21, winsaveview()['topline']) + call assert_equal([0, 21, 2, 0, 9], getcurpos()) + + " Test for z^ + norm! 22z+0 + norm! z^ + call assert_equal(' 21', getline('.')) + call assert_equal(12, winsaveview()['topline']) + call assert_equal([0, 21, 2, 0, 9], getcurpos()) + + " Test for [count]z^ + 1 + norm! 30z^ + call assert_equal(' 21', getline('.')) + call assert_equal(12, winsaveview()['topline']) + call assert_equal([0, 21, 2, 0, 9], getcurpos()) + + " cleanup + bw! +endfunc + +func! Test_normal16_z_scroll_hor() + " basic test for z commands that scroll the window + 10new + 15vsp + set nowrap listchars= + let lineA='abcdefghijklmnopqrstuvwxyz' + let lineB='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + $put =lineA + $put =lineB + 1d + + " Test for zl + 1 + norm! 5zl + call assert_equal(lineA, getline('.')) + call assert_equal(6, col('.')) + call assert_equal(5, winsaveview()['leftcol']) + norm! yl + call assert_equal('f', @0) + + " Test for zh + norm! 2zh + call assert_equal(lineA, getline('.')) + call assert_equal(6, col('.')) + norm! yl + call assert_equal('f', @0) + call assert_equal(3, winsaveview()['leftcol']) + + " Test for zL + norm! zL + call assert_equal(11, col('.')) + norm! yl + call assert_equal('k', @0) + call assert_equal(10, winsaveview()['leftcol']) + norm! 2zL + call assert_equal(25, col('.')) + norm! yl + call assert_equal('y', @0) + call assert_equal(24, winsaveview()['leftcol']) + + " Test for zH + norm! 2zH + call assert_equal(25, col('.')) + call assert_equal(10, winsaveview()['leftcol']) + norm! yl + call assert_equal('y', @0) + + " Test for zs + norm! $zs + call assert_equal(26, col('.')) + call assert_equal(25, winsaveview()['leftcol']) + norm! yl + call assert_equal('z', @0) + + " Test for ze + norm! ze + call assert_equal(26, col('.')) + call assert_equal(11, winsaveview()['leftcol']) + norm! yl + call assert_equal('z', @0) + + " cleanup + set wrap listchars=eol:$ + bw! +endfunc + +func! Test_normal17_z_scroll_hor2() + " basic test for z commands that scroll the window + " using 'sidescrolloff' setting + 10new + 20vsp + set nowrap listchars= sidescrolloff=5 + let lineA='abcdefghijklmnopqrstuvwxyz' + let lineB='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + $put =lineA + $put =lineB + 1d + + " Test for zl + 1 + norm! 5zl + call assert_equal(lineA, getline('.')) + call assert_equal(11, col('.')) + call assert_equal(5, winsaveview()['leftcol']) + norm! yl + call assert_equal('k', @0) + + " Test for zh + norm! 2zh + call assert_equal(lineA, getline('.')) + call assert_equal(11, col('.')) + norm! yl + call assert_equal('k', @0) + call assert_equal(3, winsaveview()['leftcol']) + + " Test for zL + norm! 0zL + call assert_equal(16, col('.')) + norm! yl + call assert_equal('p', @0) + call assert_equal(10, winsaveview()['leftcol']) + norm! 2zL + call assert_equal(26, col('.')) + norm! yl + call assert_equal('z', @0) + call assert_equal(15, winsaveview()['leftcol']) + + " Test for zH + norm! 2zH + call assert_equal(15, col('.')) + call assert_equal(0, winsaveview()['leftcol']) + norm! yl + call assert_equal('o', @0) + + " Test for zs + norm! $zs + call assert_equal(26, col('.')) + call assert_equal(20, winsaveview()['leftcol']) + norm! yl + call assert_equal('z', @0) + + " Test for ze + norm! ze + call assert_equal(26, col('.')) + call assert_equal(11, winsaveview()['leftcol']) + norm! yl + call assert_equal('z', @0) + + " cleanup + set wrap listchars=eol:$ sidescrolloff=0 + bw! +endfunc + +func! Test_normal18_z_fold() + " basic tests for foldopen/folddelete + if !has("folding") + return + endif + call Setup_NewWindow() + 50 + setl foldenable fdm=marker foldlevel=5 + + " Test for zF + " First fold + norm! 4zF + " check that folds have been created + call assert_equal(['50/*{{{*/', '51', '52', '53/*}}}*/'], getline(50,53)) + + " Test for zd + 51 + norm! 2zF + call assert_equal(2, foldlevel('.')) + norm! kzd + call assert_equal(['50', '51/*{{{*/', '52/*}}}*/', '53'], getline(50,53)) + norm! j + call assert_equal(1, foldlevel('.')) + + " Test for zD + " also deletes partially selected folds recursively + 51 + norm! zF + call assert_equal(2, foldlevel('.')) + norm! kV2jzD + call assert_equal(['50', '51', '52', '53'], getline(50,53)) + + " Test for zE + 85 + norm! 4zF + 86 + norm! 2zF + 90 + norm! 4zF + call assert_equal(['85/*{{{*/', '86/*{{{*/', '87/*}}}*/', '88/*}}}*/', '89', '90/*{{{*/', '91', '92', '93/*}}}*/'], getline(85,93)) + norm! zE + call assert_equal(['85', '86', '87', '88', '89', '90', '91', '92', '93'], getline(85,93)) + + " Test for zn + 50 + set foldlevel=0 + norm! 2zF + norm! zn + norm! k + call assert_equal('49', getline('.')) + norm! j + call assert_equal('50/*{{{*/', getline('.')) + norm! j + call assert_equal('51/*}}}*/', getline('.')) + norm! j + call assert_equal('52', getline('.')) + call assert_equal(0, &foldenable) + + " Test for zN + 49 + norm! zN + call assert_equal('49', getline('.')) + norm! j + call assert_equal('50/*{{{*/', getline('.')) + norm! j + call assert_equal('52', getline('.')) + call assert_equal(1, &foldenable) + + " Test for zi + norm! zi + call assert_equal(0, &foldenable) + norm! zi + call assert_equal(1, &foldenable) + norm! zi + call assert_equal(0, &foldenable) + norm! zi + call assert_equal(1, &foldenable) + + " Test for za + 50 + norm! za + norm! k + call assert_equal('49', getline('.')) + norm! j + call assert_equal('50/*{{{*/', getline('.')) + norm! j + call assert_equal('51/*}}}*/', getline('.')) + norm! j + call assert_equal('52', getline('.')) + 50 + norm! za + norm! k + call assert_equal('49', getline('.')) + norm! j + call assert_equal('50/*{{{*/', getline('.')) + norm! j + call assert_equal('52', getline('.')) + + 49 + norm! 5zF + norm! k + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('55', getline('.')) + 49 + norm! za + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('50/*{{{*/', getline('.')) + norm! j + call assert_equal('52', getline('.')) + set nofoldenable + " close fold and set foldenable + norm! za + call assert_equal(1, &foldenable) + + 50 + " have to use {count}za to open all folds and make the cursor visible + norm! 2za + norm! 2k + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('50/*{{{*/', getline('.')) + norm! j + call assert_equal('51/*}}}*/', getline('.')) + norm! j + call assert_equal('52', getline('.')) + + " Test for zA + 49 + set foldlevel=0 + 50 + norm! zA + norm! 2k + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('50/*{{{*/', getline('.')) + norm! j + call assert_equal('51/*}}}*/', getline('.')) + norm! j + call assert_equal('52', getline('.')) + + " zA on a opened fold when foldenale is not set + 50 + set nofoldenable + norm! zA + call assert_equal(1, &foldenable) + norm! k + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('55', getline('.')) + + " Test for zc + norm! zE + 50 + norm! 2zF + 49 + norm! 5zF + set nofoldenable + 50 + " There most likely is a bug somewhere: + " https://groups.google.com/d/msg/vim_dev/v2EkfJ_KQjI/u-Cvv94uCAAJ + " TODO: Should this only close the inner most fold or both folds? + norm! zc + call assert_equal(1, &foldenable) + norm! k + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('55', getline('.')) + set nofoldenable + 50 + norm! Vjzc + norm! k + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('55', getline('.')) + + " Test for zC + set nofoldenable + 50 + norm! zCk + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('55', getline('.')) + + " Test for zx + " 1) close folds at line 49-54 + set nofoldenable + 48 + norm! zx + call assert_equal(1, &foldenable) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('55', getline('.')) + + " 2) do not close fold under curser + 51 + set nofoldenable + norm! zx + call assert_equal(1, &foldenable) + norm! 3k + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('50/*{{{*/', getline('.')) + norm! j + call assert_equal('51/*}}}*/', getline('.')) + norm! j + call assert_equal('52', getline('.')) + norm! j + call assert_equal('53', getline('.')) + norm! j + call assert_equal('54/*}}}*/', getline('.')) + norm! j + call assert_equal('55', getline('.')) + + " 3) close one level of folds + 48 + set nofoldenable + set foldlevel=1 + norm! zx + call assert_equal(1, &foldenable) + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('50/*{{{*/', getline('.')) + norm! j + call assert_equal('52', getline('.')) + norm! j + call assert_equal('53', getline('.')) + norm! j + call assert_equal('54/*}}}*/', getline('.')) + norm! j + call assert_equal('55', getline('.')) + + " Test for zX + " Close all folds + set foldlevel=0 nofoldenable + 50 + norm! zX + call assert_equal(1, &foldenable) + norm! k + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('55', getline('.')) + + " Test for zm + 50 + set nofoldenable foldlevel=2 + norm! zm + call assert_equal(1, &foldenable) + call assert_equal(1, &foldlevel) + norm! zm + call assert_equal(0, &foldlevel) + norm! zm + call assert_equal(0, &foldlevel) + norm! k + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('55', getline('.')) + + " Test for zM + 48 + set nofoldenable foldlevel=99 + norm! zM + call assert_equal(1, &foldenable) + call assert_equal(0, &foldlevel) + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('55', getline('.')) + + " Test for zr + 48 + set nofoldenable foldlevel=0 + norm! zr + call assert_equal(0, &foldenable) + call assert_equal(1, &foldlevel) + set foldlevel=0 foldenable + norm! zr + call assert_equal(1, &foldenable) + call assert_equal(1, &foldlevel) + norm! zr + call assert_equal(2, &foldlevel) + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('50/*{{{*/', getline('.')) + norm! j + call assert_equal('51/*}}}*/', getline('.')) + norm! j + call assert_equal('52', getline('.')) + + " Test for zR + 48 + set nofoldenable foldlevel=0 + norm! zR + call assert_equal(0, &foldenable) + call assert_equal(2, &foldlevel) + set foldenable foldlevel=0 + norm! zR + call assert_equal(1, &foldenable) + call assert_equal(2, &foldlevel) + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('50/*{{{*/', getline('.')) + norm! j + call assert_equal('51/*}}}*/', getline('.')) + norm! j + call assert_equal('52', getline('.')) + call append(50, ['a /*{{{*/', 'b /*}}}*/']) + 48 + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('50/*{{{*/', getline('.')) + norm! j + call assert_equal('a /*{{{*/', getline('.')) + norm! j + call assert_equal('51/*}}}*/', getline('.')) + norm! j + call assert_equal('52', getline('.')) + 48 + norm! zR + call assert_equal(1, &foldenable) + call assert_equal(3, &foldlevel) + call assert_equal('48', getline('.')) + norm! j + call assert_equal('49/*{{{*/', getline('.')) + norm! j + call assert_equal('50/*{{{*/', getline('.')) + norm! j + call assert_equal('a /*{{{*/', getline('.')) + norm! j + call assert_equal('b /*}}}*/', getline('.')) + norm! j + call assert_equal('51/*}}}*/', getline('.')) + norm! j + call assert_equal('52', getline('.')) + + " clean up + setl nofoldenable fdm=marker foldlevel=0 + bw! +endfunc + +func! Test_normal19_z_spell() + throw "skipped: Nvim 'spell' requires download" + if !has("spell") || !has('syntax') + return + endif + new + call append(0, ['1 good', '2 goood', '3 goood']) + set spell spellfile=./Xspellfile.add spelllang=en + let oldlang=v:lang + lang C + + " Test for zg + 1 + norm! ]s + call assert_equal('2 goood', getline('.')) + norm! zg + 1 + let a=execute('unsilent :norm! ]s') + call assert_equal('1 good', getline('.')) + call assert_equal('search hit BOTTOM, continuing at TOP', a[1:]) + let cnt=readfile('./Xspellfile.add') + call assert_equal('goood', cnt[0]) + + " Test for zw + 2 + norm! $zw + 1 + norm! ]s + call assert_equal('2 goood', getline('.')) + let cnt=readfile('./Xspellfile.add') + call assert_equal('#oood', cnt[0]) + call assert_equal('goood/!', cnt[1]) + + " Test for zg in visual mode + let a=execute('unsilent :norm! V$zg') + call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:]) + 1 + norm! ]s + call assert_equal('3 goood', getline('.')) + let cnt=readfile('./Xspellfile.add') + call assert_equal('2 goood', cnt[2]) + " Remove "2 good" from spellfile + 2 + let a=execute('unsilent norm! V$zw') + call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:]) + let cnt=readfile('./Xspellfile.add') + call assert_equal('2 goood/!', cnt[3]) + + " Test for zG + let a=execute('unsilent norm! V$zG') + call assert_match("Word '2 goood' added to .*", a) + let fname=matchstr(a, 'to\s\+\zs\f\+$') + let cnt=readfile(fname) + call assert_equal('2 goood', cnt[0]) + + " Test for zW + let a=execute('unsilent norm! V$zW') + call assert_match("Word '2 goood' added to .*", a) + let cnt=readfile(fname) + call assert_equal('# goood', cnt[0]) + call assert_equal('2 goood/!', cnt[1]) + + " Test for zuW + let a=execute('unsilent norm! V$zuW') + call assert_match("Word '2 goood' removed from .*", a) + let cnt=readfile(fname) + call assert_equal('# goood', cnt[0]) + call assert_equal('# goood/!', cnt[1]) + + " Test for zuG + let a=execute('unsilent norm! $zG') + call assert_match("Word 'goood' added to .*", a) + let cnt=readfile(fname) + call assert_equal('# goood', cnt[0]) + call assert_equal('# goood/!', cnt[1]) + call assert_equal('goood', cnt[2]) + let a=execute('unsilent norm! $zuG') + let cnt=readfile(fname) + call assert_match("Word 'goood' removed from .*", a) + call assert_equal('# goood', cnt[0]) + call assert_equal('# goood/!', cnt[1]) + call assert_equal('#oood', cnt[2]) + " word not found in wordlist + let a=execute('unsilent norm! V$zuG') + let cnt=readfile(fname) + call assert_match("", a) + call assert_equal('# goood', cnt[0]) + call assert_equal('# goood/!', cnt[1]) + call assert_equal('#oood', cnt[2]) + + " Test for zug + call delete('./Xspellfile.add') + 2 + let a=execute('unsilent norm! $zg') + let cnt=readfile('./Xspellfile.add') + call assert_equal('goood', cnt[0]) + let a=execute('unsilent norm! $zug') + call assert_match("Word 'goood' removed from \./Xspellfile.add", a) + let cnt=readfile('./Xspellfile.add') + call assert_equal('#oood', cnt[0]) + " word not in wordlist + let a=execute('unsilent norm! V$zug') + call assert_match('', a) + let cnt=readfile('./Xspellfile.add') + call assert_equal('#oood', cnt[0]) + + " Test for zuw + call delete('./Xspellfile.add') + 2 + let a=execute('unsilent norm! Vzw') + let cnt=readfile('./Xspellfile.add') + call assert_equal('2 goood/!', cnt[0]) + let a=execute('unsilent norm! Vzuw') + call assert_match("Word '2 goood' removed from \./Xspellfile.add", a) + let cnt=readfile('./Xspellfile.add') + call assert_equal('# goood/!', cnt[0]) + " word not in wordlist + let a=execute('unsilent norm! $zug') + call assert_match('', a) + let cnt=readfile('./Xspellfile.add') + call assert_equal('# goood/!', cnt[0]) + + " add second entry to spellfile setting + set spellfile=./Xspellfile.add,./Xspellfile2.add + call delete('./Xspellfile.add') + 2 + let a=execute('unsilent norm! $2zg') + let cnt=readfile('./Xspellfile2.add') + call assert_match("Word 'goood' added to ./Xspellfile2.add", a) + call assert_equal('goood', cnt[0]) + + " clean up + exe "lang" oldlang + call delete("./Xspellfile.add") + call delete("./Xspellfile2.add") + call delete("./Xspellfile.add.spl") + call delete("./Xspellfile2.add.spl") + + " zux -> no-op + 2 + norm! $zux + call assert_equal([], glob('Xspellfile.add',0,1)) + call assert_equal([], glob('Xspellfile2.add',0,1)) + + set spellfile= + bw! +endfunc + +func! Test_normal20_exmode() + if !has("unix") + " Reading from redirected file doesn't work on MS-Windows + return + endif + call writefile(['1a', 'foo', 'bar', '.', 'w! Xfile2', 'q!'], 'Xscript') + call writefile(['1', '2'], 'Xfile') + call system(v:progpath .' -e -s < Xscript Xfile') + let a=readfile('Xfile2') + call assert_equal(['1', 'foo', 'bar', '2'], a) + + " clean up + for file in ['Xfile', 'Xfile2', 'Xscript'] + call delete(file) + endfor + bw! +endfunc + +func! Test_normal21_nv_hat() + set hidden + new + " to many buffers opened already, will not work + "call assert_fails(":b#", 'E23') + "call assert_equal('', @#) + e Xfoobar + e Xfile2 + call feedkeys("\<c-^>", 't') + call assert_equal("Xfile2", fnamemodify(bufname('%'), ':t')) + call feedkeys("f\<c-^>", 't') + call assert_equal("Xfile2", fnamemodify(bufname('%'), ':t')) + " clean up + set nohidden + bw! +endfunc + +func! Test_normal22_zet() + " Test for ZZ + " let shell = &shell + " let &shell = 'sh' + call writefile(['1', '2'], 'Xfile') + let args = ' --headless -u NONE -N -U NONE -i NONE --noplugins' + call system(v:progpath . args . ' -c "%d" -c ":norm! ZZ" Xfile') + let a = readfile('Xfile') + call assert_equal([], a) + " Test for ZQ + call writefile(['1', '2'], 'Xfile') + call system(v:progpath . args . ' -c "%d" -c ":norm! ZQ" Xfile') + let a = readfile('Xfile') + call assert_equal(['1', '2'], a) + + " clean up + for file in ['Xfile'] + call delete(file) + endfor + " let &shell = shell +endfunc + +func! Test_normal23_K() + " Test for K command + new + call append(0, ['helphelp.txt', 'man', 'aa%bb', 'cc|dd']) + let k = &keywordprg + set keywordprg=:help + 1 + norm! VK + call assert_equal('helphelp.txt', fnamemodify(bufname('%'), ':t')) + call assert_equal('help', &ft) + call assert_match('\*helphelp.txt\*', getline('.')) + helpclose + norm! 0K + call assert_equal('helphelp.txt', fnamemodify(bufname('%'), ':t')) + call assert_equal('help', &ft) + call assert_match('Help on help files', getline('.')) + helpclose + + set keywordprg=:new + set iskeyword+=% + set iskeyword+=\| + 2 + norm! K + call assert_equal('man', fnamemodify(bufname('%'), ':t')) + bwipe! + 3 + norm! K + call assert_equal('aa%bb', fnamemodify(bufname('%'), ':t')) + bwipe! + if !has('win32') + 4 + norm! K + call assert_equal('cc|dd', fnamemodify(bufname('%'), ':t')) + bwipe! + endif + set iskeyword-=% + set iskeyword-=\| + + " Only expect "man" to work on Unix + if !has("unix") + let &keywordprg = k + bw! + return + endif + set keywordprg=man\ --pager=cat + " Test for using man + 2 + let a = execute('unsilent norm! K') + call assert_match("man --pager=cat 'man'", a) + + " clean up + let &keywordprg = k + bw! +endfunc + +func! Test_normal24_rot13() + " This test uses multi byte characters + if !has("multi_byte") + return + endif + " Testing for g?? g?g? + new + call append(0, 'abcdefghijklmnopqrstuvwxyzäüö') + 1 + norm! g?? + call assert_equal('nopqrstuvwxyzabcdefghijklmäüö', getline('.')) + norm! g?g? + call assert_equal('abcdefghijklmnopqrstuvwxyzäüö', getline('.')) + + " clean up + bw! +endfunc + +func! Test_normal25_tag() + " Testing for CTRL-] g CTRL-] g] + " CTRL-W g] CTRL-W CTRL-] CTRL-W g CTRL-] + h + " Test for CTRL-] + call search('\<x\>$') + exe "norm! \<c-]>" + call assert_equal("change.txt", fnamemodify(bufname('%'), ':t')) + norm! yiW + call assert_equal("*x*", @0) + exe ":norm \<c-o>" + + " Test for g_CTRL-] + call search('\<v_u\>$') + exe "norm! g\<c-]>" + call assert_equal("change.txt", fnamemodify(bufname('%'), ':t')) + norm! yiW + call assert_equal("*v_u*", @0) + exe ":norm \<c-o>" + + " Test for g] + call search('\<i_<Esc>$') + let a = execute(":norm! g]") + call assert_match('i_<Esc>.*insert.txt', a) + + if !empty(exepath('cscope')) && has('cscope') + " setting cscopetag changes how g] works + set cst + exe "norm! g]" + call assert_equal("insert.txt", fnamemodify(bufname('%'), ':t')) + norm! yiW + call assert_equal("*i_<Esc>*", @0) + exe ":norm \<c-o>" + " Test for CTRL-W g] + exe "norm! \<C-W>g]" + call assert_equal("insert.txt", fnamemodify(bufname('%'), ':t')) + norm! yiW + call assert_equal("*i_<Esc>*", @0) + call assert_equal(3, winnr('$')) + helpclose + set nocst + endif + + " Test for CTRL-W g] + let a = execute("norm! \<C-W>g]") + call assert_match('i_<Esc>.*insert.txt', a) + + " Test for CTRL-W CTRL-] + exe "norm! \<C-W>\<C-]>" + call assert_equal("insert.txt", fnamemodify(bufname('%'), ':t')) + norm! yiW + call assert_equal("*i_<Esc>*", @0) + call assert_equal(3, winnr('$')) + helpclose + + " Test for CTRL-W g CTRL-] + exe "norm! \<C-W>g\<C-]>" + call assert_equal("insert.txt", fnamemodify(bufname('%'), ':t')) + norm! yiW + call assert_equal("*i_<Esc>*", @0) + call assert_equal(3, winnr('$')) + helpclose + + " clean up + helpclose +endfunc + +func! Test_normal26_put() + " Test for ]p ]P [p and [P + new + call append(0, ['while read LINE', 'do', ' ((count++))', ' if [ $? -ne 0 ]; then', " echo 'Error writing file'", ' fi', 'done']) + 1 + /Error/y a + 2 + norm! "a]pj"a[p + call assert_equal(['do', "echo 'Error writing file'", " echo 'Error writing file'", ' ((count++))'], getline(2,5)) + 1 + /^\s\{4}/ + exe "norm! \"a]P3Eldt'" + exe "norm! j\"a[P2Eldt'" + call assert_equal([' if [ $? -ne 0 ]; then', " echo 'Error writing'", " echo 'Error'", " echo 'Error writing file'", ' fi'], getline(6,10)) + + " clean up + bw! +endfunc + +func! Test_normal27_bracket() + " Test for [' [` ]' ]` + call Setup_NewWindow() + 1,21s/.\+/ & b/ + 1 + norm! $ma + 5 + norm! $mb + 10 + norm! $mc + 15 + norm! $md + 20 + norm! $me + + " Test for [' + 9 + norm! 2[' + call assert_equal(' 1 b', getline('.')) + call assert_equal(1, line('.')) + call assert_equal(3, col('.')) + + " Test for ]' + norm! ]' + call assert_equal(' 5 b', getline('.')) + call assert_equal(5, line('.')) + call assert_equal(3, col('.')) + + " No mark after line 21, cursor moves to first non blank on current line + 21 + norm! $]' + call assert_equal(' 21 b', getline('.')) + call assert_equal(21, line('.')) + call assert_equal(3, col('.')) + + " Test for [` + norm! 2[` + call assert_equal(' 15 b', getline('.')) + call assert_equal(15, line('.')) + call assert_equal(8, col('.')) + + " Test for ]` + norm! ]` + call assert_equal(' 20 b', getline('.')) + call assert_equal(20, line('.')) + call assert_equal(8, col('.')) + + " clean up + bw! +endfunc + +func! Test_normal28_parenthesis() + " basic testing for ( and ) + new + call append(0, ['This is a test. With some sentences!', '', 'Even with a question? And one more. And no sentence here']) + + $ + norm! d( + call assert_equal(['This is a test. With some sentences!', '', 'Even with a question? And one more. ', ''], getline(1, '$')) + norm! 2d( + call assert_equal(['This is a test. With some sentences!', '', ' ', ''], getline(1, '$')) + 1 + norm! 0d) + call assert_equal(['With some sentences!', '', ' ', ''], getline(1, '$')) + + call append('$', ['This is a long sentence', '', 'spanning', 'over several lines. ']) + $ + norm! $d( + call assert_equal(['With some sentences!', '', ' ', '', 'This is a long sentence', ''], getline(1, '$')) + + " clean up + bw! +endfunc + +fun! Test_normal29_brace() + " basic test for { and } movements + let text= ['A paragraph begins after each empty line, and also at each of a set of', + \ 'paragraph macros, specified by the pairs of characters in the ''paragraphs''', + \ 'option. The default is "IPLPPPQPP TPHPLIPpLpItpplpipbp", which corresponds to', + \ 'the macros ".IP", ".LP", etc. (These are nroff macros, so the dot must be in', + \ 'the first column). A section boundary is also a paragraph boundary.', + \ 'Note that a blank line (only containing white space) is NOT a paragraph', + \ 'boundary.', + \ '', + \ '', + \ 'Also note that this does not include a ''{'' or ''}'' in the first column. When', + \ 'the ''{'' flag is in ''cpoptions'' then ''{'' in the first column is used as a', + \ 'paragraph boundary |posix|.', + \ '{', + \ 'This is no paragaraph', + \ 'unless the ''{'' is set', + \ 'in ''cpoptions''', + \ '}', + \ '.IP', + \ 'The nroff macros IP seperates a paragraph', + \ 'That means, it must be a ''.''', + \ 'followed by IP', + \ '.LPIt does not matter, if afterwards some', + \ 'more characters follow.', + \ '.SHAlso section boundaries from the nroff', + \ 'macros terminate a paragraph. That means', + \ 'a character like this:', + \ '.NH', + \ 'End of text here'] + new + call append(0, text) + 1 + norm! 0d2} + call assert_equal(['.IP', + \ 'The nroff macros IP seperates a paragraph', 'That means, it must be a ''.''', 'followed by IP', + \ '.LPIt does not matter, if afterwards some', 'more characters follow.', '.SHAlso section boundaries from the nroff', + \ 'macros terminate a paragraph. That means', 'a character like this:', '.NH', 'End of text here', ''], getline(1,'$')) + norm! 0d} + call assert_equal(['.LPIt does not matter, if afterwards some', 'more characters follow.', + \ '.SHAlso section boundaries from the nroff', 'macros terminate a paragraph. That means', + \ 'a character like this:', '.NH', 'End of text here', ''], getline(1, '$')) + $ + norm! d{ + call assert_equal(['.LPIt does not matter, if afterwards some', 'more characters follow.', + \ '.SHAlso section boundaries from the nroff', 'macros terminate a paragraph. That means', 'a character like this:', ''], getline(1, '$')) + norm! d{ + call assert_equal(['.LPIt does not matter, if afterwards some', 'more characters follow.', ''], getline(1,'$')) + " Test with { in cpooptions + %d + call append(0, text) + " Nvim: no "{" flag in 'cpoptions'. + " set cpo+={ + " 1 + " norm! 0d2} + " call assert_equal(['{', 'This is no paragaraph', 'unless the ''{'' is set', 'in ''cpoptions''', '}', + " \ '.IP', 'The nroff macros IP seperates a paragraph', 'That means, it must be a ''.''', + " \ 'followed by IP', '.LPIt does not matter, if afterwards some', 'more characters follow.', + " \ '.SHAlso section boundaries from the nroff', 'macros terminate a paragraph. That means', + " \ 'a character like this:', '.NH', 'End of text here', ''], getline(1,'$')) + " $ + " norm! d} + " call assert_equal(['{', 'This is no paragaraph', 'unless the ''{'' is set', 'in ''cpoptions''', '}', + " \ '.IP', 'The nroff macros IP seperates a paragraph', 'That means, it must be a ''.''', + " \ 'followed by IP', '.LPIt does not matter, if afterwards some', 'more characters follow.', + " \ '.SHAlso section boundaries from the nroff', 'macros terminate a paragraph. That means', + " \ 'a character like this:', '.NH', 'End of text here', ''], getline(1,'$')) + " norm! gg} + " norm! d5} + " call assert_equal(['{', 'This is no paragaraph', 'unless the ''{'' is set', 'in ''cpoptions''', '}', ''], getline(1,'$')) + + " clean up + set cpo-={ + bw! +endfunc + +fun! Test_normal30_changecase() + " This test uses multi byte characters + if !has("multi_byte") + return + endif + new + call append(0, 'This is a simple test: äüöß') + norm! 1ggVu + call assert_equal('this is a simple test: äüöß', getline('.')) + norm! VU + call assert_equal('THIS IS A SIMPLE TEST: ÄÜÖSS', getline('.')) + norm! guu + call assert_equal('this is a simple test: äüöss', getline('.')) + norm! gUgU + call assert_equal('THIS IS A SIMPLE TEST: ÄÜÖSS', getline('.')) + norm! gugu + call assert_equal('this is a simple test: äüöss', getline('.')) + norm! gUU + call assert_equal('THIS IS A SIMPLE TEST: ÄÜÖSS', getline('.')) + norm! 010~ + call assert_equal('this is a SIMPLE TEST: ÄÜÖSS', getline('.')) + norm! V~ + call assert_equal('THIS IS A simple test: äüöss', getline('.')) + + " clean up + bw! +endfunc + +fun! Test_normal31_r_cmd() + " Test for r command + new + call append(0, 'This is a simple test: abcd') + exe "norm! 1gg$r\<cr>" + call assert_equal(['This is a simple test: abc', '', ''], getline(1,'$')) + exe "norm! 1gg2wlr\<cr>" + call assert_equal(['This is a', 'simple test: abc', '', ''], getline(1,'$')) + exe "norm! 2gg0W5r\<cr>" + call assert_equal(['This is a', 'simple ', ' abc', '', ''], getline('1', '$')) + set autoindent + call setline(2, ['simple test: abc', '']) + exe "norm! 2gg0W5r\<cr>" + call assert_equal(['This is a', 'simple ', 'abc', '', '', ''], getline('1', '$')) + exe "norm! 1ggVr\<cr>" + call assert_equal('^M^M^M^M^M^M^M^M^M', strtrans(getline(1))) + call setline(1, 'This is a') + exe "norm! 1gg05rf" + call assert_equal('fffffis a', getline(1)) + + " clean up + set noautoindent + bw! +endfunc + +func! Test_normal32_g_cmd1() + " Test for g*, g# + new + call append(0, ['abc.x_foo', 'x_foobar.abc']) + 1 + norm! $g* + call assert_equal('x_foo', @/) + call assert_equal('x_foobar.abc', getline('.')) + norm! $g# + call assert_equal('abc', @/) + call assert_equal('abc.x_foo', getline('.')) + + " clean up + bw! +endfunc + +fun! Test_normal33_g_cmd2() + if !has("jumplist") + return + endif + " Tests for g cmds + call Setup_NewWindow() + " Test for g` + clearjumps + norm! ma10j + let a=execute(':jumps') + " empty jumplist + call assert_equal('>', a[-1:]) + norm! g`a + call assert_equal('>', a[-1:]) + call assert_equal(1, line('.')) + call assert_equal('1', getline('.')) + + " Test for g; and g, + norm! g; + " there is only one change in the changelist + " currently, when we setup the window + call assert_equal(2, line('.')) + call assert_fails(':norm! g;', 'E662') + call assert_fails(':norm! g,', 'E663') + let &ul=&ul + call append('$', ['a', 'b', 'c', 'd']) + let &ul=&ul + call append('$', ['Z', 'Y', 'X', 'W']) + let a = execute(':changes') + call assert_match('2\s\+0\s\+2', a) + call assert_match('101\s\+0\s\+a', a) + call assert_match('105\s\+0\s\+Z', a) + norm! 3g; + call assert_equal(2, line('.')) + norm! 2g, + call assert_equal(105, line('.')) + + " Test for g& - global substitute + %d + call setline(1, range(1,10)) + call append('$', ['a', 'b', 'c', 'd']) + $s/\w/&&/g + exe "norm! /[1-8]\<cr>" + norm! g& + call assert_equal(['11', '22', '33', '44', '55', '66', '77', '88', '9', '110', 'a', 'b', 'c', 'dd'], getline(1, '$')) + + " Test for gv + %d + call append('$', repeat(['abcdefgh'], 8)) + exe "norm! 2gg02l\<c-v>2j2ly" + call assert_equal(['cde', 'cde', 'cde'], getreg(0, 1, 1)) + " in visual mode, gv swaps current and last selected region + exe "norm! G0\<c-v>4k4lgvd" + call assert_equal(['', 'abfgh', 'abfgh', 'abfgh', 'abcdefgh', 'abcdefgh', 'abcdefgh', 'abcdefgh', 'abcdefgh'], getline(1,'$')) + exe "norm! G0\<c-v>4k4ly" + exe "norm! gvood" + call assert_equal(['', 'abfgh', 'abfgh', 'abfgh', 'fgh', 'fgh', 'fgh', 'fgh', 'fgh'], getline(1,'$')) + + " Test for gk/gj + %d + 15vsp + set wrap listchars= sbr= + let lineA='abcdefghijklmnopqrstuvwxyz' + let lineB='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + $put =lineA + $put =lineB + + norm! 3gg0dgk + call assert_equal(['', 'abcdefghijklmno', '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'], getline(1, '$')) + set nu + norm! 3gg0gjdgj + call assert_equal(['', 'abcdefghijklmno', '0123456789AMNOPQRSTUVWXYZ'], getline(1,'$')) + + " Test for gJ + norm! 2gggJ + call assert_equal(['', 'abcdefghijklmno0123456789AMNOPQRSTUVWXYZ'], getline(1,'$')) + call assert_equal(16, col('.')) + " shouldn't do anything + norm! 10gJ + call assert_equal(1, col('.')) + + " Test for g0 g^ gm g$ + exe "norm! 2gg0gji " + call assert_equal(['', 'abcdefghijk lmno0123456789AMNOPQRSTUVWXYZ'], getline(1,'$')) + norm! g0yl + call assert_equal(12, col('.')) + call assert_equal(' ', getreg(0)) + norm! g$yl + call assert_equal(22, col('.')) + call assert_equal('3', getreg(0)) + norm! gmyl + call assert_equal(17, col('.')) + call assert_equal('n', getreg(0)) + norm! g^yl + call assert_equal(15, col('.')) + call assert_equal('l', getreg(0)) + + " Test for g Ctrl-G + set ff=unix + let a=execute(":norm! g\<c-g>") + call assert_match('Col 15 of 43; Line 2 of 2; Word 2 of 2; Byte 16 of 45', a) + + " Test for gI + norm! gIfoo + call assert_equal(['', 'fooabcdefghijk lmno0123456789AMNOPQRSTUVWXYZ'], getline(1,'$')) + + " Test for gi + wincmd c + %d + set tw=0 + call setline(1, ['foobar', 'new line']) + norm! A next word + $put ='third line' + norm! gi another word + call assert_equal(['foobar next word another word', 'new line', 'third line'], getline(1,'$')) + + " clean up + bw! +endfunc + +fun! Test_normal34_g_cmd3() + if !has("multi_byte") + return + endif + " Test for g8 + new + call append(0, 'abcdefghijklmnopqrstuvwxyzäüö') + let a=execute(':norm! 1gg$g8') + call assert_equal('c3 b6 ', a[1:]) + + " Test for gp gP + call append(1, range(1,10)) + " clean up + bw! +endfunc + +fun! Test_normal35_g_cmd4() + " Test for g< + " Cannot capture its output, + " probably a bug, therefore, test disabled: + throw "Skipped: output of g< can't be tested currently" + echo "a\nb\nc\nd" + let b=execute(':norm! g<') + call assert_true(!empty(b), 'failed `execute(g<)`') +endfunc + +fun! Test_normal36_g_cmd5() + new + call append(0, 'abcdefghijklmnopqrstuvwxyz') + set ff=unix + " Test for gp gP + call append(1, range(1,10)) + 1 + norm! 1yy + 3 + norm! gp + call assert_equal([0, 5, 1, 0, 1], getcurpos()) + $ + norm! gP + call assert_equal([0, 14, 1, 0, 1], getcurpos()) + + " Test for go + norm! 26go + call assert_equal([0, 1, 26, 0, 26], getcurpos()) + norm! 27go + call assert_equal([0, 1, 26, 0, 26], getcurpos()) + norm! 28go + call assert_equal([0, 2, 1, 0, 1], getcurpos()) + set ff=dos + norm! 29go + call assert_equal([0, 2, 1, 0, 1], getcurpos()) + set ff=unix + norm! gg0 + norm! 101go + call assert_equal([0, 13, 26, 0, 26], getcurpos()) + norm! 103go + call assert_equal([0, 14, 1, 0, 1], getcurpos()) + " count > buffer content + norm! 120go + call assert_equal([0, 14, 1, 0, 2147483647], getcurpos()) + " clean up + bw! +endfunc + +fun! Test_normal37_g_cmd6() + " basic test for gt and gT + tabnew 1.txt + tabnew 2.txt + tabnew 3.txt + norm! 1gt + call assert_equal(1, tabpagenr()) + norm! 3gt + call assert_equal(3, tabpagenr()) + norm! 1gT + " count gT goes not to the absolute tabpagenumber + " but, but goes to the count previous tabpagenumber + call assert_equal(2, tabpagenr()) + " wrap around + norm! 3gT + call assert_equal(3, tabpagenr()) + " gt does not wrap around + norm! 5gt + call assert_equal(3, tabpagenr()) + + for i in range(3) + tabclose + endfor + " clean up + call assert_fails(':tabclose', 'E784') +endfunc + +fun! Test_normal38_nvhome() + " Test for <Home> and <C-Home> key + new + call setline(1, range(10)) + $ + setl et sw=2 + norm! V10>$ + " count is ignored + exe "norm! 10\<home>" + call assert_equal(1, col('.')) + exe "norm! \<home>" + call assert_equal([0, 10, 1, 0, 1], getcurpos()) + exe "norm! 5\<c-home>" + call assert_equal([0, 5, 1, 0, 1], getcurpos()) + exe "norm! \<c-home>" + call assert_equal([0, 1, 1, 0, 1], getcurpos()) + + " clean up + bw! +endfunc + +fun! Test_normal39_cw() + " Test for cw and cW on whitespace + " and cpo+=w setting + new + set tw=0 + call append(0, 'here are some words') + norm! 1gg0elcwZZZ + call assert_equal('hereZZZare some words', getline('.')) + norm! 1gg0elcWYYY + call assert_equal('hereZZZareYYYsome words', getline('.')) + " Nvim: no "w" flag in 'cpoptions'. + " set cpo+=w + " call setline(1, 'here are some words') + " norm! 1gg0elcwZZZ + " call assert_equal('hereZZZ are some words', getline('.')) + " norm! 1gg2elcWYYY + " call assert_equal('hereZZZ areYYY some words', getline('.')) + set cpo-=w + norm! 2gg0cwfoo + call assert_equal('foo', getline('.')) + + " clean up + bw! +endfunc + +fun! Test_normal40_ctrl_bsl() + " Basic test for CTRL-\ commands + new + call append(0, 'here are some words') + exe "norm! 1gg0a\<C-\>\<C-N>" + call assert_equal('n', mode()) + call assert_equal(1, col('.')) + call assert_equal('', visualmode()) + exe "norm! 1gg0viw\<C-\>\<C-N>" + call assert_equal('n', mode()) + call assert_equal(4, col('.')) + exe "norm! 1gg0a\<C-\>\<C-G>" + call assert_equal('n', mode()) + call assert_equal(1, col('.')) + "imap <buffer> , <c-\><c-n> + set im + exe ":norm! \<c-\>\<c-n>dw" + set noim + call assert_equal('are some words', getline(1)) + call assert_false(&insertmode) + + " clean up + bw! +endfunc + +fun! Test_normal41_insert_reg() + " Test for <c-r>=, <c-r><c-r>= and <c-r><c-o>= + " in insert mode + new + set sts=2 sw=2 ts=8 tw=0 + call append(0, ["aaa\tbbb\tccc", '', '', '']) + let a=getline(1) + norm! 2gg0 + exe "norm! a\<c-r>=a\<cr>" + norm! 3gg0 + exe "norm! a\<c-r>\<c-r>=a\<cr>" + norm! 4gg0 + exe "norm! a\<c-r>\<c-o>=a\<cr>" + call assert_equal(['aaa bbb ccc', 'aaa bbb ccc', 'aaa bbb ccc', 'aaa bbb ccc', ''], getline(1, '$')) + + " clean up + set sts=0 sw=8 ts=8 + bw! +endfunc + +func! Test_normal42_halfpage() + " basic test for Ctrl-D and Ctrl-U + call Setup_NewWindow() + call assert_equal(5, &scroll) + exe "norm! \<c-d>" + call assert_equal('6', getline('.')) + exe "norm! 2\<c-d>" + call assert_equal('8', getline('.')) + call assert_equal(2, &scroll) + set scroll=5 + exe "norm! \<c-u>" + call assert_equal('3', getline('.')) + 1 + set scrolloff=5 + exe "norm! \<c-d>" + call assert_equal('10', getline('.')) + exe "norm! \<c-u>" + call assert_equal('5', getline('.')) + 1 + set scrolloff=99 + exe "norm! \<c-d>" + call assert_equal('10', getline('.')) + set scrolloff=0 + 100 + exe "norm! $\<c-u>" + call assert_equal('95', getline('.')) + call assert_equal([0, 95, 1, 0, 1], getcurpos()) + 100 + set nostartofline + exe "norm! $\<c-u>" + call assert_equal('95', getline('.')) + call assert_equal([0, 95, 2, 0, 2147483647], getcurpos()) + " cleanup + set startofline + bw! +endfunc + +fun! Test_normal43_textobject1() + " basic tests for text object aw + new + call append(0, ['foobar,eins,foobar', 'foo,zwei,foo ']) + " diw + norm! 1gg0diw + call assert_equal([',eins,foobar', 'foo,zwei,foo ', ''], getline(1,'$')) + " daw + norm! 2ggEdaw + call assert_equal([',eins,foobar', 'foo,zwei,', ''], getline(1, '$')) + %d + call append(0, ["foo\teins\tfoobar", "foo\tzwei\tfoo "]) + " diW + norm! 2ggwd2iW + call assert_equal(['foo eins foobar', 'foo foo ', ''], getline(1,'$')) + " daW + norm! 1ggd2aW + call assert_equal(['foobar', 'foo foo ', ''], getline(1,'$')) + + %d + call append(0, ["foo\teins\tfoobar", "foo\tzwei\tfoo "]) + " aw in visual line mode switches to characterwise mode + norm! 2gg$Vawd + call assert_equal(['foo eins foobar', 'foo zwei foo'], getline(1,'$')) + norm! 1gg$Viwd + call assert_equal(['foo eins ', 'foo zwei foo'], getline(1,'$')) + + " clean up + bw! +endfunc + +func! Test_normal44_textobjects2() + " basic testing for is and as text objects + new + call append(0, ['This is a test. With some sentences!', '', 'Even with a question? And one more. And no sentence here']) + " Test for dis - does not remove trailing whitespace + norm! 1gg0dis + call assert_equal([' With some sentences!', '', 'Even with a question? And one more. And no sentence here', ''], getline(1,'$')) + " Test for das - removes leading whitespace + norm! 3ggf?ldas + call assert_equal([' With some sentences!', '', 'Even with a question? And no sentence here', ''], getline(1,'$')) + " when used in visual mode, is made characterwise + norm! 3gg$Visy + call assert_equal('v', visualmode()) + " reset visualmode() + norm! 3ggVy + norm! 3gg$Vasy + call assert_equal('v', visualmode()) + " basic testing for textobjects a< and at + %d + call setline(1, ['<div> ','<a href="foobar" class="foo">xyz</a>',' </div>', ' ']) + " a< + norm! 1gg0da< + call assert_equal([' ', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$')) + norm! 1pj + call assert_equal([' <div>', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$')) + " at + norm! d2at + call assert_equal([' '], getline(1,'$')) + %d + call setline(1, ['<div> ','<a href="foobar" class="foo">xyz</a>',' </div>', ' ']) + " i< + norm! 1gg0di< + call assert_equal(['<> ', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$')) + norm! 1Pj + call assert_equal(['<div> ', '<a href="foobar" class="foo">xyz</a>', ' </div>', ' '], getline(1,'$')) + norm! d2it + call assert_equal(['<div></div>',' '], getline(1,'$')) + " basic testing for a[ and i[ text object + %d + call setline(1, [' ', '[', 'one [two]', 'thre', ']']) + norm! 3gg0di[ + call assert_equal([' ', '[', ']'], getline(1,'$')) + call setline(1, [' ', '[', 'one [two]', 'thre', ']']) + norm! 3gg0ftd2a[ + call assert_equal([' '], getline(1,'$')) + %d + " Test for i" when cursor is in front of a quoted object + call append(0, 'foo "bar"') + norm! 1gg0di" + call assert_equal(['foo ""', ''], getline(1,'$')) + + " clean up + bw! +endfunc + +func! Test_normal45_drop() + if !has("dnd") + return + endif + " basic test for :drop command + " unfortunately, without a gui, we can't really test much here, + " so simply test that ~p fails (which uses the drop register) + new + call assert_fails(':norm! "~p', 'E353') + call assert_equal([], getreg('~', 1, 1)) + " the ~ register is read only + call assert_fails(':let @~="1"', 'E354') + bw! +endfunc + +func! Test_normal46_ignore() + " This test uses multi byte characters + if !has("multi_byte") + return + endif + + new + " How to test this? + " let's just for now test, that the buffer + " does not change + call feedkeys("\<c-s>", 't') + call assert_equal([''], getline(1,'$')) + + " no valid commands + exe "norm! \<char-0x100>" + call assert_equal([''], getline(1,'$')) + + exe "norm! ä" + call assert_equal([''], getline(1,'$')) + + " clean up + bw! +endfunc + +func! Test_normal47_visual_buf_wipe() + " This was causing a crash or ml_get error. + enew! + call setline(1,'xxx') + normal $ + new + call setline(1, range(1,2)) + 2 + exe "norm \<C-V>$" + bw! + norm yp + set nomodified +endfunc + +func! Test_normal47_autocmd() + " disabled, does not seem to be possible currently + throw "Skipped: not possible to test cursorhold autocmd while waiting for input in normal_cmd" + new + call append(0, repeat('-',20)) + au CursorHold * call feedkeys('2l', '') + 1 + set updatetime=20 + " should delete 12 chars (d12l) + call feedkeys('d1', '!') + call assert_equal('--------', getline(1)) + + " clean up + au! CursorHold + set updatetime=4000 + bw! +endfunc + +func! Test_normal48_wincmd() + new + exe "norm! \<c-w>c" + call assert_equal(1, winnr('$')) + call assert_fails(":norm! \<c-w>c", "E444") +endfunc + +func! Test_normal49_counts() + new + call setline(1, 'one two three four five six seven eight nine ten') + 1 + norm! 3d2w + call assert_equal('seven eight nine ten', getline(1)) + bw! +endfunc + +func! Test_normal50_commandline() + if !has("timers") || !has("cmdline_hist") || !has("vertsplit") + return + endif + func! DoTimerWork(id) + call assert_equal('[Command Line]', bufname('')) + " should fail, with E11, but does fail with E23? + "call feedkeys("\<c-^>", 'tm') + + " should also fail with E11 + call assert_fails(":wincmd p", 'E11') + " return from commandline window + call feedkeys("\<cr>") + endfunc + + let oldlang=v:lang + lang C + set updatetime=20 + call timer_start(100, 'DoTimerWork') + try + " throws E23, for whatever reason... + call feedkeys('q:', 'x!') + catch /E23/ + " no-op + endtry + " clean up + set updatetime=4000 + exe "lang" oldlang + bw! +endfunc + +func! Test_normal51_FileChangedRO() + if !has("autocmd") + return + endif + call writefile(['foo'], 'Xreadonly.log') + new Xreadonly.log + setl ro + au FileChangedRO <buffer> :call feedkeys("\<c-^>", 'tix') + call assert_fails(":norm! Af", 'E788') + call assert_equal(['foo'], getline(1,'$')) + call assert_equal('Xreadonly.log', bufname('')) + + " cleanup + bw! + call delete("Xreadonly.log") +endfunc + +func! Test_normal52_rl() + if !has("rightleft") + return + endif + new + call setline(1, 'abcde fghij klmnopq') + norm! 1gg$ + set rl + call assert_equal(19, col('.')) + call feedkeys('l', 'tx') + call assert_equal(18, col('.')) + call feedkeys('h', 'tx') + call assert_equal(19, col('.')) + call feedkeys("\<right>", 'tx') + call assert_equal(18, col('.')) + call feedkeys("\<s-right>", 'tx') + call assert_equal(13, col('.')) + call feedkeys("\<c-right>", 'tx') + call assert_equal(7, col('.')) + call feedkeys("\<c-left>", 'tx') + call assert_equal(13, col('.')) + call feedkeys("\<s-left>", 'tx') + call assert_equal(19, col('.')) + call feedkeys("<<", 'tx') + call assert_equal(' abcde fghij klmnopq',getline(1)) + call feedkeys(">>", 'tx') + call assert_equal('abcde fghij klmnopq',getline(1)) + + " cleanup + set norl + bw! +endfunc + +func! Test_normal53_digraph() + if !has('digraphs') + return + endif + new + call setline(1, 'abcdefgh|') + exe "norm! 1gg0f\<c-k>!!" + call assert_equal(9, col('.')) + set cpo+=D + exe "norm! 1gg0f\<c-k>!!" + call assert_equal(1, col('.')) + + set cpo-=D + bw! +endfunc + +func! Test_normal54_Ctrl_bsl() + new + call setline(1, 'abcdefghijklmn') + exe "norm! df\<c-\>\<c-n>" + call assert_equal(['abcdefghijklmn'], getline(1,'$')) + exe "norm! df\<c-\>\<c-g>" + call assert_equal(['abcdefghijklmn'], getline(1,'$')) + exe "norm! df\<c-\>m" + call assert_equal(['abcdefghijklmn'], getline(1,'$')) + if !has("multi_byte") + return + endif + call setline(2, 'abcdefghijklmnāf') + norm! 2gg0 + exe "norm! df\<Char-0x101>" + call assert_equal(['abcdefghijklmn', 'f'], getline(1,'$')) + norm! 1gg0 + exe "norm! df\<esc>" + call assert_equal(['abcdefghijklmn', 'f'], getline(1,'$')) + + " clean up + bw! +endfunc diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim new file mode 100644 index 0000000000..f78d92f3ff --- /dev/null +++ b/src/nvim/testdir/test_startup.vim @@ -0,0 +1,200 @@ +" Tests for startup. + +source shared.vim + +" Check that loading startup.vim works. +func Test_startup_script() + throw 'skipped: Nvim does not need defaults.vim' + set compatible + source $VIMRUNTIME/defaults.vim + + call assert_equal(0, &compatible) +endfunc + +" Verify the order in which plugins are loaded: +" 1. plugins in non-after directories +" 2. packages +" 3. plugins in after directories +func Test_after_comes_later() + if !has('packages') + return + endif + let before = [ + \ 'set nocp viminfo+=nviminfo', + \ 'set guioptions+=M', + \ 'let $HOME = "/does/not/exist"', + \ 'set loadplugins', + \ 'set rtp=Xhere,Xafter', + \ 'set packpath=Xhere,Xafter', + \ 'set nomore', + \ ] + let after = [ + \ 'redir! > Xtestout', + \ 'scriptnames', + \ 'redir END', + \ 'quit', + \ ] + call mkdir('Xhere/plugin', 'p') + call writefile(['let done = 1'], 'Xhere/plugin/here.vim') + call mkdir('Xhere/pack/foo/start/foobar/plugin', 'p') + call writefile(['let done = 1'], 'Xhere/pack/foo/start/foobar/plugin/foo.vim') + + call mkdir('Xafter/plugin', 'p') + call writefile(['let done = 1'], 'Xafter/plugin/later.vim') + + if RunVim(before, after, '') + + let lines = readfile('Xtestout') + let expected = ['Xbefore.vim', 'here.vim', 'foo.vim', 'later.vim', 'Xafter.vim'] + let found = [] + for line in lines + for one in expected + if line =~ one + call add(found, one) + endif + endfor + endfor + call assert_equal(expected, found) + endif + + call delete('Xtestout') + call delete('Xhere', 'rf') + call delete('Xafter', 'rf') +endfunc + +func Test_help_arg() + if !has('unix') && has('gui') + " this doesn't work with gvim on MS-Windows + return + endif + if RunVim([], [], '--help >Xtestout') + let lines = readfile('Xtestout') + call assert_true(len(lines) > 20) + call assert_match('Usage:', lines[0]) + + " check if couple of lines are there + let found = [] + for line in lines + if line =~ '-R.*Readonly mode' + call add(found, 'Readonly mode') + endif + " Watch out for a second --version line in the Gnome version. + if line =~ '--version.*Print version information and exit' + call add(found, "--version") + endif + endfor + call assert_equal(['--version'], found) + endif + call delete('Xtestout') +endfunc + +func Test_compatible_args() + throw "skipped: Nvim is always 'nocompatible'" + let after = [ + \ 'call writefile([string(&compatible)], "Xtestout")', + \ 'set viminfo+=nviminfo', + \ 'quit', + \ ] + if RunVim([], after, '-C') + let lines = readfile('Xtestout') + call assert_equal('1', lines[0]) + endif + + if RunVim([], after, '-N') + let lines = readfile('Xtestout') + call assert_equal('0', lines[0]) + endif + + call delete('Xtestout') +endfunc + +func Test_file_args() + let after = [ + \ 'call writefile(argv(), "Xtestout")', + \ 'qall', + \ ] + if RunVim([], after, '') + let lines = readfile('Xtestout') + call assert_equal(0, len(lines)) + endif + + if RunVim([], after, 'one') + let lines = readfile('Xtestout') + call assert_equal(1, len(lines)) + call assert_equal('one', lines[0]) + endif + + if RunVim([], after, 'one two three') + let lines = readfile('Xtestout') + call assert_equal(3, len(lines)) + call assert_equal('one', lines[0]) + call assert_equal('two', lines[1]) + call assert_equal('three', lines[2]) + endif + + if RunVim([], after, 'one -c echo two') + let lines = readfile('Xtestout') + call assert_equal(2, len(lines)) + call assert_equal('one', lines[0]) + call assert_equal('two', lines[1]) + endif + + if RunVim([], after, 'one -- -c echo two') + let lines = readfile('Xtestout') + call assert_equal(4, len(lines)) + call assert_equal('one', lines[0]) + call assert_equal('-c', lines[1]) + call assert_equal('echo', lines[2]) + call assert_equal('two', lines[3]) + endif + + call delete('Xtestout') +endfunc + +func Test_startuptime() + if !has('startuptime') + return + endif + let after = ['qall'] + if RunVim([], after, '--startuptime Xtestout one') + let lines = readfile('Xtestout') + let expected = ['parsing arguments', 'inits 3', 'opening buffers'] + let found = [] + for line in lines + for exp in expected + if line =~ exp + call add(found, exp) + endif + endfor + endfor + call assert_equal(expected, found) + endif + call delete('Xtestout') +endfunc + +func Test_read_stdin() + let after = [ + \ 'write Xtestout', + \ 'quit!', + \ ] + if RunVimPiped([], after, '-', 'echo something | ') + let lines = readfile('Xtestout') + " MS-Windows adds a space after the word + call assert_equal(['something'], split(lines[0])) + endif + call delete('Xtestout') +endfunc + +func Test_progpath() + " Tests normally run with "./vim" or "../vim", these must have been expanded + " to a full path. + if has('unix') + call assert_equal('/', v:progpath[0]) + elseif has('win32') + call assert_equal(':', v:progpath[1]) + call assert_match('[/\\]', v:progpath[2]) + endif + + " Only expect "vim" to appear in v:progname. + call assert_match('vim\c', v:progname) +endfunc diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index d8a333f44c..2044c23f79 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -6,4 +6,63 @@ func Test_ptag_with_notagstack() call assert_fails('ptag does_not_exist_tag_name', 'E426') set tagstack&vim endfunc -" vim: sw=2 et + +func Test_cancel_ptjump() + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "word\tfile1\tcmd1", + \ "word\tfile2\tcmd2"], + \ 'Xtags') + + only! + call feedkeys(":ptjump word\<CR>\<CR>", "xt") + help + call assert_equal(2, winnr('$')) + + call delete('Xtags') + quit +endfunc + +func Test_static_tagjump() + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "one\tXfile1\t/^one/;\"\tf\tfile:\tsignature:(void)", + \ "word\tXfile2\tcmd2"], + \ 'Xtags') + new Xfile1 + call setline(1, ['empty', 'one()', 'empty']) + write + tag one + call assert_equal(2, line('.')) + + bwipe! + set tags& + call delete('Xtags') + call delete('Xfile1') +endfunc + +func Test_duplicate_tagjump() + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "thesame\tXfile1\t1;\"\td\tfile:", + \ "thesame\tXfile1\t2;\"\td\tfile:", + \ "thesame\tXfile1\t3;\"\td\tfile:", + \ ], + \ 'Xtags') + new Xfile1 + call setline(1, ['thesame one', 'thesame two', 'thesame three']) + write + tag thesame + call assert_equal(1, line('.')) + tnext + call assert_equal(2, line('.')) + tnext + call assert_equal(3, line('.')) + + bwipe! + set tags& + call delete('Xtags') + call delete('Xfile1') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index d92cbe6897..56f9feef66 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -52,4 +52,5 @@ func Test_retain_partial() call garbagecollect() sleep 200m endfunc -" vim: ts=2 sw=0 et + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index fc61d1f223..9ff73fd870 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -131,6 +131,39 @@ func Test_undo_del_chars() close! endfunc +func Test_undolist() + new + set ul=100 + + let a=execute('undolist') + call assert_equal("\nNothing to undo", a) + + " 1 leaf (2 changes). + call feedkeys('achange1', 'xt') + call feedkeys('achange2', 'xt') + let a=execute('undolist') + call assert_match("^\nnumber changes when *saved\n *2 *2 .*$", a) + + " 2 leaves. + call feedkeys('u', 'xt') + call feedkeys('achange3\<Esc>', 'xt') + let a=execute('undolist') + call assert_match("^\nnumber changes when *saved\n *2 *2 *.*\n *3 *2 .*$", a) + close! +endfunc + +func Test_U_command() + new + set ul=100 + call feedkeys("achange1\<Esc>", 'xt') + call feedkeys("achange2\<Esc>", 'xt') + norm! U + call assert_equal('', getline(1)) + norm! U + call assert_equal('change1change2', getline(1)) + close! +endfunc + func Test_undojoin() new call feedkeys("Goaaaa\<Esc>", 'xt') diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index b7f41a711b..569a78a0ed 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -67,4 +67,4 @@ function Test_window_cmd_wincmd_gf() augroup! test_window_cmd_wincmd_gf endfunc -" vim: sw=2 et +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/version.c b/src/nvim/version.c index 5cdd0d96fe..398a3e6542 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -79,7 +79,7 @@ static int included_patches[] = { // 2365 NA // 2364, // 2363 NA - // 2362, + 2362, // 2361 NA // 2360, // 2359 NA @@ -88,13 +88,13 @@ static int included_patches[] = { // 2356, // 2355, // 2354, - // 2353, + 2353, // 2352 NA // 2351 NA // 2350, // 2349, - // 2348, - // 2347, + 2348, + 2347, // 2346, // 2345 NA // 2344 NA @@ -105,28 +105,28 @@ static int included_patches[] = { // 2339, // 2338 NA 2337, - // 2336, + 2336, 2335, // 2334, - // 2333, + 2333, // 2332 NA 2331, // 2330, // 2329, // 2328, // 2327 NA - // 2326, + 2326, // 2325 NA // 2324, - // 2323, - // 2322, + 2323, + 2322, 2321, // 2320, // 2319 NA // 2318, - // 2317, + 2317, // 2316 NA - // 2315, + 2315, 2314, 2313, 2312, @@ -138,19 +138,19 @@ static int included_patches[] = { // 2306, 2305, // 2304 NA - // 2303, + 2303, // 2302 NA // 2301 NA 2300, 2299, // 2298 NA // 2297 NA - // 2296, + 2296, 2295, 2294, - // 2293, + 2293, 2292, - // 2291, + 2291, // 2290 NA // 2289 NA // 2288 NA @@ -158,10 +158,10 @@ static int included_patches[] = { // 2286 NA // 2285 NA 2284, - // 2283, + 2283, // 2282 NA // 2281 NA - // 2280, + 2280, 2279, // 2278 NA 2277, @@ -175,7 +175,7 @@ static int included_patches[] = { // 2269, // 2268, // 2267 NA - // 2266, + 2266, 2265, 2264, // 2263, @@ -193,7 +193,7 @@ static int included_patches[] = { 2251, // 2250, 2249, - // 2248, + 2248, // 2247 NA // 2246, // 2245, @@ -213,14 +213,14 @@ static int included_patches[] = { // 2231, 2230, // 2229, - // 2228, + 2228, 2227, 2226, 2225, // 2224, 2223, 2222, - // 2221, + 2221, 2220, 2219, // 2218 NA @@ -257,27 +257,27 @@ static int included_patches[] = { 2187, // 2186 NA 2185, - // 2184, + 2184, 2183, // 2182 NA // 2181, // 2180, // 2179, - // 2178, - // 2177, + 2178, + 2177, // 2176 NA 2175, 2174, // 2173, - // 2172, + 2172, // 2171, // 2170, - // 2169, + 2169, // 2168 NA // 2167 NA // 2166 NA // 2165, - // 2164, + 2164, 2163, 2162, // 2161, diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 458d23fcad..87e9889465 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -325,6 +325,8 @@ enum { #define DIP_START 0x08 // also use "start" directory in 'packpath' #define DIP_OPT 0x10 // also use "opt" directory in 'packpath' #define DIP_NORTP 0x20 // do not use 'runtimepath' +#define DIP_NOAFTER 0x40 // skip "after" directories +#define DIP_AFTER 0x80 // only use "after" directories // Lowest number used for window ID. Cannot have this many windows per tab. #define LOWEST_WIN_ID 1000 |