[vim/vim] Add configuration thesaurusfunc (similar to omnifunc) (#8950)

37 views
Skip to first unread message

Matěj Cepl

unread,
Oct 3, 2021, 6:05:09 PM10/3/21
to vim/vim, Subscribed

Thesaurus is a very useful function for anybody who wants to write a longer text. Vim's support for this functionality is rudimentary at best and it has plenty of shortcomings. These two Reddit posts lists some of them (while discussing using Mobby Thesaurus as a source of data):

  1. mthesaur.txt contains whole phrases as synonyms - eg 'under the weather' for 'down'. Vim then suggests 'under', 'the' and 'weather' as seperate matches.

  2. Because of the way Vim finds auto-complete matches, the suggestions sometimes number upwards of 10,000 making it really unusable.

  3. Vim can't accept lines longer than 512 characters. Since each entry in mthesaur.txt is one line, they can contain many thousands of characters.

There are some Vim plugins which are dealing with some of these problems (e.g., https://github.com/Ron89/thesaurus_query.vim, https://github.com/reedes/vim-lexical.git), but unfortunately the first thing they have to do is to completely ignore built-in thesaurus in vim (i_CTRL-X_CTRL-T), because this key combination is hardcoded.

Example of omnifunc, where lively ecosystem of completion engines flourishes, and the negative example of both thesaurus and spellchecking, which are limited to the limited built-in implementations (I just have to mention my old PR #2500 and yes, in the end, it seems to me that the original solution before the built-in spellchecker was added could be better if better integrated and/or some simple spelling engine was included).

Some of these issues were mentioned in #1611 but they were mostly ignored so far.

There are some heroic efforts to make the built-in thesaurus working, but none of them seem to be able to overcome the problems in the built-in implementaiton.

Describe the solution you'd like
Introduce configuration settings thesaurusfunc (and possibly even spellsfunc) with the similar syntax to omnifunc, which would allow using external engine for thesaurus functionality.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub.
Triage notifications on the go with GitHub Mobile for iOS or Android.

Yegappan Lakshmanan

unread,
Oct 4, 2021, 8:59:14 PM10/4/21
to vim_dev, reply+ACY5DGAJ5MFLCEVHYH...@reply.github.com, vim/vim, Subscribed
Hi,

I have added the thesaurusfunc option and the changes are available at https://github.com/yegappan/vim/tree/thesaurus.
Can you try this and let me know if it works for you?

Thanks,
Yegappan

 

vim-dev ML

unread,
Oct 4, 2021, 8:59:35 PM10/4/21
to vim/vim, vim-dev ML, Your activity

Hi,


On Sun, Oct 3, 2021 at 3:05 PM Matěj Cepl ***@***.***> wrote:

> Thesaurus is a very useful function for anybody who wants to write a
> longer text. Vim's support for this functionality is rudimentary at best
> and it has plenty of shortcomings. These two
> <https://www.reddit.com/r/vim/comments/m5lawj/more_sensible_vim_thesaurus_matching_using/>
> Reddit posts
> <https://www.reddit.com/r/vim/comments/55y53e/allow_spaces_in_thesaurus_entries/>

> lists some of them (while discussing using Mobby Thesaurus as a source of
> data):
>
> 1.

>
> mthesaur.txt contains whole phrases as synonyms - eg 'under the
> weather' for 'down'. Vim then suggests 'under', 'the' and 'weather' as
> seperate matches.
> 2.

>
> Because of the way Vim finds auto-complete matches, the suggestions
> sometimes number upwards of 10,000 making it really unusable.
> 3.

>
> Vim can't accept lines longer than 512 characters. Since each entry in
> mthesaur.txt is one line, they can contain many thousands of characters.
>
> There are some Vim plugins which are dealing with some of these problems
> (e.g., https://github.com/Ron89/thesaurus_query.vim,
> https://github.com/reedes/vim-lexical.git), but unfortunately the first
> thing they have to do is to completely ignore built-in thesaurus in vim (
> i_CTRL-X_CTRL-T), because this key combination is hardcoded.
>
> Example of omnifunc, where lively ecosystem of completion engines
> flourishes, and the negative example of both thesaurus and spellchecking,
> which are limited to the limited built-in implementations (I just have to
> mention my old PR #2500 <https://github.com/vim/vim/pull/2500> and yes,

> in the end, it seems to me that the original solution before the built-in
> spellchecker was added could be better if better integrated and/or some
> simple spelling engine was included).
>
> Some of these issues were mentioned in #1611
> <https://github.com/vim/vim/issues/1611> but they were mostly ignored so

> far.
>
> There are some heroic efforts to make the built-in thesaurus working, but
> none of them seem to be able to overcome the problems in the built-in
> implementaiton.
>
> - Also, https://thesynack.com/posts/vim-thesaurus/
>
> *Describe the solution you'd like*

> Introduce configuration settings thesaurusfunc (and possibly even
> spellsfunc) with the similar syntax to omnifunc, which would allow using
> external engine for thesaurus functionality.
>
>
>
I have added the thesaurusfunc option and the changes are available at
https://github.com/yegappan/vim/tree/thesaurus.
Can you try this and let me know if it works for you?

Thanks,
Yegappan

Yegappan Lakshmanan

unread,
Oct 16, 2021, 11:46:51 AM10/16/21
to vim/vim, vim-dev ML, Comment

The support for the 'thesaurusfunc' option has been merged now 160e994 (patch 8.2.3520).


You are receiving this because you commented.

Bram Moolenaar

unread,
Oct 16, 2021, 11:50:59 AM10/16/21
to vim/vim, vim-dev ML, Comment

Closed #8950.


You are receiving this because you commented.

Magnus Groß

unread,
Oct 16, 2021, 6:33:22 PM10/16/21
to vim/vim, vim-dev ML, Comment

I have set up my thesaurusfunc to work with the well known aiksaurus CLI Thesaurus tool. I am sharing my config here, in case it is useful to someone else:

Screenshot_20211017_002933

"vimrc
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
if has('patch-8.2.3520')
	set thesaurusfunc=Thesaur
endif


You are receiving this because you commented.

Bram Moolenaar

unread,
Oct 17, 2021, 8:37:42 AM10/17/21
to vim...@googlegroups.com, Magnus Groß

Magnus Groß wrote:

> I have set up my `thesaurusfunc` to work with the well known
> `aiksaurus` CLI Thesaurus tool. I am sharing my config here, in case
> it is useful to someone else:

Thanks, let me include this in the help. Please check it when it's on
github. For an example it should be as simple and clear as we can make
it.

--
The CIA drives around in cars with the "Intel inside" logo.

/// Bram Moolenaar -- Br...@Moolenaar.net -- http://www.Moolenaar.net \\\
/// \\\
\\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///

Matěj Cepl

unread,
Oct 24, 2021, 1:16:59 PM10/24/21
to vim/vim, vim-dev ML, Comment

I have set up my thesaurusfunc to work with the well known aiksaurus CLI Thesaurus tool. I am sharing my config here, in case it is useful to someone else:

Seriously not bad, thank you. However, I have a bit of comments:

  • Could you make it into a separate plugin, so we have issue tracker, pull requests, etc., please?
  • It parses well synonyms, but unfortunately, the first word I tried it on was madhouse. Plain aiksaurus reply is:
plugin@stitny (master)$ aiksaurus madhouse
*** No synonyms known. ***
Alphabetically similar known words are:
	mad
	mad about
	mad dog
	madam
	madame
	madcap
	madden
	maddening
	made
	mademoiselle
	madly
	madman
	madness
	madrigal
	Mae West
	maelstrom
	maestro
	Mafia
	Mafioso
	magazine
  1. your function doesn't have any protection against No synonyms known. error
  2. perhaps those similar words would be a good potential substitution? Or perhaps not.


You are receiving this because you commented.

Magnus Groß

unread,
Oct 24, 2021, 1:52:00 PM10/24/21
to vim/vim, vim-dev ML, Comment

  1. your function doesn't have any protection against No synonyms known. error
  2. perhaps those similar words would be a good potential substitution? Or perhaps not.

The following updated snippet supports showing the alphabetical suggestions for the No synonyms known. error. It shows a crystal ball next to the suggestions, so that it is clear that no synonyms were found and that it is just guessing. You can easily replace the crystal ball 🔮 with another character, if your terminal does not support these Emoji. I have also updated the original snippet above.
Screenshot_20211024_193557

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 ==# 'Alphabetically similar known words are: '

				let h = '🔮'

			elseif l[0] =~ '\a' || (h ==# '🔮' && l[0] ==# "\t")

				call extend(res, map(split(substitute(l, '^\t', '', ''), ', '), {_, val -> {'word': val, 'menu': h}}))

			
endif

		endfor

		return res

	endif

endfunc

set thesaurusfunc=Thesaur


You are receiving this because you commented.

Magnus Groß

unread,
Oct 24, 2021, 1:52:20 PM10/24/21
to vim/vim, vim-dev ML, Comment

Could you make it into a separate plugin, so we have issue tracker, pull requests, etc., please?

I am not sure if this little snippet is worth it to create an extra repo for it. But I do see that in f4d8b76 this snippet was added to the official vim help, so maybe Bram could update it there.
The diff would be (it also includes a small optimization):

diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt

index 7d218930d..7941ec5a1 100644

--- a/runtime/doc/insert.txt

+++ b/runtime/doc/insert.txt

@@ -884,18 +884,18 @@ Groß): >

 		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.')'}}))

+		        let h = '('.substitute(l[4:], ' =*$', ')', '')

+		    elseif l ==# 'Alphabetically similar known words are: '

+		        let h = '🔮'

+		    elseif l[0] =~ '\a' || (h ==# '🔮' && l[0] ==# "\t")

+		        call extend(res, map(split(substitute(l, '^\t', '', ''), ', '), {_, val -> {'word': val, 'menu': h}}))

 		    endif

 		endfor

 		return res

 	    endif

 	endfunc

 

-	if exists('+thesaurusfunc')

-	    set thesaurusfunc=Thesaur

-	endif

+set thesaurusfunc=Thesaur

 

 

 Completing keywords in the current and included files	*compl-keyword*


You are receiving this because you commented.

Yegappan Lakshmanan

unread,
Oct 24, 2021, 2:06:21 PM10/24/21
to vim_dev, reply+ACY5DGFVZ5X4U2AIH4...@reply.github.com, vim/vim, vim-dev ML, Comment
Hi,

On Sun, Oct 24, 2021 at 10:52 AM Magnus Groß <vim-dev...@256bit.org> wrote:

Could you make it into a separate plugin, so we have issue tracker, pull requests, etc., please?

I am not sure if this little snippet is worth it to create an extra repo for it. But I do see that in f4d8b76 this snippet was added to the official vim help, so maybe Bram could update it there.
The diff would be (it also includes a small optimization):

diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt
index 7d218930d..7941ec5a1 100644
--- a/runtime/doc/insert.txt
+++ b/runtime/doc/insert.txt
@@ -884,18 +884,18 @@ Groß): >
 		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.')'}}))
+		        let h = '('.substitute(l[4:], ' =*$', ')', '')
+		    elseif l ==# 'Alphabetically similar known words are: '
+		        let h = '🔮'

As some terminals may not be able to display the crystal ball character, it might be better to use

          let h = "\U0001f52e"

The below if condition can also be updated to use this. 

Regards,
Yegappan

vim-dev ML

unread,
Oct 24, 2021, 2:06:39 PM10/24/21
to vim/vim, vim-dev ML, Your activity

Hi,

On Sun, Oct 24, 2021 at 10:52 AM Magnus Groß ***@***.***>

wrote:

> Could you make it into a separate plugin, so we have issue tracker, pull
> requests, etc., please?
>
> I am not sure if this little snippet is worth it to create an extra repo
> for it. But I do see that in f4d8b76
> <https://github.com/vim/vim/commit/f4d8b76d304dabc39c06d2344cd4c7b28484811b>

> this snippet was added to the official vim help, so maybe Bram could update
> it there.
> The diff would be (it also includes a small optimization):
>
> diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt
> index 7d218930d..7941ec5a1 100644--- a/runtime/doc/insert.txt+++ b/runtime/doc/insert.txt@@ -884,18 +884,18 @@ Groß): >

> 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.')'}}))+ let h = '('.substitute(l[4:], ' =*$', ')', '')+ elseif l ==# 'Alphabetically similar known words are: '+ let h = '🔮'

>
>
As some terminals may not be able to display the crystal ball character, it
might be better to use

let h = "\U0001f52e"

The below if condition can also be updated to use this.

Regards,
Yegappan

+ elseif l[0] =~ '\a' || (h ==# '🔮' && l[0] ==# "\t")+
call extend(res, map(split(substitute(l, '^\t', '', ''), ', '), {_,
val -> {'word': val, 'menu': h}}))
> endif
> endfor
> return res
> endif
> endfunc
> - if exists('+thesaurusfunc')- set thesaurusfunc=Thesaur- endif+set thesaurusfunc=Thesaur

>
> Completing keywords in the current and included files *compl-keyword*
>
>

lacygoill

unread,
Oct 24, 2021, 2:08:25 PM10/24/21
to vim/vim, vim-dev ML, Comment

This block:

let line = getline('.')
let start = col('.') - 1
while start > 0 && line[start - 1] =~ '\a'
   let start -= 1
endwhile
return start

Can be simplified into:

return searchpos('\<', 'bnW', line('.'))[1] - 1

And in this line:

for l in split(system('aiksaurus '.shellescape(a:base)), '\n')

split() can be removed by replacing system() with systemlist():

for l in systemlist('aiksaurus ' .. shellescape(a:base))

FWIW, this is what I use:

vim9script

if !executable('aiksaurus')
    echomsg "'thesaurusfunc' needs the package aiksaurus to be installed"
    var pass: string = inputsecret('[sudo] password for ' .. $USER .. ': ')
    system('sudo -S apt-get --yes install aiksaurus', pass .. "\n")
endif
&thesaurusfunc = 'Thesaurusfunc'

def g:Thesaurusfunc(findstart: bool, base: string): any
    if findstart
        return searchpos('\<', 'bnW', line('.'))[1] - 1
    endif
    var lines: list<string> = systemlist('aiksaurus ' .. shellescape(base))
    if lines[0] =~ 'No synonyms known'
        return []
    endif
    var matches: list<dict<string>>
    var meaning: string
    for line: string in lines
        if line =~ '^==='
            meaning = line->matchstr('\k\+')
        elseif line =~ '^\k'
            var match: list<dict<string>> = line
                ->split(', ')
                ->mapnew((_, v: string): dict<string> => ({
                    word: v,
                    menu: '(' .. meaning .. ')'
                }))
            matches->extend(match)
        endif
    endfor
    return matches
enddef


You are receiving this because you commented.

Magnus Groß

unread,
Oct 24, 2021, 2:17:40 PM10/24/21
to vim/vim, vim-dev ML, Comment

Thanks both of you, these are indeed useful additions. Here is an updated diff for Bram to include in the next runtime update:

diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt

index 7d218930d..708b0049c 100644

--- a/runtime/doc/insert.txt

+++ b/runtime/doc/insert.txt

@@ -873,20 +873,17 @@ 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

+		return searchpos('\<', 'bnW', line('.'))[1] - 1

 	    else

 		let res = []

 		let h = ''

-		for l in split(system('aiksaurus '.shellescape(a:base)), '\n')

+		for l in systemlist('aiksaurus '.shellescape(a:base))

 		    if l[:3] == '=== '

-		    	let h = substitute(l[4:], ' =*$', '', '')

-		    elseif l[0] =~ '\a'

-			call extend(res, map(split(l, ', '), {_, val -> {'word': val, 'menu': '('.h.')'}}))

+		        let h = '('.substitute(l[4:], ' =*$', ')', '')

+		    elseif l ==# 'Alphabetically similar known words are: '

+		        let h = "\U0001f52e"

+		    elseif l[0] =~ '\a' || (h ==# "\U0001f52e" && l[0] ==# "\t")

+		        call extend(res, map(split(substitute(l, '^\t', '', ''), ', '), {_, val -> {'word': val, 'menu': h}}))

 		    endif

 		endfor

 		return res

I have also updated the snippets above.


You are receiving this because you commented.

Bram Moolenaar

unread,
Oct 24, 2021, 4:01:48 PM10/24/21
to vim/vim, vim-dev ML, Comment

Thanks. I'll also reduce the indent to avoid wrapping.


You are receiving this because you commented.

lacygoill

unread,
Oct 24, 2021, 4:11:16 PM10/24/21
to vim/vim, vim-dev ML, Comment

I'll also reduce the indent to avoid wrapping.

To further reduce the indent, the else statement can be removed.

Before:

if condition
    return value
else
    do thing 1
    do thing 2
    do thing 3
    ...
    return other_value
endif

After:

if condition
    return value
endif
do thing 1
do thing 2
do thing 3
...
return other_value


You are receiving this because you commented.

Reply all
Reply to author
Forward
0 new messages