Vimscript: Aide au chargement automatique, à la portée et au <SID>

9

J'ai travaillé sur la modularisation et la conversion d'un code dans mon vimrcen quelques bundles / plugins autonomes et réutilisables. J'ai rencontré un problème avec le chargement automatique et la portée que j'ai du mal à comprendre. Je l' ai lu :h autoload, :h <sid>, :h script-local, mais je ne suis pas encore tout à fait clair sur la façon dont cela fonctionne.

J'ai regardé des plugins bien développés pour comprendre certains modèles couramment utilisés et j'ai structuré mes plugins comme suit:

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

Au début de mon vimrc, j'exécute le plugin avec:

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

Tout cela semble fonctionner correctement et comme prévu - mais chaque fois qu'une fonction est appelée, la s:set_default(...)fonction est appelée pour chaque indicateur, ce qui est inefficace - j'ai donc tenté de les déplacer hors des fonctions:

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

Mais cela provoque des erreurs que je ne sais pas comment résoudre:

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

Je ne comprends toujours pas bien la portée de vim, mais d'après ce que j'ai lu - il semble que vim implémente une forme de manipulation de nom avec des scripts pour fournir une «portée». Il attribue (pas sûr de savoir comment fonctionne exactement ce processus) un SID unique pour chaque fichier qui est chargé au moment de l'exécution - et lorsque vous appelez une fonction qui est préfixée avec un identificateur de portée de script ( s:), il remplace de manière transparente cet identifiant par un SID mappé .

Dans certains cas, j'ai vu des scripts qui appellent des fonctions comme celle-ci (mais cela ne fonctionne pas dans mon cas, je ne comprends pas pourquoi, et j'espère que quelqu'un peut expliquer cela):

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

Ce qui suit fonctionne, mais je ne sais pas si c'est un bon modèle:

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

Dans le script local, il indique:

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

Il voit que cela pourrait être l'approche que je dois adopter, mais je ne sais pas vraiment pourquoi, ni exactement comment l'utiliser. Quelqu'un peut-il donner un aperçu?

nfarrar
la source

Réponses:

4

Dans le premier cas, l'erreur que vous obtenez est que vous essayez d'appeler une fonction avant son existence. Autrement dit, Vim progresse dans votre script. Lorsqu'il voit la callligne, il n'a pas encore traité la functionligne qui crée ce que vous voulez appeler, ce qui entraîne l'erreur unknown function.

Si vous déplacez votre appel à la configuration par défaut à la fin de votre script (avant de restaurer cpomais après tous vos functions, il n'y aura pas d'erreur, car Vim aura traité le script pour créer les fonctions en premier, donc une fois qu'il sera aux calllignes, les fonctions existent.

"....

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

Je ne sais pas pourquoi la syntaxe alternative d'appeler votre en set_defaulttant que fonction de chargement automatique à partir du script de chargement automatique fonctionne alors que la fonction n'a pas encore été définie. Je suppose que c'est un effet secondaire de l'implémentation (où un script déjà lu n'est pas relu, ou vous auriez une récursion infinie). Je ne compterais pas sur le fait qu'il fonctionne toujours de cette façon.

John O'M.
la source
Si je comprends bien, vous dites que le fichier entier n'est pas sourcé et exécuté avant l'exécution de l'appel de fonction défini dans mon vimrc? Peut-être que je me méprends ... mais il me semble que tout le script de chargement automatique provient et s'exécute en premier. Si j'ajoute une instruction echom 'this is the function call'dans la fonction appelée depuis vimrc et une autre echom 'file was sourced'n'importe où ailleurs dans le fichier (pas dans une fonction), je vois la dernière en premier, puis la première.
nfarrar
Désolé, je viens de réaliser ce que vous dites - et que vous avez raison. Étant donné que l'appel de fonction se produit dans le script tel qu'il est généré, il doit se produire une fois la fonction définie. Je vous remercie!
nfarrar
13

Je recommande cette structure:

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

Ceci est compatible avec tous les gestionnaires de plugins modernes et garde les choses propres. Parmi ceux-ci:

  • myplugin/doc/myplugin.txt devrait être le fichier d'aide
  • myplugin/plugin/myplugin.vim Devrait contenir:
    • vérifie les prérequis nécessaires à votre plugin, tels que la version minimale de Vim et les fonctionnalités de compilation de Vim
    • mappages de touches
    • initialisation unique, comme la définition des valeurs par défaut
  • myplugin/autoload/myplugin.vim devrait contenir le code principal de votre plugin.

Les portées sont en fait assez simples:

  • les fonctions dont le nom commence par s:peuvent apparaître n'importe où, mais sont locales au fichier dans lequel elles sont définies; vous ne pouvez les appeler qu'à partir du fichier où ils sont définis;
  • les fonctions dans myplugin/autoload/myplugin.vimdoivent avoir des noms myplugin#function()et sont globales; vous pouvez les appeler de partout (mais attention, les appeler provoque le myplugin/autoload/myplugin.vimchargement du fichier );
  • toutes les autres fonctions doivent avoir des noms commençant par une majuscule, comme Function(), et sont également globales; vous pouvez les appeler de partout.

<SID>et <Plug>sont utilisés pour les mappages, et c'est un sujet que vous devriez probablement éviter jusqu'à ce que vous ayez une compréhension complète de la façon dont ils fonctionnent et du problème qu'ils sont censés résoudre.

<SNR> est quelque chose que vous ne devriez jamais utiliser directement.

lcd047
la source
Pourquoi séparer les répertoires autoload/et plugin/? J'ai toujours tout mis plugin/et ça semble bien marcher?
Martin Tournoij
2
Le répertoire de chargement automatique ne charge son contenu qu'en cas de besoin. Cela peut accélérer quelque peu l'heure de démarrage de vim. En d'autres termes, cela fonctionne à peu près de la même manière, plugin/sauf qu'il n'est chargé qu'une fois qu'il est nécessaire au lieu d'être chargé au démarrage.
EvergreenTree
2
@Carpetsmoker Presque comme l'a dit @EvergreenTree. Bien sûr, cela n'a pas vraiment d'importance pour les "petits" plugins, mais c'est toujours une bonne pratique. Avec Vim, vous avez très peu de contrôle sur le ramasse-miettes, et charger des objets uniquement quand et s'ils sont nécessaires peut faire la différence. D'un autre côté, il y a des inconvénients subtils à tout déplacer vers autoload, par exemple , vous ne pouvez pas tester l'existence d'une fonction si le fichier dans lequel elle se trouve n'a pas été chargé.
lcd047