Per un autocmd in un ftplugin, dovrei usare il pattern matching o <buffer>?


14

Ho un autocmd per i file TeX e Markdown per salvare automaticamente il file. Niente di insolito:

autocmd CursorHold *.tex,*.md w

Tuttavia, con l'aumentare delle impostazioni personalizzate per questi file, li ho suddivisi in ftplugin/tex.vime ftplugin/markdown.vim:

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

Ora, questi file provengono solo per i file appropriati, quindi la corrispondenza del modello è ridondante. Apparentemente, autocmds può essere buffer-local. Da :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>

Questo sembra essere pensato per tale uso. Ora, entrambi ftplugin/tex.vime ftplugin/markdown.vimpossono avere:

autocmd CursorHold <buffer> w

Io non sono davvero preoccupati per l'estensione effettiva fino a quando il tipo di file è corretto, quindi questo mi evita di dover preoccuparsi *.mde *.markdowndi qualsiasi altro estensioni sono validi per Markdown.

Questo uso è <buffer>corretto? Ci sono delle insidie ​​di cui dovrei essere a conoscenza? Le cose diventeranno confuse se pulisco un buffer e ne apro un altro (speriamo che i numeri non si scontrino, ma ...)?


1
Sono abbastanza sicuro che se si cancella un buffer, vengono cancellati anche tutti gli autocmds buffer-local.
Tumbler41,

@ Tumbler41 davvero. Lo dice nell'aiuto, alcuni paragrafi in basso.
muru,

1
Non esattamente quello che hai chiesto, ma up(abbreviazione di :update) sarebbe meglio che wnella tua autocmd (evitare scritture non necessarie).
mMontu,

@mMontu Nice. Risolve anche un problema che ho avuto quando l'autocmd si è attivato per un file che stavo esaminando dalla cronologia di Git. Il buffer era di sola lettura e wnon funzionava. Ripetutamente. :upnon fa nulla in quel caso. :)
muru,

Sono contento che ti sia piaciuto :) Per inciso, potresti anche trovare utile l'opzione 'autowrite' (a seconda della tua motivazione, potresti far cadere le autocmds).
mMontu,

Risposte:


11

Questo uso di <buffer> è corretto?

Penso che sia corretto, ma devi solo avvolgerlo all'interno di un augroup e cancellare quest'ultimo, per assicurarti che autocmd non venga duplicato ogni volta che esegui un comando che ricarica lo stesso buffer.

Come spiegato, il modello speciale <buffer>consente di fare affidamento sul meccanismo di rilevamento del tipo di file incorporato, implementato all'interno del file $VIMRUNTIME/filetype.vim.

In questo file, puoi trovare gli autocmds integrati di Vim che sono responsabili dell'impostazione del tipo di file corretto per ogni dato buffer. Ad esempio, per il markdown:

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

All'interno del plug-in del tipo di file, è possibile copiare gli stessi schemi per ogni autocmd che si installa. Ad esempio, per salvare automaticamente il buffer quando il cursore non si è mosso per alcuni secondi:

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

Ma <buffer>è molto meno prolisso:

au CursorHold <buffer> update

Inoltre, se un giorno un altro interno è valido e $VIMRUNTIME/filetype.vimviene aggiornato per includerlo, i tuoi autocmds non verranno informati. E dovrai aggiornare tutti i loro pattern all'interno dei tuoi plugin di tipo file.


Le cose diventeranno confuse se pulisco un buffer e ne apro un altro (speriamo che i numeri non si scontrino, ma ...)?

Non sono sicuro ma non credo che Vim possa riutilizzare il numero di buffer di un buffer cancellato. Non sono riuscito a trovare la sezione pertinente dall'aiuto, ma ho trovato questo paragrafo da vim.wikia.com :

No. Vim non riutilizzerà il numero di buffer di un buffer eliminato per un nuovo buffer. Vim assegnerà sempre il numero sequenziale successivo per un nuovo buffer.

Inoltre, come ha spiegato @ Tumbler41 , quando si cancella un buffer, i suoi autocmds vengono rimossi. Da :h autocmd-buflocal:

Quando un buffer viene cancellato, anche i suoi autocomandi locali del buffer scompaiono, ovviamente.

Se vuoi controllare te stesso, puoi farlo aumentando il livello di verbosità di Vim a 6. Puoi farlo temporaneamente, solo per un comando, usando il :verbosemodificatore. Quindi, all'interno del buffer di markdown, è possibile eseguire:

:6verbose bwipe

Quindi, se controlli i messaggi di Vim:

:messages

Dovresti vedere una linea simile a questa:

auto-removing autocommand: CursorHold <buffer=42>

Dov'era 42il numero del buffer di markdown.


Ci sono delle insidie ​​di cui dovrei essere a conoscenza?

Ci sono 3 situazioni che considererei insidie ​​e che coinvolgono il modello speciale <buffer>. In due di essi, <buffer>potrebbe essere un problema, nell'altro è una soluzione.

Pitfall 1

Innanzitutto, dovresti stare attento al modo in cui cancelli i gruppi di file degli autocmds buffer-locali. Devi conoscere questo frammento:

augroup your_group_name
    autocmd!
    autocmd Event pattern command
augroup END

Quindi, potresti essere tentato di usarlo per i tuoi autocmds buffer-locali, non modificati, in questo modo:

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

Ma questo avrà un effetto indesiderato. La prima volta che carichi un buffer Amarkdown, chiamiamolo , il suo autocmd verrà installato correttamente. Quindi, quando ricarichi A, l'autocd verrà eliminato (a causa di autocmd!) e reinstallato. Pertanto, l'augroup impedirà correttamente la duplicazione di autocmd.

Supponiamo ora di caricare un secondo buffer di markdown, chiamiamolo B, in una seconda finestra. TUTTI gli autocmd di augroup verranno cancellati: questo è autocmd di Ae quello di B. Quindi, verrà installato un SINGOLO autocmd per B.

Quindi, quando effettui qualche modifica Be attendi qualche secondo per CursorHoldessere licenziato, verrà automaticamente salvato. Ma se torni a Afare la stessa cosa, il buffer non verrà salvato. Questo perché l'ultima volta che hai caricato un buffer markdown, c'era uno squilibrio tra ciò che hai rimosso e ciò che hai aggiunto. Hai rimosso più di quello che hai aggiunto.

La soluzione è quella di non rimuovere TUTTI gli autocmds, ma solo quelli del buffer corrente, passando lo schema speciale <buffer>a :autocmd!:

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

Si noti che è possibile sostituire CursorHoldcon una stella per abbinare qualsiasi evento nella riga che rimuove autocmds:

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

In questo modo, non è necessario specificare tutti gli eventi a cui i vostri autocmds stanno ascoltando quando si desidera cancellare l'augroup.


Pitfall 2

C'è un'altra trappola, ma questa volta <buffer>non è il problema, è la soluzione.

Quando includi un'opzione locale in un plug-in di tipo di file, probabilmente lo fai in questo modo:

setlocal option1=value
setlocal option2

Funzionerà come previsto per le opzioni buffer-local, ma non sempre per quelle window-local. Per illustrare il problema, puoi provare il seguente esperimento. Crea il file ~/.vim/after/ftdetect/potion.vime al suo interno scrivi:

autocmd BufNewFile,BufRead *.pn setfiletype potion

Questo file imposterà automaticamente il tipo di file potionper qualsiasi file la cui estensione è .pn. Non è necessario avvolgerlo all'interno di un augroup perché, per questo particolare tipo di file, Vim lo farà automaticamente (vedi :h ftdetect).

Se le directory intermedie non esistono sul tuo sistema, puoi crearle.

Quindi, crea il plugin filetype ~/.vim/after/ftplugin/potion.vime al suo interno scrivi:

setlocal list

Per impostazione predefinita, in un potionfile, questa impostazione farà apparire i caratteri della scheda come ^Ie la fine delle righe come $.

Ora crea un minimo vimrc; dentro /tmp/vimrcscrivi:

filetype plugin on

... per abilitare i plugin del tipo di file.

Inoltre, crea un file pozione /tmp/pn.pne un file casuale /tmp/file. Nel file di pozioni, scrivi qualcosa:

foo
bar
baz

Nel file casuale, scrivi il percorso nel file pozione /tmp/pn.pn:

/tmp/pn.pn

Ora, avvia Vim con un minimo di inizializzazioni, procurando semplicemente il vimrc, e apri entrambi i file nelle finestre verticali:

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

Dovresti vedere 2 finestre verticali. Il file di pozione a sinistra mostra la fine delle linee con segni di dollaro, il file casuale a destra non le mostra affatto.

Dai lo stato attivo al file casuale e premi gfper visualizzare il file di pozione il cui percorso è sotto il cursore. Ora vedi lo stesso buffer di pozioni nella vista corretta, ma questa volta la fine delle linee non viene visualizzata con i simboli del dollaro. E se digiti :setlocal list?, Vim dovrebbe rispondere con nolist:

inserisci qui la descrizione dell'immagine

L'intera catena di eventi:

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

... non si è verificato, perché il primo di essi BufRead, non si è verificato quando hai premuto gf. Il buffer era già caricato.

Può sembrare inaspettato, perché quando hai aggiunto setlocal listil plug-in del tipo di file di pozioni, potresti aver pensato che avrebbe abilitato l' 'list'opzione in qualsiasi finestra che mostra un buffer di pozioni.

Il problema non è specifico per questo nuovo tipo di potionfile. Puoi provarlo anche con un markdownfile.

Non è nemmeno specifico per l' 'list'opzione. Si può sperimentare con altre impostazioni della finestra locale, come 'conceallevel', 'foldmethod', 'foldexpr', 'foldtitle', ...

Non è nemmeno specifico per il gfcomando. È possibile sperimentarlo con altri comandi che possono modificare il buffer visualizzato nella finestra corrente: segno globale, C-o(spostarsi indietro nella jumplist locale della finestra) :b {buffer_number}, ...

Per riassumere, le opzioni locali della finestra saranno impostate correttamente, se e solo se:

  • il file non è stato letto durante l'attuale sessione di Vim (perché BufReaddovrà essere attivato)
  • il file viene visualizzato in una finestra in cui le opzioni locali della finestra erano già impostate correttamente
  • la nuova finestra viene creata con un comando come :split(in questo caso, dovrebbe ereditare le opzioni finestra locale dalla finestra in cui è stato eseguito il comando)

Altrimenti, le opzioni locali della finestra potrebbero non essere impostate correttamente.

Una possibile soluzione sarebbe quella di impostarli non direttamente da un plug-in di tipo di file, ma da un autocmd installato in quest'ultimo, che ascolterebbe BufWinEnter. Questo evento deve essere generato ogni volta che viene visualizzato un buffer in una finestra.

Quindi, ad esempio, invece di scrivere questo:

setlocal list

Scriveresti questo:

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

E qui trovi di nuovo lo schema speciale <buffer>.

inserisci qui la descrizione dell'immagine


Pitfall 3

Se si modifica il tipo di file del buffer, rimarranno gli autocmds. Se vuoi rimuoverli, devi configurare b:undo_ftplugin(vedi :h undo_ftplugin) e includere questo comando al suo interno:

exe 'au! my_markdown * <buffer>'

Tuttavia, non tentare di rimuovere l'ugroup stesso, perché potrebbero esserci ancora alcuni buffer di markdown che contengono autocmds al suo interno.

FWIW, questo è lo snippet UltiSnips che sto usando per impostare 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

Ed ecco un esempio di valore che ho in ~/.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>'
                    \  "

Come nota a margine, capisco perché hai posto la domanda, perché quando ho cercato tutte le linee in cui è <buffer>stato utilizzato lo schema speciale nei file predefiniti di Vim:

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

Ho trovato solo 9 corrispondenze (potresti trovare più o meno, sto usando Vim versione 8.0, con patch fino a 134). E tra le 9 partite, 7 sono nella documentazione, solo 2 sono effettivamente di provenienza. Dovresti trovarli in $ VIMRUNTIME / syntax / dircolors.vim :

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

Non so se può causare un problema, ma non sono all'interno di un augroup, il che significa che ogni volta che si ricarica un buffer il cui tipo di file è dircolors(succede se si modifica un file di nome .dircolors, .dir_colorso le cui estremità percorso con /etc/DIR_COLORS), il plugin di sintassi aggiungerà un nuovo autocmd buffer-local.

Puoi verificarlo in questo modo:

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

L'ultimo comando dovrebbe visualizzare questo:

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

Ora ricaricare il buffer e chiedere di nuovo quali sono gli autocmds buffer-locali per il buffer corrente:

:e
:au * <buffer>

Questa volta vedrai:

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

Dopo ogni ricarico del file, s:reset_colors()e s:preview_color('.')si chiamerà una volta in più, ogni volta che uno degli eventi CursorHold, CursorHoldI, CursorMoved, CursorMovedIviene licenziato.

Probabilmente non è un grosso problema, perché anche dopo aver ricaricato un dircolorsfile più volte, non ho visto un rallentamento evidente o un comportamento inaspettato da Vim.

Se si tratta di un problema per te, puoi contattare il manutentore del plug-in di sintassi, ma nel frattempo, se vuoi impedire la duplicazione di autocmds, puoi creare il tuo plug-in di sintassi per i dircolorsfile, utilizzando il file ~/.vim/syntax/dircolors.vim. Al suo interno verrai importato il contenuto del plugin di sintassi originale:

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

Quindi, in quest'ultimo caso, avresti semplicemente avvolto gli autocmds all'interno di un augroup che elimineresti. Quindi, sostituiresti queste righe:

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

... con questi:

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

Nota che se hai creato il tuo dircolorsplugin di sintassi con il file ~/.vim/after/syntax/dircolors.vim, non funzionerebbe, perché il plugin di sintassi predefinito sarebbe stato fornito in precedenza. Usando ~/.vim/syntax/dircolors.vim, il tuo plug-in di sintassi verrà fornito prima di quello predefinito e imposterà la variabile buffer-local b:current_syntax, che impedirà di ottenere il plug-in di sintassi predefinito perché contiene questo guard:

if exists("b:current_syntax")
    finish
endif

La regola generale sembra essere: utilizzare le directory ~/.vim/ftplugine ~/.vim/syntaxper creare un plug-in di tipo file / sintassi personalizzato e impedire che il plug-in successivo (per lo stesso tipo di file) nel percorso di runtime venga fornito (inclusi quelli predefiniti). E usa ~/.vim/after/ftplugin, ~/.vim/after/syntaxnon per impedire la provenienza di altri plugin, ma solo per avere l'ultima parola sul valore di alcune impostazioni.


1
Vorrei poter votare più duramente.
Ricco il

3
@Rich ho votato più duramente per te. La mia unica lamentela è la mancanza di un sommario "tl; dr". Pagine di minuzie testuali, per quanto vitali per la comprensione, fanno soffrire la mia anima invecchiata. La sostituzione di autocmd!con autocmd! CursorHold <buffer>in augroupblocchi è un Gotcha particolarmente critica - e avrebbe dovuto essere evidenziata in anticipo. Tuttavia ... questo è un investimento palesemente sorprendente di tempo, fatica e lacrime di sangue.
Cecil Curry,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.