Привет, коллеги. Продолжаем обзор возможностей автодополнения. Вим может предложить вам варианты того, что вы начали печатать: всей строки или одного слова, причем слово он может искать в разных местах: этом же файле или других файлах, в специальном словаре, в словаре синонимов, в словарях для проверки орфографии, по тегам и пр., причем можно объединить ряд источников, пользуясь сразу всеми (см. опцию complete). Мы обсудили саму концепцию автодополнения, а также дополнение по словарю и по тезаурусу (словарю синонимов).
А сегодня обсудим возможность "глубокой настройки": создать свой механизм автодополнения. Вим дает вам такую возможность, и даже две.
Обе работают одинаково: вы указываете в опции (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.
Удачи, коллеги.