Найти тему
Блокнот математика

Рубрика "Секреты Вим". Свои автодополнения

Привет, коллеги. Продолжаем обзор возможностей автодополнения. Вим может предложить вам варианты того, что вы начали печатать: всей строки или одного слова, причем слово он может искать в разных местах: этом же файле или других файлах, в специальном словаре, в словаре синонимов, в словарях для проверки орфографии, по тегам и пр., причем можно объединить ряд источников, пользуясь сразу всеми (см. опцию complete). Мы обсудили саму концепцию автодополнения, а также дополнение по словарю и по тезаурусу (словарю синонимов).

А сегодня обсудим возможность "глубокой настройки": создать свой механизм автодополнения. Вим дает вам такую возможность, и даже две.

https://www.factinate.com/wp-content/uploads/2020/01/shutterstock_1206781150.jpg
https://www.factinate.com/wp-content/uploads/2020/01/shutterstock_1206781150.jpg

Обе работают одинаково: вы указываете в опции (omnifunc или completefunc) имя особой функции, которая и будет вызываться по нажатию предусмотренной комбинации (<C-X><C-O> для первой и <C-X><C-U> для второй). Первая возможность называется "Omnicompletion" и она довольно известна, а вторая называется "user-defined completion" и она известна меньше.

Почему две? Ну, потому что омни предполагается настраиваемой для типа файла, а пользовательская - личная. При этом какое-то хитрое автодополнение может включиться для, например, файлов в ТеХе, но при этом у вас есть ваше, своё, персональное. Скажем, первое дополняет по синтаксису (см. далее), а второе вставляет термины из вашей области науки.

Функция довольно сложная. Она принимает два параметра: findstart и base. И вызывается два раза: первый раз, чтобы определить начало того фрагмента, который надлежит заменить, а второй - чтобы получить собственно то, на что заменять. При первом вызове findstart равен 1, а второй параметр роли не играет; а при втором вызове findstart равен 0, а base содержит тот фрагмент, который найден на первом этапе.

При первом вызове функция должна вернуть номер столбца, то есть позиции в строке. Минимально возможное значение 0, максимальное - позиция курсора, которая доступна как col('.'). Естественно заменять неполное слово, то есть надо "отступить" по строке до первого пробела или до начала строки. Заменяться будет текст между указанным значением и позицией курсора. Соответственно, вы можете заменить всё начало строки или вообще ничего, и все промежуточные варианты.

Можно вернуть отрицательное число до -3 включительно, отменяя автодополнение (разница в выводе сообщения об ошибке и надо ли выходить из режима автодополнения).

При втором вызове надо вернуть список возможных дополнений. Я не описываю более сложные возвраты, которые есть (см. Справку). Можно, например, игнорировать регистр или описывать вариант автодополнения сокращенно (в списке вариантов, например, ДНК, а реально будет "дезоксирибонуклеиновая кислота").

Можно передавать варианты по одному и давать пользователю возможность выбрать из предложенного (прервав поиск, если пользователя что-то удовлетворило). Это полезно, если варианты генерируются медленно.

Есть один естественный способ автодополнения: по синтаксису. В самом деле, для раскраски синтаксиса необходимо "знать" много ключевых слов данного языка, и они описаны в специальных файлах: почему бы эти файлы не использовать как словари для автодополнения?

Встроенного способа такого автодополнения нет, но вот функция для omnicompletion идет в поставке. Это функция syntaxcomplete#Complete.

Присвойте это имя (вместе с решеткой!) опции omnifunc и сочетание <C-X><C-O> будет дополнять по синтаксису: это очень удобно! А можете сделать так, что это будет включаться само по типу файла, еще проще.

Например, у меня файл в ТеХ, распознан как таковой, и я решаю добавить новый пакет. Пишу: \usep<C-X><C-O> и получаю \usepackage

Вся функция состоит из двух ветвей условного оператора, в зависимости от значения параметра findstart. В первой ветке ищется фрагмент, который будем заменять, а во второй подбираем ему замену. Вот пример из Справки: замена месяцев.

fun! CompleteMonths(findstart, base)
if a:findstart
" locate the start of the word
let line = getline('.')
let start = col('.') - 1
while start > 0 && line[start - 1] =~ '\a'
let start -= 1
endwhile
return start
else
" find months matching with "a:base"
let res = []
for m in split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec")
if m =~ '^' . a:base
call add(res, m)
endif
endfor
return res
endif
endfun

Вся функция состоит из двух ветвей условного оператора, в зависимости от значения параметра findstart. В первой ветке ищется фрагмент, который будем заменять: по сути, это поиск первого несловного символа по строке влево. Извлекаем строку и шагаем по ней влево, пока символы остаются словными (\a).

Во второй ветке мы получаем то, что надлежит заменить, и должны вернуть то, на что заменять его. Берем список названий месяцев (тут они сокращены) и добавляем месяц в возвращаемый список, если введенный фрагмент, как регулярное выражение, совпадает с ним. Например, фрагмент Ja совпадает в слове Jan.

Несложно добавить, например, преобразования числа в месяц. Или добавить в список все русские месяцы во всех падежных формах.

Второй пример: синонимы. Как я упоминал в заметке про синонимы, дополнение по тезаурусу предполагает, что синоним - это одно слово. Это не всегда удобно, потому что часто синоним - это именно словосочетание. В некоторых словарях такие синонимы-словосочетания разделены каким-нибудь символом вроде |.

Теперь у вас нет проблемы сделать себе автодополнение по такому словарю. Проделаем это в отдельном материале.

Последний пример: вычисление выражений. Возьмите функцию выше и вторую ветвь (после else но до endif) замените на

return [string(eval(a:base))]

А в первой замените \a на \S, чтобы отмотать до первого пробела.

Теперь автодополнение будет вычислять выражения. Только их надо писать без пробелов. Конвертация в строку (string) нужна, чтобы работать с вещественными числами: автоматически они не преобразуются. Можете повесить это автодополнение на ==:

imap == <C-X><C-U>

И можно удобно вычислять выражение: набираете atan(1.0)*4== и получите вместо этого всего 3.141593.

Удачи, коллеги.

Научно-популярные каналы на Дзене: путеводитель
Новости популярной науки12 марта 2022