Для autocmd в ftplugin я должен использовать сопоставление с шаблоном или <буфер>?

14

У меня есть autocmd для файлов TeX и Markdown для автоматического сохранения файла. Ничего необычного

autocmd CursorHold *.tex,*.md w

Однако, поскольку пользовательские настройки для этих файлов увеличились, я разделил их на ftplugin/tex.vimи ftplugin/markdown.vim:

" ftplugin/tex.vim
autocmd CursorHold *.tex w
" ftplugin/markdown.vim
autocmd CursorHold *.md w

Теперь эти файлы поставляются только для соответствующих файлов, поэтому сопоставление с образцом является избыточным. Очевидно, autocmds может быть локальным по отношению к буферу. От :h autocmd-buffer-local:

Buffer-local autocommands are attached to a specific buffer.  They are useful
if the buffer does not have a name and when the name does not match a specific
pattern.  But it also means they must be explicitly added to each buffer.

Instead of a pattern buffer-local autocommands use one of these forms:
        <buffer>        current buffer
        <buffer=99>     buffer number 99
        <buffer=abuf>   using <abuf> (only when executing autocommands)
                        <abuf>

Это, кажется, предназначено для такого использования. Теперь оба ftplugin/tex.vimи ftplugin/markdown.vimмогут иметь:

autocmd CursorHold <buffer> w

Я не очень обеспокоен фактическое расширение до тех пор , как Filetype правильно, так что это избавляет меня от необходимости беспокоиться о *.mdи *.markdownи любых других расширениях действительны для Markdown.

Это использование <buffer>правильное? Есть ли какие-либо подводные камни, о которых я должен знать? Будет ли что-то запутаться, если я вытираю буфер и открываю другой (надеюсь, числа не будут сталкиваться, но…)?

Мур
источник
1
Я уверен, что если вы очистите буфер, все локальные буферные autocmds тоже будут уничтожены.
Tumbler41
@ Tumbler41 действительно. Это говорит о том, что в справке несколько абзацев вниз.
Муру
1
Не совсем то, что вы просили, но up(сокращенно :update) будет лучше, чем wв вашем autocmd (избегайте ненужных записей).
mMontu
@mMontu Отлично. Это даже решает проблему, с которой я столкнулся, когда autocmd активировал файл, который я изучал из истории git. Буфер был только для чтения и wвышел из строя. Несколько раз. :upничего не делает в этом случае. :)
Муру
Рад, что вам понравилось :) Кстати, вы также можете найти полезной опцию 'autowrite' (в зависимости от вашей мотивации, вы можете отказаться от autocmds).
mMontu

Ответы:

11

Это использование <buffer> правильно?

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

Как вы объяснили, специальный шаблон <buffer>позволяет вам полагаться на встроенный механизм обнаружения типов файлов, реализованный внутри файла $VIMRUNTIME/filetype.vim.

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

" Markdown
au BufNewFile,BufRead *.markdown,*.mdown,*.mkd,*.mkdn,*.mdwn,*.md  setf markdown

Внутри вашего плагина filetype вы можете копировать одни и те же шаблоны для каждого устанавливаемого вами autocmd. Например, чтобы автоматически сохранить буфер, когда курсор не двигался в течение нескольких секунд:

au CursorHold *.markdown,*.mdown,*.mkd,*.mkdn,*.mdwn,*.md  update

Но <buffer>это гораздо менее многословно:

au CursorHold <buffer> update

Кроме того, если однажды другое расширение будет действительным и $VIMRUNTIME/filetype.vimбудет обновлено, чтобы включить его, ваши autocmds не будут проинформированы. И вам придется обновить все их шаблоны внутри ваших плагинов файловых типов.


Будет ли что-то запутаться, если я вытираю буфер и открываю другой (надеюсь, числа не будут сталкиваться, но…)?

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

Нет. Vim не будет повторно использовать номер буфера удаленного буфера для нового буфера. Vim всегда назначает следующий последовательный номер для нового буфера.

Кроме того, как объяснил @ Tumbler41 , когда вы стираете буфер, его autocmds удаляются. От :h autocmd-buflocal:

Конечно, когда буфер очищается, его локальные автокоманды также исчезают.

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

:6verbose bwipe

Затем, если вы проверите сообщения Vim:

:messages

Вы должны увидеть строку, похожую на эту:

auto-removing autocommand: CursorHold <buffer=42>

Где 42был номер вашего буфера уценки.


Есть ли какие-либо подводные камни, о которых я должен знать?

Есть 3 ситуации, которые я бы назвал подводными камнями и которые связаны с особой схемой <buffer>. В двух из них <buffer>может быть проблема, в другом - решение.

Ловушка 1

Во-первых, вы должны быть осторожны с тем, как очищать augroups ваших локальных буферов autocmds. Вы должны быть знакомы с этим фрагментом:

augroup your_group_name
    autocmd!
    autocmd Event pattern command
augroup END

Таким образом, у вас может возникнуть соблазн использовать его для ваших локальных буферов autocmds, без изменений, например:

augroup my_markdown
    autocmd!
    autocmd CursorHold <buffer> update
augroup END

Но это будет иметь нежелательный эффект. При первой загрузке буфера уценки, давайте назовем его A, его autocmd будет правильно установлен. Затем, когда вы перезагрузите компьютер A, autocmd будет удален (из-за autocmd!) и переустановлен. Таким образом, augroup правильно предотвратит дублирование autocmd.

Теперь предположим, что вы загружаете второй буфер уценки, давайте назовем его Bво втором окне. ВСЕ autocmds augroup будут очищены: это autocmd из Aи один из B. Затем будет установлен одиночный autocmd B.

Поэтому, когда вы вносите какие-либо изменения Bи ждете несколько секунд CursorHold, пока не произойдет выстрел, он будет автоматически сохранен. Но если вы вернетесь Aи сделаете то же самое, буфер не будет сохранен. Это потому, что в последний раз, когда вы загружали буфер уценки, был дисбаланс между тем, что вы удалили, и тем, что вы добавили. Вы удалили больше, чем добавили.

Решение состоит в том, чтобы не удалить ВСЕ autocmds, но только те из текущего буфера, передавая специальный шаблон <buffer>для :autocmd!:

augroup my_markdown
    autocmd! CursorHold <buffer>
    autocmd CursorHold <buffer> update
augroup END

Обратите внимание, что вы можете заменить CursorHoldзвездочкой, чтобы соответствовать любому событию в строке, которая удаляет autocmds:

augroup my_markdown
    autocmd! * <buffer>
    autocmd CursorHold <buffer> update
augroup END

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


Ловушка 2

Есть еще одна ловушка, но на этот раз <buffer>это не проблема, а решение.

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

setlocal option1=value
setlocal option2

Это будет работать, как и ожидалось, для локальных параметров буфера, но не всегда для локальных окон. Чтобы проиллюстрировать проблему, вы можете попробовать следующий эксперимент. Создайте файл ~/.vim/after/ftdetect/potion.vimи внутри него напишите:

autocmd BufNewFile,BufRead *.pn setfiletype potion

Этот файл автоматически устанавливает тип potionфайла для любого файла с расширением .pn. Вам не нужно заключать его в группу augroup, потому что для этого конкретного типа файлов Vim сделает это автоматически (см. :h ftdetect).

Если промежуточные каталоги не существуют в вашей системе, вы можете их создать.

Затем создайте плагин filetype ~/.vim/after/ftplugin/potion.vimи внутри него напишите:

setlocal list

По умолчанию в potionфайле этот параметр приводит к тому, что символы табуляции отображаются как, ^Iа конец строк как $.

Теперь создайте минимум vimrc; внутри /tmp/vimrcнаписать:

filetype plugin on

... чтобы включить плагины файловых типов.

Кроме того, создайте файл зелья /tmp/pn.pnи случайный файл /tmp/file. В файле зелья напишите что-нибудь:

foo
bar
baz

В случайном файле напишите путь к файлу зелья /tmp/pn.pn:

/tmp/pn.pn

Теперь запустите Vim с минимумом инициализаций, просто загрузите vimrcи откройте оба файла в вертикальных окнах просмотра:

$ vim -Nu /tmp/vimrc -O /tmp/pn.pn /tmp/file

Вы должны увидеть 2 вертикальных видовых экрана. Файл зелья слева отображает конец строк со знаками доллара, случайный файл справа не отображает их вообще.

Установите фокус на случайный файл и нажмите, gfчтобы отобразить файл зелья, путь которого находится под курсором. Теперь вы видите тот же буфер зелья в правом окне просмотра, но на этот раз, конец строки не отображается со знаками доллара. И если вы напечатаете :setlocal list?, Vim должен ответить nolist:

введите описание изображения здесь

Вся цепочка событий:

BufRead event → set 'filetype' option → load filetype plugins

... не произошло, потому что первое из них BufReadне произошло, когда вы нажали gf. Буфер уже загружен.

Это может показаться неожиданным, потому что, когда вы добавляете setlocal listв свой плагин типа файла зелья, вы, возможно, думали, что он включит 'list'опцию в любом окне, отображающем буфер зелья.

Проблема не связана с этим новым potionтипом файла. Вы можете испытать это и с markdownфайлом.

Это не специфично для 'list'варианта. Вы можете испытать его с другой оконной локальной настройкой, как 'conceallevel', 'foldmethod', 'foldexpr', 'foldtitle', ...

Это не специфично для gfкоманды. Вы можете испытать это с другими командами, которые могут изменить буфер, отображаемый в текущем окне: глобальная метка, C-o(переместиться назад в локальном списке переходов),, :b {buffer_number}...

Подводя итог, локальные параметры окна будут правильно установлены, если и только если:

  • файл не был прочитан во время текущего сеанса Vim (потому BufReadчто должен быть запущен)
  • файл отображается в окне, где локальные параметры окна уже были правильно установлены
  • новое окно создается с помощью команды, такой как :split(в этом случае оно должно наследовать локальные параметры окна от окна, в котором была выполнена команда)

В противном случае локальные параметры окна могут быть установлены неправильно.

Возможным решением было бы установить их не напрямую из плагина filetype, а из autocmd, установленного в последнем, который будет слушать BufWinEnter. Это событие должно запускаться каждый раз, когда в окне отображается буфер.

Так, например, вместо того, чтобы писать это:

setlocal list

Вы бы написали это:

augroup my_potion
    au! * <buffer>
    au BufWinEnter <buffer> setlocal list
augroup END

И здесь вы снова обнаруживаете особый узор <buffer>.

введите описание изображения здесь


Ловушка 3

Если вы измените тип файла вашего буфера, autocmds останется. Если вы хотите удалить их, вам нужно настроить b:undo_ftplugin(см. :h undo_ftplugin) И включить в него следующую команду:

exe 'au! my_markdown * <buffer>'

Однако не пытайтесь удалить саму группу, потому что все еще могут быть некоторые буферы уценки, в которых есть autocmds.

Кстати, это фрагмент кода UltiSnips, который я использую для установки b:undo_ftplugin:

snippet undo "undo ftplugin settings" bm
" teardown {{{1

let b:undo_ftplugin =         get(b:, 'undo_ftplugin', '')
\                     .(empty(get(b:, 'undo_ftplugin', '')) ? '' : '|')
\                     ."${1:
\                          setl ${2:option}<}${3:
\                        | exe '${4:n}unmap <buffer> ${5:lhs}'}${6:
\                        | exe 'au! ${7:group_name} * <buffer>'}${8:
\                        | unlet! b:${9:variable}}${10:
\                        | delcommand ${11:Cmd}}
\                      "
$0
endsnippet

И вот пример значения, которое я имею в ~/.vim/after/ftplugin/awk.vim:

let b:undo_ftplugin =         get(b:, 'undo_ftplugin', '')
                    \ .(empty(get(b:, 'undo_ftplugin', '')) ? '' : '|')
                    \ ."
                    \   setl cms< cocu< cole< fdm< fdt< tw<
                    \|  exe 'nunmap <buffer> K'
                    \|  exe 'au! my_awk * <buffer>'
                    \|  exe 'au! my_awk_format * <buffer>'
                    \  "

В качестве примечания, я понимаю, почему вы задали вопрос, потому что, когда я искал все строки, где использовался специальный шаблон <buffer>в файлах Vim по умолчанию:

:vim /au\%[tocmd!].\{-}<buffer>/ $VIMRUNTIME/**/*

Я нашел только 9 совпадений (вы можете найти более или менее, я использую Vim версии 8.0, с исправлениями до 134). И среди 9 матчей 7 находятся в документации, только 2 фактически получены. Вы должны найти их в $ VIMRUNTIME / syntax / dircolors.vim :

autocmd CursorMoved,CursorMovedI <buffer> call s:preview_color('.')
autocmd CursorHold,CursorHoldI   <buffer> call s:reset_colors()

Я не знаю , если это может вызвать проблемы, но они не внутри augroup, что означает каждый раз , когда вы перезагружать буфер которого является Filetype dircolors(это происходит , если вы редактируете файл с именем .dircolors, .dir_colorsили чей путь концы с /etc/DIR_COLORS), плагин синтаксиса добавит новый локальный буфер autocmd.

Вы можете проверить это так:

$ vim ~/.dir_colors
:au * <buffer>

Последняя команда должна отобразить это:

CursorHold
    <buffer=1>
              call s:reset_colors()
CursorHoldI
    <buffer=1>
              call s:reset_colors()
CursorMoved
    <buffer=1>
              call s:preview_color('.')
CursorMovedI
    <buffer=1>
              call s:preview_color('.')

Теперь перезагрузите буфер и снова спросите, каковы локальные для буфера autocmds для текущего буфера:

:e
:au * <buffer>

На этот раз вы увидите:

CursorHold
    <buffer=1>
              call s:reset_colors()
              call s:reset_colors()
CursorHoldI
    <buffer=1>
              call s:reset_colors()
              call s:reset_colors()
CursorMoved
    <buffer=1>
              call s:preview_color('.')
              call s:preview_color('.')
CursorMovedI
    <buffer=1>
              call s:preview_color('.')
              call s:preview_color('.')

После каждой перезагрузки файла, s:reset_colors()и s:preview_color('.')будет называться еще один раз, каждый раз , когда один из события CursorHold, CursorHoldI, CursorMoved, CursorMovedIобжигают.

Вероятно, это не большая проблема, потому что даже после перезагрузки dircolorsфайла несколько раз я не заметил заметного замедления или неожиданного поведения Vim.

Если это проблема для вас, вы можете связаться с разработчиком модуля синтаксиса, но в то же время, если вы хотите предотвратить дублирование autocmds, вы можете создать свой собственный модуль синтаксиса для dircolorsфайлов, используя файл ~/.vim/syntax/dircolors.vim. Внутри него вы импортируете содержимое исходного синтаксического плагина:

$ vim ~/.vim/syntax/dircolors.vim
:r $VIMRUNTIME/syntax/dircolors.vim

Затем в последнем случае вы просто оборачиваете autocmds в группу augroup, которую очищаете. Итак, вы бы заменили эти строки:

autocmd CursorMoved,CursorMovedI <buffer> call s:preview_color('.')
autocmd CursorHold,CursorHoldI   <buffer> call s:reset_colors()

... с этими:

augroup my_dircolors_syntax
    autocmd! * <buffer>
    autocmd CursorMoved,CursorMovedI <buffer> call s:preview_color('.')
    autocmd CursorHold,CursorHoldI   <buffer> call s:reset_colors()
augroup END

Обратите внимание, что если вы создали dircolorsплагин синтаксиса с файлом ~/.vim/after/syntax/dircolors.vim, он не будет работать, потому что плагин синтаксиса по умолчанию будет поставлен раньше. При использовании ~/.vim/syntax/dircolors.vim, ваш синтаксический плагин будет получен раньше, чем по умолчанию, и он установит локальную переменную буфера b:current_syntax, которая предотвратит источник синтаксического плагина по умолчанию, потому что он содержит этот сторож:

if exists("b:current_syntax")
    finish
endif

Общее правило выглядит следующим образом: используйте каталоги ~/.vim/ftpluginand ~/.vim/syntaxдля создания пользовательского подключаемого модуля типа / синтаксиса и предотвращайте выбор следующего подключаемого модуля (для того же типа файла) в пути времени выполнения (включая стандартные). И используйте ~/.vim/after/ftplugin, ~/.vim/after/syntaxне для того, чтобы другие источники плагинов были получены, а просто чтобы иметь последнее слово в значении некоторых настроек.

user852573
источник
1
Я хотел бы поднять это сложнее.
Богатое
3
@ Богатый, я проголосовал за это сложнее для тебя. Моя единственная жалоба - отсутствие краткого описания "tl; dr". Страницы текстовых подробностей, какими бы важными для понимания они ни были, причиняют боль моей стареющей душе. Замена autocmd!с autocmd! CursorHold <buffer>in в augroupблоках является особенно важной ошибкой - и должна была быть выдвинута на первый план. Тем не менее ... это явно удивительное вложение времени, усилий и кровавых слез.
Сесил Карри