Para um autocmd em um ftplugin, devo usar a correspondência de padrões ou <buffer>?

14

Eu tenho um autocmd para arquivos TeX e Markdown para salvar o arquivo automaticamente. Nada incomum:

autocmd CursorHold *.tex,*.md w

No entanto, como configurações personalizadas para esses arquivos aumentou, eu dividi-los para dentro ftplugin/tex.vime ftplugin/markdown.vim:

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

Agora, esses arquivos são originários apenas dos arquivos apropriados, portanto, a correspondência de padrões é redundante. Aparentemente, autocmds pode ser local de buffer. De :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>

Isso parece destinado a esse uso. Agora, ambos ftplugin/tex.vime ftplugin/markdown.vimpodem ter:

autocmd CursorHold <buffer> w

Eu realmente não estou preocupado com a extensão real enquanto o filetype está correta, então isso me poupa de ter que se preocupar *.mde *.markdowne quaisquer outras extensões são válidos para Markdown.

Esse uso está <buffer>correto? Existem armadilhas das quais devo estar ciente? As coisas ficarão confusas se eu limpar um buffer e abrir outro (espero que os números não colidam, mas ...)?

muru
fonte
1
Eu tenho certeza que se você limpar um buffer, qualquer autocmds local do buffer também será apagado.
precisa saber é o seguinte
@ Tumbler41 de fato. Diz que na ajuda, alguns parágrafos abaixo.
Muru
1
Não é exatamente o que você pediu, mas up(abreviação :update) seria melhor do que wno seu autocmd (evite gravações desnecessárias).
MMontu
@mMontu Nice. Ele até resolve um problema que tive quando o autocmd foi ativado para um arquivo que eu estava examinando a partir do histórico do git. O buffer foi somente leitura e wfalhou. Repetidamente. :upnão faz nada nesse caso. :)
muru
Que bom que você gostou :) Aliás, você também pode encontrar a opção 'gravação automática' útil (dependendo da sua motivação, você pode largar os autocmds).
MMontu

Respostas:

11

Esse uso do <buffer> está correto?

Eu acho que está correto, mas você só precisa envolvê-lo dentro de um augroup e limpar o último, para garantir que o autocmd não seja duplicado toda vez que você executar um comando que recarrega o mesmo buffer.

Como você explicou, o padrão especial <buffer>permite confiar no mecanismo de detecção de tipo de arquivo interno, implementado dentro do arquivo $VIMRUNTIME/filetype.vim.

Nesse arquivo, você pode encontrar os autocmds internos do Vim, responsáveis ​​por definir o tipo de arquivo correto para qualquer buffer. Por exemplo, para remarcação:

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

Dentro do seu plugin de tipo de arquivo, você pode copiar os mesmos padrões para cada autocmd instalado. Por exemplo, para salvar automaticamente o buffer quando o cursor não se mover durante alguns segundos:

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

Mas <buffer>é bem menos detalhado:

au CursorHold <buffer> update

Além disso, se algum dia outra extensão for válida e $VIMRUNTIME/filetype.vimfor atualizada para incluí-la, seus autocmds não serão informados. E você terá que atualizar todos os seus padrões dentro dos plugins do tipo de arquivo.


As coisas ficarão confusas se eu limpar um buffer e abrir outro (espero que os números não colidam, mas ...)?

Não tenho certeza, mas não acho que o Vim possa reutilizar o número do buffer de um buffer limpo. Não encontrei a seção relevante na ajuda, mas encontrei este parágrafo em vim.wikia.com :

Não. O Vim não reutilizará o número do buffer de um buffer excluído para um novo buffer. O Vim sempre atribuirá o próximo número seqüencial para um novo buffer.

Além disso, como o @ Tumbler41 explicou, quando você limpa um buffer, seus autocmds são removidos. De :h autocmd-buflocal:

Quando um buffer é eliminado, os comandos automáticos locais do buffer também desaparecem, é claro.

Se você quiser verificar a si mesmo, poderá fazê-lo aumentando o nível de verbosidade do Vim para 6. Você pode fazê-lo temporariamente, apenas para um comando, usando o :verbosemodificador. Portanto, dentro do seu buffer de remarcação, você pode executar:

:6verbose bwipe

Então, se você verificar as mensagens do Vim:

:messages

Você deve ver uma linha parecida com esta:

auto-removing autocommand: CursorHold <buffer=42>

Onde 42estava o número do seu buffer de remarcação.


Existem armadilhas das quais devo estar ciente?

Existem três situações que eu consideraria armadilhas e que envolvem um padrão especial <buffer>. Em dois deles, <buffer>pode ser um problema, no outro, é uma solução.

Armadilha 1

Primeiro, você deve ter cuidado com a maneira de limpar os augroups de seus autocmds locais de buffer. Você deve estar familiarizado com este trecho:

augroup your_group_name
    autocmd!
    autocmd Event pattern command
augroup END

Portanto, você pode ser tentado a usá-lo para seus autocmds locais de buffer, sem modificação, assim:

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

Mas isso terá um efeito indesejado. Na primeira vez que você carregar um buffer de redução, vamos chamá-lo A, seu autocmd será instalado corretamente. Então, quando você recarregar A, o autocmd será excluído (por causa de autocmd!) e reinstalado. Portanto, o augroup impedirá corretamente a duplicação do autocmd.

Agora, suponha que você carregue um segundo buffer de redução, vamos chamá-lo B, em uma segunda janela. TODOS os autocmds do augroup serão limpos: esse é o autocmd de Ae esse de B. Em seguida, um único autocmd será instalado para B.

Portanto, quando você fizer alguma alteração Be esperar alguns segundos para CursorHoldser acionado, ele será salvo automaticamente. Mas se você voltar Ae fizer a mesma coisa, o buffer não será salvo. Isso ocorre porque na última vez em que você carregou um buffer de redução, houve um desequilíbrio entre o que você removeu e o que adicionou. Você removeu mais do que adicionou.

A solução é não remover TODOS os autocmds, mas apenas os do buffer atual, passando o padrão especial <buffer>para :autocmd!:

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

Observe que você pode substituir CursorHoldpor uma estrela para corresponder a qualquer evento na linha que remova autocmds:

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

Dessa forma, você não precisa especificar todos os eventos que seus autocmds estão ouvindo quando deseja limpar o augroup.


Armadilha 2

Há outra armadilha, mas desta vez <buffer>não é o problema, é a solução.

Ao incluir uma opção local em um plug-in de tipo de arquivo, você provavelmente faz o seguinte:

setlocal option1=value
setlocal option2

Isso funcionará conforme o esperado para opções locais de buffer, mas nem sempre para opções locais de janelas. Para ilustrar o problema, você pode tentar o seguinte experimento. Crie o arquivo ~/.vim/after/ftdetect/potion.vime, dentro dele, escreva:

autocmd BufNewFile,BufRead *.pn setfiletype potion

Este arquivo definirá automaticamente o tipo potionde arquivo para qualquer arquivo cuja extensão seja .pn. Você não precisa agrupá-lo dentro de um augroup porque, para esse tipo específico de arquivo, o Vim o fará automaticamente (consulte :h ftdetect).

Se os diretórios intermediários não existirem no seu sistema, você poderá criá-los.

Em seguida, crie o plugin do tipo de arquivo ~/.vim/after/ftplugin/potion.vime, dentro dele, escreva:

setlocal list

Por padrão, em um potionarquivo, essa configuração fará com que os caracteres de tabulação sejam exibidos como ^Ie o final das linhas como $.

Agora, crie um mínimo vimrc; dentro de /tmp/vimrcescrever:

filetype plugin on

... para ativar os plugins de tipo de arquivo.

Além disso, crie um arquivo de poção /tmp/pn.pn, e um arquivo aleatório /tmp/file. No arquivo de poções, escreva algo:

foo
bar
baz

No arquivo aleatório, escreva o caminho para o arquivo de poção /tmp/pn.pn:

/tmp/pn.pn

Agora, inicie o Vim com um mínimo de inicializações, apenas localizando vimrce abra os dois arquivos em viewports verticais:

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

Você deve ver duas viewports verticais. O arquivo de poção à esquerda exibe o final das linhas com cifrões, o arquivo aleatório à direita não os exibe.

Concentre o foco no arquivo aleatório e pressione gfpara exibir o arquivo de poção cujo caminho está sob o cursor. Agora você vê o mesmo buffer de poção na janela de exibição correta, mas desta vez o final das linhas não é exibido com cifrões. E se você digitar :setlocal list?, o Vim deverá responder com nolist:

insira a descrição da imagem aqui

Toda a cadeia de eventos:

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

... não ocorreu, porque o primeiro deles, BufReadnão ocorreu quando você pressionou gf. O buffer já estava carregado.

Pode parecer inesperado, porque quando você adicionou setlocal listdentro do seu plug-in de tipo de arquivo de poção, pode ter pensado que ele habilitaria a 'list'opção em qualquer janela que exibisse um buffer de poção.

O problema não é específico para este novo potiontipo de arquivo. Você pode experimentar isso com um markdownarquivo também.

Também não é específico para a 'list'opção. Você pode experimentá-lo com outras definições da janela-local, como 'conceallevel', 'foldmethod', 'foldexpr', 'foldtitle', ...

Também não é específico para o gfcomando. Você pode experimentar com outros comandos que podem alterar o buffer exibido na janela atual: marca global, C-o(retroceder no jumplist local da janela),, :b {buffer_number}...

Para resumir, as opções locais da janela serão definidas corretamente, se e somente se:

  • o arquivo não foi lido durante a sessão atual do Vim (porque BufReadprecisará ser acionado)
  • o arquivo está sendo exibido em uma janela em que as opções locais da janela já estavam definidas corretamente
  • a nova janela é criada com um comando como :split(nesse caso, ele deve herdar as opções locais da janela da janela em que o comando foi executado)

Caso contrário, as opções locais da janela podem não estar definidas corretamente.

Uma solução possível seria configurá-los não diretamente a partir de um plug-in de tipo de arquivo, mas a partir de um autocmd instalado no último, que seria escutado BufWinEnter. Este evento deve ser acionado toda vez que um buffer for exibido em uma janela.

Então, por exemplo, em vez de escrever isso:

setlocal list

Você escreveria isto:

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

E aqui, você encontra novamente o padrão especial <buffer>.

insira a descrição da imagem aqui


Armadilha 3

Se você alterar o tipo de arquivo do seu buffer, os autocmds permanecerão. Se você deseja removê-los, é necessário configurar b:undo_ftplugin(consulte :h undo_ftplugin) e incluir este comando:

exe 'au! my_markdown * <buffer>'

No entanto, não tente remover o augroup em si, porque ainda pode haver alguns buffers de remarcação que possuem autocmds dentro dele.

FWIW, este é o trecho UltiSnips que estou usando para definir 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

E aqui está um exemplo de valor que eu tenho ~/.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>'
                    \  "

Como observação, entendo por que você fez a pergunta, porque quando procurei todas as linhas em que o padrão especial <buffer>foi usado nos arquivos padrão do Vim:

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

Encontrei apenas 9 correspondências (você pode encontrar mais ou menos, estou usando o Vim versão 8.0, com correções de até 134). E entre as 9 correspondências, 7 estão na documentação, apenas 2 são realmente fornecidas. Você deve encontrá-los em $ VIMRUNTIME / syntax / dircolors.vim :

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

Eu não sei se isso pode causar um problema, mas eles não estão dentro de um augroup, o que significa que cada vez que você recarregar um tampão cujo filetype é dircolors(acontece se você editar um arquivo chamado .dircolors, .dir_colorsou cujas extremidades caminho com /etc/DIR_COLORS), o plug-in de sintaxe adicionará um novo autocmd local de buffer.

Você pode verificá-lo assim:

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

O último comando deve exibir isso:

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('.')

Agora, recarregue o buffer e pergunte novamente quais são os autocmds locais do buffer para o buffer atual:

:e
:au * <buffer>

Desta vez, você verá:

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('.')

Após cada recarga do arquivo, s:reset_colors()e s:preview_color('.')será chamado uma vez adicional, cada vez que um dos eventos CursorHold, CursorHoldI, CursorMoved, CursorMovedIé acionado.

Provavelmente não é um grande problema, porque mesmo depois de recarregar um dircolorsarquivo várias vezes, não vi uma desaceleração perceptível ou comportamento inesperado do Vim.

Se é um problema para você, você poderia entrar em contato com o mantenedor do plugin sintaxe, mas ao mesmo tempo, se você queria evitar a duplicação de autocmds, você pode criar seu próprio plugin sintaxe para dircolorsarquivos, usando o arquivo ~/.vim/syntax/dircolors.vim. Dentro dele, você importaria o conteúdo do plug-in de sintaxe original:

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

Então, no último, você apenas colocaria os autocmds dentro de um augroup que você limparia. Então, você substituiria estas linhas:

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

... com estes:

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

Observe que, se você criou seu dircolorsplug-in de sintaxe com o arquivo ~/.vim/after/syntax/dircolors.vim, ele não funcionaria, porque o plug-in de sintaxe padrão seria originário antes. Ao usar ~/.vim/syntax/dircolors.vim, seu plug-in de sintaxe será originado antes do padrão e definirá a variável buffer-local b:current_syntax, o que impedirá que o plug-in de sintaxe padrão seja originado porque contém esta proteção:

if exists("b:current_syntax")
    finish
endif

A regra geral parece ser: use os diretórios ~/.vim/ftplugine ~/.vim/syntaxpara criar um plug-in de tipo de arquivo / sintaxe personalizado e impedir que o próximo plug-in (para o mesmo tipo de arquivo) no caminho de tempo de execução seja originado (incluindo os padrão). E uso ~/.vim/after/ftplugin, ~/.vim/after/syntaxe não para evitar que outros plugins de ser originária, mas apenas para ter a última palavra sobre o valor de alguns ajustes.

user852573
fonte
1
Eu gostaria de poder votar mais difícil.
Rich
3
@ Rich Eu votei isso mais difícil para você. Minha única reclamação é a falta de um resumo "tl; dr". Páginas de minúcias textuais, por mais vitais que sejam para a compreensão, fazem com que minha alma envelheça percorrer. A substituição de autocmd!com autocmd! CursorHold <buffer>em augroupblocos é uma questão particularmente crítica - e deveria ter sido destacada antecipadamente. No entanto ... este é um investimento surpreendentemente incrível de tempo, esforço e lágrimas de sangue.
Cecil Curry