Vimscript: Ayuda con carga automática, alcance y <SID>

9

He estado trabajando en modularizar y convertir un código en mi vimrcen algunos paquetes / complementos reutilizables y autónomos. Me he encontrado con un problema con la carga automática y el alcance que tengo dificultades para comprender. He leído a través de :h autoload, :h <sid>, :h script-local, pero todavía no estoy muy claro sobre cómo funciona este.

He estado buscando algunos complementos bien desarrollados para descubrir algunos patrones comúnmente utilizados, y he estructurado mis complementos de la siguiente manera:

" ~/.vim/autoload/myplugin.vim

if exists('g:loaded_myplugin')
  finish
endif

let g:loaded_myplugin = 1
let g:myplugin_version = 0.0.1

" Save cpoptions.
let s:cpo_save = &cpo
set cpo&vim

function! myplugin#init() " {{{
  " Default 'init' function. This will run the others with default values,
  " but the intent is that they can be called individually if not all are
  " desired.
  call myplugin#init_thing_one()
  call myplugin#init_thing_two()
endfunction" }}}

function! myplugin#init_thing_one() " {{{
  " init thing one
  call s:set_default('g:myplugin_thing_one_flag', 1)
  " do some things ...
endfunction " }}}

function! myplugin#init_thing_two() " {{{
  " init thing two
  call s:set_default('g:myplugin_thing_two_flag', 1)
  " do some things ...
endfunction " }}}

function! s:set_default(name, default) " {{{
" Helper function for setting default values.
  if !exists(a:name)
    let {a:name} = a:default
  endif
endfunction " }}}

" Restore cpotions.
let &cpo = s:cpo_save
unlet s:cpo_save

Al comienzo de mi vimrc, ejecuto el complemento con:

if has('vim_starting')
  if &compatible | set nocompatible | endif
  let g:myplugin_thing_one_flag = 0
  let g:myplugin_thing_two_flag = 2
  call myplugin#init()
endif

Todo esto parece funcionar correctamente y como se esperaba, pero cada vez que se llama a una función, se llama a la s:set_default(...)función para cada indicador, lo cual es ineficaz, así que intenté moverlas fuera de las funciones:

" ~/.vim/autoload/myplugin.vim
" ...
set cpo&vim

" Set all defaults once, the first time this plugin is referenced:
call s:set_default('g:myplugin_thing_one_flag', 1)
call s:set_default('g:myplugin_thing_two_flag', 1)

function! myplugin#init() " {{{
" ...

Pero esto causa errores que no estoy seguro de cómo debería resolver:

Error detected while processing /Users/nfarrar/.vim/myplugin.vim
line   40:
E117: Unknown function: <SNR>3_set_default

Todavía no entiendo sólidamente el alcance de vim, pero por lo que he leído, parece que vim implementa una forma de manipulación de nombres con scripts para proporcionar 'alcance'. Asigna (no estoy seguro de cómo funciona exactamente este proceso) un SID único para cada archivo que se carga en tiempo de ejecución, y cuando llama a una función que tiene el prefijo con un identificador de alcance de script ( s:), reemplaza ese identificador de forma transparente con un SID asignado .

En algunos casos, he visto scripts que llaman a funciones como esta (pero no funciona en mi caso, no entiendo por qué y espero que alguien pueda explicar esto):

call <SID>set_default('g:myplugin_thing_one_flag', 1)
call <SNR>set_default('g:myplugin_thing_one_flag', 1)

Lo siguiente funciona, pero no estoy seguro si es un buen patrón:

" ~/.vim/autoload/myplugin.vim
" ...
set cpo&vim

" Set all defaults once, the first time this plugin is referenced:
call myplugin#set_default('g:myplugin_thing_one_flag', 1)
call myplugin#set_default('g:myplugin_thing_two_flag', 1)

function! myplugin#init() " {{{
" ...

function! myplugin#set_default(name, default) " {{{
    " ...
endfunction " }}}

En script local, dice:

When executing an autocommand or a user command, it will run in the context of
the script it was defined in.  This makes it possible that the command calls a
local function or uses a local mapping.

Otherwise, using "<SID>" outside of a script context is an error.

If you need to get the script number to use in a complicated script, you can
use this function:

    function s:SID()
      return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$')
    endfun

Parece que este podría ser el enfoque que debo adoptar, pero no estoy completamente seguro de por qué, o exactamente cómo usarlo. ¿Alguien puede dar alguna idea?

nfarrar
fuente

Respuestas:

4

En el primer caso, el error que obtiene es que está intentando llamar a una función antes de su existencia. Es decir, Vim está progresando a través de su script. Cuando ve la calllínea, aún no ha procesado la functionlínea que crea lo que desea llamar, lo que resulta en el error unknown function.

Si mueve su llamada a los valores predeterminados de configuración al final de su secuencia de comandos (antes de restaurar cpopero después de todos sus functioncorreos electrónicos, no habrá un error, porque Vim habrá procesado la secuencia de comandos para crear las funciones primero, así que una vez que se obtenga a las calllíneas, las funciones existen.

"....

function! s:set_default(name, default) " {{{
  " Helper function for setting default values.
  if !exists(a:name)
    let {a:name} = a:default
  endif
endfunction " }}}

" Set all defaults once, the first time this plugin is referenced:
call s:set_default('g:myplugin_thing_one_flag', 1)
call s:set_default('g:myplugin_thing_two_flag', 1)

" Restore cpotions.
let &cpo = s:cpo_save
unlet s:cpo_save

No sé por qué la sintaxis alternativa de llamar a tu set_defaultcomo una función de carga automática desde el script de carga automática funciona cuando la función aún no se ha definido. Supongo que este es un efecto secundario de la implementación (donde un script ya leído no se vuelve a leer, o tendría una recursión infinita). No contaría con que siempre funcione de esa manera.

John O'M.
fuente
Si lo entiendo correctamente, ¿está diciendo que todo el archivo no se obtiene y ejecuta antes de la ejecución de la llamada de función definida en mi vimrc? Tal vez estoy malentendido ... pero me parece que todo el script de carga automática se obtiene y ejecuta primero. Si agrego un enunciado echom 'this is the function call'en la función que se llama desde vimrc y otro en echom 'file was sourced'cualquier otro lugar del archivo (no en una función), veo el último primero, luego el primero.
nfarrar
Lo siento, me acabo de dar cuenta de lo que estás diciendo, y de que estás en lo correcto. Dado que la llamada a la función está ocurriendo en el script a medida que se origina, debe suceder después de que se define la función. ¡Gracias!
nfarrar
13

Recomiendo esta estructura:

.
└── myplugin
    ├── LICENSE.txt
    ├── README.md
    ├── autoload
    │   └── myplugin.vim
    ├── doc
    │   └── myplugin.txt
    └── plugin
        └── myplugin.vim

Esto es compatible con todos los administradores de complementos modernos y mantiene las cosas limpias. De estos:

  • myplugin/doc/myplugin.txt debería ser el archivo de ayuda
  • myplugin/plugin/myplugin.vim debería contener:
    • comprueba los requisitos previos que necesita su complemento, como la versión mínima de Vim y las características de tiempo de compilación de Vim
    • asignaciones de teclas
    • Inicialización única, como establecer valores predeterminados
  • myplugin/autoload/myplugin.vim debe contener el código principal de su complemento.

Los ámbitos son realmente bastante simples:

  • las funciones con nombres que comienzan s:pueden aparecer en cualquier lugar, pero son locales al archivo en el que están definidas; solo puede llamarlos desde el archivo donde están definidos;
  • las funciones en myplugin/autoload/myplugin.vimdeben tener nombres myplugin#function()y son globales; puede llamarlos desde cualquier lugar (pero tenga en cuenta que llamarlos hace myplugin/autoload/myplugin.vimque se cargue el archivo );
  • todas las demás funciones deben tener nombres que comiencen con una letra mayúscula, como Function(), y también son globales; puedes llamarlos desde todas partes.

<SID>y <Plug>se usan para mapeos, y son un tema que probablemente debería evitar hasta que comprenda por completo cómo funcionan y qué problema se supone que deben resolver.

<SNR> es algo que nunca deberías usar directamente.

lcd047
fuente
¿Por qué separar entre el autoload/y plugin/directorios? Siempre he puesto todo plugin/y parece que funciona bien?
Martin Tournoij
2
El directorio de carga automática solo carga su contenido cuando es necesario. Esto puede acelerar un poco el tiempo de inicio de vim. En otras palabras, funciona casi igual que, plugin/excepto que solo se carga una vez que se necesita en lugar de cargarse en el inicio.
EvergreenTree
2
@Carpetsmoker Más o menos como @EvergreenTree dijo. Por supuesto, en realidad no importa para complementos "pequeños", pero sigue siendo una buena práctica. Con Vim, usted tiene muy poco control sobre el recolector de basura, y cargar cosas solo cuando sea necesario puede marcar la diferencia. Por otro lado, hay inconvenientes sutiles para mover todo a autoload, si no puede probar la existencia de una función si el archivo en el que vive no se ha cargado.
lcd047