Vimscript: Ajuda com carregamento automático, escopo e <SID>

9

Eu tenho trabalhado na modularização e conversão de um código no meu vimrcem alguns plugins / plugins de pacotes / plugins independentes e reutilizáveis. Ocorreu um problema com o carregamento automático e o escopo que estou tendo dificuldade para entender. Eu li através de :h autoload, :h <sid>, :h script-local, mas eu ainda não sou muito claro sobre como isso funciona.

Eu estive analisando alguns plugins bem desenvolvidos para descobrir alguns padrões usados ​​com frequência e estruturei meus plugins da seguinte maneira:

" ~/.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

No início do meu vimrc, eu executo o plugin com:

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

Tudo isso parece funcionar corretamente e conforme o esperado - mas cada vez que uma função é chamada, a s:set_default(...)função é chamada para cada sinalizador, o que é ineficaz - então tentei movê-los para fora das funções:

" ~/.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() " {{{
" ...

Mas isso causa erros que não tenho certeza de como resolver:

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

Ainda não entendo muito bem o escopo do vim, mas pelo que li - parece que o vim implementa uma forma de manipulação de nomes com scripts para fornecer 'escopo'. Ele atribui (não sabe exatamente como esse processo funciona) um SID exclusivo para cada arquivo carregado em tempo de execução - e quando você chama uma função prefixada com um identificador de escopo de script ( s:), substitui esse identificador de forma transparente por um SID mapeado .

Em alguns casos, vi scripts que chamam funções como essa (mas não funciona no meu caso, não entendo o porquê e espero que alguém possa explicar isso):

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

O seguinte funciona, mas não tenho certeza se é um bom padrão:

" ~/.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 " }}}

No script local, ele afirma:

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 essa pode ser a abordagem que preciso adotar, mas não sei ao certo por que ou exatamente como usá-lo. Alguém pode fornecer algumas dicas?

nfarrar
fonte

Respostas:

4

No primeiro caso, o erro que você está recebendo é que você está tentando chamar uma função antes de sua existência. Ou seja, o Vim está progredindo no seu script. Quando vê a calllinha, ainda não processou a functionlinha que cria o que você deseja chamar, resultando no erro unknown function.

Se você mover sua chamada para configurar os padrões para o final do seu script (antes de restaurar, cpomas depois de todos os seus functions, não haverá erro, porque o Vim processará o script para criar as funções primeiro, portanto, quando para as calllinhas, as funções existem.

"....

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

Não sei por que a sintaxe alternativa de chamar seu set_defaultcomo uma função de carregamento automático a partir do script de carregamento automático funciona quando a função ainda não foi definida. Meu palpite é que esse é um efeito colateral da implementação (onde um script já lido não é relido, ou você teria uma recursão infinita). Eu não contaria com isso sempre trabalhando dessa maneira.

John O'M.
fonte
Se estou entendendo corretamente, você está dizendo que o arquivo inteiro não é originado e executado antes da execução da chamada de função definida no meu vimrc? Talvez eu esteja entendendo mal ... mas parece-me que todo o script de carregamento automático é originado e executado primeiro. Se eu adicionar uma instrução echom 'this is the function call'na função que está sendo chamada de vimrc e outra em echom 'file was sourced'qualquer outro lugar do arquivo (não em uma função), vejo a última primeiro, depois a primeira.
Nfarrar 28/06
Desculpe, acabei de perceber o que você está dizendo - e que você está correto. Como a chamada de função está acontecendo no script conforme é originada, ela precisa ocorrer após a definição da função. Obrigado!
Nfarrar 28/06
13

Eu recomendo esta estrutura:

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

Isso é compatível com todos os gerenciadores de plugins modernos e mantém as coisas limpas. Destes:

  • myplugin/doc/myplugin.txt deve ser o arquivo de ajuda
  • myplugin/plugin/myplugin.vim deve conter:
    • verifica todos os pré-requisitos que seu plug-in precisa, como versão mínima do Vim e recursos de tempo de compilação do Vim
    • mapeamentos de chaves
    • inicialização única, como definir padrões
  • myplugin/autoload/myplugin.vim deve conter o código principal do seu plugin.

Os escopos são realmente muito simples:

  • funções com nomes começando com s:podem aparecer em qualquer lugar, mas são locais no arquivo em que estão definidas; você pode chamá-los apenas do arquivo em que estão definidos;
  • funções em myplugin/autoload/myplugin.vimdevem ter nomes myplugin#function()e são globais; você pode chamá-los de qualquer lugar (mas lembre-se de que chamá-los faz com que o arquivo myplugin/autoload/myplugin.vimseja carregado);
  • todas as outras funções devem ter nomes começando com uma letra maiúscula, como Function(), e também são globais; você pode chamá-los de qualquer lugar.

<SID>e <Plug>são usados ​​para mapeamentos, e são um tópico que você provavelmente deve evitar até ter um entendimento completo de como eles funcionam e que problema eles devem resolver.

<SNR> é algo que você nunca deve usar diretamente.

lcd047
fonte
Por que separar entre os diretórios autoload/e plugin/? Eu sempre coloquei tudo plugin/e isso parece funcionar bem?
Martin Tournoij
2
O diretório de carregamento automático apenas carrega seu conteúdo quando necessário. Isso pode acelerar um pouco a hora de início do vim. Em outras palavras, funciona da mesma forma que, plugin/exceto que é carregado apenas uma vez necessário, em vez de carregado na inicialização.
EvergreenTree
2
@Carpetsmoker Praticamente como o @EvergreenTree disse. Claro, isso realmente não importa para plugins "pequenos", mas ainda é uma boa prática. Com o Vim, você tem muito pouco controle sobre o coletor de lixo, e carregar coisas somente quando e se elas forem necessárias pode fazer a diferença. Por outro lado, há desvantagens sutis em mudar tudo autoload, se você não pode testar a existência de uma função se o arquivo em que ele vive não tiver sido carregado.
Lcd047