Skip to content

Instantly share code, notes, and snippets.

@ColinKennedy
Created January 6, 2019 16:27
Show Gist options
  • Save ColinKennedy/54aa6d63b48f5bcbccd723a3501cce3a to your computer and use it in GitHub Desktop.
Save ColinKennedy/54aa6d63b48f5bcbccd723a3501cce3a to your computer and use it in GitHub Desktop.
Use the 'Ron89/thesaurus_query.vim' GitHub Vim plugin and an active spellfile to make Vim's thesaurus feature actually useful
" Copied from thesaurus_query.vim. Otherwise, the `thesaurus_query` module may not be directly importable.
"
" Reference: https://github.com/Ron89/thesaurus_query.vim/blob/master/autoload/thesaurus_query.vim#L205-L214
"
pythonx << EOF
# IMPORT STANDARD LIBRARIES
import sys
import os
# IMPORT THIRD-PARTY LIBRARIES
import vim
for path in vim.eval('&runtimepath').split(','):
directory = os.path.join(path, 'autoload')
if os.path.exists(os.path.join(directory, 'thesaurus_query')):
if directory not in sys.path:
sys.path.append(directory)
break
# IMPORT STANDARD LIBRARIES
import tempfile
import logging
import sys
import os
import re
# IMPORT THIRD-PARTY LIBRARIES
from thesaurus_query import thesaurus_query
import vim
_CACHE = dict()
_WORDS = re.compile(r'(\w+)')
_HANDLER = logging.FileHandler(os.path.join(tempfile.gettempdir(), 'thesaurus.log'))
_HANDLER.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
LOGGER = logging.getLogger('vim_plugins.thesaurus')
LOGGER.addHandler(_HANDLER)
THESAURUS_ALLOW_UNMANAGED_WORDS = 'g:thesaurus_allow_unmanaged_words'
THESAURUS_SPELLFILE_OVERRIDE = 'g:thesaurus_filter_spellfile'
_THESAURUS_FILE = tempfile.NamedTemporaryFile(suffix='.thesaurus', mode='w')
vim.command('set thesaurus={_THESAURUS_FILE.name}'.format(_THESAURUS_FILE=_THESAURUS_FILE))
def allow_unmanaged_words():
'''bool: If no filtered-thesaurus results are found, just return all results.'''
try:
vim.eval(THESAURUS_ALLOW_UNMANAGED_WORDS) == '1'
except Exception:
return True
def query(handler, word):
'''Find the results and filtered results of `word`.
Args:
handler (`thesaurus_query.Thesaurus_Query_Handler`):
The object which is used to get thesaurus results for `word`.
word (str):
The word to find thesaurus results for.
Returns:
tuple[list[str], list[str]]:
The filtered word list (which can be empty if `word` has
no thesaurus entries and the given, unfiltered word list.
'''
allowed = set(get_spellfile_items())
original_results = handler.query(word)
words = []
for state, items in original_results:
filtered_items = list(set(items) & allowed)
if filtered_items:
words.append([state, filtered_items])
return (words, original_results)
def get_options(word):
'''Find the thesaurus result for `word`.
If you have `let g:thesaurus_allow_unmanaged_words = 1` in your .vimrc and
`word` is not listed in your spellfile then every thesaurus result is returned.
Returns:
list[str]: The thesaurus results for `word`.
'''
framework = thesaurus_query.Thesaurus_Query_Handler()
framework.session_terminate()
framework.session_init()
results, original_results = query(framework, word)
if not results and allow_unmanaged_words():
results = original_results
framework.session_terminate()
return results
def get_contextless_options(word):
'''Find every thesaurus result for `word`.
Args:
word (str): The word to search with.
Returns:
tuple[list[str], bool]:
The found thesaurus results and if the results were created automatically.
The bool is False if the thesaurus results are retrieved from a cache.
'''
if word in _CACHE:
return (_CACHE[word], False)
options = get_options(word)
sorted_options = sorted(set([item for _, items in options for item in items]))
_CACHE[word] = sorted_options
return (sorted_options, True)
def get_current_spellfile():
'''Find the user's spellfile.
If `let g:thesaurus_filter_spellfile = /some/path/on/disk` then this
spellfile will be read and used, no matter what spellfile the user has
Returns:
str: The active spellfile, if one could be found.
'''
try:
return vim.eval(THESAURUS_SPELLFILE_OVERRIDE)
except Exception:
pass
try:
return vim.eval('&spellfile')
except Exception:
return ''
def get_spellfile_items():
'''list[str]: Every word found in the user's active/global spellfile.'''
def read_spellfile(path):
with open(path, 'r') as file_:
items = []
comment_prefix = '#'
for line in file_.readlines():
line = line.strip()
if line and not line.startswith(comment_prefix):
items.append(line)
return items
spellfile = get_current_spellfile()
if not spellfile:
return []
return read_spellfile(spellfile)
def add_word_to_temporary_thesaurus(word):
'''Add `word` and its thesaurus results to the current thesaurus, if needed.'''
def match_style(word, options):
if word.istitle():
return [item.capitalize() for item in options]
return options
# Note: If we try to get thesaurus results for a capitalized word like "Filter"
# then it is very unlikely to get any matches. But if it's all lower-case
# it will work. So we make the search case-insensitive
#
thesaurus_search_word = word.lower()
options, generated = get_contextless_options(thesaurus_search_word)
if not generated:
return
options = [word] + match_style(word, options)
thesaurus = vim.eval('&thesaurus')
with open(thesaurus, 'a') as handler:
handler.write(','.join(options) + '\n')
def has_text_on_current_row():
'''bool: If there are any words on the user's current line.'''
(row, _) = vim.current.window.cursor
row -= 1
line = vim.current.buffer[row]
return bool(_WORDS.findall(line))
def get_actual_word():
'''str: Find the word that the user's cursor is on.'''
(row, column) = vim.current.window.cursor
row -= 1
line = vim.current.buffer[row]
character = line[column]
if character.isalpha():
return vim.eval("expand('<cword>')")
try:
return _WORDS.findall(line[:column])[-1]
except IndexError:
return ''
def generate_and_initialize_menu():
'''Show the filtered thesaurus results for the word under the current cursor.'''
word = get_actual_word()
if not word:
return
add_word_to_temporary_thesaurus(word)
if not has_text_on_current_row():
return
initialize_menu(word=word)
def initialize_menu(word=''):
'''Show the filtered thesaurus results for the word under the current cursor.
Note:
Even if your cursor is in the middle of a word, the entire word will be
replaced by thesaurus matches (as it should be! Honestly, why is this
not a default feature of Vim?)
Args:
word (str, optional):
If given, a menu will show all the thesaurus matches for `word`.
Otherwise, thesaurus results for the word under the user's cursor
will be shown, instead.
'''
if not word:
word = get_actual_word()
if not word:
return
(row, column) = vim.current.window.cursor
row -= 1
character = vim.current.buffer[row][column]
is_on_whitespace_on_the_next_word = character not in word
if is_on_whitespace_on_the_next_word:
vim.command('startinsert')
return
try:
next_character = vim.current.buffer[row][column + 1]
except IndexError:
next_character = ''
if next_character and next_character.isalpha():
vim.command('execute "normal e"')
vim.command('call feedkeys("a")')
EOF
function! GenerateAndShowThesaurusMenuForCurrentWord()
pythonx << EOF
try:
generate_and_initialize_menu()
except Exception:
LOGGER.exception('An error happened while creating and showing the thesaurus.')
EOF
call feedkeys("\<C-x>\<C-t>")
endfunction
function! ShowThesaurusMenuForCurrentWord()
pythonx << EOF
try:
initialize_menu()
except Exception:
LOGGER.exception('An error happened while showing the thesaurus.')
EOF
call feedkeys("\<C-x>\<C-t>")
endfunction
nnoremap <leader>t :call GenerateAndShowThesaurusMenuForCurrentWord()<CR>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment