Vimscript: Hilfe beim Autoloading, Scope & <SID>

9

Ich habe daran gearbeitet, einen Code in meinem Computer vimrcin einige eigenständige und wiederverwendbare Bundles / Plugins-Plugins zu modularisieren und zu konvertieren . Ich bin auf ein Problem mit Autoloading & Scope gestoßen, das ich nur schwer verstehen kann. Ich habe gelesen , durch :h autoload, :h <sid>, :h script-local, aber ich bin immer noch nicht ganz klar, wie dies funktioniert.

Ich habe mir einige gut entwickelte Plugins angesehen, um einige häufig verwendete Muster herauszufinden, und meine Plugins wie folgt strukturiert:

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

Zu Beginn meines vimrc starte ich das Plugin mit:

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

Dies alles scheint korrekt und wie erwartet zu funktionieren - aber jedes Mal, wenn eine Funktion aufgerufen wird, wird die s:set_default(...)Funktion für jedes Flag aufgerufen, was ineffizient ist. Deshalb habe ich versucht, sie aus den Funktionen zu entfernen:

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

Dies führt jedoch zu Fehlern, bei denen ich mir nicht sicher bin, wie ich sie beheben soll:

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

Ich verstehe das Scoping von vim immer noch nicht genau, aber nach dem, was ich gelesen habe, scheint es, dass vim eine Form der Namensverfälschung mit Skripten implementiert, um "Scope" bereitzustellen. Es weist jeder Datei, die zur Laufzeit geladen wird, eine eindeutige SID zu (nicht sicher, wie genau dieser Prozess funktioniert). Wenn Sie eine Funktion aufrufen, der eine Skriptbereichskennung ( s:) vorangestellt ist , wird diese Kennung transparent durch eine zugeordnete SID ersetzt .

In einigen Fällen habe ich Skripte gesehen, die Funktionen wie diese aufrufen (aber in meinem Fall funktioniert es nicht, ich verstehe nicht warum und hoffe, dass jemand dies erklären kann):

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

Folgendes funktioniert, aber ich bin mir nicht sicher, ob es ein gutes Muster ist:

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

In script local heißt es:

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

Es sieht so aus, als wäre dies der Ansatz, den ich wählen muss, aber ich bin mir nicht ganz sicher, warum oder wie ich ihn genau verwenden soll. Kann jemand einen Einblick geben?

nfarrar
quelle

Antworten:

4

Im ersten Fall erhalten Sie den Fehler, dass Sie versuchen, eine Funktion vor ihrer Existenz aufzurufen. Das heißt, Vim durchläuft Ihr Skript. Wenn die callZeile angezeigt wird, hat sie die Zeile, die functiondas erstellt, was Sie aufrufen möchten, noch nicht verarbeitet , was zu dem Fehler führt unknown function.

Wenn Sie Ihren Aufruf zum Einrichten der Standardeinstellungen an das Ende Ihres Skripts verschieben (vor dem Wiederherstellen, cpoaber nach all Ihren functions, tritt kein Fehler auf, da Vim das Skript verarbeitet hat, um die Funktionen zuerst zu erstellen zu den callZeilen existieren die Funktionen, z

"....

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

Ich weiß nicht, warum die alternative Syntax zum Aufrufen von set_defaultals Autoload-Funktion aus dem Autoload-Skript heraus funktioniert, wenn die Funktion noch nicht definiert wurde. Ich vermute, dies ist ein Nebeneffekt der Implementierung (wenn ein bereits gelesenes Skript nicht erneut gelesen wird oder Sie eine unendliche Rekursion haben würden). Ich würde nicht damit rechnen, dass es immer so funktioniert.

John O'M.
quelle
Wenn ich das richtig verstehe, sagen Sie, dass die gesamte Datei nicht vor der Ausführung des in meinem vimrc definierten Funktionsaufrufs bezogen und ausgeführt wird? Vielleicht verstehe ich das falsch ... aber es scheint mir, dass das gesamte Autoload-Skript zuerst bezogen und ausgeführt wird. Wenn ich eine Anweisung echom 'this is the function call'in der Funktion hinzufüge , die von vimrc aufgerufen wird, und eine echom 'file was sourced'andere an einer anderen Stelle in der Datei (nicht in einer Funktion), sehe ich zuerst die letztere, dann die erstere.
Nfarrar
Entschuldigung, ich habe gerade gemerkt, was Sie sagen - und dass Sie Recht haben. Da der Funktionsaufruf im Skript ausgeführt wird, während er bezogen wird, muss er erfolgen, nachdem die Funktion definiert wurde. Vielen Dank!
Nfarrar
12

Ich empfehle diese Struktur:

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

Dies ist mit allen modernen Plugin-Managern kompatibel und hält die Dinge sauber. Von diesen:

  • myplugin/doc/myplugin.txt sollte die Hilfedatei sein
  • myplugin/plugin/myplugin.vim sollte beinhalten:
    • prüft auf alle Voraussetzungen, die Ihr Plugin benötigt, wie z. B. minimale Vim-Version und Funktionen zur Vim-Kompilierungszeit
    • Tastenzuordnungen
    • Einmalige Initialisierung, z. B. Festlegen von Standardeinstellungen
  • myplugin/autoload/myplugin.vim sollte den Hauptcode Ihres Plugins enthalten.

Bereiche sind eigentlich ziemlich einfach:

  • Funktionen mit Namen, die mit beginnen, s:können überall angezeigt werden, sind jedoch lokal für die Datei, in der sie definiert sind. Sie können sie nur aus der Datei aufrufen, in der sie definiert sind.
  • Funktionen in myplugin/autoload/myplugin.vimmüssen Namen haben myplugin#function()und sind global; Sie können sie von überall aus aufrufen (achten Sie jedoch darauf, dass beim Aufrufen die Datei myplugin/autoload/myplugin.vimgeladen wird).
  • Alle anderen Funktionen müssen Namen haben, die mit einem Großbuchstaben beginnen, z. B. Function()und sind ebenfalls global. Sie können sie von überall anrufen.

<SID>und <Plug>werden für Zuordnungen verwendet, und sie sind ein Thema, das Sie wahrscheinlich vermeiden sollten, bis Sie ein umfassendes Verständnis dafür haben, wie sie funktionieren und welches Problem sie lösen sollen.

<SNR> ist etwas, das Sie niemals direkt verwenden sollten.

lcd047
quelle
Warum zwischen den Verzeichnissen autoload/und trennen plugin/? Ich habe immer alles reingesteckt plugin/und das scheint gut zu funktionieren?
Martin Tournoij
1
Das Autoload-Verzeichnis lädt seinen Inhalt nur bei Bedarf. Dies kann die Startzeit von vim etwas beschleunigen. Mit anderen Worten, es funktioniert ungefähr so, plugin/außer dass es nur geladen wird, wenn es benötigt wird, anstatt beim Start geladen zu werden.
EvergreenTree
2
@Carpetsmoker So ziemlich wie @EvergreenTree sagte. Natürlich ist es für "kleine" Plugins nicht wirklich wichtig, aber es ist immer noch eine gute Praxis. Mit Vim haben Sie nur sehr wenig Kontrolle über den Garbage Collector, und das Laden von Dingen nur dann, wenn und wenn sie benötigt werden, kann einen Unterschied machen. Auf der anderen Seite gibt es subtile Nachteile, autoloadwenn Sie alles verschieben , z. B. können Sie nicht prüfen, ob eine Funktion vorhanden ist, wenn die Datei, in der sie sich befindet, nicht geladen wurde.
lcd047