diff options
Diffstat (limited to 'src')
28 files changed, 1344 insertions, 111 deletions
diff --git a/src/clint.py b/src/clint.py index 944946bd16..28f6031a57 100755 --- a/src/clint.py +++ b/src/clint.py @@ -651,6 +651,9 @@ def Error(filename, linenum, category, confidence, message): elif _cpplint_state.output_format == 'eclipse': sys.stdout.write('%s:%s: warning: %s [%s] [%d]\n' % ( filename, linenum, message, category, confidence)) + elif _cpplint_state.output_format == 'gh_action': + sys.stdout.write('::error file=%s,line=%s::%s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) else: sys.stdout.write('%s:%s: %s [%s] [%d]\n' % ( filename, linenum, message, category, confidence)) @@ -3053,7 +3056,7 @@ def ParseArguments(args): if opt == '--help': PrintUsage(None) elif opt == '--output': - if val not in ('emacs', 'vs7', 'eclipse'): + if val not in ('emacs', 'vs7', 'eclipse', 'gh_action'): PrintUsage('The only allowed output formats are emacs,' ' vs7 and eclipse.') output_format = val diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index a0b8f16a39..de4af8e77b 100755 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -778,6 +778,12 @@ add_custom_command( add_download(${LINT_SUPPRESS_FILE} ${LINT_SUPPRESS_URL} off) +if(CI_BUILD) + set(LINT_OUTPUT_FORMAT gh_action) +else() + set(LINT_OUTPUT_FORMAT vs7) +endif() + set(LINT_NVIM_REL_SOURCES) foreach(sfile ${LINT_NVIM_SOURCES}) get_test_target("" "${sfile}" r suffix) @@ -787,7 +793,7 @@ foreach(sfile ${LINT_NVIM_SOURCES}) set(touch_file "${TOUCHES_DIR}/ran-clint-${suffix}") add_custom_command( OUTPUT ${touch_file} - COMMAND ${LINT_PRG} --suppress-errors=${suppress_file} ${rsfile} + COMMAND ${LINT_PRG} --suppress-errors=${suppress_file} --output=${LINT_OUTPUT_FORMAT} ${rsfile} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${CMAKE_COMMAND} -E touch ${touch_file} DEPENDS ${LINT_PRG} ${sfile} ${LINT_SUPPRESSES_TOUCH_FILE} @@ -807,7 +813,7 @@ add_glob_targets( add_custom_target( lintcfull COMMAND - ${LINT_PRG} --suppress-errors=${LINT_SUPPRESS_FILE} ${LINT_NVIM_REL_SOURCES} + ${LINT_PRG} --suppress-errors=${LINT_SUPPRESS_FILE} --output=${LINT_OUTPUT_FORMAT} ${LINT_NVIM_REL_SOURCES} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} DEPENDS ${LINT_PRG} ${LINT_NVIM_SOURCES} ${LINT_SUPPRESS_FILE} lintuncrustify ) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 7bdb905dfa..411705cfa3 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -89,6 +89,7 @@ static char *msg_loclist = N_("[Location List]"); static char *msg_qflist = N_("[Quickfix List]"); static char *e_auabort = N_("E855: Autocommands caused command to abort"); +static char *e_buflocked = N_("E937: Attempt to delete a buffer that is in use"); // Number of times free_buffer() was called. static int buf_free_count = 0; @@ -438,7 +439,7 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i // Disallow deleting the buffer when it is locked (already being closed or // halfway a command that relies on it). Unloading is allowed. if (buf->b_locked > 0 && (del_buf || wipe_buf)) { - emsg(_("E937: Attempt to delete a buffer that is in use")); + emsg(_(e_buflocked)); return false; } @@ -529,7 +530,9 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i } if (buf->terminal) { + buf->b_locked++; terminal_close(&buf->terminal, -1); + buf->b_locked--; } // Always remove the buffer when there is no file name. @@ -1182,6 +1185,10 @@ int do_buffer(int action, int start, int dir, int count, int forceit) if (unload) { int forward; bufref_T bufref; + if (buf->b_locked) { + emsg(_(e_buflocked)); + return FAIL; + } set_bufref(&bufref, buf); // When unloading or deleting a buffer that's already unloaded and diff --git a/src/nvim/edit.c b/src/nvim/edit.c index d21583e951..26422f06b1 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -7330,7 +7330,7 @@ static void replace_do_bs(int limit_col) } /// Check that C-indenting is on. -static bool cindent_on(void) +bool cindent_on(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return !p_paste && (curbuf->b_p_cin || *curbuf->b_p_inde != NUL); @@ -8373,7 +8373,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) } // delete characters until we are at or before want_vcol - while (vcol > want_vcol + while (vcol > want_vcol && curwin->w_cursor.col > 0 && (cc = *(get_cursor_pos_ptr() - 1), ascii_iswhite(cc))) { ins_bs_one(&vcol); } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index b539d50784..f3f60ebfb7 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -503,8 +503,7 @@ EXTERN int stdout_isatty INIT(= true); EXTERN int stdin_fd INIT(= -1); // true when doing full-screen output, otherwise only writing some messages. -// volatile because it is used in a signal handler. -EXTERN volatile int full_screen INIT(= false); +EXTERN int full_screen INIT(= false); /// Non-zero when only "safe" commands are allowed, e.g. when sourcing .exrc or /// .vimrc in current directory. @@ -724,7 +723,8 @@ EXTERN bool need_highlight_changed INIT(= true); EXTERN FILE *scriptout INIT(= NULL); ///< Stream to write script to. -// volatile because it is used in a signal handler. +// Note that even when handling SIGINT, volatile is not necessary because the +// callback is not called directly from the signal handlers. EXTERN bool got_int INIT(= false); // set to true when interrupt signal occurred EXTERN bool bangredo INIT(= false); // set to true with ! command EXTERN int searchcmdlen; // length of previous search cmd diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 9d60cf9dfe..271498d41a 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -656,6 +656,9 @@ int get_lisp_indent(void) } } } + if (*that == NUL) { + break; + } } if ((*that == '(') || (*that == '[')) { parencount++; diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 2402ea3035..0bf5875269 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -607,6 +607,8 @@ void mark_view_restore(fmark_T *fm) { if (fm != NULL && fm->view.topline_offset >= 0) { linenr_T topline = fm->mark.lnum - fm->view.topline_offset; + // If the mark does not have a view, topline_offset is MAXLNUM, + // and this check can prevent restoring mark view in that case. if (topline >= 1) { set_topline(curwin, topline); } diff --git a/src/nvim/mark_defs.h b/src/nvim/mark_defs.h index 16d85a6e51..a78056c5f9 100644 --- a/src/nvim/mark_defs.h +++ b/src/nvim/mark_defs.h @@ -64,9 +64,10 @@ typedef enum { /// Represents view in which the mark was created typedef struct fmarkv { linenr_T topline_offset; ///< Amount of lines from the mark lnum to the top of the window. + ///< Use MAXLNUM to indicate that the mark does not have a view. } fmarkv_T; -#define INIT_FMARKV { 0 } +#define INIT_FMARKV { MAXLNUM } /// Structure defining single local mark typedef struct filemark { diff --git a/src/nvim/normal.c b/src/nvim/normal.c index c7f7b569e7..ed624c0c9f 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2494,21 +2494,30 @@ static void prep_redo_cmd(cmdarg_T *cap) /// Note that only the last argument can be a multi-byte char. void prep_redo(int regname, long num, int cmd1, int cmd2, int cmd3, int cmd4, int cmd5) { + prep_redo_num2(regname, num, cmd1, cmd2, 0L, cmd3, cmd4, cmd5); +} + +/// Prepare for redo of any command with extra count after "cmd2". +void prep_redo_num2(int regname, long num1, int cmd1, int cmd2, long num2, int cmd3, int cmd4, + int cmd5) +{ ResetRedobuff(); if (regname != 0) { // yank from specified buffer AppendCharToRedobuff('"'); AppendCharToRedobuff(regname); } - if (num) { - AppendNumberToRedobuff(num); + if (num1 != 0) { + AppendNumberToRedobuff(num1); } - if (cmd1 != NUL) { AppendCharToRedobuff(cmd1); } if (cmd2 != NUL) { AppendCharToRedobuff(cmd2); } + if (num2 != 0) { + AppendNumberToRedobuff(num2); + } if (cmd3 != NUL) { AppendCharToRedobuff(cmd3); } @@ -6157,6 +6166,16 @@ static void nv_g_cmd(cmdarg_T *cap) i = curwin->w_leftcol + curwin->w_width_inner - col_off - 1; coladvance((colnr_T)i); + // if the character doesn't fit move one back + if (curwin->w_cursor.col > 0 && utf_ptr2cells((const char *)get_cursor_pos_ptr()) > 1) { + colnr_T vcol; + + getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol); + if (vcol >= curwin->w_leftcol + curwin->w_width - col_off) { + curwin->w_cursor.col--; + } + } + // Make sure we stick in this column. validate_virtcol(); curwin->w_curswant = curwin->w_virtcol; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index a8198cfce9..2f29e94ff1 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -4421,6 +4421,7 @@ void format_lines(linenr_T line_count, int avoid_fex) int smd_save; long count; bool need_set_indent = true; // set indent of next paragraph + linenr_T first_line = curwin->w_cursor.lnum; bool force_format = false; const int old_State = State; @@ -4547,9 +4548,24 @@ void format_lines(linenr_T line_count, int avoid_fex) */ if (is_end_par || force_format) { if (need_set_indent) { - // replace indent in first line with minimal number of - // tabs and spaces, according to current options - (void)set_indent(get_indent(), SIN_CHANGED); + int indent = 0; // amount of indent needed + + // Replace indent in first line of a paragraph with minimal + // number of tabs and spaces, according to current options. + // For the very first formatted line keep the current + // indent. + if (curwin->w_cursor.lnum == first_line) { + indent = get_indent(); + } else if (curbuf->b_p_lisp) { + indent = get_lisp_indent(); + } else { + if (cindent_on()) { + indent = *curbuf->b_p_inde != NUL ? get_expr_indent() : get_c_indent(); + } else { + indent = get_indent(); + } + } + (void)set_indent(indent, SIN_CHANGED); } // put cursor on last non-space @@ -6218,6 +6234,15 @@ static void get_op_vcol(oparg_T *oap, colnr_T redo_VIsual_vcol, bool initial) oap->start = curwin->w_cursor; } +/// Information for redoing the previous Visual selection. +typedef struct { + int rv_mode; ///< 'v', 'V', or Ctrl-V + linenr_T rv_line_count; ///< number of lines + colnr_T rv_vcol; ///< number of cols or end column + long rv_count; ///< count for Visual operator + int rv_arg; ///< extra argument +} redo_VIsual_T; + /// Handle an operator after Visual mode or when the movement is finished. /// "gui_yank" is true when yanking text for the clipboard. void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) @@ -6229,11 +6254,8 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) int lbr_saved = curwin->w_p_lbr; // The visual area is remembered for redo - static int redo_VIsual_mode = NUL; // 'v', 'V', or Ctrl-V - static linenr_T redo_VIsual_line_count; // number of lines - static colnr_T redo_VIsual_vcol; // number of cols or end column - static long redo_VIsual_count; // count for Visual operator - static int redo_VIsual_arg; // extra argument + static redo_VIsual_T redo_VIsual = { NUL, 0, 0, 0, 0 }; + bool include_line_break = false; old_cursor = curwin->w_cursor; @@ -6316,28 +6338,27 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (redo_VIsual_busy) { // Redo of an operation on a Visual area. Use the same size from - // redo_VIsual_line_count and redo_VIsual_vcol. + // redo_VIsual.rv_line_count and redo_VIsual.rv_vcol. oap->start = curwin->w_cursor; - curwin->w_cursor.lnum += redo_VIsual_line_count - 1; + curwin->w_cursor.lnum += redo_VIsual.rv_line_count - 1; if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; } - VIsual_mode = redo_VIsual_mode; - if (redo_VIsual_vcol == MAXCOL || VIsual_mode == 'v') { + VIsual_mode = redo_VIsual.rv_mode; + if (redo_VIsual.rv_vcol == MAXCOL || VIsual_mode == 'v') { if (VIsual_mode == 'v') { - if (redo_VIsual_line_count <= 1) { + if (redo_VIsual.rv_line_count <= 1) { validate_virtcol(); - curwin->w_curswant = - curwin->w_virtcol + redo_VIsual_vcol - 1; + curwin->w_curswant = curwin->w_virtcol + redo_VIsual.rv_vcol - 1; } else { - curwin->w_curswant = redo_VIsual_vcol; + curwin->w_curswant = redo_VIsual.rv_vcol; } } else { curwin->w_curswant = MAXCOL; } coladvance(curwin->w_curswant); } - cap->count0 = redo_VIsual_count; + cap->count0 = redo_VIsual.rv_count; cap->count1 = (cap->count0 == 0 ? 1 : cap->count0); } else if (VIsual_active) { if (!gui_yank) { @@ -6424,7 +6445,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) virtual_op = virtual_active(); if (VIsual_active || redo_VIsual_busy) { - get_op_vcol(oap, redo_VIsual_vcol, true); + get_op_vcol(oap, redo_VIsual.rv_vcol, true); if (!redo_VIsual_busy && !gui_yank) { // Prepare to reselect and redo Visual: this is based on the @@ -6469,6 +6490,8 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) get_op_char(oap->op_type), get_extra_op_char(oap->op_type), oap->motion_force, cap->cmdchar, cap->nchar); } else if (cap->cmdchar != ':' && cap->cmdchar != K_COMMAND) { + int opchar = get_op_char(oap->op_type); + int extra_opchar = get_extra_op_char(oap->op_type); int nchar = oap->op_type == OP_REPLACE ? cap->nchar : NUL; // reverse what nv_replace() did @@ -6477,15 +6500,20 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } else if (nchar == REPLACE_NL_NCHAR) { nchar = NL; } - prep_redo(oap->regname, 0L, NUL, 'v', get_op_char(oap->op_type), - get_extra_op_char(oap->op_type), nchar); + + if (opchar == 'g' && extra_opchar == '@') { + // also repeat the count for 'operatorfunc' + prep_redo_num2(oap->regname, 0L, NUL, 'v', cap->count0, opchar, extra_opchar, nchar); + } else { + prep_redo(oap->regname, 0L, NUL, 'v', opchar, extra_opchar, nchar); + } } if (!redo_VIsual_busy) { - redo_VIsual_mode = resel_VIsual_mode; - redo_VIsual_vcol = resel_VIsual_vcol; - redo_VIsual_line_count = resel_VIsual_line_count; - redo_VIsual_count = cap->count0; - redo_VIsual_arg = cap->arg; + redo_VIsual.rv_mode = resel_VIsual_mode; + redo_VIsual.rv_vcol = resel_VIsual_vcol; + redo_VIsual.rv_line_count = resel_VIsual_line_count; + redo_VIsual.rv_count = cap->count0; + redo_VIsual.rv_arg = cap->arg; } } @@ -6735,12 +6763,20 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) op_format(oap, true); // use internal function break; - case OP_FUNCTION: + case OP_FUNCTION: { + redo_VIsual_T save_redo_VIsual = redo_VIsual; + // Restore linebreak, so that when the user edits it looks as // before. curwin->w_p_lbr = lbr_saved; - op_function(oap); // call 'operatorfunc' + // call 'operatorfunc' + op_function(oap); + + // Restore the info for redoing Visual mode, the function may + // invoke another operator and unintentionally change it. + redo_VIsual = save_redo_VIsual; break; + } case OP_INSERT: case OP_APPEND: @@ -6821,7 +6857,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } else { VIsual_active = true; curwin->w_p_lbr = lbr_saved; - op_addsub(oap, (linenr_T)cap->count1, redo_VIsual_arg); + op_addsub(oap, (linenr_T)cap->count1, redo_VIsual.rv_arg); VIsual_active = false; } check_cursor_col(); diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index c6c43aac92..581f025a0f 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -165,8 +165,7 @@ static char *signal_name(int signum) // This function handles deadly signals. // It tries to preserve any swap files and exit properly. // (partly from Elvis). -// NOTE: Avoid unsafe functions, such as allocating memory, they can result in -// a deadlock. +// NOTE: this is scheduled on the event loop, not called directly from a signal handler. static void deadly_signal(int signum) FUNC_ATTR_NORETURN { diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 304fd30b36..2aadc2258e 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1469,7 +1469,9 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att } // Copy the line into "buf" and append the start of the next line if - // possible. + // possible. Note: this ml_get_buf() may make "line" invalid, check + // for empty line first. + bool empty_line = *skipwhite((const char *)line) == NUL; STRCPY(buf, line); if (lnum < wp->w_buffer->b_ml.ml_line_count) { spell_cat_line(buf + STRLEN(buf), @@ -1613,7 +1615,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att --capcol; // But after empty line check first word in next line - if (*skipwhite((char *)line) == NUL) { + if (empty_line) { capcol = 0; } } @@ -2327,11 +2329,11 @@ char *did_set_spelllang(win_T *wp) } } } + redraw_later(wp, NOT_VALID); theend: xfree(spl_copy); recursive = false; - redraw_later(wp, NOT_VALID); return ret_msg; } @@ -7021,8 +7023,9 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) n = arridx[depth] + curi[depth]; ++curi[depth]; c = byts[n]; - if (c == 0) { - // End of word, deal with the word. + if (c == 0 || depth >= MAXWLEN - 1) { + // End of word or reached maximum length, deal with the + // word. // Don't use keep-case words in the fold-case tree, // they will appear in the keep-case tree. // Only use the word when the region matches. diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index a532c106ef..04eb9dd2bc 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -733,7 +733,7 @@ slang_T *spell_load_file(char_u *fname, char_u *lang, slang_T *old_lp, bool sile if (lp->sl_syllable == NULL) { goto endFAIL; } - if (init_syl_tab(lp) == FAIL) { + if (init_syl_tab(lp) != OK) { goto endFAIL; } break; @@ -2379,7 +2379,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) || cur_aff->ah_flag == aff->af_needcomp || cur_aff->ah_flag == aff->af_comproot) { smsg(_("Affix also used for " - "BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST" + "BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST " "in %s line %d: %s"), fname, lnum, items[1]); } diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index f1092af358..dfcde37f62 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -30,6 +30,10 @@ set switchbuf= mapclear mapclear! +" Make "Q" switch to Ex mode. +" This does not work for all tests. +nnoremap Q gQ + " Prevent Nvim log from writing to stderr. let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 660801d575..fcef57e47a 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -169,7 +169,9 @@ func Test_autocmd_bufunload_avoiding_SEGV_01() exe 'autocmd BufUnload <buffer> ' . (lastbuf + 1) . 'bwipeout!' augroup END - call assert_fails('edit bb.txt', 'E937:') + " Todo: check for E937 generated first + " call assert_fails('edit bb.txt', 'E937:') + call assert_fails('edit bb.txt', 'E517:') autocmd! test_autocmd_bufunload augroup! test_autocmd_bufunload @@ -540,7 +542,7 @@ func Test_three_windows() e Xtestje2 sp Xtestje1 call assert_fails('e', 'E937:') - call assert_equal('Xtestje2', expand('%')) + call assert_equal('Xtestje1', expand('%')) " Test changing buffers in a BufWipeout autocommand. If this goes wrong " there are ml_line errors and/or a Crash. @@ -563,7 +565,6 @@ func Test_three_windows() au! enew - bwipe! Xtestje1 call delete('Xtestje1') call delete('Xtestje2') call delete('Xtestje3') diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim index 7ba8ef3397..ccc8168c09 100644 --- a/src/nvim/testdir/test_cindent.vim +++ b/src/nvim/testdir/test_cindent.vim @@ -5306,11 +5306,20 @@ func Test_cindent_case() set cindent norm! f:a: call assert_equal('case x:: // x', getline(1)) - set cindent& bwipe! endfunc +" Test for changing multiple lines (using c) with cindent +func Test_cindent_change_multline() + new + setlocal cindent + call setline(1, ['if (a)', '{', ' i = 1;', '}']) + normal! jc3jm = 2; + call assert_equal("\tm = 2;", getline(2)) + close! +endfunc + " This was reading past the end of the line func Test_cindent_check_funcdecl() new diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index aedcad5296..8ee894c35f 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -1226,6 +1226,16 @@ func Test_cmdline_expand_home() call delete('Xdir', 'rf') endfunc +" Test for normal mode commands not supported in the cmd window +func Test_cmdwin_blocked_commands() + call assert_fails('call feedkeys("q:\<C-T>\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-]>\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-^>\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:Q\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:Z\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<F1>\<CR>", "xt")', 'E11:') +endfunc + " test that ";" works to find a match at the start of the first line func Test_zero_line_search() new diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index 275c8f7a15..c14afbe5b9 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -270,6 +270,10 @@ func Test_edit_10() call cursor(1, 4) call feedkeys("A\<s-home>start\<esc>", 'txin') call assert_equal(['startdef', 'ghi'], getline(1, '$')) + " start select mode again with gv + set selectmode=cmd + call feedkeys('gvabc', 'xt') + call assert_equal('abctdef', getline(1)) set selectmode= keymodel= bw! endfunc diff --git a/src/nvim/testdir/test_indent.vim b/src/nvim/testdir/test_indent.vim index 516b3fbdd1..3b5b643177 100644 --- a/src/nvim/testdir/test_indent.vim +++ b/src/nvim/testdir/test_indent.vim @@ -121,4 +121,159 @@ func Test_copyindent() close! endfunc +" Test for changing multiple lines with lisp indent +func Test_lisp_indent_change_multiline() + new + setlocal lisp autoindent + call setline(1, ['(if a', ' (if b', ' (return 5)))']) + normal! jc2j(return 4)) + call assert_equal(' (return 4))', getline(2)) + close! +endfunc + +func Test_lisp_indent() + new + setlocal lisp autoindent + call setline(1, ['(if a', ' ;; comment', ' \ abc', '', ' " str1\', ' " st\b', ' (return 5)']) + normal! jo;; comment + normal! jo\ abc + normal! jo;; ret + normal! jostr1" + normal! jostr2" + call assert_equal([' ;; comment', ' ;; comment', ' \ abc', ' \ abc', '', ' ;; ret', ' " str1\', ' str1"', ' " st\b', ' str2"'], getline(2, 11)) + close! +endfunc + +func Test_lisp_indent_quoted() + " This was going past the end of the line + new + setlocal lisp autoindent + call setline(1, ['"[', '=']) + normal Gvk= + + bwipe! +endfunc + +" Test for setting the 'indentexpr' from a modeline +func Test_modeline_indent_expr() + let modeline = &modeline + set modeline + func GetIndent() + return line('.') * 2 + endfunc + call writefile(['# vim: indentexpr=GetIndent()'], 'Xfile.txt') + set modelineexpr + new Xfile.txt + call assert_equal('GetIndent()', &indentexpr) + exe "normal Oa\nb\n" + call assert_equal([' a', ' b'], getline(1, 2)) + + set modelineexpr& + delfunc GetIndent + let &modeline = modeline + close! + call delete('Xfile.txt') +endfunc + +func Test_indent_func_with_gq() + + function GetTeXIndent() + " Sample indent expression for TeX files + let lnum = prevnonblank(v:lnum - 1) + " At the start of the file use zero indent. + if lnum == 0 + return 0 + endif + let line = getline(lnum) + let ind = indent(lnum) + " Add a 'shiftwidth' after beginning of environments. + if line =~ '\\begin{center}' + let ind = ind + shiftwidth() + endif + return ind + endfunction + + new + setl et sw=2 sts=2 ts=2 tw=50 indentexpr=GetTeXIndent() + put =[ '\documentclass{article}', '', '\begin{document}', '', + \ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce ut enim non', + \ 'libero efficitur aliquet. Maecenas metus justo, facilisis convallis blandit', + \ 'non, semper eu urna. Suspendisse diam diam, iaculis faucibus lorem eu,', + \ 'fringilla condimentum lectus. Quisque euismod diam at convallis vulputate.', + \ 'Pellentesque laoreet tortor sit amet mauris euismod ornare. Sed varius', + \ 'bibendum orci vel vehicula. Pellentesque tempor, ipsum et auctor accumsan,', + \ 'metus lectus ultrices odio, sed elementum mi ante at arcu.', '', '\begin{center}', '', + \ 'Proin nec risus consequat nunc dapibus consectetur. Mauris lacinia est a augue', + \ 'tristique accumsan. Morbi pretium, felis molestie eleifend condimentum, arcu', + \ 'ipsum congue nisl, quis euismod purus libero in ante.', '', + \ 'Donec id semper purus.', + \ 'Suspendisse eget aliquam nunc. Maecenas fringilla mauris vitae maximus', + \ 'condimentum. Cras a quam in mi dictum eleifend at a lorem. Sed convallis', + \ 'ante a commodo facilisis. Nam suscipit vulputate odio, vel dapibus nisl', + \ 'dignissim facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et', + \ 'ultrices posuere cubilia curae;', '', ''] + 1d_ + call cursor(5, 1) + ka + call cursor(14, 1) + kb + norm! 'agqap + norm! 'bgqG + let expected = [ '\documentclass{article}', '', '\begin{document}', '', + \ 'Lorem ipsum dolor sit amet, consectetur adipiscing', + \ 'elit. Fusce ut enim non libero efficitur aliquet.', + \ 'Maecenas metus justo, facilisis convallis blandit', + \ 'non, semper eu urna. Suspendisse diam diam,', + \ 'iaculis faucibus lorem eu, fringilla condimentum', + \ 'lectus. Quisque euismod diam at convallis', + \ 'vulputate. Pellentesque laoreet tortor sit amet', + \ 'mauris euismod ornare. Sed varius bibendum orci', + \ 'vel vehicula. Pellentesque tempor, ipsum et auctor', + \ 'accumsan, metus lectus ultrices odio, sed', + \ 'elementum mi ante at arcu.', '', '\begin{center}', '', + \ ' Proin nec risus consequat nunc dapibus', + \ ' consectetur. Mauris lacinia est a augue', + \ ' tristique accumsan. Morbi pretium, felis', + \ ' molestie eleifend condimentum, arcu ipsum congue', + \ ' nisl, quis euismod purus libero in ante.', + \ '', + \ ' Donec id semper purus. Suspendisse eget aliquam', + \ ' nunc. Maecenas fringilla mauris vitae maximus', + \ ' condimentum. Cras a quam in mi dictum eleifend', + \ ' at a lorem. Sed convallis ante a commodo', + \ ' facilisis. Nam suscipit vulputate odio, vel', + \ ' dapibus nisl dignissim facilisis. Vestibulum', + \ ' ante ipsum primis in faucibus orci luctus et', + \ ' ultrices posuere cubilia curae;', '', ''] + call assert_equal(expected, getline(1, '$')) + + bwipe! + delmark ab + delfunction GetTeXIndent +endfu + +func Test_formatting_keeps_first_line_indent() + let lines =<< trim END + foo() + { + int x; // manually positioned + // more text that will be formatted + // but not reindented + END + new + call setline(1, lines) + setlocal sw=4 cindent tw=45 et + normal! 4Ggqj + let expected =<< trim END + foo() + { + int x; // manually positioned + // more text that will be + // formatted but not + // reindented + END + call assert_equal(expected, getline(1, '$')) + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index b44d65c2a4..82ba2cfd48 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -115,8 +115,8 @@ func Test_normal01_keymodel() bw! endfunc +" Test for select mode func Test_normal02_selectmode() - " some basic select mode tests call Setup_NewWindow() 50 norm! gHy @@ -345,7 +345,7 @@ func Test_normal08_fold() bw! endfunc -func Test_normal09_operatorfunc() +func Test_normal09a_operatorfunc() " Test operatorfunc call Setup_NewWindow() " Add some spaces for counting @@ -375,7 +375,7 @@ func Test_normal09_operatorfunc() bw! endfunc -func Test_normal09a_operatorfunc() +func Test_normal09b_operatorfunc() " Test operatorfunc call Setup_NewWindow() " Add some spaces for counting @@ -397,10 +397,45 @@ func Test_normal09a_operatorfunc() " clean up unmap <buffer> ,, set opfunc= + call assert_fails('normal Vg@', 'E774:') bw! unlet! g:opt endfunc +func OperatorfuncRedo(_) + let g:opfunc_count = v:count +endfunc + +func Underscorize(_) + normal! '[V']r_ +endfunc + +func Test_normal09c_operatorfunc() + " Test redoing operatorfunc + new + call setline(1, 'some text') + set operatorfunc=OperatorfuncRedo + normal v3g@ + call assert_equal(3, g:opfunc_count) + let g:opfunc_count = 0 + normal . + call assert_equal(3, g:opfunc_count) + + bw! + unlet g:opfunc_count + + " Test redoing Visual mode + set operatorfunc=Underscorize + new + call setline(1, ['first', 'first', 'third', 'third', 'second']) + normal! 1GVjg@ + normal! 5G. + normal! 3G. + call assert_equal(['_____', '_____', '_____', '_____', '______'], getline(1, '$')) + bwipe! + set operatorfunc= +endfunc + func Test_normal10_expand() " Test for expand() 10new @@ -456,8 +491,12 @@ func Test_normal12_nv_error() 10new call setline(1, range(1,5)) " should not do anything, just beep - exe "norm! <c-k>" + call assert_beeps('exe "norm! <c-k>"') call assert_equal(map(range(1,5), 'string(v:val)'), getline(1,'$')) + call assert_beeps('normal! G2dd') + call assert_beeps("normal! g\<C-A>") + call assert_beeps("normal! g\<C-X>") + call assert_beeps("normal! g\<C-B>") bw! endfunc @@ -1654,6 +1693,14 @@ fun! Test_normal30_changecase() throw 'Skipped: Turkish locale not available' endtry + call setline(1, ['aaaaaa', 'aaaaaa']) + normal! gg10~ + call assert_equal(['AAAAAA', 'aaaaaa'], getline(1, 2)) + set whichwrap+=~ + normal! gg10~ + call assert_equal(['aaaaaa', 'AAAAaa'], getline(1, 2)) + set whichwrap& + " clean up bw! endfunc @@ -1683,8 +1730,8 @@ fun! Test_normal31_r_cmd() bw! endfunc +" Test for g*, g# func Test_normal32_g_cmd1() - " Test for g*, g# new call append(0, ['abc.x_foo', 'x_foobar.abc']) 1 @@ -1699,11 +1746,12 @@ func Test_normal32_g_cmd1() bw! endfunc +" Test for g`, g;, g,, g&, gv, gk, gj, gJ, g0, g^, g_, gm, g$, gM, g CTRL-G, +" gi and gI commands fun! Test_normal33_g_cmd2() if !has("jumplist") return endif - " Tests for g cmds call Setup_NewWindow() " Test for g` clearjumps @@ -1715,6 +1763,10 @@ fun! Test_normal33_g_cmd2() call assert_equal('>', a[-1:]) call assert_equal(1, line('.')) call assert_equal('1', getline('.')) + call cursor(10, 1) + norm! g'a + call assert_equal('>', a[-1:]) + call assert_equal(1, line('.')) " Test for g; and g, norm! g; @@ -1756,14 +1808,20 @@ fun! Test_normal33_g_cmd2() exe "norm! G0\<c-v>4k4ly" exe "norm! gvood" call assert_equal(['', 'abfgh', 'abfgh', 'abfgh', 'fgh', 'fgh', 'fgh', 'fgh', 'fgh'], getline(1,'$')) + " gv cannot be used in operator pending mode + call assert_beeps('normal! cgv') + " gv should beep without a previously selected visual area + new + call assert_beeps('normal! gv') + close " Test for gk/gj %d 15vsp set wrap listchars= sbr= - let lineA='abcdefghijklmnopqrstuvwxyz' - let lineB='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' - let lineC='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + let lineA = 'abcdefghijklmnopqrstuvwxyz' + let lineB = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + let lineC = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' $put =lineA $put =lineB @@ -1796,8 +1854,39 @@ fun! Test_normal33_g_cmd2() norm! g^yl call assert_equal(15, col('.')) call assert_equal('l', getreg(0)) + call assert_beeps('normal 5g$') + + " Test for g$ with double-width character half displayed + vsplit + 9wincmd | + setlocal nowrap nonumber + call setline(2, 'asdfasdfヨ') + 2 + normal 0g$ + call assert_equal(8, col('.')) + 10wincmd | + normal 0g$ + call assert_equal(9, col('.')) + + setlocal signcolumn=yes + 11wincmd | + normal 0g$ + call assert_equal(8, col('.')) + 12wincmd | + normal 0g$ + call assert_equal(9, col('.')) - norm! 2ggdd + close + + " Test for g_ + call assert_beeps('normal! 100g_') + call setline(2, [' foo ', ' foobar ']) + normal! 2ggg_ + call assert_equal(5, col('.')) + normal! 2g_ + call assert_equal(8, col('.')) + + norm! 2ggdG $put =lineC " Test for gM @@ -1839,6 +1928,17 @@ fun! Test_normal33_g_cmd2() $put ='third line' norm! gi another word call assert_equal(['foobar next word another word', 'new line', 'third line'], getline(1,'$')) + call setline(1, 'foobar') + normal! Ggifirst line + call assert_equal('foobarfirst line', getline(1)) + " Test gi in 'virtualedit' mode with cursor after the end of the line + set virtualedit=all + call setline(1, 'foo') + exe "normal! Abar\<Right>\<Right>\<Right>\<Right>" + call setline(1, 'foo') + normal! Ggifirst line + call assert_equal('foo first line', getline(1)) + set virtualedit& " clean up bw! @@ -1927,8 +2027,8 @@ func Test_g_ctrl_g() bwipe! endfunc +" Test for g8 fun! Test_normal34_g_cmd3() - " Test for g8 new let a=execute(':norm! 1G0g8') call assert_equal("\nNUL", a) @@ -1945,11 +2045,10 @@ fun! Test_normal34_g_cmd3() bw! endfunc +" Test 8g8 which finds invalid utf8 at or after the cursor. func Test_normal_8g8() new - " Test 8g8 which finds invalid utf8 at or after the cursor. - " With invalid byte. call setline(1, "___\xff___") norm! 1G08g8g @@ -1978,8 +2077,8 @@ func Test_normal_8g8() bw! endfunc +" Test for g< 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" @@ -1988,6 +2087,7 @@ fun! Test_normal35_g_cmd4() call assert_true(!empty(b), 'failed `execute(g<)`') endfunc +" Test for gp gP go fun! Test_normal36_g_cmd5() new call append(0, 'abcdefghijklmnopqrstuvwxyz') @@ -2026,8 +2126,8 @@ fun! Test_normal36_g_cmd5() bw! endfunc +" Test for gt and gT fun! Test_normal37_g_cmd6() - " basic test for gt and gT tabnew 1.txt tabnew 2.txt tabnew 3.txt @@ -2050,11 +2150,11 @@ fun! Test_normal37_g_cmd6() tabclose endfor " clean up - call assert_fails(':tabclose', 'E784') + call assert_fails(':tabclose', 'E784:') endfunc +" Test for <Home> and <C-Home> key fun! Test_normal38_nvhome() - " Test for <Home> and <C-Home> key new call setline(1, range(10)) $ @@ -2069,11 +2169,14 @@ fun! Test_normal38_nvhome() call assert_equal([0, 5, 1, 0, 1], getcurpos()) exe "norm! \<c-home>" call assert_equal([0, 1, 1, 0, 1], getcurpos()) + exe "norm! G\<c-kHome>" + call assert_equal([0, 1, 1, 0, 1], getcurpos()) " clean up bw! endfunc +" Test for cw cW ce fun! Test_normal39_cw() " Test for cw and cW on whitespace " and cpo+=w setting @@ -2095,12 +2198,27 @@ fun! Test_normal39_cw() norm! 2gg0cwfoo call assert_equal('foo', getline('.')) + call setline(1, 'one; two') + call cursor(1, 1) + call feedkeys('cwvim', 'xt') + call assert_equal('vim; two', getline(1)) + call feedkeys('0cWone', 'xt') + call assert_equal('one two', getline(1)) + "When cursor is at the end of a word 'ce' will change until the end of the + "next word, but 'cw' will change only one character + call setline(1, 'one two') + call feedkeys('0ecwce', 'xt') + call assert_equal('once two', getline(1)) + call setline(1, 'one two') + call feedkeys('0ecely', 'xt') + call assert_equal('only', getline(1)) + " clean up bw! endfunc +" Test for CTRL-\ commands fun! Test_normal40_ctrl_bsl() - " Basic test for CTRL-\ commands new call append(0, 'here are some words') exe "norm! 1gg0a\<C-\>\<C-N>" @@ -2124,9 +2242,8 @@ fun! Test_normal40_ctrl_bsl() bw! endfunc +" Test for <c-r>=, <c-r><c-r>= and <c-r><c-o>= in insert mode 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", '', '', '']) @@ -2144,8 +2261,8 @@ fun! Test_normal41_insert_reg() bw! endfunc +" Test for Ctrl-D and Ctrl-U func Test_normal42_halfpage() - " basic test for Ctrl-D and Ctrl-U call Setup_NewWindow() call assert_equal(5, &scroll) exe "norm! \<c-d>" @@ -2181,8 +2298,8 @@ func Test_normal42_halfpage() bw! endfunc +" Tests for text object aw fun! Test_normal43_textobject1() - " basic tests for text object aw new call append(0, ['foobar,eins,foobar', 'foo,zwei,foo ']) " diw @@ -2212,8 +2329,8 @@ fun! Test_normal43_textobject1() bw! endfunc +" Test for is and as text objects 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 @@ -2504,6 +2621,18 @@ func Test_gr_command() normal 4gro call assert_equal('ooooecond line', getline(2)) let &cpo = save_cpo + normal! ggvegrx + call assert_equal('xxxxx line', getline(1)) + exe "normal! gggr\<C-V>122" + call assert_equal('zxxxx line', getline(1)) + set virtualedit=all + normal! 15|grl + call assert_equal('zxxxx line l', getline(1)) + set virtualedit& + set nomodifiable + call assert_fails('normal! grx', 'E21:') + call assert_fails('normal! gRx', 'E21:') + set modifiable& enew! endfunc @@ -2701,10 +2830,11 @@ fun! Test_normal_gdollar_cmd() bw! endfunc -func Test_normal_gk() +func Test_normal_gk_gj() " needs 80 column new window new vert 80new + call assert_beeps('normal gk') put =[repeat('x',90)..' {{{1', 'x {{{1'] norm! gk " In a 80 column wide terminal the window will be only 78 char @@ -2719,12 +2849,12 @@ func Test_normal_gk() norm! gk call assert_equal(95, col('.')) call assert_equal(95, virtcol('.')) - bw! - bw! + %bw! " needs 80 column new window new vert 80new + call assert_beeps('normal gj') set number set numberwidth=10 set cpoptions+=n @@ -2743,9 +2873,14 @@ func Test_normal_gk() call assert_equal(1, col('.')) norm! gj call assert_equal(76, col('.')) - bw! - bw! - set cpoptions& number& numberwidth& + " When 'nowrap' is set, gk and gj behave like k and j + set nowrap + normal! gk + call assert_equal([2, 76], [line('.'), col('.')]) + normal! gj + call assert_equal([3, 76], [line('.'), col('.')]) + %bw! + set cpoptions& number& numberwidth& wrap& endfunc " Test for cursor movement with '-' in 'cpoptions' @@ -2765,6 +2900,54 @@ func Test_normal_cpo_minus() close! endfunc +" Test for using : to run a multi-line Ex command in operator pending mode +func Test_normal_yank_with_excmd() + new + call setline(1, ['foo', 'bar', 'baz']) + let @a = '' + call feedkeys("\"ay:if v:true\<CR>normal l\<CR>endif\<CR>", 'xt') + call assert_equal('f', @a) + close! +endfunc + +" Test for supplying a count to a normal-mode command across a cursorhold call +func Test_normal_cursorhold_with_count() + throw 'Skipped: Nvim removed <CursorHold> key' + func s:cHold() + let g:cHold_Called += 1 + endfunc + new + augroup normalcHoldTest + au! + au CursorHold <buffer> call s:cHold() + augroup END + let g:cHold_Called = 0 + call feedkeys("3\<CursorHold>2ix", 'xt') + call assert_equal(1, g:cHold_Called) + call assert_equal(repeat('x', 32), getline(1)) + augroup normalcHoldTest + au! + augroup END + au! normalcHoldTest + close! + delfunc s:cHold +endfunc + +" Test for using a count and a command with CTRL-W +func Test_wincmd_with_count() + call feedkeys("\<C-W>12n", 'xt') + call assert_equal(12, winheight(0)) +endfunc + +" Test for 'b', 'B' 'ge' and 'gE' commands +func Test_backward_motion() + normal! gg + call assert_beeps('normal! b') + call assert_beeps('normal! B') + call assert_beeps('normal! gE') + call assert_beeps('normal! ge') +endfunc + " Some commands like yy, cc, dd, >>, << and !! accept a count after " typing the first letter of the command. func Test_normal_count_after_operator() diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index d0895a48b4..5099818384 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -16,6 +16,8 @@ func TearDown() call delete('Xtest.latin1.add.spl') call delete('Xtest.latin1.spl') call delete('Xtest.latin1.sug') + " set 'encoding' to clear the word list + set encoding=utf-8 endfunc func Test_wrap_search() @@ -131,6 +133,106 @@ foobar/? set spell& endfunc +func Test_spell_file_missing() + let s:spell_file_missing = 0 + augroup TestSpellFileMissing + autocmd! SpellFileMissing * let s:spell_file_missing += 1 + augroup END + + set spell spelllang=ab_cd + let messages = GetMessages() + " This message is not shown in Nvim because of #3027 + " call assert_equal('Warning: Cannot find word list "ab.utf-8.spl" or "ab.ascii.spl"', messages[-1]) + call assert_equal(1, s:spell_file_missing) + + new XTestSpellFileMissing + augroup TestSpellFileMissing + autocmd! SpellFileMissing * bwipe + augroup END + call assert_fails('set spell spelllang=ab_cd', 'E797:') + + " clean up + augroup TestSpellFileMissing + autocmd! SpellFileMissing + augroup END + augroup! TestSpellFileMissing + unlet s:spell_file_missing + set spell& spelllang& + %bwipe! +endfunc + +func Test_spelldump() + " In case the spell file is not found avoid getting the download dialog, we + " would get stuck at the prompt. + let g:en_not_found = 0 + augroup TestSpellFileMissing + au! SpellFileMissing * let g:en_not_found = 1 + augroup END + set spell spelllang=en + spellrare! emacs + if g:en_not_found + call assert_report("Could not find English spell file") + else + spelldump + + " Check assumption about region: 1: us, 2: au, 3: ca, 4: gb, 5: nz. + call assert_equal('/regions=usaucagbnz', getline(1)) + call assert_notequal(0, search('^theater/1$')) " US English only. + call assert_notequal(0, search('^theatre/2345$')) " AU, CA, GB or NZ English. + + call assert_notequal(0, search('^emacs/?$')) " ? for a rare word. + call assert_notequal(0, search('^the the/!$')) " ! for a wrong word. + endif + + " clean up + unlet g:en_not_found + augroup TestSpellFileMissing + autocmd! SpellFileMissing + augroup END + augroup! TestSpellFileMissing + bwipe + set spell& +endfunc + +func Test_spelldump_bang() + new + call setline(1, 'This is a sample sentence.') + redraw + + " In case the spell file is not found avoid getting the download dialog, we + " would get stuck at the prompt. + let g:en_not_found = 0 + augroup TestSpellFileMissing + au! SpellFileMissing * let g:en_not_found = 1 + augroup END + + set spell + + if g:en_not_found + call assert_report("Could not find English spell file") + else + redraw + spelldump! + + " :spelldump! includes the number of times a word was found while updating + " the screen. + " Common word count starts at 10, regular word count starts at 0. + call assert_notequal(0, search("^is\t11$")) " common word found once. + call assert_notequal(0, search("^the\t10$")) " common word never found. + call assert_notequal(0, search("^sample\t1$")) " regular word found once. + call assert_equal(0, search("^screen\t")) " regular word never found. + endif + + " clean up + unlet g:en_not_found + augroup TestSpellFileMissing + autocmd! SpellFileMissing + augroup END + augroup! TestSpellFileMissing + %bwipe! + set spell& +endfunc + func Test_spelllang_inv_region() set spell spelllang=en_xx let messages = GetMessages() @@ -185,6 +287,18 @@ func Test_spellreall() bwipe! endfunc +func Test_spell_dump_word_length() + " this was running over MAXWLEN + new + noremap 0 0a0zW0000000 + sil! norm 0z=0 + sil norm 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + sil! norm 0z=0 + + bwipe! + nunmap 0 +endfunc + " Test spellsuggest({word} [, {max} [, {capital}]]) func Test_spellsuggest() " Verify suggestions are given even when spell checking is not enabled. @@ -626,6 +740,10 @@ func Test_zz_sal_and_addition() set spl=Xtest_ca.latin1.spl call assert_equal("elequint", FirstSpellWord()) call assert_equal("elekwint", SecondSpellWord()) + + bwipe! + set spellfile= + set spl& endfunc func Test_spellfile_value() @@ -709,9 +827,6 @@ func Test_spell_good_word_invalid() sil! norm z= bwipe! - " clear the internal word list - " set enc=latin1 - set enc=utf-8 endfunc func LoadAffAndDic(aff_contents, dic_contents) diff --git a/src/nvim/testdir/test_spell_utf8.vim b/src/nvim/testdir/test_spell_utf8.vim index 3d240a8f2c..b7e3da37cb 100644 --- a/src/nvim/testdir/test_spell_utf8.vim +++ b/src/nvim/testdir/test_spell_utf8.vim @@ -13,6 +13,8 @@ func TearDown() call delete('Xtest.utf-8.add.spl') call delete('Xtest.utf-8.spl') call delete('Xtest.utf-8.sug') + " set 'encoding' to clear the word list + set encoding=utf-8 endfunc let g:test_data_aff1 = [ @@ -484,7 +486,6 @@ let g:test_data_aff_sal = [ \ ] func LoadAffAndDic(aff_contents, dic_contents) - set enc=utf-8 set spellfile= call writefile(a:aff_contents, "Xtest.aff") call writefile(a:dic_contents, "Xtest.dic") @@ -760,6 +761,7 @@ func Test_spell_sal_and_addition() call assert_equal("elequint", FirstSpellWord()) call assert_equal("elekwint", SecondSpellWord()) + bwipe! set spellfile= set spl& endfunc @@ -803,10 +805,20 @@ func Test_word_index() sil norm z= bwipe! - " clear the word list - set enc=utf-8 call delete('Xtmpfile') endfunc +func Test_check_empty_line() + " This was using freed memory + enew + spellgood! fl + norm z= + norm yy + sil! norm P]svc + norm P]s + + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_spellfile.vim b/src/nvim/testdir/test_spellfile.vim index 0f48ab8f6f..f1331a9f2a 100644 --- a/src/nvim/testdir/test_spellfile.vim +++ b/src/nvim/testdir/test_spellfile.vim @@ -167,10 +167,604 @@ func Test_spell_normal() call assert_equal([], glob('Xspellfile.add',0,1)) call assert_equal([], glob('Xspellfile2.add',0,1)) - set spellfile= + set spellfile= spell& spelllang& bw! endfunc +" Spell file content test. Write 'content' to the spell file prefixed by the +" spell file header and then enable spell checking. If 'emsg' is not empty, +" then check for error. +func Spellfile_Test(content, emsg) + let splfile = './Xtest/spell/Xtest.utf-8.spl' + " Add the spell file header and version (VIMspell2) + let v = 0z56494D7370656C6C32 + a:content + call writefile(v, splfile, 'b') + set runtimepath=./Xtest + set spelllang=Xtest + if a:emsg != '' + call assert_fails('set spell', a:emsg) + else + " FIXME: With some invalid spellfile contents, there are no error + " messages. So don't know how to check for the test result. + set spell + endif + set nospell spelllang& rtp& +endfunc + +" Test for spell file format errors. +" The spell file format is described in spellfile.c +func Test_spellfile_format_error() + let save_rtp = &rtp + call mkdir('Xtest/spell', 'p') + let splfile = './Xtest/spell/Xtest.utf-8.spl' + + " empty spell file + call writefile([], splfile) + set runtimepath=./Xtest + set spelllang=Xtest + call assert_fails('set spell', 'E757:') + set nospell spelllang& + + " invalid file ID + call writefile(0z56494D, splfile, 'b') + set runtimepath=./Xtest + set spelllang=Xtest + call assert_fails('set spell', 'E757:') + set nospell spelllang& + + " missing version number + call writefile(0z56494D7370656C6C, splfile, 'b') + set runtimepath=./Xtest + set spelllang=Xtest + call assert_fails('set spell', 'E771:') + set nospell spelllang& + + " invalid version number + call writefile(0z56494D7370656C6C7A, splfile, 'b') + set runtimepath=./Xtest + set spelllang=Xtest + call assert_fails('set spell', 'E772:') + set nospell spelllang& + + " no sections + call Spellfile_Test(0z, 'E758:') + + " missing section length + call Spellfile_Test(0z00, 'E758:') + + " unsupported required section + call Spellfile_Test(0z7A0100000004, 'E770:') + + " unsupported not-required section + call Spellfile_Test(0z7A0000000004, 'E758:') + + " SN_REGION: invalid number of region names + call Spellfile_Test(0z0000000000FF, 'E759:') + + " SN_CHARFLAGS: missing <charflagslen> length + call Spellfile_Test(0z010000000004, 'E758:') + + " SN_CHARFLAGS: invalid <charflagslen> length + call Spellfile_Test(0z0100000000010201, '') + + " SN_CHARFLAGS: charflagslen == 0 and folcharslen != 0 + call Spellfile_Test(0z01000000000400000101, 'E759:') + + " SN_CHARFLAGS: missing <folcharslen> length + call Spellfile_Test(0z01000000000100, 'E758:') + + " SN_PREFCOND: invalid prefcondcnt + call Spellfile_Test(0z03000000000100, 'E759:') + + " SN_PREFCOND: invalid condlen + call Spellfile_Test(0z0300000000020001, 'E759:') + + " SN_REP: invalid repcount + call Spellfile_Test(0z04000000000100, 'E758:') + + " SN_REP: missing rep + call Spellfile_Test(0z0400000000020004, 'E758:') + + " SN_REP: zero repfromlen + call Spellfile_Test(0z040000000003000100, 'E759:') + + " SN_REP: invalid reptolen + call Spellfile_Test(0z0400000000050001014101, '') + + " SN_REP: zero reptolen + call Spellfile_Test(0z0400000000050001014100, 'E759:') + + " SN_SAL: missing salcount + call Spellfile_Test(0z05000000000102, 'E758:') + + " SN_SAL: missing salfromlen + call Spellfile_Test(0z050000000003080001, 'E758:') + + " SN_SAL: missing saltolen + call Spellfile_Test(0z0500000000050400010161, 'E758:') + + " SN_WORDS: non-NUL terminated word + call Spellfile_Test(0z0D000000000376696D, 'E758:') + + " SN_WORDS: very long word + let v = eval('0z0D000000012C' .. repeat('41', 300)) + call Spellfile_Test(v, 'E759:') + + " SN_SOFO: missing sofofromlen + call Spellfile_Test(0z06000000000100, 'E758:') + + " SN_SOFO: missing sofotolen + call Spellfile_Test(0z06000000000400016100, 'E758:') + + " SN_SOFO: missing sofoto + call Spellfile_Test(0z0600000000050001610000, 'E759:') + + " SN_COMPOUND: compmax is less than 2 + call Spellfile_Test(0z08000000000101, 'E759:') + + " SN_COMPOUND: missing compsylmax and other options + call Spellfile_Test(0z0800000000020401, 'E759:') + + " SN_COMPOUND: missing compoptions + call Spellfile_Test(0z080000000005040101, 'E758:') + + " SN_INFO: missing info + call Spellfile_Test(0z0F0000000005040101, '') + + " SN_MIDWORD: missing midword + call Spellfile_Test(0z0200000000040102, '') + + " SN_MAP: missing midword + call Spellfile_Test(0z0700000000040102, '') + + " SN_SYLLABLE: missing SYLLABLE item + call Spellfile_Test(0z0900000000040102, '') + + " SN_SYLLABLE: More than SY_MAXLEN size + let v = eval('0z090000000022612F' .. repeat('62', 32)) + call Spellfile_Test(v, '') + + " LWORDTREE: missing + call Spellfile_Test(0zFF, 'E758:') + + " LWORDTREE: missing tree node + call Spellfile_Test(0zFF00000004, 'E758:') + + " LWORDTREE: missing tree node value + call Spellfile_Test(0zFF0000000402, 'E758:') + + " KWORDTREE: missing tree node + call Spellfile_Test(0zFF0000000000000004, 'E758:') + + " PREFIXTREE: missing tree node + call Spellfile_Test(0zFF000000000000000000000004, 'E758:') + + let &rtp = save_rtp + call delete('Xtest', 'rf') +endfunc + +" Test for format errors in suggest file +func Test_sugfile_format_error() + let save_rtp = &rtp + call mkdir('Xtest/spell', 'p') + let splfile = './Xtest/spell/Xtest.utf-8.spl' + let sugfile = './Xtest/spell/Xtest.utf-8.sug' + + " create an empty spell file with a suggest timestamp + call writefile(0z56494D7370656C6C320B00000000080000000000000044FF000000000000000000000000, splfile, 'b') + + " 'encoding' is set before each test to clear the previously loaded suggest + " file from memory. + + " empty suggest file + set encoding=utf-8 + call writefile([], sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E778:') + set nospell spelllang& + + " zero suggest version + set encoding=utf-8 + call writefile(0z56494D73756700, sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E779:') + set nospell spelllang& + + " unsupported suggest version + set encoding=utf-8 + call writefile(0z56494D7375671F, sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E780:') + set nospell spelllang& + + " missing suggest timestamp + set encoding=utf-8 + call writefile(0z56494D73756701, sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E781:') + set nospell spelllang& + + " incorrect suggest timestamp + set encoding=utf-8 + call writefile(0z56494D7375670100000000000000FF, sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E781:') + set nospell spelllang& + + " missing suggest wordtree + set encoding=utf-8 + call writefile(0z56494D737567010000000000000044, sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E782:') + set nospell spelllang& + + " invalid suggest word count in SUGTABLE + set encoding=utf-8 + call writefile(0z56494D7375670100000000000000440000000022, sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E782:') + set nospell spelllang& + + " missing sugline in SUGTABLE + set encoding=utf-8 + call writefile(0z56494D7375670100000000000000440000000000000005, sugfile) + set runtimepath=./Xtest + set spelllang=Xtest + set spell + call assert_fails("let s = spellsuggest('abc')", 'E782:') + set nospell spelllang& + + let &rtp = save_rtp + call delete('Xtest', 'rf') +endfunc + +" Test for using :mkspell to create a spell file from a list of words +func Test_wordlist_dic() + " duplicate encoding + let lines =<< trim [END] + # This is an example word list + + /encoding=latin1 + /encoding=latin1 + example + [END] + call writefile(lines, 'Xwordlist.dic') + let output = execute('mkspell Xwordlist.spl Xwordlist.dic') + call assert_match('Duplicate /encoding= line ignored in Xwordlist.dic line 4: /encoding=latin1', output) + + " multiple encoding for a word + let lines =<< trim [END] + example + /encoding=latin1 + example + [END] + call writefile(lines, 'Xwordlist.dic') + let output = execute('mkspell! Xwordlist.spl Xwordlist.dic') + call assert_match('/encoding= line after word ignored in Xwordlist.dic line 2: /encoding=latin1', output) + + " unsupported encoding for a word + let lines =<< trim [END] + /encoding=Xtest + example + [END] + call writefile(lines, 'Xwordlist.dic') + let output = execute('mkspell! Xwordlist.spl Xwordlist.dic') + call assert_match('Conversion in Xwordlist.dic not supported: from Xtest to utf-8', output) + + " duplicate region + let lines =<< trim [END] + /regions=usca + /regions=usca + example + [END] + call writefile(lines, 'Xwordlist.dic') + let output = execute('mkspell! Xwordlist.spl Xwordlist.dic') + call assert_match('Duplicate /regions= line ignored in Xwordlist.dic line 2: regions=usca', output) + + " maximum regions + let lines =<< trim [END] + /regions=uscauscauscauscausca + example + [END] + call writefile(lines, 'Xwordlist.dic') + let output = execute('mkspell! Xwordlist.spl Xwordlist.dic') + call assert_match('Too many regions in Xwordlist.dic line 1: uscauscauscauscausca', output) + + " unsupported '/' value + let lines =<< trim [END] + /test=abc + example + [END] + call writefile(lines, 'Xwordlist.dic') + let output = execute('mkspell! Xwordlist.spl Xwordlist.dic') + call assert_match('/ line ignored in Xwordlist.dic line 1: /test=abc', output) + + " unsupported flag + let lines =<< trim [END] + example/+ + [END] + call writefile(lines, 'Xwordlist.dic') + let output = execute('mkspell! Xwordlist.spl Xwordlist.dic') + call assert_match('Unrecognized flags in Xwordlist.dic line 1: +', output) + + " non-ascii word + call writefile(["ʀʀ"], 'Xwordlist.dic') + let output = execute('mkspell! -ascii Xwordlist.spl Xwordlist.dic') + call assert_match('Ignored 1 words with non-ASCII characters', output) + + call delete('Xwordlist.spl') + call delete('Xwordlist.dic') +endfunc + +" Test for the :mkspell command +func Test_mkspell() + call assert_fails('mkspell Xtest_us.spl', 'E751:') + call assert_fails('mkspell a b c d e f g h i j k', 'E754:') + + call writefile([], 'Xtest.spl') + call writefile([], 'Xtest.dic') + call assert_fails('mkspell Xtest.spl Xtest.dic', 'E13:') + call delete('Xtest.spl') + call delete('Xtest.dic') + + call mkdir('Xtest.spl') + call assert_fails('mkspell! Xtest.spl Xtest.dic', 'E17:') + call delete('Xtest.spl', 'rf') + + call assert_fails('mkspell en en_US abc_xyz', 'E755:') +endfunc + +" Tests for :mkspell with a .dic and .aff file +func Test_aff_file_format_error() + " FIXME: For some reason, the :mkspell command below doesn't fail on the + " MS-Windows CI build. Disable this test on MS-Windows for now. + CheckNotMSWindows + + " No word count in .dic file + call writefile([], 'Xtest.dic') + call writefile([], 'Xtest.aff') + call assert_fails('mkspell! Xtest.spl Xtest', 'E760:') + + " create a .dic file for the tests below + call writefile(['1', 'work'], 'Xtest.dic') + + " Invalid encoding in .aff file + call writefile(['# comment', 'SET Xinvalidencoding'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Conversion in Xtest.aff not supported: from xinvalidencoding', output) + + " Invalid flag in .aff file + call writefile(['FLAG xxx'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Invalid value for FLAG in Xtest.aff line 1: xxx', output) + + " set FLAGS after using flag for an affix + call writefile(['SFX L Y 1', 'SFX L 0 re [^x]', 'FLAG long'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('FLAG after using flags in Xtest.aff line 3: long', output) + + " INFO in affix file + let save_encoding = &encoding + call mkdir('Xrtp/spell', 'p') + call writefile(['1', 'work'], 'Xrtp/spell/Xtest.dic') + call writefile(['NAME klingon', 'VERSION 1.4', 'AUTHOR Spock'], + \ 'Xrtp/spell/Xtest.aff') + silent mkspell! Xrtp/spell/Xtest.utf-8.spl Xrtp/spell/Xtest + let save_rtp = &rtp + set runtimepath=./Xrtp + set spelllang=Xtest + set spell + let output = split(execute('spellinfo'), "\n") + call assert_equal("NAME klingon", output[1]) + call assert_equal("VERSION 1.4", output[2]) + call assert_equal("AUTHOR Spock", output[3]) + let &rtp = save_rtp + call delete('Xrtp', 'rf') + set spell& spelllang& spellfile& + %bw! + " 'encoding' must be set again to clear the spell file in memory + let &encoding = save_encoding + + " COMPOUNDFORBIDFLAG flag after PFX in an affix file + call writefile(['PFX L Y 1', 'PFX L 0 re x', 'COMPOUNDFLAG c', 'COMPOUNDFORBIDFLAG x'], + \ 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in Xtest.aff line 4', output) + + " COMPOUNDPERMITFLAG flag after PFX in an affix file + call writefile(['PFX L Y 1', 'PFX L 0 re x', 'COMPOUNDPERMITFLAG c'], + \ 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in Xtest.aff line 3', output) + + " Wrong COMPOUNDRULES flag value in an affix file + call writefile(['COMPOUNDRULES a'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Wrong COMPOUNDRULES value in Xtest.aff line 1: a', output) + + " Wrong COMPOUNDWORDMAX flag value in an affix file + call writefile(['COMPOUNDWORDMAX 0'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Wrong COMPOUNDWORDMAX value in Xtest.aff line 1: 0', output) + + " Wrong COMPOUNDMIN flag value in an affix file + call writefile(['COMPOUNDMIN 0'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Wrong COMPOUNDMIN value in Xtest.aff line 1: 0', output) + + " Wrong COMPOUNDSYLMAX flag value in an affix file + call writefile(['COMPOUNDSYLMAX 0'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Wrong COMPOUNDSYLMAX value in Xtest.aff line 1: 0', output) + + " Wrong CHECKCOMPOUNDPATTERN flag value in an affix file + call writefile(['CHECKCOMPOUNDPATTERN 0'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Wrong CHECKCOMPOUNDPATTERN value in Xtest.aff line 1: 0', output) + + " Duplicate affix entry in an affix file + call writefile(['PFX L Y 1', 'PFX L 0 re x', 'PFX L Y 1', 'PFX L 0 re x'], + \ 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Duplicate affix in Xtest.aff line 3: L', output) + + " Duplicate affix entry in an affix file + call writefile(['PFX L Y 1', 'PFX L Y 1'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Unrecognized or duplicate item in Xtest.aff line 2: PFX', output) + + " Different combining flags in an affix file + call writefile(['PFX L Y 1', 'PFX L 0 re x', 'PFX L N 1'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Different combining flag in continued affix block in Xtest.aff line 3', output) + + " Try to reuse a affix used for BAD flag + call writefile(['BAD x', 'PFX x Y 1', 'PFX x 0 re x'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in Xtest.aff line 2: x', output) + + " Trailing characters in an affix entry + call writefile(['PFX L Y 1 Test', 'PFX L 0 re x'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Trailing text in Xtest.aff line 1: Test', output) + + " Trailing characters in an affix entry + call writefile(['PFX L Y 1', 'PFX L 0 re x Test'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Trailing text in Xtest.aff line 2: Test', output) + + " Incorrect combine flag in an affix entry + call writefile(['PFX L X 1', 'PFX L 0 re x'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Expected Y or N in Xtest.aff line 1: X', output) + + " Invalid count for REP item + call writefile(['REP a'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Expected REP(SAL) count in Xtest.aff line 1', output) + + " Trailing characters in REP item + call writefile(['REP 1', 'REP f ph test'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Trailing text in Xtest.aff line 2: test', output) + + " Invalid count for MAP item + call writefile(['MAP a'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Expected MAP count in Xtest.aff line 1', output) + + " Duplicate character in a MAP item + call writefile(['MAP 2', 'MAP xx', 'MAP yy'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Duplicate character in MAP in Xtest.aff line 2', output) + + " Use COMPOUNDSYLMAX without SYLLABLE + call writefile(['COMPOUNDSYLMAX 2'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('COMPOUNDSYLMAX used without SYLLABLE', output) + + " Missing SOFOTO + call writefile(['SOFOFROM abcdef'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Missing SOFOTO line in Xtest.aff', output) + + " Length of SOFOFROM and SOFOTO differ + call writefile(['SOFOFROM abcde', 'SOFOTO ABCD'], 'Xtest.aff') + call assert_fails('mkspell! Xtest.spl Xtest', 'E759:') + + " Both SAL and SOFOFROM/SOFOTO items + call writefile(['SOFOFROM abcd', 'SOFOTO ABCD', 'SAL CIA X'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Both SAL and SOFO lines in Xtest.aff', output) + + " use an alphabet flag when FLAG is num + call writefile(['FLAG num', 'SFX L Y 1', 'SFX L 0 re [^x]'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Flag is not a number in Xtest.aff line 2: L', output) + + " use number and alphabet flag when FLAG is num + call writefile(['FLAG num', 'SFX 4f Y 1', 'SFX 4f 0 re [^x]'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Affix name too long in Xtest.aff line 2: 4f', output) + + " use a single character flag when FLAG is long + call writefile(['FLAG long', 'SFX L Y 1', 'SFX L 0 re [^x]'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('Illegal flag in Xtest.aff line 2: L', output) + + " duplicate word in the .dic file + call writefile(['2', 'good', 'good', 'good'], 'Xtest.dic') + call writefile(['NAME vim'], 'Xtest.aff') + let output = execute('mkspell! Xtest.spl Xtest') + call assert_match('First duplicate word in Xtest.dic line 3: good', output) + call assert_match('2 duplicate word(s) in Xtest.dic', output) + + call delete('Xtest.dic') + call delete('Xtest.aff') + call delete('Xtest.spl') + call delete('Xtest.sug') +endfunc + +func Test_spell_add_word() + set spellfile= + call assert_fails('spellgood abc', 'E764:') + + set spellfile=Xtest.utf-8.add + call assert_fails('2spellgood abc', 'E765:') + + edit Xtest.utf-8.add + call setline(1, 'sample') + call assert_fails('spellgood abc', 'E139:') + set spellfile& + %bw! +endfunc + +" When 'spellfile' is not set, adding a new good word will automatically set +" the 'spellfile' +func Test_init_spellfile() + let save_rtp = &rtp + let save_encoding = &encoding + call mkdir('Xrtp/spell', 'p') + call writefile(['vim'], 'Xrtp/spell/Xtest.dic') + silent mkspell Xrtp/spell/Xtest.utf-8.spl Xrtp/spell/Xtest.dic + set runtimepath=./Xrtp + set spelllang=Xtest + set spell + silent spellgood abc + call assert_equal('./Xrtp/spell/Xtest.utf-8.add', &spellfile) + call assert_equal(['abc'], readfile('Xrtp/spell/Xtest.utf-8.add')) + call assert_true(filereadable('Xrtp/spell/Xtest.utf-8.spl')) + set spell& spelllang& spellfile& + call delete('Xrtp', 'rf') + let &encoding = save_encoding + let &rtp = save_rtp + %bw! +endfunc + +" Test for the 'mkspellmem' option +func Test_mkspellmem_opt() + call assert_fails('set mkspellmem=1000', 'E474:') + call assert_fails('set mkspellmem=1000,', 'E474:') + call assert_fails('set mkspellmem=1000,50', 'E474:') + call assert_fails('set mkspellmem=1000,50,', 'E474:') + call assert_fails('set mkspellmem=1000,50,10,', 'E474:') + call assert_fails('set mkspellmem=1000,50,0', 'E474:') +endfunc + " Test CHECKCOMPOUNDPATTERN (see :help spell-CHECKCOMPOUNDPATTERN) func Test_spellfile_CHECKCOMPOUNDPATTERN() call writefile(['4', diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index 8483435062..bd44079882 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -140,7 +140,7 @@ func Test_substitute_repeat() " This caused an invalid memory access. split Xfile s/^/x - call feedkeys("gQsc\<CR>y", 'tx') + call feedkeys("Qsc\<CR>y", 'tx') bwipe! endfunc diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 09eed4e10d..5c67efb831 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -279,7 +279,7 @@ func Test_ex_mode() endfunc let timer = timer_start(40, function('g:Foo'), {'repeat':-1}) " This used to throw error E749. - exe "normal gQsleep 100m\rvi\r" + exe "normal Qsleep 100m\rvi\r" call timer_stop(timer) endfunc diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim index 250b896532..522ca17675 100644 --- a/src/nvim/testdir/test_virtualedit.vim +++ b/src/nvim/testdir/test_virtualedit.vim @@ -301,6 +301,9 @@ func Test_replace_on_tab() call append(0, "'r'\t") normal gg^5lrxAy call assert_equal("'r' x y", getline(1)) + call setline(1, 'aaaaaaaaaaaa') + exe "normal! gg2lgR\<Tab>" + call assert_equal("aa\taaaa", getline(1)) bwipe! set virtualedit= endfunc diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 492750fa66..349c4fde50 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -700,7 +700,6 @@ func Test_linewise_select_mode() exe "normal GkkgH\<Del>" call assert_equal(['', 'b', 'c'], getline(1, '$')) - " linewise select mode: delete middle two lines call deletebufline('', 1, '$') call append('$', ['a', 'b', 'c']) @@ -722,6 +721,15 @@ func Test_linewise_select_mode() bwipe! endfunc +" Test for blockwise select mode (g CTRL-H) +func Test_blockwise_select_mode() + new + call setline(1, ['foo', 'bar']) + call feedkeys("g\<BS>\<Right>\<Down>mm", 'xt') + call assert_equal(['mmo', 'mmr'], getline(1, '$')) + close! +endfunc + func Test_visual_mode_put() new @@ -1105,6 +1113,73 @@ func Test_block_insert_replace_tabs() bwipe! endfunc +" Test for * register in : +func Test_star_register() + call assert_fails('*bfirst', 'E16:') + new + call setline(1, ['foo', 'bar', 'baz', 'qux']) + exe "normal jVj\<ESC>" + *yank r + call assert_equal("bar\nbaz\n", @r) + + delmarks < > + call assert_fails('*yank', 'E20:') + close! +endfunc + +" Test for using visual mode maps in select mode +func Test_select_mode_map() + new + vmap <buffer> <F2> 3l + call setline(1, 'Test line') + call feedkeys("gh\<F2>map", 'xt') + call assert_equal('map line', getline(1)) + + vmap <buffer> <F2> ygV + call feedkeys("0gh\<Right>\<Right>\<F2>cwabc", 'xt') + call assert_equal('abc line', getline(1)) + + vmap <buffer> <F2> :<C-U>let v=100<CR> + call feedkeys("gggh\<Right>\<Right>\<F2>foo", 'xt') + call assert_equal('foo line', getline(1)) + + " reselect the select mode using gv from a visual mode map + vmap <buffer> <F2> gv + set selectmode=cmd + call feedkeys("0gh\<F2>map", 'xt') + call assert_equal('map line', getline(1)) + set selectmode& + + close! +endfunc + +" Test for changing text in visual mode with 'exclusive' selection +func Test_exclusive_selection() + new + call setline(1, ['one', 'two']) + set selection=exclusive + call feedkeys("vwcabc", 'xt') + call assert_equal('abctwo', getline(1)) + call setline(1, ["\tone"]) + set virtualedit=all + call feedkeys('0v2lcl', 'xt') + call assert_equal('l one', getline(1)) + set virtualedit& + set selection& + close! +endfunc + +" Test for starting visual mode with a count +" This test should be run withou any previous visual modes. So this should be +" run as a first test. +func Test_AAA_start_visual_mode_with_count() + new + call setline(1, ['aaaaaaa', 'aaaaaaa', 'aaaaaaa', 'aaaaaaa']) + normal! gg2Vy + call assert_equal("aaaaaaa\naaaaaaa\n", @") + close! +endfunc + func Test_visual_put_in_block() new call setline(1, ['xxxx', 'y∞yy', 'zzzz']) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index f492792b20..68df5819e2 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -137,8 +137,7 @@ struct TUIData { char *space_buf; }; -static bool volatile got_winch = false; -static bool did_user_set_dimensions = false; +static int got_winch = 0; static bool cursor_style_enabled = false; #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -535,7 +534,7 @@ static void sigcont_cb(SignalWatcher *watcher, int signum, void *data) static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data) { - got_winch = true; + got_winch++; UI *ui = data; if (tui_is_stopped(ui)) { return; @@ -987,7 +986,7 @@ static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height) r->right = MIN(r->right, grid->width); } - if (!got_winch && (!data->is_starting || did_user_set_dimensions)) { + if (!got_winch && !data->is_starting) { // Resize the _host_ terminal. UNIBI_SET_NUM_VAR(data->params[0], (int)height); UNIBI_SET_NUM_VAR(data->params[1], (int)width); @@ -997,7 +996,7 @@ static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height) reset_scroll_region(ui, ui->width == grid->width); } } else { // Already handled the SIGWINCH signal; avoid double-resize. - got_winch = false; + got_winch = got_winch > 0 ? got_winch - 1 : 0; grid->row = -1; } } @@ -1504,23 +1503,13 @@ static void tui_guess_size(UI *ui) TUIData *data = ui->data; int width = 0, height = 0; - // 1 - look for non-default 'columns' and 'lines' options during startup - if (data->is_starting && (Columns != DFLT_COLS || Rows != DFLT_ROWS)) { - did_user_set_dimensions = true; - assert(Columns >= 0); - assert(Rows >= 0); - width = Columns; - height = Rows; - goto end; - } - - // 2 - try from a system call(ioctl/TIOCGWINSZ on unix) + // 1 - try from a system call(ioctl/TIOCGWINSZ on unix) if (data->out_isatty && !uv_tty_get_winsize(&data->output_handle.tty, &width, &height)) { goto end; } - // 3 - use $LINES/$COLUMNS if available + // 2 - use $LINES/$COLUMNS if available const char *val; int advance; if ((val = os_getenv("LINES")) @@ -1530,7 +1519,7 @@ static void tui_guess_size(UI *ui) goto end; } - // 4 - read from terminfo if available + // 3 - read from terminfo if available height = unibi_get_num(data->ut, unibi_lines); width = unibi_get_num(data->ut, unibi_columns); |