diff --git a/runtime/doc/spell.txt b/runtime/doc/spell.txt index 29e4a7b0aa..269d52352d 100644 --- a/runtime/doc/spell.txt +++ b/runtime/doc/spell.txt @@ -51,6 +51,17 @@ To search for the next misspelled word: *[S* [S Like "]S" but search backwards. + *]r* +]r Move to next "rare" word after the cursor. + A count before the command can be used to repeat. + 'wrapscan' applies. + + *[r* +[r Like "]r" but search backwards, find the "rare" + word before the cursor. Doesn't recognize words + split over two lines, thus may stop at words that are + not highlighted as rare. + To add words to your own word list: diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 283f7d9d61..07944081da 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -1415,7 +1415,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s pos_T pos = wp->w_cursor; wp->w_cursor.lnum = lnum; wp->w_cursor.col = linecol; - size_t len = spell_move_to(wp, FORWARD, true, true, &spell_hlf); + size_t len = spell_move_to(wp, FORWARD, SMT_ALL, true, &spell_hlf); // spell_move_to() may call ml_get() and make "line" invalid line = ml_get_buf(wp->w_buffer, lnum); diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 1fc80e88c4..0b4b79c6ca 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -8308,7 +8308,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, EvalFuncData fptr size_t len = 0; if (argvars[0].v_type == VAR_UNKNOWN) { // Find the start and length of the badly spelled word. - len = spell_move_to(curwin, FORWARD, true, true, &attr); + len = spell_move_to(curwin, FORWARD, SMT_ALL, true, &attr); if (len != 0) { word = get_cursor_pos_ptr(); curwin->w_set_curswant = true; diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 15ec0ab08d..c51b3b916d 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -4570,7 +4570,7 @@ void free_insexpand_stuff(void) static void spell_back_to_badword(void) { pos_T tpos = curwin->w_cursor; - spell_bad_len = spell_move_to(curwin, BACKWARD, true, true, NULL); + spell_bad_len = spell_move_to(curwin, BACKWARD, SMT_ALL, true, NULL); if (curwin->w_cursor.col != tpos.col) { start_arrow(&tpos); } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 2f1477b9d5..02bd3d05c1 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2710,7 +2710,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar) // off this fails and find_ident_under_cursor() is // used below. emsg_off++; - len = spell_move_to(curwin, FORWARD, true, true, NULL); + len = spell_move_to(curwin, FORWARD, SMT_ALL, true, NULL); emsg_off--; if (len != 0 && curwin->w_cursor.col <= pos.col) { ptr = ml_get_pos(&curwin->w_cursor); @@ -4272,12 +4272,15 @@ static void nv_brackets(cmdarg_T *cap) cap->count1) == false) { clearopbeep(cap->oap); } - } else if (cap->nchar == 's' || cap->nchar == 'S') { - // "[s", "[S", "]s" and "]S": move to next spell error. + } else if (cap->nchar == 'r' || cap->nchar == 's' || cap->nchar == 'S') { + // "[r", "[s", "[S", "]r", "]s" and "]S": move to next spell error. setpcmark(); for (n = 0; n < cap->count1; n++) { if (spell_move_to(curwin, cap->cmdchar == ']' ? FORWARD : BACKWARD, - cap->nchar == 's', false, NULL) == 0) { + cap->nchar == 's' + ? SMT_ALL + : cap->nchar == 'r' ? SMT_RARE : SMT_BAD, + false, NULL) == 0) { clearopbeep(cap->oap); break; } diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 190501028e..6f0c7bab8e 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1290,11 +1290,11 @@ static inline bool can_syn_spell(win_T *wp, linenr_T lnum, int col) /// to after badly spelled word before the cursor. /// /// @param dir FORWARD or BACKWARD -/// @param allwords true for "[s"/"]s", false for "[S"/"]S" +/// @param behaviour Behaviour of the function /// @param attrp return: attributes of bad word or NULL (only when "dir" is FORWARD) /// /// @return 0 if not found, length of the badly spelled word otherwise. -size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *attrp) +size_t spell_move_to(win_T *wp, int dir, smt_T behaviour, bool curline, hlf_T *attrp) { pos_T found_pos; size_t found_len = 0; @@ -1398,7 +1398,9 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att if (attr != HLF_COUNT) { // We found a bad word. Check the attribute. - if (allwords || attr == HLF_SPB) { + if (behaviour == SMT_ALL + || (behaviour == SMT_BAD && attr == HLF_SPB) + || (behaviour == SMT_RARE && attr == HLF_SPR)) { // When searching forward only accept a bad word after // the cursor. if (dir == BACKWARD diff --git a/src/nvim/spell.h b/src/nvim/spell.h index adbdd3705e..85e16d7f6c 100644 --- a/src/nvim/spell.h +++ b/src/nvim/spell.h @@ -21,6 +21,13 @@ extern char *e_format; extern char *repl_from; extern char *repl_to; +/// Values for behaviour in spell_move_to +typedef enum { + SMT_ALL = 0, ///< Move to "all" words + SMT_BAD, ///< Move to "bad" words only + SMT_RARE, ///< Move to "rare" words only +} smt_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "spell.h.generated.h" #endif diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c index 32480443c4..a7de20d14e 100644 --- a/src/nvim/spellsuggest.c +++ b/src/nvim/spellsuggest.c @@ -484,7 +484,7 @@ void spell_suggest(int count) badlen = get_cursor_line_len() - curwin->w_cursor.col; } // Find the start of the badly spelled word. - } else if (spell_move_to(curwin, FORWARD, true, true, NULL) == 0 + } else if (spell_move_to(curwin, FORWARD, SMT_ALL, true, NULL) == 0 || curwin->w_cursor.col > prev_cursor.col) { // No bad word or it starts after the cursor: use the word under the // cursor. diff --git a/test/old/testdir/test_spellrare.vim b/test/old/testdir/test_spellrare.vim new file mode 100644 index 0000000000..bbb13c27c2 --- /dev/null +++ b/test/old/testdir/test_spellrare.vim @@ -0,0 +1,61 @@ +" Test spell checking + +source check.vim +CheckFeature spell + +" Test spellbadword() with argument, specifically to move to "rare" words +" in normal mode. +func Test_spellrareword() + set spell + + " Create a small word list to test that spellbadword('...') + " can return ['...', 'rare']. + let lines =<< trim END + foo + foobar/? + foobara/? +END + call writefile(lines, 'Xwords', 'D') + + mkspell! Xwords.spl Xwords + set spelllang=Xwords.spl + call assert_equal(['foobar', 'rare'], spellbadword('foo foobar')) + + new + call setline(1, ['foo', '', 'foo bar foo bar foobara foo foo foo foobar', '', 'End']) + set spell wrapscan + normal ]s + call assert_equal('foo', expand('')) + normal ]s + call assert_equal('bar', expand('')) + + normal ]r + call assert_equal('foobara', expand('')) + normal ]r + call assert_equal('foobar', expand('')) + normal ]r + call assert_equal('foobara', expand('')) + normal 2]r + call assert_equal('foobara', expand('')) + + normal [r + call assert_equal('foobar', expand('')) + normal [r + call assert_equal('foobara', expand('')) + normal [r + call assert_equal('foobar', expand('')) + normal 2[r + call assert_equal('foobar', expand('')) + + bwipe! + set nospell + + call delete('Xwords.spl') + set spelllang& + set spell& + + " set 'encoding' to clear the word list + set encoding=utf-8 +endfunc + +" vim: shiftwidth=2 sts=2 expandtab