Как создавать текстовые объекты Vim в Lua

«Вы не понимаете. Vim — это как язык! Вы будете говорить на Vim, когда будете писать! Когда вы идете на рынок! Вы будете говорить на Vim со своей кошкой! Когда ты будешь думать, в твоей голове будет Слово Vim™! Вы увидите! Это изменит вашу жизнь!»

Именно такие аргументы любой Vim-хиппи будет петь бедным еретикам, пытаясь обратить их в «Вечный редактор». Такой хиппи, как я, который сейчас пишет статью об одном из главных компонентов этого «языка» — тексте-объекте. Повторяйте за мной: слава текстовым объектам!

Их использование — это мощно, это правда; но мы можем сделать лучше. Мы могли бы создавать новые тексты-объекты, как бог создает жизнь! Мы могли бы формировать Хаос, чтобы навести порядок в галактике!

Греческий бог Гефест создал огонь в своей легендарной кузнице. Как и ему, нам тоже нужны инструменты для создания наших собственных текстовых объектов. Что мы можем использовать? Vimscript?

С помощью Neovim мы можем использовать Lua для настройки нашего любимого редактора; мы также можем скомпилировать Vim с поддержкой Lua. На мой взгляд, один из лучших способов научиться настраивать Vim — это брать уже написанные функции в Vimscript и преобразовывать их в Lua.

Преимущества:

  1. Если вы не знаете Vimscript, анализ кода для преобразования его в Lua покажет вам полезные функции, которые вы сможете использовать для дальнейшей настройки. Это необходимо для использования возможностей Vim.
  2. Если вы не знаете Lua, вы тоже можете изучить его таким образом. Это отличный язык программирования, используемый многими другими инструментами, такими как, например, фантастический Pandoc.
  3. Вы получите более глубокое понимание того, как работает Vim, что позволит вам улучшить свой рабочий процесс.

Итак, давайте попробуем силу функций Vimscript в сочетании с конструкциями Lua, реализуя полезные текстовые объекты. Более конкретно мы рассмотрим в этой статье:

  • Общие принципы работы с текстовыми объектами.
  • Как создать текстовый объект, представляющий линию.
  • Как создавать текстовые объекты, разделенные парой символов, например двумя запятыми.
  • Как создать очень полезный — и довольно универсальный — текстовый объект, соответствующий некоторому уровню отступа.

Тексты-объекты, которые мы будем создавать, вдохновлены другими авторами из The Great Internet™. Источники вдохновения вы найдете в конце статьи.

Я предполагаю, что вы в той или иной степени владеете Vim. Если вы не понимаете, что происходит в этой статье, вы можете обратиться к моей серии статей по изучению Vim.

Если вы впервые пишете на Lua, я бы посоветовал вам доработать примеры, которые мы рассмотрим в этой статье. Чтобы использовать правильный синтаксис, вам следует держать под рукой краткое руководство, как, например, это.

Готовы ли вы погрузиться в Vimscript, Lua и текстовые объекты? Я уверен, что да! Поехали!

Что такое текстовый объект?

Прежде чем перейти в Vim для создания наших новых восхитительных текстовых объектов, давайте сначала сделаем то, что должен делать любой разработчик, когда ему нужно решить проблему: подумаем. Давайте разложим, что такое текстовый объект:

  1. Текст-объект — это нажатие клавиш в НОРМАЛЬНОМ режиме с использованием двух клавиш, первая из которых может быть только «a» (для «around») или «i» (для «inside»).
  2. Текст-объект представляет собой определенную строку символов с началом и концом.
  3. Его можно использовать только после оператора или после переключения в ВИЗУАЛЬНЫЙ режим работы с символами (используя «v» в НОРМАЛЬНОМ режиме).
  4. Оператор действует на текст-объект, а режим VISUAL выделяет сам текст-объект.
  5. Чтобы применить оператор к некоторым текстовым объектам, курсор не обязательно должен находиться на самом текстовом объекте. В этом случае оператор будет действовать на следующий текстовый объект строки.

Если вы никогда не слышали о правиле 5, оно может изменить вашу жизнь и принести вам славу и богатство за пару дней. Не меньше! Если вы набираете di( в НОРМАЛЬНОМ режиме, и ваш курсор находится перед парой круглых скобок, вы переместитесь внутрь них. Оператор d удалит все, что находится внутри. Отлично!

Как я уже говорил выше, текст-объект может начинаться с буквы «a» или «i». Я понимаю текстовые объекты, начинающиеся с «a», как «around», даже если справка Vim будет называть их неопределенным артиклем a, например delete a word. Я предпочитаю «вокруг», потому что такие текстовые объекты часто включают некоторые символы вокруг «внутреннего» текстового объекта. Это хорошая мнемоника для запоминания разницы между ними.

Движения очень похожи на текст-объекты, но это не одно и то же. Использование оператора перед движением приводит к выполнению действия от позиции курсора до конечного результата движения. Текст-объект не обязательно начинается с позиции курсора, и это самое большое различие.

Например, daw удалит d вокруг aорда w, независимо от буквы слова, на котором находится ваш курсор. Используя движение, dw удалит от позиции курсора до следующего wорда.

Теперь, когда мы исследовали пространство проблемы, давайте войдем в пространство решения. Чтобы создать текстовый объект с началом и концом, мы можем просто:

  1. Выделите нужные нам символы.
  2. Примените любой оператор к выделению.

Это замечательно, потому что в Vim есть несколько отображений, позволяющих нам легко это сделать.

Справка Vim

  • :help text-object
  • :help motion

Сопоставление операторов

Основы

Когда мы нажимаем оператор в НОРМАЛЬНОМ режиме (например, d, y или c), мы беззвучно переключаемся в режим OPERATOR-PENDING. Именно в этом режиме мы будем вводить наше движение (или текст-объект), с которым хотим работать. Затем мы автоматически переключаемся обратно в НОРМАЛЬНЫЙ режим.

Чтобы определить новые текстовые объекты (или движения), которые могут быть использованы в режиме OPERATOR-PENDING, вы можете использовать operator-pending mapping: omap или, если вам не нужен рекурсивный маппинг, onoremap. Благодаря им нам нужно беспокоиться только о выборе символов, входящих в наши текстовые объекты; сами операторы нас не волнуют.

Строка: Наш новый текстовый объект

Давайте теперь создадим первый текстовый объект этой статьи: строку.

  • Текст-объект alaround the line) удалит все, включая возможные отступы в начале.
  • Текст-объект il (iвнутри line) не удалит отступы.

Вот первое отображение для il:

onoremap <silent> il :<c-u>normal! $v^<cr>
Войти в полноэкранный режим Выход из полноэкранного режима

Если вы не знаете, что это значит, вот некоторые пояснения:

Например, если вы нажмете yil или dil, ваши операторы yank и delete будут действовать на наш новый текст-объект. Если мы хотим только выделить его, нам понадобится другое отображение для режима VISUAL:

xnoremap <silent> il :<c-u>normal! $v^<cr>
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь вы можете нажать vil и восхититься потенциалом ваших бесконечных навыков.

Теперь у нас есть «внутренняя» версия нашего текстового объекта; а как насчет «внешней»?

xnoremap <silent> al :<c-u>normal! $v0<cr>
onoremap <silent> al :<c-u>normal! $v0<cr>
Вход в полноэкранный режим Выход из полноэкранного режима

Эти отображения следуют тем же принципам, за исключением того, что на этот раз мы выделяем все, включая возможные пробельные символы в начале строки. Мы также можем использовать V вместо $v0.

Справка Vim

  • :help omap-info

Недостающие текстовые объекты

Наши новые текстовые объекты — это здорово, но мы можем сделать лучше. На этот раз мы создадим целую серию, разделенную разными парами символов.

Между двумя символами

Удалить пару круглых скобок очень просто: a(, a), или даже ab. Как всегда, есть и варианты «внутри». Но как насчет удаления всего между двумя точками? Между двумя дефисами? Между двумя запятыми?

Как насчет создания текстовых объектов, разделенных этими символами: ,,. ,;,: ,+,-,=,~,_,*,#,/,|,,& , $, ? ?

Чтобы решить эту проблему, мы можем рассмотреть простой пример:

Hello, how are you, you?
Вход в полноэкранный режим Выход из полноэкранного режима

Если мы хотим изменить символы между двумя запятыми, мы можем:

  1. Подвести курсор к первой запятой.
  2. Выделить все до второй запятой.
  3. Включение запятых в выделение или нет будет являться разницей между текстовым объектом «вокруг» и «внутри».

Этот сценарий требует, чтобы наш курсор находился между двумя запятыми, по крайней мере, сейчас. Мы улучшим это в ближайшее время.

Чтобы переместить курсор к первой или второй запятой, мы можем использовать f, F, t и T в НОРМАЛЬНОМ режиме. Вот возможное решение:

xnoremap <silent> i, :<c-u>normal! T,vt,<cr>
onoremap <silent> i, :<c-u>normal! T,vt,<cr>
xnoremap <silent> a, :<c-u>normal! F,ft,<cr>
onoremap <silent> a, :<c-u>normal! F,ft,<cr>
Войдите в полноэкранный режим Выход из полноэкранного режима

Мы могли бы повторить эти сопоставления для каждого символа, который мы хотим включить. Но это было бы довольно трудно читать и поддерживать. Вдохновившись фрагментом Zsh, о котором я уже рассказывал в этой статье, мы могли бы:

  1. Перебрать все различные символы.
  2. Перебрать все различные режимы, которые мы хотим отобразить (режим OPERATOR-PENDING и режим VISUAL).
  3. Создавать различные отображения для каждой итерации.

Вот возможная реализация в Vimscript:

let s:chars = [ '_', '.', ':', ',', ';', '<bar>', '/', '<bslash>', '*', '+', '%', '`', '?' ]
for char in s:chars
    for mode in [ 'xnoremap', 'onoremap' ]
        execute printf('%s <silent> i%s :<C-u>normal! T%svt%s<CR>', mode, char, char, char)
        execute printf('%s <silent> a%s :<C-u>normal! F%svf%s<CR>', mode, char, char, char)
    endfor
endfor
Вход в полноэкранный режим Выход из полноэкранного режима

Мне нравится использовать Vimscript для простых конфигураций, но как только мне нужно добраться до циклов или условий (я ненавижу равенство-операторы в Vimscript), я переключаюсь на Lua. Если вы используете Neovim, или если у вас есть Vim, скомпилированный с Lua, вы можете использовать следующее:

function basic_text_objects()
    local chars = { '_', '.', ':', ',', ';', '|', '/', '\', '*', '+', '%', '`', '?' }
    for _,char in ipairs(chars) do
        for _,mode in ipairs({ 'x', 'o' }) do
            vim.api.nvim_set_keymap(mode, "i" .. char, string.format(':<C-u>normal! T%svt%s<CR>', char, char), { noremap = true, silent = true })
            vim.api.nvim_set_keymap(mode, "a" .. char, string.format(':<C-u>normal! F%svf%s<CR>', char, char), { noremap = true, silent = true })
        end
    end
end

return {
    basic_text_objects = basic_text_objects
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Я бы посоветовал вам хранить ваши скрипты Lua в папке lua/<namespace>; <namespace> может быть вашим именем пользователя или любым другим именем, которое вы захотите. Например, мои скрипты находятся в $XDG_CONFIG_HOME/nvim/lua/hypnos.

Затем вы можете напрямую вызывать ваши новые функции в вашем vimrc. Если это файл Vimscript, вам понадобится префикс lua, как показано ниже:

lua require('<namespace>/text_objects').basic_text_objects()
Вход в полноэкранный режим Выход из полноэкранного режима

К следующему текстовому объекту

Сейчас нам нужно, чтобы наш курсор находился между двумя символами, чтобы действовать на наши новые текстовые объекты. Также было бы неплохо иметь возможность действовать с первой парой этих символов, когда наш курсор находится перед ними. Как мы видели выше в этой статье, это уже можно сделать с помощью круглых скобок или кавычек.

Рассмотрим следующий пример:

She felt, suddenly, deeply in love with Vim.
Войти в полноэкранный режим Выйти из полноэкранного режима

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

onoremap <silent> i, :<C-u>silent! normal! f,F,lvt,<cr>
Войти в полноэкранный режим Выход из полноэкранного режима

Представим, что курсор находится на первом d из suddenly, между запятыми.

Теперь представим, что наш курсор находится на S из She, перед запятыми.

Обратите внимание, что Ex-команда normal! проваливается на втором шаге. По умолчанию она останавливается на этом, не выполняя шаги 3 и 4. Поэтому мы добавляем здесь Ex-команду :silent!; она заставит normal! выполняться до конца.

Для текста-объекта «вокруг» мы можем следовать тем же принципам. Единственное отличие: мы также выделяем две запятые.

onoremap a, :<C-u>silent! normal! f,F,vf,<cr>
Войти в полноэкранный режим Выход из полноэкранного режима

Вот окончательный результат на языке Lua:

function basic_text_objects()
    local chars = { '_', '.', ':', ',', ';', '|', '/', '\', '*', '+', '%', '`', '?' }
    for idx,char in ipairs(chars) do
        for idx,mode in ipairs({ 'x', 'o' }) do
            vim.api.nvim_set_keymap(mode, "i" .. char, string.format(':<C-u>silent! normal! f%sF%slvt%s<CR>', char, char), { noremap = true, silent = true })
            vim.api.nvim_set_keymap(mode, "a" .. char, string.format(':<C-u>silent! normal! f%sF%svf%s<CR>', char, char), { noremap = true, silent = true })
        end
    end
end
Войти в полноэкранный режим Выйти из полноэкранного режима

Имейте в виду, что это наивный подход. Во-первых, заставлять команду normal! не срабатывать, кажется немного халтурой. Кроме того, это довольно сложно для понимания. С учетом этого, иногда хак достаточно хорош, чтобы удовлетворить наши потребности и двигаться вперед. Я бы не стал использовать это в плагине, который может быть использован другими, но для моего собственного использования этого вполне достаточно.

Каждый раз, когда мы создавали наши текстовые объекты, мы сначала формулировали проблему, а затем пытались найти путь к ее решению. Это хороший итеративный подход, но он также означает, что наши текстовые объекты могут не охватывать все возможные сценарии. Мы все еще можем что-то улучшить впоследствии, если увидим, что наши текстовые объекты ведут себя не так, как ожидалось в определенных ситуациях.

Это отличается от установки плагина: вы часто не знаете, как он работает, и не можете итеративно модифицировать его, чтобы он отвечал вашим конкретным потребностям.

Текстовые объекты на основе отступов

Давайте создадим еще один текстовый объект, на этот раз более сложный. Он может быть полезен любому разработчику.

Основы

Если вы посмотрите на текстовые объекты, уже доступные в ванильном Vim, их границы основаны на определенных символах. Есть и другие важные элементы, которые вы найдете в большинстве кодовых баз и которые можно использовать для текстового объекта: отступы.

Вот как выделить наши новые текстовые объекты:

  1. Получите начальный отступ текущей строки. Это будет эталонный отступ, с которым мы будем сравнивать все остальные строки.
  2. Двигайтесь вверх строка за строкой, пока отступ равен или выше начального отступа.
  3. Остановитесь, когда следующая строка уже не будет соответствовать правилу 2, и начните режим VISUAL построчно.
  4. Двигайтесь вниз, пока отступ не станет равен или больше начального отступа.

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

function! IndentTextObject()
  let startindent = indent(line('.'))

  " Move up till we are at the top of the buffer
  " or till the indentation is less than the starting one
  let prevline = line('.') - 1
  while prevline > 0 && indent(prevline) >= startindent
    -
    let prevline = line('.') - 1
  endwhile

  " Begin linewise-visual selection
  normal! 0V

  " Move down till we are at the bottom of the buffer
  " or till the indentation is less than the starting one
  let nextline = line('.') + 1
  let lastline = line('$')
  while nextline <= lastline && indent(nextline) >= startindent
    +
    let nextline = line('.') + 1
  endwhile
endfunction

onoremap <silent>ai :<C-U>call IndentTextObject()<CR>
onoremap <silent>ii :<C-U>call IndentTextObject()<CR>
xnoremap <silent>ai :<C-U>call IndentTextObject()<CR>
xnoremap <silent>ii :<C-U>call IndentTextObject()<CR>
Войти в полноэкранный режим Выход из полноэкранного режима

Опять же, я всегда предпочитаю использовать Lua, когда все становится немного сложнее:

function select_indent()
    local start_indent = vim.fn.indent(vim.fn.line('.'))
    local prev_line = vim.fn.line('.') - 1

    while prev_line > 0 and vim.fn.indent(prev_line) >= start_indent do
        vim.cmd('-')
        prev_line = vim.fn.line('.') - 1
    end

    vim.cmd('normal! 0V')

    local next_line = vim.fn.line('.') + 1
    local last_line = vim.fn.line('$')
    while next_line <= last_line and vim.fn.indent(next_line) >= start_indent do
        vim.cmd('+')
        next_line = vim.fn.line('.') + 1
    end
end

function indent_text_objects()
    for _,mode in ipairs({ 'x', 'o' }) do
        vim.api.nvim_set_keymap(mode, 'ii', ':<c-u>lua select_indent()<cr>', { noremap = true, silent = true })
        vim.api.nvim_set_keymap(mode, 'ai', ':<c-u>lua select_indent()<cr>', { noremap = true, silent = true })
    end
end

return {
    indent_text_objects = indent_text_objects,
}
Вход в полноэкранный режим Выход из полноэкранного режима

Основу функций составляют два цикла while. Они выполняют:

  1. Перемещать курсор вверх до тех пор, пока предыдущая строка не будет иметь меньший отступ, чем начальная.
  2. Начать режим VISUAL построчно (с normal! 0V) и двигаться вниз строка за строкой, выбирая те, которые находятся на том же уровне отступа или выше.

Обратите внимание, что для перемещения вверх и вниз мы используем Ex-команды - и +. Да, это Ex-команды: попробуйте, например, :+. Вы также можете использовать normal! k или normal! <up>.

Улучшение реализации

Наша маленькая функция работает отлично, но она быстро показывает свои пределы. Возможно, самый очевидный: текст-объект «внутри» и «вокруг» имеют совершенно одинаковое поведение. Ужас и проклятие!

Допустим, что «вокруг» текстового объекта нужно выделить еще две строки, причем первые строки должны иметь меньшие отступы при перемещении вверх и вниз.

Для иллюстрации рассмотрим следующий код. Символ «┃» обозначает курсор:

-- Comment
function super_function()
 ┃  if true then
       print('youpi')
       print('wuhu')
    end
end
-- Comment
Войти в полноэкранный режим Выход из полноэкранного режима

Нажатие dii удалит весь блок if, но оставит все остальное нетронутым. Нажатие dai удалит все, кроме двух комментариев.

Отсюда у нас есть два решения:

  1. Создайте две разные функции: одну для текстового объекта «вокруг», другую для «внутри».
  2. Передать булевский флаг для использования текстового объекта «вокруг» или «внутри» в одной и той же функции.

Если мы примем первое решение, у нас будут почти идентичные функции; скорее всего, нам придется менять обе функции каждый раз, когда мы захотим улучшить наши текст-объекты. Поэтому давайте выберем второе решение. Тем не менее, если появляется слишком много условий, поскольку между двумя текстовыми объектами появляется все больше и больше различий, я бы определенно разделил функцию.

Вот возможная реализация:

function select_indent(around)
    local start_indent = vim.fn.indent(vim.fn.line('.'))
    local prev_line = vim.fn.line('.') - 1

    while prev_line > 0 and vim.fn.indent(prev_line) >= start_indent do
        vim.cmd('-')
        prev_line = vim.fn.line('.') - 1
    end
    if around then
        vim.cmd('-')
    end

    vim.cmd('normal! 0V')

    local next_line = vim.fn.line('.') + 1
    local last_line = vim.fn.line('$')
    while next_line <= last_line and vim.fn.indent(next_line) >= start_indent do
        vim.cmd('+')
        next_line = vim.fn.line('.') + 1
    end
    if around then
        vim.cmd('+')
    end
end

function indent_text_objects()
    for _,mode in ipairs({ 'x', 'o' }) do
        vim.api.nvim_set_keymap(mode, 'ii', ':<c-u>lua select_indent()<cr>', { noremap = true, silent = true })
        vim.api.nvim_set_keymap(mode, 'ai', ':<c-u>lua select_indent(true)<cr>', { noremap = true, silent = true })
    end
end

return {
    indent_text_objects = indent_text_objects,
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Далее, мы могли бы игнорировать пустые строки. Сейчас они нас не особо волнуют, но они неизбежно будут останавливать наше движение вверх и вниз, потому что их уровень отступа всегда будет меньше начального. То есть, если ваш курсор с самого начала не находился на строке без отступов, в этом случае вы выделите весь буфер.

Мы можем определить пустую строку как строку, содержащую только белое пространство (пробелы, табуляции и новые строки). Чтобы узнать, действительно ли предыдущая или следующая строка пустая, мы можем использовать регекс:

local blank_line_pattern = '^%s*$'
Войти в полноэкранный режим Выйти из полноэкранного режима

Регексы в Lua немного странные: если во многих других системах регексов для обозначения пробелов используется s, то в Lua — %s.

Теперь нам нужно проверять на каждой итерации, является ли следующая строка пустой. Для этого мы можем создать небольшую функцию:

local prev_blank_line = function(line) return string.match(vim.fn.getline(line), blank_line_pattern) end
Вход в полноэкранный режим Выход из полноэкранного режима

Нам также нужно изменить условия для наших двух циклов while. Например:

while prev_line > 0 and (prev_blank_line(prev_line) or vim.fn.indent(prev_line) >= start_indent) do
Войти в полноэкранный режим Выйти из полноэкранного режима

И последняя деталь, которая может вам пригодиться. Когда ваш курсор находится на пустой строке, когда вы вызываете текст-объект своими проворными пальцами, вы можете игнорировать нажатие клавиши. Если эта идея покажется вам привлекательной, то перед первым циклом while можно добавить следующее:

if string.match(vim.fn.getline('.'), blank_line_pattern) then
    return
end
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь мы исправили самые очевидные недостатки нашего текстового объекта. Но мы можем сделать лучше! Как насчет возможности задать счетчик для выбора более низких уровней отступов?

Например, dai будет работать как сейчас, но d2ai будет выбирать более низкий уровень отступа, а d3ai будет выбирать еще больше.

Для этого нам просто нужно увеличить начальный отступ в зависимости от заданного числа. Сейчас мы знаем только уровень отступа текущей строки с помощью нашей переменной start_indent, но мы не знаем количество отступов для каждого уровня.

Чтобы получить эту информацию, мы можем посмотреть на значение опции shiftwidth.

Вот реализация:

local start_indent = vim.fn.indent(vim.fn.line('.'))
if vim.v.count > 0 then
    start_indent = start_indent - vim.o.shiftwidth * (vim.v.count - 1)
    if start_indent < 0 then
        start_indent = 0
    end
end
Вход в полноэкранный режим Выход из полноэкранного режима

Вы можете задаться вопросом о вычислениях:

start_indent = start_indent - vim.o.shiftwidth * (vim.v.count - 1)
Вход в полноэкранный режим Выход из полноэкранного режима

Во-первых, vim.v.count — это счетчик, присвоенный текстовому объекту. В Vimscript это было бы v:count.

Что касается самого вычисления, то нам нужно умножить полученное значение на количество отступов на уровень (опция shiftwidth) и вычесть его из отступов текущей строки. Это дает нам количество отступов на один уровень выше.

Мы решаем, что отсчет 1 не должен ничего изменить (dai такой же, как d1ai), поэтому мы вычитаем 1 из отсчета. Мы также проверяем, не меньше ли начальный отступ 0, что может произойти, если мы вводим счет на строке без отступа. В этом случае мы устанавливаем начальный отступ равным 0.

Вот заключительная функция:

function select_indent(around)
    local start_indent = vim.fn.indent(vim.fn.line('.'))
    local blank_line_pattern = '^%s*$'

    if string.match(vim.fn.getline('.'), blank_line_pattern) then
        return
    end

    if vim.v.count > 0 then
        start_indent = start_indent - vim.o.shiftwidth * (vim.v.count - 1)
        if start_indent < 0 then
            start_indent = 0
        end
    end

    local prev_line = vim.fn.line('.') - 1
    local prev_blank_line = function(line) return string.match(vim.fn.getline(line), blank_line_pattern) end
    while prev_line > 0 and (prev_blank_line(prev_line) or vim.fn.indent(prev_line) >= start_indent) do
        vim.cmd('-')
        prev_line = vim.fn.line('.') - 1
    end
    if around then
        vim.cmd('-')
    end

    vim.cmd('normal! 0V')

    local next_line = vim.fn.line('.') + 1
    local next_blank_line = function(line) return string.match(vim.fn.getline(line), blank_line_pattern) end
    local last_line = vim.fn.line('$')
    while next_line <= last_line and (next_blank_line(next_line) or vim.fn.indent(next_line) >= start_indent) do
        vim.cmd('+')
        next_line = vim.fn.line('.') + 1
    end
    if around then
        vim.cmd('+')
    end
end
Войти в полноэкранный режим Выход из полноэкранного режима

Вот и все! Мы создали прекрасный текст-объект силой наших соединенных духов. С радостью и гордостью я нарекаю тебя Божественным Кузнецом Текстового Объекта©.

Сопоставление функции Lua

До сих пор мы использовали некоторые строки Vimscript для отображения наших функций Lua на наши новые текстовые объекты. Neovim 0.7, который вышел за пару дней до публикации этой статьи, позволяет нам напрямую отображать функции Lua без использования Vimscript.

Вы можете попробовать посмотреть, например, :help vim.keymap.set, чтобы еще больше улучшить наш код.

Сила объекта Text-Object

Как всегда, придумывание новых идей текстовых объектов зависит от ваших потребностей. Я всегда предпочитаю решать болевые точки, которые я замечаю при написании в Vim, вместо того, чтобы пытаться придумать идеи улучшений из ничего. Например, я всегда хотел иметь текстовый объект для работы с функциями; в то же время, я хотел что-то, что могло бы работать с большинством языков программирования. Наличие текстового объекта, основанного на уровнях отступов, является для меня лучшим компромиссом.

Итак, что мы увидели в этой статье?

  • Когда вы нажимаете оператор в НОРМАЛЬНОМ режиме, вы переключаетесь в режим OPERATOR-PENDING. Отсюда вы можете дать движение или текст-объект, чтобы оперировать тем, чем вы хотите.
  • Текст-объект — это только набор символов, с которыми можно работать, используя операторы.
  • Легко создавать базовые текстовые объекты благодаря отображениям, зависящим от оператора, omap и onoremap.
  • Когда вам нужны более сложные текстовые объекты, проще написать функцию. Тогда вы можете положиться на богатый набор функций Vimscript для перемещения курсора и выбора того, что вам нужно.
  • Vimscript — это язык с множеством странных дизайнерских решений. Я предпочитаю использовать Lua, когда поток данных идет по нескольким ветвям.

Хотите больше статей, где мы используем Vimscript и Lua, чтобы улучшить ваши возможности по настройке Vim? Не стесняйтесь высказывать свои мысли, отзывы и улучшения в разделе комментариев. Ставьте лайк, делитесь и любите.

Ссылки

  • Vim wiki — Отступ текста-объекта
  • Vim wiki — Создание новых текстовых объектов
  • Отложенные транзакции — Дилан МакКлюр
  • Начало использования Lua в Neovim — Тимофей Штерле
  • Выучить X за Y минут — Lua — Тайлер Нейлон

Учимся играть в Vim: Увлекательное руководство по изучению лучшего редактора

Я начал писать очень амбициозное руководство по изучению Vim с нуля. Благодаря отличным отзывам моих читателей, я смогу решить проблемы, на которые жалуются многие новички при изучении Vim. Например:

  • Как перемещаться по нескольким файлам и проектам в Vim?
  • Как отлаживать в Vim?
  • Как искать, находить и заменять?

Это руководство расскажет о наиболее полезных функциях Vim, а также о некоторых мощных плагинах, которые обогатят ваш опыт.

Помогите мне повлиять на мир Vim! Вы можете подписаться на рассылку и рассказать мне обо всем, что вы хотите видеть в книге. Ранняя скидка гарантирована!

Я лично отвечаю на каждое письмо, поэтому не стесняйтесь задавать столько вопросов, сколько хотите. Мне всегда приятно помочь.

И последнее, но не менее важное: Я также написал книгу о создании собственной среды разработки без использования мыши, так что если она вас тоже заинтересовала, нажмите на эту блестящую ссылку.


Оцените статью
devanswers.ru
Добавить комментарий