Untuk autocmd di ftplugin, haruskah saya menggunakan pencocokan pola atau <buffer>?

14

Saya memiliki autocmd untuk file TeX dan Markdown untuk menyimpan file secara otomatis. Tidak ada yang aneh:

autocmd CursorHold *.tex,*.md w

Namun, saat pengaturan khusus untuk file ini meningkat, saya membaginya menjadi ftplugin/tex.vimdan ftplugin/markdown.vim:

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

Sekarang, file-file ini bersumber hanya untuk file yang sesuai, sehingga pencocokan pola menjadi berlebihan. Rupanya, autocmds bisa menjadi buffer-lokal. Dari :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>

Itu sepertinya dimaksudkan untuk penggunaan seperti itu. Sekarang, keduanya ftplugin/tex.vimdan ftplugin/markdown.vimdapat memiliki:

autocmd CursorHold <buffer> w

Saya tidak terlalu peduli dengan ekstensi yang sebenarnya selama filetype itu benar, jadi ini menyelamatkan saya dari keharusan khawatir *.mddan *.markdowndan ekstensi apa pun lainnya yang valid untuk penurunan harga.

Apakah penggunaan ini <buffer>benar? Apakah ada jebakan yang harus saya waspadai? Akankah segalanya menjadi berantakan jika saya menghapus buffer dan membuka yang lain (semoga angkanya tidak akan bertabrakan, tapi ...)?

muru
sumber
1
Saya cukup yakin jika Anda menghapus buffer, semua autocmds buffer-lokal juga dihapus.
Tumbler41
@ Tumbler41 memang. Itu mengatakan bahwa dalam bantuan, beberapa paragraf turun.
muru
1
Tidak persis apa yang Anda minta, tetapi up(kependekan :update) akan lebih baik daripada wdi autocmd Anda (hindari menulis yang tidak perlu).
mMontu
@ Montu Bagus. Bahkan memecahkan masalah yang saya miliki ketika autocmd diaktifkan untuk file yang saya periksa dari sejarah git. Buffer hanya dibaca, dan wgagal. Berkali-kali. :uptidak melakukan apa pun dalam kasus itu. :)
muru
Senang Anda suka :) Kebetulan, Anda juga bisa menemukan opsi 'autowrite' berguna (tergantung pada motivasi Anda, Anda bisa drop autocmds).
mMontu

Jawaban:

11

Apakah penggunaan <buffer> ini benar?

Saya pikir itu benar, tetapi Anda hanya perlu membungkusnya di dalam sebuah augroup, dan menghapus yang terakhir, untuk memastikan bahwa autocmd tidak akan diduplikasi setiap kali Anda menjalankan perintah yang memuat buffer yang sama.

Seperti yang Anda jelaskan, pola khusus <buffer>memungkinkan Anda untuk mengandalkan mekanisme deteksi tipe file bawaan, yang diterapkan di dalam file $VIMRUNTIME/filetype.vim.

Dalam file ini, Anda dapat menemukan autocmds bawaan Vim yang bertanggung jawab untuk mengatur tipe file yang benar untuk setiap buffer yang diberikan. Misalnya, untuk penurunan harga:

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

Di dalam plugin filetype Anda, Anda bisa menyalin pola yang sama untuk setiap autocmd yang Anda instal. Misalnya, untuk secara otomatis menyimpan buffer ketika kursor Anda belum bergerak selama beberapa detik:

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

Tetapi <buffer>jauh lebih sedikit verbose:

au CursorHold <buffer> update

Selain itu, jika suatu hari ekstensi lain valid, dan $VIMRUNTIME/filetype.vimdiperbarui untuk memasukkannya, autocmds Anda tidak akan diinformasikan. Dan Anda harus memperbarui semua polanya di dalam plugin tipe file Anda.


Akankah segalanya menjadi berantakan jika saya menghapus buffer dan membuka yang lain (semoga angkanya tidak akan bertabrakan, tapi ...)?

Saya tidak yakin tetapi saya tidak berpikir bahwa Vim dapat menggunakan kembali nomor buffer dari buffer yang dihapus. Saya tidak dapat menemukan bagian yang relevan dari bantuan, tetapi saya menemukan paragraf ini dari vim.wikia.com :

Tidak. Vim tidak akan menggunakan kembali nomor buffer dari buffer yang dihapus untuk buffer baru. Vim akan selalu menetapkan nomor urut berikutnya untuk buffer baru.

Selain itu, seperti yang dijelaskan oleh @ Tumbler41 , ketika Anda menghapus buffer, autocmds-nya dihapus. Dari :h autocmd-buflocal:

Ketika buffer dihapus, autocommand lokal-buffer juga hilang, tentu saja.

Jika Anda ingin memeriksa diri Anda sendiri, Anda dapat melakukannya dengan meningkatkan tingkat verbositas Vim menjadi 6. Anda dapat melakukannya sementara, hanya untuk satu perintah, dengan menggunakan :verbosepengubah. Jadi, di dalam buffer penurunan harga Anda, Anda dapat menjalankan:

:6verbose bwipe

Kemudian, jika Anda memeriksa pesan Vim:

:messages

Anda akan melihat garis yang terlihat seperti ini:

auto-removing autocommand: CursorHold <buffer=42>

Di mana 42nomor buffer markdown Anda.


Apakah ada jebakan yang harus saya waspadai?

Ada 3 situasi yang saya anggap sebagai jebakan dan yang melibatkan pola khusus <buffer>. Di dua dari mereka, <buffer>mungkin menjadi masalah, yang lain itu adalah solusi.

Perangkap 1

Pertama, Anda harus berhati-hati dengan cara Anda menghapus augroup autocmds lokal Anda. Anda harus terbiasa dengan cuplikan ini:

augroup your_group_name
    autocmd!
    autocmd Event pattern command
augroup END

Jadi, Anda bisa tergoda untuk menggunakannya untuk autocmds buffer-local Anda, tidak dimodifikasi, seperti ini:

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

Tetapi ini akan memiliki efek yang tidak diinginkan. Pertama kali Anda memuat buffer markdown, sebut saja A, autocmd-nya akan dipasang dengan benar. Kemudian, ketika Anda akan memuat ulang A, autocmd akan dihapus (karena autocmd!), dan diinstal ulang. Jadi, augroup dengan benar akan mencegah duplikasi autocmd.

Sekarang, misalkan Anda memuat buffer markdown ke-2, sebut saja B, di jendela ke-2. SEMUA autocmds dari augroup akan dihapus: itu adalah autocmd dari Adan yang dari B. Kemudian, autocmd TUNGGAL akan diinstal untuk B.

Jadi, ketika Anda membuat beberapa perubahan B, dan menunggu beberapa detik untuk CursorHolddipecat, itu akan disimpan secara otomatis. Tetapi jika Anda kembali ke Adan melakukan hal yang sama, buffer tidak akan disimpan. Ini karena terakhir kali Anda memuat buffer markdown, ada ketidakseimbangan antara apa yang Anda hapus dan apa yang Anda tambahkan. Anda menghapus lebih dari yang Anda tambahkan.

Solusinya adalah tidak menghapus SEMUA autocmds, tetapi hanya yang buffer saat ini, dengan meneruskan pola khusus <buffer>ke :autocmd!:

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

Perhatikan bahwa Anda dapat mengganti CursorHolddengan bintang untuk mencocokkan acara apa pun di baris yang menghapus autocmds:

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

Dengan cara ini, Anda tidak perlu menentukan semua acara yang sedang didengarkan autocmds Anda saat ingin menghapus augroup.


Perangkap 2

Ada jebakan lain, tapi kali <buffer>ini bukan masalahnya, itu solusinya.

Saat Anda memasukkan opsi lokal dalam plugin tipe file, Anda mungkin melakukannya seperti ini:

setlocal option1=value
setlocal option2

Ini akan berfungsi seperti yang diharapkan untuk opsi buffer-local, tetapi tidak selalu untuk yang window-local. Untuk mengilustrasikan masalah ini, Anda dapat mencoba eksperimen berikut. Buat file ~/.vim/after/ftdetect/potion.vim, dan di dalamnya tulis:

autocmd BufNewFile,BufRead *.pn setfiletype potion

File ini akan secara otomatis mengatur filetype potionuntuk file apa pun yang memiliki ekstensi .pn. Anda tidak perlu membungkusnya di dalam augroup karena, untuk jenis file ini, Vim akan melakukannya secara otomatis (lihat :h ftdetect).

Jika direktori perantara tidak ada di sistem Anda, Anda dapat membuatnya.

Selanjutnya, buat plugin filetype ~/.vim/after/ftplugin/potion.vim, dan di dalamnya tulis:

setlocal list

Secara default, dalam potionfile, pengaturan ini akan menyebabkan karakter tab ditampilkan sebagai ^Idan akhir baris sebagai $.

Sekarang, buat minimal vimrc; di dalam /tmp/vimrctulis:

filetype plugin on

... untuk mengaktifkan plugin tipe file.

Juga, buat file ramuan /tmp/pn.pn,, dan file acak /tmp/file. Dalam file ramuan, tulis sesuatu:

foo
bar
baz

Dalam file acak, tulis path ke file ramuan /tmp/pn.pn:

/tmp/pn.pn

Sekarang, mulai Vim dengan inisialisasi minimum, hanya sumber vimrc, dan buka kedua file dalam viewports vertikal:

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

Anda akan melihat 2 viewports vertikal. File ramuan di sebelah kiri menampilkan ujung baris dengan tanda dolar, file acak di sebelah kanan tidak menampilkannya sama sekali.

Berikan fokus ke file acak, dan tekan gfuntuk menampilkan file ramuan yang jalurnya di bawah kursor. Anda sekarang melihat buffer ramuan yang sama di viewport kanan, tetapi kali ini, akhir baris tidak ditampilkan dengan tanda dolar. Dan jika Anda mengetik :setlocal list?, Vim harus menjawab dengan nolist:

masukkan deskripsi gambar di sini

Seluruh rangkaian acara:

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

... tidak terjadi, karena yang pertama BufRead,, tidak terjadi ketika Anda menekan gf. Buffer sudah dimuat.

Ini mungkin tampak tak terduga, karena ketika Anda menambahkan setlocal listdi dalam plugin filetype ramuan Anda, Anda mungkin berpikir bahwa itu akan mengaktifkan 'list'opsi di jendela mana pun yang menampilkan buffer ramuan.

Masalahnya tidak spesifik untuk potiontipe file baru ini . Anda dapat mengalaminya dengan markdownfile juga.

Ini juga tidak spesifik untuk 'list'opsi. Anda dapat mengalaminya dengan pengaturan jendela-lokal lainnya, seperti 'conceallevel', 'foldmethod', 'foldexpr', 'foldtitle', ...

Itu juga tidak spesifik untuk gfperintah. Anda dapat mengalaminya dengan perintah lain yang dapat mengubah buffer ditampilkan di jendela saat ini: tanda global, C-o(bergerak mundur dalam jendela-jumplist lokal) :b {buffer_number},, ...

Untuk meringkas, opsi jendela-lokal akan ditetapkan dengan benar, jika dan hanya jika:

  • file belum dibaca selama sesi Vim saat ini (karena BufReadharus dipecat)
  • file sedang ditampilkan di jendela di mana opsi jendela-lokal sudah diatur dengan benar
  • jendela baru dibuat dengan perintah seperti :split(dalam hal ini, ia harus mewarisi opsi jendela-lokal dari jendela tempat perintah itu dijalankan)

Jika tidak, opsi jendela lokal mungkin tidak diatur dengan benar.

Solusi yang mungkin adalah dengan mengaturnya tidak secara langsung dari plugin tipe file, tetapi dari autocmd yang terinstal di yang terakhir, yang akan mendengarkan BufWinEnter. Acara ini harus dipecat setiap kali buffer ditampilkan di jendela.

Jadi, misalnya, alih-alih menulis ini:

setlocal list

Anda akan menulis ini:

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

Dan di sini, Anda menemukan lagi pola khusus <buffer>.

masukkan deskripsi gambar di sini


Perangkap 3

Jika Anda mengubah tipe file buffer Anda, autocmds akan tetap ada. Jika Anda ingin menghapusnya, Anda perlu mengkonfigurasi b:undo_ftplugin(lihat :h undo_ftplugin), dan sertakan perintah ini di dalamnya:

exe 'au! my_markdown * <buffer>'

Namun, jangan mencoba menghapus augroup itu sendiri, karena mungkin masih ada beberapa buffer penurunan harga yang memiliki autocmds di dalamnya.

FWIW, ini cuplikan UltiSnips yang saya gunakan untuk menyetel 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

Dan inilah contoh nilai yang saya miliki di ~/.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>'
                    \  "

Sebagai catatan, saya mengerti mengapa Anda mengajukan pertanyaan, karena ketika saya mencari semua baris di mana pola khusus <buffer>digunakan dalam file default Vim:

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

Saya hanya menemukan 9 kecocokan (Anda mungkin menemukan lebih atau kurang, saya menggunakan Vim versi 8.0, dengan tambalan hingga 134). Dan di antara 9 pertandingan, 7 ada dalam dokumentasi, hanya 2 yang benar-benar bersumber. Anda harus menemukannya di $ VIMRUNTIME / syntax / dircolors.vim :

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

Saya tidak tahu apakah itu bisa menyebabkan masalah, tetapi mereka tidak dalam sebuah augroup, yang berarti setiap kali Anda ulang penyangga yang filetype adalah dircolors(itu terjadi jika Anda mengedit file bernama .dircolors, .dir_colorsatau yang jalan berakhir dengan /etc/DIR_COLORS), plugin sintaks akan menambahkan autocmd lokal-buffer baru.

Anda dapat memeriksanya seperti ini:

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

Perintah terakhir harus menampilkan ini:

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

Sekarang, muat ulang buffer, dan tanyakan lagi apa autocmds lokal-buffer untuk buffer saat ini:

:e
:au * <buffer>

Kali ini, Anda akan melihat:

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

Setelah setiap reload file, s:reset_colors()dan s:preview_color('.')akan disebut satu waktu tambahan, setiap kali salah satu acara CursorHold, CursorHoldI, CursorMoved, CursorMovedIdipecat.

Ini mungkin bukan masalah besar, karena bahkan setelah memuat ulang dircolorsfile beberapa kali, saya tidak melihat perlambatan nyata, atau perilaku tak terduga dari Vim.

Jika ini merupakan masalah bagi Anda, Anda dapat menghubungi pengelola plugin sintaksis, tetapi sementara itu, jika Anda ingin mencegah duplikasi autocmds, Anda bisa membuat plugin sintaksis Anda sendiri untuk dircolorsfile, menggunakan file ~/.vim/syntax/dircolors.vim. Di dalamnya Anda akan mengimpor konten plugin sintaksis asli:

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

Kemudian, dalam yang terakhir, Anda hanya akan membungkus autocmds di dalam sebuah grup yang akan Anda hapus. Jadi, Anda akan mengganti baris ini:

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

... dengan yang ini:

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

Perhatikan bahwa jika Anda membuat dircolorsplugin sintaksis dengan file ~/.vim/after/syntax/dircolors.vim, itu tidak akan berfungsi, karena plugin sintaksis default akan bersumber sebelumnya. Dengan menggunakan ~/.vim/syntax/dircolors.vim, plugin sintaksis Anda akan bersumber sebelum yang default, dan itu akan mengatur variabel buffer-local b:current_syntax, yang akan mencegah plugin sintaksis bersumber karena mengandung penjaga ini:

if exists("b:current_syntax")
    finish
endif

Aturan umumnya tampaknya: gunakan direktori ~/.vim/ftplugindan ~/.vim/syntaxuntuk membuat plugin filetype / sintaks khusus dan mencegah plugin berikutnya (untuk filetype yang sama) di jalur runtime yang akan bersumber (termasuk yang default). Dan gunakan ~/.vim/after/ftplugin,, ~/.vim/after/syntaxbukan untuk mencegah plugin lain dari sumber, tetapi hanya untuk memiliki kata terakhir pada nilai beberapa pengaturan.

pengguna852573
sumber
1
Saya berharap saya bisa memperbaiki ini lebih keras.
Kaya
3
@ Kaya aku memutakhirkan ini lebih sulit untukmu. Satu-satunya keluhan saya adalah kurangnya ringkasan "tl; dr". Halaman-halaman hal-hal kecil yang tekstual, betapapun vitalnya untuk dipahami, menyakitkan jiwaku yang tua untuk mengarungi. Penggantian autocmd!dengan autocmd! CursorHold <buffer>dalam augroupblok adalah gotcha yang sangat penting - dan seharusnya disorot di muka. Meskipun demikian ... ini adalah investasi waktu, usaha, dan air mata yang luar biasa.
Cecil Curry