changeset 17682:3dbff5d37520 v8.1.1838

patch 8.1.1838: there is :spellwrong and :spellgood but not :spellrare commit https://github.com/vim/vim/commit/08cc374dabd2a02785129fa1c0100f7745c244ad Author: Bram Moolenaar <Bram@vim.org> Date: Sun Aug 11 22:51:14 2019 +0200 patch 8.1.1838: there is :spellwrong and :spellgood but not :spellrare Problem: There is :spellwrong and :spellgood but not :spellrare. Solution: Add :spellrare. (Martin Tournoij, closes https://github.com/vim/vim/issues/4291)
author Bram Moolenaar <Bram@vim.org>
date Sun, 11 Aug 2019 23:00:07 +0200
parents fbdef42903ae
children 7e02187c975e
files runtime/doc/spell.txt src/ex_cmdidxs.h src/ex_cmds.h src/normal.c src/proto/spellfile.pro src/spell.h src/spellfile.c src/testdir/Make_all.mak src/testdir/test_normal.vim src/testdir/test_spellfile.vim src/version.c
diffstat 11 files changed, 226 insertions(+), 174 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/spell.txt
+++ b/runtime/doc/spell.txt
@@ -121,6 +121,23 @@ zuG			Undo |zW| and |zG|, remove the wor
 :spellw[rong]! {word}	Add {word} as a wrong (bad) word to the internal word
 			list, like with |zW|.
 
+							*:spellr* *:spellrare*
+:[count]spellr[are] {word}
+			Add {word} as a rare word to 'spellfile', similar to
+			|zw|.  Without count the first name is used, with
+			a count of two the second entry, etc.
+
+			There are no normal mode commands to mark words as
+			rare as this is a fairly uncommon command and all
+			intuitive commands for this are already taken. If you
+			want you can add mappings with e.g.: >
+		nnoremap z?  :exe ':spellrare  ' . expand('<cWORD>')<CR>
+		nnoremap z/  :exe ':spellrare! ' . expand('<cWORD>')<CR>
+<			|:spellundo|, |zuw|, or |zuW| can be used to undo this.
+
+:spellr[rare]! {word}	Add {word} as a rare word to the internal word
+			list, similar to |zW|.
+
 :[count]spellu[ndo] {word}				*:spellu* *:spellundo*
 			Like |zuw|.  [count] used as with |:spellgood|.
 
--- a/src/ex_cmdidxs.h
+++ b/src/ex_cmdidxs.h
@@ -24,13 +24,13 @@ static const unsigned short cmdidxs1[26]
   /* q */ 358,
   /* r */ 361,
   /* s */ 381,
-  /* t */ 449,
-  /* u */ 494,
-  /* v */ 505,
-  /* w */ 523,
-  /* x */ 537,
-  /* y */ 547,
-  /* z */ 548
+  /* t */ 450,
+  /* u */ 495,
+  /* v */ 506,
+  /* w */ 524,
+  /* x */ 538,
+  /* y */ 548,
+  /* z */ 549
 };
 
 /*
@@ -59,7 +59,7 @@ static const unsigned char cmdidxs2[26][
   /* p */ {  1,  0,  3,  0,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  7,  9,  0,  0, 16, 17, 26,  0, 27,  0, 28,  0 },
   /* q */ {  2,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
   /* r */ {  0,  0,  0,  0,  0,  0,  0,  0, 12,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 14, 19,  0,  0,  0,  0 },
-  /* s */ {  2,  6, 15,  0, 19, 23,  0, 25, 26,  0,  0, 29, 31, 35, 39, 41,  0, 49,  0, 50,  0, 62, 63,  0, 64,  0 },
+  /* s */ {  2,  6, 15,  0, 19, 23,  0, 25, 26,  0,  0, 29, 31, 35, 39, 41,  0, 50,  0, 51,  0, 63, 64,  0, 65,  0 },
   /* t */ {  2,  0, 19,  0, 24, 26,  0, 27,  0, 28,  0, 29, 33, 36, 38, 39,  0, 40, 42,  0, 43,  0,  0,  0,  0,  0 },
   /* u */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
   /* v */ {  0,  0,  0,  0,  1,  0,  0,  0,  4,  0,  0,  0,  9, 12,  0,  0,  0,  0, 15,  0, 16,  0,  0,  0,  0,  0 },
@@ -69,4 +69,4 @@ static const unsigned char cmdidxs2[26][
   /* z */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 }
 };
 
-static const int command_count = 561;
+static const int command_count = 562;
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -1378,6 +1378,9 @@ EXCMD(CMD_split,	"split",	ex_splitview,
 EXCMD(CMD_spellgood,	"spellgood",	ex_spell,
 	EX_BANG|EX_RANGE|EX_NEEDARG|EX_EXTRA|EX_TRLBAR,
 	ADDR_OTHER),
+EXCMD(CMD_spellrare,	"spellrare",	ex_spell,
+	EX_BANG|EX_RANGE|EX_NEEDARG|EX_EXTRA|EX_TRLBAR,
+	ADDR_OTHER),
 EXCMD(CMD_spelldump,	"spelldump",	ex_spelldump,
 	EX_BANG|EX_TRLBAR,
 	ADDR_NONE),
--- a/src/normal.c
+++ b/src/normal.c
@@ -5127,7 +5127,8 @@ dozet:
 		    if (ptr == NULL && (len = find_ident_under_cursor(&ptr,
 							    FIND_IDENT)) == 0)
 			return;
-		    spell_add_word(ptr, len, nchar == 'w' || nchar == 'W',
+		    spell_add_word(ptr, len, nchar == 'w' || nchar == 'W'
+					      ? SPELL_ADD_BAD : SPELL_ADD_GOOD,
 					    (nchar == 'G' || nchar == 'W')
 						       ? 0 : (int)cap->count1,
 					    undo);
--- a/src/proto/spellfile.pro
+++ b/src/proto/spellfile.pro
@@ -5,5 +5,5 @@ int spell_check_msm(void);
 void ex_mkspell(exarg_T *eap);
 void mkspell(int fcount, char_u **fnames, int ascii, int over_write, int added_word);
 void ex_spell(exarg_T *eap);
-void spell_add_word(char_u *word, int len, int bad, int idx, int undo);
+void spell_add_word(char_u *word, int len, int what, int idx, int undo);
 /* vim: set ft=c : */
--- a/src/spell.h
+++ b/src/spell.h
@@ -298,4 +298,8 @@ SPELL_EXTERN char e_format[] SPELL_INIT(
 SPELL_EXTERN spelltab_T   spelltab;
 SPELL_EXTERN int	  did_set_spelltab;
 
+// Values for "what" argument of spell_add_word()
+#define SPELL_ADD_GOOD	0
+#define SPELL_ADD_BAD	1
+#define SPELL_ADD_RARE	2
 #endif
--- a/src/spellfile.c
+++ b/src/spellfile.c
@@ -6125,28 +6125,31 @@ spell_message(spellinfo_T *spin, char_u 
 
 /*
  * ":[count]spellgood  {word}"
- * ":[count]spellwrong  {word}"
+ * ":[count]spellwrong {word}"
  * ":[count]spellundo  {word}"
+ * ":[count]spellrare  {word}"
  */
     void
 ex_spell(exarg_T *eap)
 {
-    spell_add_word(eap->arg, (int)STRLEN(eap->arg), eap->cmdidx == CMD_spellwrong,
+    spell_add_word(eap->arg, (int)STRLEN(eap->arg),
+		eap->cmdidx == CMD_spellwrong ? SPELL_ADD_BAD :
+		eap->cmdidx == CMD_spellrare ? SPELL_ADD_RARE : SPELL_ADD_GOOD,
 				   eap->forceit ? 0 : (int)eap->line2,
 				   eap->cmdidx == CMD_spellundo);
 }
 
 /*
- * Add "word[len]" to 'spellfile' as a good or bad word.
+ * Add "word[len]" to 'spellfile' as a good, rare or bad word.
  */
     void
 spell_add_word(
     char_u	*word,
     int		len,
-    int		bad,
-    int		idx,	    /* "zG" and "zW": zero, otherwise index in
-			       'spellfile' */
-    int		undo)	    /* TRUE for "zug", "zuG", "zuw" and "zuW" */
+    int		what,	    // SPELL_ADD_ values
+    int		idx,	    // "zG" and "zW": zero, otherwise index in
+			    // 'spellfile'
+    int		undo)	    // TRUE for "zug", "zuG", "zuw" and "zuW"
 {
     FILE	*fd = NULL;
     buf_T	*buf = NULL;
@@ -6213,7 +6216,7 @@ spell_add_word(
 	fname = fnamebuf;
     }
 
-    if (bad || undo)
+    if (what == SPELL_ADD_BAD || undo)
     {
 	/* When the word appears as good word we need to remove that one,
 	 * since its flags sort before the one with WF_BANNED. */
@@ -6280,8 +6283,10 @@ spell_add_word(
 	    semsg(_(e_notopen), fname);
 	else
 	{
-	    if (bad)
+	    if (what == SPELL_ADD_BAD)
 		fprintf(fd, "%.*s/!\n", len, word);
+	    else if (what == SPELL_ADD_RARE)
+		fprintf(fd, "%.*s/?\n", len, word);
 	    else
 		fprintf(fd, "%.*s\n", len, word);
 	    fclose(fd);
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -236,6 +236,7 @@ NEW_TESTS = \
 	test_source \
 	test_source_utf8 \
 	test_spell \
+	test_spellfile \
 	test_startup \
 	test_startup_utf8 \
 	test_stat \
@@ -411,6 +412,7 @@ NEW_TESTS_RES = \
 	test_sound.res \
 	test_source.res \
 	test_spell.res \
+	test_spellfile.res \
 	test_startup.res \
 	test_stat.res \
 	test_substitute.res \
--- a/src/testdir/test_normal.vim
+++ b/src/testdir/test_normal.vim
@@ -1089,160 +1089,6 @@ func Test_normal18_z_fold()
   bw!
 endfunc
 
-func Test_normal19_z_spell()
-  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])
-
-  " Test for :spellgood!
-  let temp = execute(':spe!0/0')
-  call assert_match('Invalid region', temp)
-  let spellfile = matchstr(temp, 'Invalid region nr in \zs.*\ze line \d: 0')
-  call assert_equal(['# goood', '# goood/!', '#oood', '0/0'], readfile(spellfile))
-  call delete(spellfile)
-
-  " 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
new file mode 100644
--- /dev/null
+++ b/src/testdir/test_spellfile.vim
@@ -0,0 +1,172 @@
+" Test for commands that operate on the spellfile.
+
+source shared.vim
+source check.vim
+
+CheckFeature spell
+CheckFeature syntax
+
+func Test_spell_normal()
+  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 :spellrare
+  spellrare rare
+  let cnt=readfile('./Xspellfile.add')
+  call assert_equal(['#oood', 'goood/!', 'rare/?'], cnt)
+
+  " Make sure :spellundo works for rare words.
+  spellundo rare
+  let cnt=readfile('./Xspellfile.add')
+  call assert_equal(['#oood', 'goood/!', '#are/?'], cnt)
+
+  " 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[3])
+  " 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[4])
+
+  " 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])
+
+  " Test for :spellgood!
+  let temp = execute(':spe!0/0')
+  call assert_match('Invalid region', temp)
+  let spellfile = matchstr(temp, 'Invalid region nr in \zs.*\ze line \d: 0')
+  call assert_equal(['# goood', '# goood/!', '#oood', '0/0'], readfile(spellfile))
+
+  " Test for :spellrare!
+  :spellrare! raare
+  call assert_equal(['# goood', '# goood/!', '#oood', '0/0', 'raare/?'], readfile(spellfile))
+  call delete(spellfile)
+
+  " 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
--- a/src/version.c
+++ b/src/version.c
@@ -770,6 +770,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1838,
+/**/
     1837,
 /**/
     1836,