Created
January 6, 2019 16:27
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
" 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