Vimscript: aiuto con caricamento automatico, ambito e <SID>


9

Ho lavorato sulla modularizzazione e la conversione di un codice nel mio vimrcin alcuni plugin / plugin plug-in autonomi e riutilizzabili. Ho riscontrato un problema con il caricamento automatico e l'ambito che ho difficoltà a comprendere. Ho letto attraverso :h autoload, :h <sid>, :h script-local, ma io non sono ancora del tutto chiaro su come funziona.

Ho cercato alcuni plugin ben sviluppati per capire alcuni schemi comunemente usati e ho strutturato i miei plugin come segue:

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

All'inizio del mio vimrc, eseguo il plugin 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

Tutto questo sembra funzionare correttamente e come previsto - ma ogni volta che viene chiamata una funzione, la s:set_default(...)funzione viene chiamata per ogni flag, il che è inefficace - quindi ho tentato di spostarli fuori dalle funzioni:

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

Ma questo causa errori che non sono sicuro di come dovrei risolvere:

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

Ancora non capisco perfettamente l'ambito di Vim, ma da quello che ho letto - sembra che Vim implementa una forma di modifica del nome con script per fornire 'ambito'. Assegna (non sono sicuro di come funzioni esattamente questo processo) un SID univoco per ciascun file che viene caricato in fase di esecuzione - e quando chiamate una funzione che ha il prefisso con un identificatore dell'ambito dello script ( s:), sostituisce in modo trasparente quell'identificatore con un SID mappato .

In alcuni casi, ho visto degli script che chiamano funzioni come questa (ma non funziona nel mio caso, non capisco perché, e spero che qualcuno possa spiegarlo):

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

Quanto segue funziona, ma non sono sicuro che sia un buon modello:

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

Nello script locale, afferma:

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

Vede in questo modo potrebbe essere l'approccio che devo adottare, ma non sono completamente sicuro del perché, o esattamente come usarlo. Qualcuno può fornire alcune informazioni?

Risposte:


4

Nel primo caso, l'errore che si verifica è che si sta tentando di chiamare una funzione prima della sua esistenza. Cioè, Vim sta procedendo attraverso la tua sceneggiatura. Quando vede la calllinea, non ha ancora elaborato la functionlinea che crea ciò che si desidera chiamare, causando l'errore unknown function.

Se sposti la chiamata per impostare le impostazioni predefinite alla fine dello script (prima del ripristino cpoma dopo tutti i tuoi messaggi function, non ci sarà un errore, perché Vim avrà elaborato lo script per creare prima le funzioni, quindi una volta ottenuto alle calllinee esistono le funzioni, ad es

"....

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

Non so perché la sintassi alternativa della chiamata set_defaultcome funzione di caricamento automatico all'interno dello script di caricamento automatico funzioni quando la funzione non è stata ancora definita. La mia ipotesi è che questo sia un effetto collaterale dell'implementazione (in cui uno script già letto non viene riletto o avresti una ricorsione infinita). Non contare sul fatto che funzioni sempre in questo modo.


Se sto capendo correttamente, stai dicendo che l'intero file non è di provenienza ed eseguito prima dell'esecuzione della chiamata di funzione definita nel mio vimrc? Forse sto fraintendendo ... ma mi sembra che l'intero script di caricamento automatico sia di provenienza e eseguito per primo. Se aggiungo un'istruzione echom 'this is the function call'nella funzione chiamata da vimrc e un'altra in echom 'file was sourced'qualsiasi altra parte del file (non in una funzione), vedo prima la seconda, poi la prima.
nfarrar,

Scusa, ho appena capito cosa stai dicendo - e che hai ragione. Poiché la chiamata di funzione sta avvenendo nello script man mano che viene fornita, deve avvenire dopo che la funzione è stata definita. Grazie!
nfarrar,

13

Consiglio questa struttura:

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

Questo è compatibile con tutti i moderni gestori di plugin e mantiene le cose pulite. Di questi:

  • myplugin/doc/myplugin.txt dovrebbe essere il file di aiuto
  • myplugin/plugin/myplugin.vim dovrebbe contenere:
    • verifica la presenza di prerequisiti necessari per il tuo plug-in, come la versione minima di Vim e le funzionalità di compilazione di Vim
    • mappature chiave
    • inizializzazione singola, come l'impostazione delle impostazioni predefinite
  • myplugin/autoload/myplugin.vim dovrebbe contenere il codice principale del tuo plugin.

Gli ambiti sono in realtà piuttosto semplici:

  • le funzioni con nomi che iniziano con s:possono apparire ovunque, ma sono locali al file in cui sono definite; puoi chiamarli solo dal file dove sono definiti;
  • le funzioni in myplugin/autoload/myplugin.vimdevono avere nomi myplugin#function()e sono globali; puoi chiamarli da qualsiasi luogo (ma fai attenzione che chiamarli causa il myplugin/autoload/myplugin.vimcaricamento del file );
  • tutte le altre funzioni devono avere nomi che iniziano con una lettera maiuscola, come Function(), e sono anche globali; puoi chiamarli da ogni parte.

<SID>e <Plug>vengono utilizzati per le mappature e sono un argomento che dovresti probabilmente evitare fino a quando non avrai una piena comprensione di come funzionano e quali problemi dovrebbero risolvere.

<SNR> è qualcosa che non dovresti mai usare direttamente.


Perché separare tra le directory autoload/e plugin/? Ho sempre inserito tutto plugin/e sembra funzionare bene?
Martin Tournoij,

2
La directory di caricamento automatico carica i contenuti solo quando necessario. Questo può accelerare un po 'l'ora di inizio di vim. In altre parole, funziona più o meno allo stesso modo in plugin/cui viene caricato solo una volta che è necessario invece di essere caricato all'avvio.
EvergreenTree,

2
@Carpetsmoker Praticamente come diceva @EvergreenTree. Ovviamente, non ha importanza per i plugin "piccoli", ma è comunque una buona pratica. Con Vim hai pochissimo controllo sul garbage collector e caricare le cose solo quando e se sono necessarie può fare la differenza. D'altra parte, ci sono degli aspetti negativi nel spostare tutto in autoload, fi non è possibile verificare l'esistenza di una funzione se il file in cui vive non è stato caricato.
lcd047,
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.