From ff674debea5ec3d2e2da74e3684a7672fb511dd8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= <mcepl@cepl.eu>
Date: Sun, 17 Oct 2021 16:37:29 +0200
Subject: [PATCH] vim-patch:8.2.3528: 'thesaurus' and 'thesaurusfunc' do not
 have the same scope

Problem:    'thesaurus' and 'thesaurusfunc' do not have the same scope.
Solution:   Make 'thesaurusfunc' global-local.
https://github.com/vim/vim/commit/f4d8b76d304dabc39c06d2344cd4c7b28484811b
---
 runtime/doc/insert.txt         | 72 ++++++++++++++++++++++++++--------
 runtime/doc/options.txt        | 25 ++++++------
 src/nvim/buffer_defs.h         |  2 +-
 src/nvim/edit.c                | 24 ++++++------
 src/nvim/option.c              | 14 +++++--
 src/nvim/option_defs.h         |  3 +-
 src/nvim/options.lua           |  4 +-
 src/nvim/testdir/test_edit.vim | 14 +++++--
 8 files changed, 106 insertions(+), 52 deletions(-)

diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt
index 236537dd09e168..fd1d0f8ea64c85 100644
--- a/runtime/doc/insert.txt
+++ b/runtime/doc/insert.txt
@@ -804,6 +804,9 @@ CTRL-X CTRL-K		Search the files given with the 'dictionary' option
 	CTRL-P		Search backwards for next matching keyword.  This
 			keyword replaces the previous matching keyword.
 
+
+Completing words in 'thesaurus'				*compl-thesaurus*
+
 							*i_CTRL-X_CTRL-T*
 CTRL-X CTRL-T		Works as CTRL-X CTRL-K, but in a special way.  It uses
 			the 'thesaurus' option instead of 'dictionary'.  If a
@@ -812,22 +815,6 @@ CTRL-X CTRL-T		Works as CTRL-X CTRL-K, but in a special way.  It uses
 			matches, even though they don't complete the word.
 			Thus a word can be completely replaced.
 
-			For an example, imagine the 'thesaurus' file has a
-			line like this: >
-				angry furious mad enraged
-<			Placing the cursor after the letters "ang" and typing
-			CTRL-X CTRL-T would complete the word "angry";
-			subsequent presses would change the word to "furious",
-			"mad" etc.
-			Other uses include translation between two languages,
-			or grouping API functions by keyword.
-
-			If the 'thesaurusfunc' option is set, then the user
-			specified function is invoked to get the list of
-			completion matches and the 'thesaurus' option is not
-			used. See |complete-functions| for an explanation of
-			how the function is invoked and what it should return.
-
 	CTRL-T	or
 	CTRL-N		Search forward for next matching keyword.  This
 			keyword replaces the previous matching keyword.
@@ -835,6 +822,59 @@ CTRL-X CTRL-T		Works as CTRL-X CTRL-K, but in a special way.  It uses
 	CTRL-P		Search backwards for next matching keyword.  This
 			keyword replaces the previous matching keyword.
 
+In the file used by the 'thesaurus' option each line in the file should
+contain words with similar meaning, separated by non-keyword characters (white
+space is preferred).  Maximum line length is 510 bytes.
+
+For an example, imagine the 'thesaurus' file has a line like this: >
+	angry furious mad enraged
+<Placing the cursor after the letters "ang" and typing CTRL-X CTRL-T would
+complete the word "angry"; subsequent presses would change the word to
+"furious", "mad" etc.
+
+Other uses include translation between two languages, or grouping API
+functions by keyword.
+
+An English word list was added to this github issue:
+https://github.com/vim/vim/issues/629#issuecomment-443293282
+Unpack thesaurus_pkg.zip, put the thesaurus.txt file somewhere, e.g.
+~/.vim/thesaurus/english.txt, and the 'thesaurus' option to this file name.
+
+					
+Completing keywords with 'thesaurusfunc'		*compl-thesaurusfunc*
+
+If the 'thesaurusfunc' option is set, then the user specified function is
+invoked to get the list of completion matches and the 'thesaurus' option is
+not used. See |complete-functions| for an explanation of how the function is
+invoked and what it should return.
+
+Here is an example that uses the "aiksaurus" command (provided by Magnus
+Groß): >
+
+	func Thesaur(findstart, base)
+	    if a:findstart
+		let line = getline('.')
+		let start = col('.') - 1
+		while start > 0 && line[start - 1] =~ '\a'
+		   let start -= 1
+		endwhile
+		return start
+	    else
+		let res = []
+		let h = ''
+		for l in split(system('aiksaurus '.shellescape(a:base)), '\n')
+		    if l[:3] == '=== '
+		    	let h = substitute(l[4:], ' =*$', '', '')
+		    elseif l[0] =~ '\a'
+			call extend(res, map(split(l, ', '), {_, val -> {'word': val, 'menu': '('.h.')'}}))
+		    endif
+		endfor
+		return res
+	    endif
+	endfunc
+
+	set thesaurusfunc=Thesaur
+
 
 Completing keywords in the current and included files	*compl-keyword*
 
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index bbd107ab237307..eed31c4c114023 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -6455,27 +6455,26 @@ A jump table for the options with a short description can be found at |Q_op|.
 'thesaurus' 'tsr'	string	(default "")
 			global or local to buffer |global-local|
 	List of file names, separated by commas, that are used to lookup words
-	for thesaurus completion commands |i_CTRL-X_CTRL-T|.
+	for thesaurus completion commands |i_CTRL-X_CTRL-T|.  See
+	|compl-thesaurus|.
 
-	Each line in the file should contain words with similar meaning,
-	separated by non-keyword characters (white space is preferred).
-	Maximum line length is 510 bytes.
+	This option is not used if 'thesaurusfunc' is set, either for the
+	buffer or globally.
 
 	To include a comma in a file name precede it with a backslash.  Spaces
 	after a comma are ignored, otherwise spaces are included in the file
-	name.  See |option-backslash| about using backslashes.
-	The use of |:set+=| and |:set-=| is preferred when adding or removing
-	directories from the list.  This avoids problems when a future version
-	uses another default.
-	Backticks cannot be used in this option for security reasons.
+	name.  See |option-backslash| about using backslashes.  The use of
+	|:set+=| and |:set-=| is preferred when adding or removing directories
+	from the list.  This avoids problems when a future version uses
+	another default.  Backticks cannot be used in this option for security
+	reasons.
 
 						*'thesaurusfunc'* *'tsrfu'*
 'thesaurusfunc' 'tsrfu'	string	(default: empty)
-			local to buffer
+			global or local to buffer |global-local|
 	This option specifies a function to be used for thesaurus completion
-	with CTRL-X CTRL-T. |i_CTRL-X_CTRL-T|
-	See |complete-functions| for an explanation of how the function is
-	invoked and what it should return.
+	with CTRL-X CTRL-T. |i_CTRL-X_CTRL-T| See |compl-thesaurusfunc|.
+
 	This option cannot be set from a |modeline| or in the |sandbox|, for
 	security reasons.
 
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index b5c31916ad7947..bd9c5efa441a61 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -697,7 +697,6 @@ struct file_buffer {
 #endif
   char_u *b_p_cfu;              ///< 'completefunc'
   char_u *b_p_ofu;              ///< 'omnifunc'
-  char_u *b_p_tsrfu;            ///< 'thesaurusfunc'
   char_u *b_p_tfu;              ///< 'tagfunc'
   int b_p_eol;                  ///< 'endofline'
   int b_p_fixeol;               ///< 'fixendofline'
@@ -767,6 +766,7 @@ struct file_buffer {
   unsigned b_tc_flags;          ///< flags for 'tagcase'
   char_u *b_p_dict;             ///< 'dictionary' local value
   char_u *b_p_tsr;              ///< 'thesaurus' local value
+  char_u *b_p_tsrfu;            ///< 'thesaurusfunc' local value
   long b_p_ul;                  ///< 'undolevels' local value
   int b_p_udf;                  ///< 'undofile'
   char_u *b_p_lw;               ///< 'lispwords' local value
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 4f2a945e5e5ca9..ca903fdcf79fb8 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -2071,7 +2071,8 @@ static bool check_compl_option(bool dict_opt)
 {
   if (dict_opt
       ? (*curbuf->b_p_dict == NUL && *p_dict == NUL && !curwin->w_p_spell)
-      : (*curbuf->b_p_tsr == NUL && *p_tsr == NUL && *curbuf->b_p_tsrfu == NUL)) {
+      : (*curbuf->b_p_tsr == NUL && *p_tsr == NUL
+         && *curbuf->b_p_tsrfu == NUL && *p_tsrfu == NUL)) {
     ctrl_x_mode = CTRL_X_NORMAL;
     edit_submode = NULL;
     msg_attr((dict_opt
@@ -2544,7 +2545,7 @@ static void ins_compl_longest_match(compl_T *match)
  * Add an array of matches to the list of matches.
  * Frees matches[].
  */
-static void ins_compl_add_matches(int num_matches, char_u * *matches, int icase)
+static void ins_compl_add_matches(int num_matches, char_u **matches, int icase)
   FUNC_ATTR_NONNULL_ALL
 {
   int add_r = OK;
@@ -2899,7 +2900,7 @@ static void ins_compl_dictionaries(char_u *dict_start, char_u *pat, int flags, i
   char_u *ptr;
   char_u *buf;
   regmatch_T regmatch;
-  char_u * *files;
+  char_u **files;
   int count;
   int save_p_scs;
   Direction dir = compl_direction;
@@ -2990,7 +2991,7 @@ static void ins_compl_dictionaries(char_u *dict_start, char_u *pat, int flags, i
   xfree(buf);
 }
 
-static void ins_compl_files(int count, char_u * *files, int thesaurus, int flags,
+static void ins_compl_files(int count, char_u **files, int thesaurus, int flags,
                             regmatch_T *regmatch, char_u *buf, Direction *dir)
   FUNC_ATTR_NONNULL_ARG(2, 7)
 {
@@ -3930,7 +3931,7 @@ static char_u *get_complete_funcname(int type) {
   case CTRL_X_OMNI:
     return curbuf->b_p_ofu;
   case CTRL_X_THESAURUS:
-    return curbuf->b_p_tsrfu;
+    return *curbuf->b_p_tsrfu == NUL ? p_tsrfu : curbuf->b_p_tsrfu;
   default:
     return (char_u *)"";
   }
@@ -4108,15 +4109,14 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast)
     return FAIL;
   }
   return ins_compl_add((char_u *)word, -1, NULL,
-                       (char_u * *)cptext, true, &user_data, dir, flags, dup);
+                       (char_u **)cptext, true, &user_data, dir, flags, dup);
 }
 
-/// Returns TRUE when using a user-defined function for thesaurus completion.
-static int thesaurus_func_complete(int type)
+/// Returns true when using a user-defined function for thesaurus completion.
+static bool thesaurus_func_complete(int type)
 {
-  return (type == CTRL_X_THESAURUS
-          && curbuf->b_p_tsrfu != NULL
-          && *curbuf->b_p_tsrfu != NUL);
+  return type == CTRL_X_THESAURUS
+         && (*curbuf->b_p_tsrfu != NUL || *p_tsrfu != NUL);
 }
 
 // Get the next expansion(s), using "compl_pattern".
@@ -4136,7 +4136,7 @@ static int ins_compl_get_exp(pos_T *ini)
   static buf_T *ins_buf = NULL;          // buffer being scanned
 
   pos_T *pos;
-  char_u * *matches;
+  char_u **matches;
   int save_p_scs;
   bool save_p_ws;
   int save_p_ic;
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 4922557850dbfc..c29691df7f56b8 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -139,7 +139,6 @@ static char_u *p_cpt;
 static char_u *p_cfu;
 static char_u *p_ofu;
 static char_u *p_tfu;
-static char_u *p_thsfu;
 static int p_eol;
 static int p_fixeol;
 static int p_et;
@@ -2035,6 +2034,7 @@ void check_buf_options(buf_T *buf)
   check_string_option(&buf->b_p_tc);
   check_string_option(&buf->b_p_dict);
   check_string_option(&buf->b_p_tsr);
+  check_string_option(&buf->b_p_tsrfu);
   check_string_option(&buf->b_p_lw);
   check_string_option(&buf->b_p_bkc);
   check_string_option(&buf->b_p_menc);
@@ -5537,6 +5537,9 @@ void unset_global_local_option(char *name, void *from)
   case PV_TSR:
     clear_string_option(&buf->b_p_tsr);
     break;
+  case PV_TSRFU:
+    clear_string_option(&buf->b_p_tsrfu);
+    break;
   case PV_FP:
     clear_string_option(&buf->b_p_fp);
     break;
@@ -5620,6 +5623,8 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags)
       return (char_u *)&(curbuf->b_p_dict);
     case PV_TSR:
       return (char_u *)&(curbuf->b_p_tsr);
+    case PV_TSRFU:
+      return (char_u *)&(curbuf->b_p_tsrfu);
     case PV_TFU:
       return (char_u *)&(curbuf->b_p_tfu);
     case PV_SBR:
@@ -5696,6 +5701,9 @@ static char_u *get_varp(vimoption_T *p)
   case PV_TSR:
     return *curbuf->b_p_tsr != NUL
            ? (char_u *)&(curbuf->b_p_tsr) : p->var;
+  case PV_TSRFU:
+    return *curbuf->b_p_tsrfu != NUL
+           ? (char_u *)&(curbuf->b_p_tsrfu) : p->var;
   case PV_FP:
     return *curbuf->b_p_fp != NUL
            ? (char_u *)&(curbuf->b_p_fp) : p->var;
@@ -5915,8 +5923,6 @@ static char_u *get_varp(vimoption_T *p)
     return (char_u *)&(curbuf->b_p_sw);
   case PV_TFU:
     return (char_u *)&(curbuf->b_p_tfu);
-  case PV_TSRFU:
-    return (char_u *)&(curbuf->b_p_thsfu);
   case PV_TS:
     return (char_u *)&(curbuf->b_p_ts);
   case PV_TW:
@@ -6186,7 +6192,6 @@ void buf_copy_options(buf_T *buf, int flags)
 #endif
       buf->b_p_cfu = vim_strsave(p_cfu);
       buf->b_p_ofu = vim_strsave(p_ofu);
-      buf->b_p_thsfu = vim_strsave(p_thsfu);
       buf->b_p_tfu = vim_strsave(p_tfu);
       buf->b_p_sts = p_sts;
       buf->b_p_sts_nopaste = p_sts_nopaste;
@@ -6257,6 +6262,7 @@ void buf_copy_options(buf_T *buf, int flags)
       buf->b_p_inex = vim_strsave(p_inex);
       buf->b_p_dict = empty_option;
       buf->b_p_tsr = empty_option;
+      buf->b_p_tsrfu = empty_option;
       buf->b_p_qe = vim_strsave(p_qe);
       buf->b_p_udf = p_udf;
       buf->b_p_lw = empty_option;
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 8068604bcf4e67..19cb33a354955e 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -686,6 +686,7 @@ EXTERN long p_titlelen;         ///< 'titlelen'
 EXTERN char_u *p_titleold;      ///< 'titleold'
 EXTERN char_u *p_titlestring;   ///< 'titlestring'
 EXTERN char_u *p_tsr;           ///< 'thesaurus'
+EXTERN char_u *p_tsrfu;         ///< 'thesaurusfunc'
 EXTERN int p_tgc;               ///< 'termguicolors'
 EXTERN int p_ttimeout;          ///< 'ttimeout'
 EXTERN long p_ttm;              ///< 'ttimeoutlen'
@@ -826,7 +827,7 @@ enum {
   BV_SW,
   BV_SWF,
   BV_TFU,
-  BV_THSFU,
+  BV_TSRFU,
   BV_TAGS,
   BV_TC,
   BV_TS,
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index a4a80a754c37c4..71208dfc682851 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -2538,10 +2538,10 @@ return {
     {
       full_name='thesaurusfunc', abbreviation='tsrfu',
       short_desc=N_("function used for thesaurus completion"),
-      type='string', scope={'buffer'},
+      type='string', scope={'global', 'buffer'},
       secure=true,
       alloced=true,
-      varname='p_thsfu',
+      varname='p_tsrfu',
       defaults={if_true=""}
     },
     {
diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim
index 9a351292326c1c..23ad8dbfc5bd38 100644
--- a/src/nvim/testdir/test_edit.vim
+++ b/src/nvim/testdir/test_edit.vim
@@ -900,16 +900,24 @@ endfunc
 
 func Test_thesaurus_func()
   new
-  set thesaurus=
-  set thesaurusfunc=MyThesaurus
+  set thesaurus=notused
+  set thesaurusfunc=NotUsed
+  setlocal thesaurusfunc=MyThesaurus
   call setline(1, "an ki")
   call cursor(1, 1)
   call feedkeys("A\<c-x>\<c-t>\<c-n>\<cr>\<esc>", 'tnix')
   call assert_equal(['an amiable', ''], getline(1, '$'))
+
+  setlocal thesaurusfunc=NonExistingFunc
+  call assert_fails("normal $a\<C-X>\<C-T>", 'E117:')
+
+  setlocal thesaurusfunc=
   set thesaurusfunc=NonExistingFunc
   call assert_fails("normal $a\<C-X>\<C-T>", 'E117:')
-  set thesaurusfunc&
   %bw!
+
+  set thesaurusfunc=
+  set thesaurus=
 endfunc
 
 func Test_edit_CTRL_U()