Para un autocmd en un ftplugin, ¿debo usar la coincidencia de patrones o <buffer>?

14

Tengo un autocmd para los archivos TeX y Markdown para guardar el archivo automáticamente. Nada inusual:

autocmd CursorHold *.tex,*.md w

Sin embargo, a medida que aumentaron las configuraciones personalizadas para estos archivos, los dividí en ftplugin/tex.vimy ftplugin/markdown.vim:

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

Ahora, estos archivos se obtienen solo para los archivos apropiados, por lo que la coincidencia de patrones es redundante. Aparentemente, autocmds puede ser local en el búfer. 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>

Eso parece estar destinado a tal uso. Ahora, ambos ftplugin/tex.vimy ftplugin/markdown.vimpueden tener:

autocmd CursorHold <buffer> w

No estoy realmente preocupado por la extensión real, siempre y cuando el tipo de archivo es correcta, así que esto me ahorra tener que preocuparse por *.mdy *.markdowny cualquier otro extensiones son válidos para Markdown.

¿Es este uso <buffer>correcto? ¿Hay alguna dificultad que deba tener en cuenta? ¿Se complicarán las cosas si borro un búfer y abro otro (espero que los números no choquen, pero ...)?

muru
fuente
1
Estoy bastante seguro de que si borras un búfer, cualquier autocmds local del búfer también se borrará.
Tumbler41
@ Tumbler41 de hecho. Sí dice que en la ayuda, algunos párrafos abajo.
muru
1
No es exactamente lo que pidió, pero up(abreviatura de :update) sería mejor que wen su autocmd (evite escrituras innecesarias).
mMontu
@mMontu Nice. Incluso resuelve un problema que tuve cuando el autocmd se activó para un archivo que estaba examinando desde el historial de git. El búfer fue de solo lectura y wfalló. Repetidamente. :upno hace nada en ese caso. :)
muru
Me alegro de que te haya gustado :) Por cierto, también puedes encontrar útil la opción 'autoescribir' (dependiendo de tu motivación, puedes descartar los autocmds).
mMontu

Respuestas:

11

¿Es correcto este uso de <buffer>?

Creo que es correcto, pero solo necesita envolverlo dentro de un grupo de aug, y borrar el último, para asegurarse de que el autocmd no se duplicará cada vez que ejecute un comando que recargue el mismo búfer.

Como explicó, el patrón especial le <buffer>permite confiar en el mecanismo de detección de tipo de archivo incorporado, implementado dentro del archivo $VIMRUNTIME/filetype.vim.

En este archivo, puede encontrar los autocmds integrados de Vim que son responsables de establecer el tipo de archivo correcto para cualquier búfer dado. Por ejemplo, para rebajas:

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

Dentro de su complemento de tipo de archivo, puede copiar los mismos patrones para cada autocmd que instale. Por ejemplo, para guardar automáticamente el búfer cuando el cursor no se ha movido durante unos segundos:

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

Pero <buffer>es mucho menos detallado:

au CursorHold <buffer> update

Además, si algún día otra extensión es válida y $VIMRUNTIME/filetype.vimse actualiza para incluirla, sus autocmds no serán informados. Y tendrá que actualizar todos sus patrones dentro de sus complementos de tipo de archivo.


¿Se complicarán las cosas si borro un búfer y abro otro (espero que los números no choquen, pero ...)?

No estoy seguro, pero no creo que Vim pueda reutilizar el número de búfer de un búfer borrado. No pude encontrar la sección relevante de la ayuda, pero encontré este párrafo en vim.wikia.com :

No. Vim no reutilizará el número de búfer de un búfer eliminado para un nuevo búfer. Vim siempre asignará el siguiente número secuencial para un nuevo búfer.

Además, como explicó @ Tumbler41 , cuando limpia un búfer, se eliminan sus autocmds. De :h autocmd-buflocal:

Cuando se borra un búfer, sus autocomandos locales de búfer también desaparecen, por supuesto.

Si desea comprobarlo usted mismo, puede hacerlo aumentando el nivel de verbosidad de Vim a 6. Puede hacerlo temporalmente, solo para un comando, utilizando el :verbosemodificador. Entonces, dentro de su búfer de reducción, puede ejecutar:

:6verbose bwipe

Luego, si revisas los mensajes de Vim:

:messages

Deberías ver una línea como esta:

auto-removing autocommand: CursorHold <buffer=42>

¿Dónde 42estaba el número de su búfer de descuento?


¿Hay alguna dificultad que deba tener en cuenta?

Hay 3 situaciones que consideraría como escollos y que involucran el patrón especial <buffer>. En dos de ellos, <buffer>puede ser un problema, en el otro es una solución.

Trampa 1

Primero, debe tener cuidado con la forma en que borra los grupos de sus autocmds de búfer local. Debe estar familiarizado con este fragmento:

augroup your_group_name
    autocmd!
    autocmd Event pattern command
augroup END

Por lo tanto, podría tener la tentación de usarlo para sus autocmds locales de búfer, sin modificar, como este:

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

Pero esto tendrá un efecto no deseado. La primera vez que cargue un búfer de Areducción, llamémoslo, su autocmd se instalará correctamente. Luego, cuando vuelva a cargar A, el autocmd se eliminará (debido a autocmd!) y se volverá a instalar. Entonces, el augroup evitará correctamente la duplicación del autocmd.

Ahora, suponga que carga un segundo búfer de reducción, llamémoslo B, en una segunda ventana. TODOS los autocmds del augroup se borrarán: ese es el autocmd de Ay el de B. Luego, se instalará un solo autocmd para B.

Por lo tanto, cuando realice algún cambio By espere unos segundos para CursorHoldque se dispare, se guardará automáticamente. Pero si vuelve Ay hace lo mismo, el búfer no se guardará. Esto se debe a que la última vez que cargó un búfer de reducción, hubo un desequilibrio entre lo que eliminó y lo que agregó. Eliminaste más de lo que agregaste.

La solución es no eliminar TODOS los autocmds, sino solo los del búfer actual, pasando el patrón especial <buffer>a :autocmd!:

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

Tenga en cuenta que puede reemplazar CursorHoldcon una estrella para que coincida con cualquier evento en la línea que elimine autocmds:

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

De esta manera, no tiene que especificar todos los eventos a los que están escuchando sus autocmds cuando desea borrar el augroup.


Pitfall 2

Hay otra trampa, pero esta vez <buffer>no es el problema, es la solución.

Cuando incluye una opción local en un complemento de tipo de archivo, probablemente lo haga así:

setlocal option1=value
setlocal option2

Esto funcionará como se espera para las opciones locales de búfer, pero no siempre para las locales de ventana. Para ilustrar el problema, puede intentar el siguiente experimento. Crea el archivo ~/.vim/after/ftdetect/potion.vim, y dentro de él escribe:

autocmd BufNewFile,BufRead *.pn setfiletype potion

Este archivo configurará automáticamente el tipo potionde archivo para cualquier archivo cuya extensión sea .pn. No necesita envolverlo dentro de un augroup porque, para este tipo particular de archivo, Vim lo hará automáticamente (ver :h ftdetect).

Si los directorios intermedios no existen en su sistema, puede crearlos.

A continuación, cree el complemento de tipo de archivo ~/.vim/after/ftplugin/potion.vimy, dentro, escriba:

setlocal list

De manera predeterminada, en un potionarchivo, esta configuración hará que los caracteres de tabulación se muestren como ^Iy el final de las líneas como $.

Ahora, crea un mínimo vimrc; /tmp/vimrcescritura interior :

filetype plugin on

... para habilitar los complementos de tipo de archivo.

Además, cree un archivo de pociones /tmp/pn.pn, y un archivo aleatorio /tmp/file. En el archivo de pociones, escribe algo:

foo
bar
baz

En el archivo aleatorio, escriba la ruta al archivo de la poción /tmp/pn.pn:

/tmp/pn.pn

Ahora, inicie Vim con un mínimo de inicializaciones, solo obtenga vimrcy abra ambos archivos en ventanas verticales:

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

Debería ver 2 ventanas verticales. El archivo de pociones a la izquierda muestra el final de las líneas con signos de dólar, el archivo aleatorio a la derecha no los muestra en absoluto.

Centra la atención en el archivo aleatorio y presiona gfpara mostrar el archivo de pociones cuya ruta está debajo del cursor. Ahora verá el mismo búfer de pociones en la ventana gráfica correcta, pero esta vez, el final de las líneas no se muestra con signos de dólar. Y si escribe :setlocal list?, Vim debería responder con nolist:

ingrese la descripción de la imagen aquí

Toda la cadena de eventos:

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

... no ocurrió, porque el primero de ellos, BufReadno ocurrió cuando presionaste gf. El búfer ya estaba cargado.

Puede parecer inesperado, porque cuando agregó setlocal listdentro de su plugin de tipo de archivo de poción, puede haber pensado que habilitaría la 'list'opción en cualquier ventana que muestre un búfer de poción.

El problema no es específico de este nuevo tipo de potionarchivo. También puedes experimentarlo con un markdownarchivo.

Tampoco es específico de la 'list'opción. Se puede experimentar con otras configuraciones de ventana local, como 'conceallevel', 'foldmethod', 'foldexpr', 'foldtitle', ...

Tampoco es específico del gfcomando. Puede experimentarlo con otros comandos que pueden cambiar el búfer que se muestra en la ventana actual: marca global, C-o(retroceder en el jumplista local de la ventana) :b {buffer_number}, ...

Para resumir, las opciones locales de la ventana se establecerán correctamente, si y solo si:

  • el archivo no ha sido leído durante la sesión actual de Vim (porque BufReadtendrá que ser despedido)
  • el archivo se muestra en una ventana donde las opciones locales de la ventana ya estaban configuradas correctamente
  • la nueva ventana se crea con un comando como :split(en este caso, debe heredar las opciones locales de la ventana donde se ejecutó el comando)

De lo contrario, las opciones locales de la ventana pueden no estar configuradas correctamente.

Una posible solución sería establecerlos no directamente desde un complemento de tipo de archivo, sino desde un autocmd instalado en este último, que escucharía BufWinEnter. Este evento debe activarse cada vez que se muestra un búfer en una ventana.

Entonces, por ejemplo, en lugar de escribir esto:

setlocal list

Escribirías esto:

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

Y aquí, encuentras de nuevo el patrón especial <buffer>.

ingrese la descripción de la imagen aquí


Trampa 3

Si cambia el tipo de archivo de su búfer, los autocmds permanecerán. Si desea eliminarlos, debe configurar b:undo_ftplugin(ver :h undo_ftplugin) e incluir este comando en él:

exe 'au! my_markdown * <buffer>'

Sin embargo, no intente eliminar el augroup en sí, porque aún podría haber algunos buffers de rebajas que tienen autocmds en su interior.

FWIW, este es el fragmento de UltiSnips que estoy usando para configurar 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

Y aquí hay un ejemplo de valor que tengo en ~/.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 nota al margen, entiendo por qué hizo la pregunta, porque cuando busqué todas las líneas donde <buffer>se utilizó el patrón especial en los archivos predeterminados de Vim:

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

Solo encontré 9 coincidencias (puede encontrar más o menos, estoy usando Vim versión 8.0, con parches hasta 134). Y entre las 9 coincidencias, 7 están en la documentación, solo 2 son de origen. Debería encontrarlos en $ VIMRUNTIME / syntax / dircolors.vim :

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

No sé si puede causar un problema, pero no están dentro de un augroup, lo que significa que cada vez que se vuelve a cargar un búfer cuyo tipo de archivo es dircolors(sucede si edita un archivo llamado .dircolors, .dir_colorso cuyos extremos trayectoria con /etc/DIR_COLORS), el complemento de sintaxis agregará un nuevo autocmd local de búfer.

Puedes verificarlo así:

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

El último comando debería mostrar esto:

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

Ahora, vuelva a cargar el búfer y vuelva a preguntar cuáles son los autocmds locales del búfer para el búfer actual:

:e
:au * <buffer>

Esta vez, verás:

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

Después de cada carga de los archivos, s:reset_colors()y s:preview_color('.')será llamado una vez adicional, cada vez que uno de los eventos CursorHold, CursorHoldI, CursorMoved, CursorMovedIes despedido.

Probablemente no sea un gran problema, porque incluso después de volver a cargar un dircolorsarchivo varias veces, no vi una desaceleración notable o un comportamiento inesperado de Vim.

Si es un problema para usted, puede contactar al responsable del complemento de sintaxis, pero mientras tanto, si desea evitar la duplicación de autocmds, puede crear su propio complemento de sintaxis para los dircolorsarchivos, utilizando el archivo ~/.vim/syntax/dircolors.vim. Dentro, importaría el contenido del complemento de sintaxis original:

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

Luego, en este último, simplemente envolvería los autocmds dentro de un augroup que borraría. Entonces, reemplazarías estas líneas:

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

... con estos:

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

Tenga en cuenta que si creó su dircolorscomplemento de sintaxis con el archivo ~/.vim/after/syntax/dircolors.vim, no funcionaría, porque el complemento de sintaxis predeterminado se obtendría antes. Al usarlo ~/.vim/syntax/dircolors.vim, su complemento de sintaxis se generará antes que el predeterminado, y establecerá la variable local del búfer b:current_syntax, lo que evitará que se obtenga el complemento de sintaxis predeterminado porque contiene esta protección:

if exists("b:current_syntax")
    finish
endif

La regla general parece ser: use los directorios ~/.vim/ftpluginy ~/.vim/syntaxpara crear un complemento de tipo de archivo / sintaxis personalizado y evitar que se obtenga el siguiente complemento (para el mismo tipo de archivo) en la ruta de tiempo de ejecución (incluidos los predeterminados). Y use ~/.vim/after/ftplugin, ~/.vim/after/syntaxno para evitar que se obtengan otros complementos, sino solo para tener la última palabra sobre el valor de algunas configuraciones.

usuario852573
fuente
1
Desearía poder votar más fuerte.
Rico
3
@ Rich Voté esto más duro para ti. Mi única queja es la falta de un resumen "tl; dr". Las páginas de minucias textuales, por vitales que sean para la comprensión, hacen que mi alma envejezca. El reemplazo de autocmd!con autocmd! CursorHold <buffer>en augroupbloques es un problema particularmente crítico, y debería haberse destacado por adelantado. No obstante ... esta es una inversión descaradamente sorprendente de tiempo, esfuerzo y lágrimas sangrientas.
Cecil Curry