Wie speichere und stelle ich ein Mapping wieder her?


12

Ich entwickle ein Plugin für Vim und möchte ein Mapping definieren, das nur während der "Ausführung des Plugins" zur Verfügung steht.

Bisher sieht der (vereinfachte) Workflow des Plugins folgendermaßen aus:

  1. Der Benutzer ruft einen Befehl des Plugins auf
  2. Der Befehl ruft die Vorbehandlungsfunktion auf:

    function! s:PreTreatmentFunction(function, ...)
        " Do some pretreatment stuff
    
        " Create a mapping to call the TearDown
        nnoremap <C-c> :call TeardDown()<CR>
    
        " Call a function depending on the parameter passed to this one
        if function == "foo"
            call Foo()
        else
            call Bar()
        endif
    endfunction
    
  3. Es wird eine andere Funktion aufgerufen, die den Zustand des Puffers ( Foo()oder Bar()in den letzten Zeilen der vorherigen Funktion) ändert.

  4. Der Benutzer verwendet das Mapping, um die Teardown-Funktion aufzurufen
  5. Die Abreißfunktion entfernt das erstellte Mapping:

    function! s:TearDown()
        " Do some tear down stuff
    
        " Remove the mapping
        unmap <C-c>
    endfunction
    

Ich bin nicht zufrieden mit der Art und Weise, wie ich mit meinem Mapping umgehe: Wenn der Benutzer es bereits auf etwas anderes abgebildet hat, verliert er sein ursprüngliches Mapping.

Meine Frage lautet also: Wie kann ich das, was zugeordnet <C-c>ist (wenn es zugeordnet ist), speichern und in meiner Abrissfunktion wiederherstellen? Gibt es eine integrierte Funktion, um dies zu tun? Ich denke über grepdas Ergebnis nach, :nmap <C-c>aber das fühlt sich nicht wirklich "sauber" an.

Ein paar Randnotizen:

  • Ich weiß, dass LearnVimScriptTheHardWay einen Abschnitt darüber hat , aber sie sagen, dass sie ein ftplugin verwenden sollen, was hier nicht möglich ist: Das Plugin ist nicht abhängig von einem Dateityp
  • Ich könnte eine Variable erstellen, mit der der Benutzer auswählen kann, welche Schlüssel verwendet werden sollen: Wahrscheinlich ist es das, was ich tun werde, aber ich interessiere mich hauptsächlich für das Speichern und Wiederherstellen.
  • Ich könnte einen lokalen Anführer gebrauchen, aber ich denke, es ist ein bisschen übertrieben und ich bin immer noch hauptsächlich neugierig auf das Speichern und Wiederherstellen.

Antworten:


24

Sie könnten die maparg()Funktion verwenden.

Um zu testen, ob der Benutzer <C-c>im normalen Modus etwas zugeordnet hat, schreiben Sie:

if !empty(maparg('<C-c>', 'n'))

Wenn der Benutzer etwas zugeordnet hat, um das {rhs}in einer Variablen zu speichern , würden Sie schreiben:

let rhs_save = maparg('<C-c>', 'n')

Wenn Sie weitere Informationen zum Mapping wünschen, z.

  • ist es still ( <silent>argument)?
  • Ist es lokal für den aktuellen Puffer ( <buffer>Argument)?
  • ist die {rhs}Bewertung eines Ausdrucks ( <expr>Argument)?
  • wird das {rhs}( nnoremapvs nmap) neu zugeordnet?
  • Wenn der Benutzer eine andere Zuordnung hat, die mit "" beginnt <C-c>, wartet Vim darauf, dass weitere Zeichen eingegeben werden ( <nowait>Argument)?
  • ...

Dann könnten Sie ein drittes und ein viertes Argument anführen: 0und 1.
0weil Sie nach einer Zuordnung suchen und nicht nach einer Abkürzung, und 1weil Sie ein Wörterbuch mit einem Maximum an Informationen und nicht nur dem {rhs}Wert wollen:

let map_save = maparg('<C-c>', 'n', 0, 1)

Angenommen, der Benutzer hat in seinem Mapping kein spezielles Argument verwendet und das Mapping wird nicht neu zugeordnet. Um {rhs}es wiederherzustellen, können Sie einfach schreiben:

let rhs_save = maparg('<C-c>', 'n')

" do some stuff which changes the mapping

exe 'nnoremap <C-c> ' . rhs_save

Oder um sicher zu gehen und alle möglichen Argumente wiederherzustellen:

let map_save = maparg('<C-c>', 'n', 0, 1)

" do some stuff which changes the mapping

exe (map_save.noremap ? 'nnoremap' : 'nmap') .
     \ (map_save.buffer ? ' <buffer> ' : '') .
     \ (map_save.expr ? ' <expr> ' : '') .
     \ (map_save.nowait ? ' <nowait> ' : '') .
     \ (map_save.silent ? ' <silent> ' : '') .
     \ ' <C-c> ' .
     \ map_save.rhs

Bearbeiten: Sorry, ich habe gerade festgestellt, dass es nicht wie erwartet funktioniert, wenn der Benutzer eine skriptlokale Funktion im {rhs}Mapping aufruft .

Angenommen, der Benutzer hat die folgende Zuordnung in seinem vimrc:

nnoremap <C-c> :<C-U>call <SID>FuncA()<CR>

function! s:FuncA()
    echo 'hello world!'
endfunction

Wenn er trifft <C-c>, wird die Nachricht angezeigt hello world!.

Und in Ihrem Plugin speichern Sie ein Wörterbuch mit allen Informationen und ändern dann vorübergehend seine Zuordnung wie folgt:

let map_save = maparg('<C-c>', 'n', 0, 1)
nnoremap <C-c> :<C-U>call <SID>FuncB()<CR>

function! s:FuncB()
    echo 'bye all!'
endfunction

Jetzt wird es angezeigt bye all!. Ihr Plugin leistet einige Arbeit, und wenn es vorbei ist, versucht es, die Zuordnung mit dem vorherigen Befehl wiederherzustellen.

Es wird wahrscheinlich mit einer Nachricht fehlschlagen, die so aussieht:

E117: Unknown function: <SNR>61_FuncA

61ist nur die Kennung des Skripts, in dem Ihr Zuordnungsbefehl ausgeführt werden würde. Es könnte jede andere Nummer sein. Wenn es sich bei Ihrem Plugin um die 42. Datei auf dem System des Benutzers handelt, ist dies der Fall 42.

Wenn in einem Skript ein Zuordnungsbefehl ausgeführt wird, übersetzt Vim die Notation automatisch <SID>in den speziellen Schlüsselcode <SNR>, gefolgt von einer für das Skript eindeutigen Zahl und einem Unterstrich. Dies muss geschehen, da <C-c>das Mapping bei einem Treffer des Benutzers außerhalb des Skripts ausgeführt wird und daher nicht weiß, in welchem ​​Skript FuncA()es definiert ist.

Das Problem ist, dass das ursprüngliche Mapping in einem anderen Skript als Ihrem Plugin erstellt wurde, sodass hier die automatische Übersetzung falsch ist. Dabei wird die Kennung Ihres Skripts verwendet, während die Kennung des Benutzers verwendet werden sollte vimrc.

Sie können die Übersetzung aber auch manuell vornehmen. Das Wörterbuch map_saveenthält einen Schlüssel mit dem Namen, 'sid'dessen Wert der richtige Bezeichner ist.
Um den vorherigen Wiederherstellungsbefehl robuster zu machen, können Sie ihn ersetzen map_save.rhsdurch:

substitute(map_save.rhs, '<SID>', '<SNR>' . map_save.sid . '_', 'g')

Wenn die {rhs}des Originalmappings enthalten ist <SID>, sollte es richtig übersetzt werden. Ansonsten sollte nichts geändert werden.

Und wenn Sie den Code ein wenig verkürzen möchten, können Sie die 4 Zeilen, die sich um die speziellen Argumente kümmern, ersetzen durch:

join(map(['buffer', 'expr', 'nowait', 'silent'], 'map_save[v:val] ? "<" . v:val . ">": ""'))

Die map()Funktion sollte jedes Element aus der Liste ['buffer', 'expr', 'nowait', 'silent']in das entsprechende Zuordnungsargument konvertieren, jedoch nur, wenn sein Schlüssel map_savenicht Null ist. Und join()sollte alle Elemente zu einer Zeichenfolge verbinden.

Ein robusterer Weg zum Speichern und Wiederherstellen des Mappings könnte also sein:

let map_save = maparg('<C-c>', 'n', 0, 1)

" do some stuff which changes the mapping

exe (map_save.noremap ? 'nnoremap' : 'nmap') .
    \ join(map(['buffer', 'expr', 'nowait', 'silent'], 'map_save[v:val] ? "<" . v:val . ">": ""')) .
    \ map_save.lhs . ' ' .
    \ substitute(map_save.rhs, '<SID>', '<SNR>' . map_save.sid . '_', 'g')

Edit2:

Ich stehe vor dem gleichen Problem wie Sie: Speichern und Wiederherstellen eines Mappings in einem Zeichen-Plugin. Und ich glaube, ich habe zwei Probleme gefunden, die die ursprüngliche Antwort zum Zeitpunkt meines Schreibens nicht gesehen hat. Tut mir leid.

Angenommen, der Benutzer verwendet das erste Problem <C-c>in einer globalen Zuordnung, aber auch in einer pufferlokalen Zuordnung. Beispiel:

nnoremap          <C-c>    :echo 'global mapping'<CR>
nnoremap <buffer> <C-c>    :echo 'local  mapping'<CR>

In diesem Fall maparg()wird der lokalen Zuordnung Vorrang eingeräumt:

:echo maparg('<C-c>', 'n', 0, 1)

---> {'silent': 0, 'noremap': 1, 'lhs': '<C-C>', 'mode': 'n', 'nowait': 0, 'expr': 0, 'sid': 7, 'rhs': ':echo ''local  mapping''<CR>', 'buffer': 1}

Welches ist bestätigt in :h maparg():

    The mappings local to the current buffer are checked first,
    then the global mappings.

Aber vielleicht interessiert Sie das pufferlokale Mapping nicht, vielleicht möchten Sie das globale.
Die einzige Möglichkeit, die Informationen zur globalen Zuordnung zuverlässig abzurufen, besteht darin, die Zuordnung einer potenziellen, schattierenden, pufferlokalen Zuordnung mit demselben Schlüssel vorübergehend aufzuheben.

Dies könnte in 4 Schritten erfolgen:

  1. Speichern Sie ein (potentielles) pufferlokales Mapping mit der Taste <C-c>
  2. ausführen :silent! nunmap <buffer> <C-c>, um ein (potentielles) pufferlokales Mapping zu löschen
  3. speichere das globale Mapping ( maparg('<C-c>', 'n', 0, 1))
  4. Stellen Sie die pufferlokale Zuordnung wieder her

Das zweite Problem ist das folgende. Angenommen, der Benutzer hat nichts zugeordnet <C-c>, dann ist die Ausgabe von maparg()ein leeres Wörterbuch. In diesem Fall besteht der Wiederherstellungsprozess nicht in der Installation eines Mappings ( :nnoremap), sondern in der Zerstörung eines Mappings ( :nunmap).

Um diese zwei neuen Probleme zu lösen, können Sie diese Funktion verwenden, um Zuordnungen zu speichern:

fu! Save_mappings(keys, mode, global) abort
    let mappings = {}

    if a:global
        for l:key in a:keys
            let buf_local_map = maparg(l:key, a:mode, 0, 1)

            sil! exe a:mode.'unmap <buffer> '.l:key

            let map_info        = maparg(l:key, a:mode, 0, 1)
            let mappings[l:key] = !empty(map_info)
                                \     ? map_info
                                \     : {
                                        \ 'unmapped' : 1,
                                        \ 'buffer'   : 0,
                                        \ 'lhs'      : l:key,
                                        \ 'mode'     : a:mode,
                                        \ }

            call Restore_mappings({l:key : buf_local_map})
        endfor

    else
        for l:key in a:keys
            let map_info        = maparg(l:key, a:mode, 0, 1)
            let mappings[l:key] = !empty(map_info)
                                \     ? map_info
                                \     : {
                                        \ 'unmapped' : 1,
                                        \ 'buffer'   : 1,
                                        \ 'lhs'      : l:key,
                                        \ 'mode'     : a:mode,
                                        \ }
        endfor
    endif

    return mappings
endfu

... und dieses, um sie wiederherzustellen:

fu! Restore_mappings(mappings) abort

    for mapping in values(a:mappings)
        if !has_key(mapping, 'unmapped') && !empty(mapping)
            exe     mapping.mode
               \ . (mapping.noremap ? 'noremap   ' : 'map ')
               \ . (mapping.buffer  ? ' <buffer> ' : '')
               \ . (mapping.expr    ? ' <expr>   ' : '')
               \ . (mapping.nowait  ? ' <nowait> ' : '')
               \ . (mapping.silent  ? ' <silent> ' : '')
               \ .  mapping.lhs
               \ . ' '
               \ . substitute(mapping.rhs, '<SID>', '<SNR>'.mapping.sid.'_', 'g')

        elseif has_key(mapping, 'unmapped')
            sil! exe mapping.mode.'unmap '
                                \ .(mapping.buffer ? ' <buffer> ' : '')
                                \ . mapping.lhs
        endif
    endfor

endfu

Die Save_mappings()Funktion kann zum Speichern von Zuordnungen verwendet werden.
Es werden 3 Argumente erwartet:

  1. eine Liste von Schlüsseln; Beispiel:['<C-a>', '<C-b>', '<C-c>']
  2. Ein Modus; Beispiel: nfür normalen Modus oder xfür visuellen Modus
  3. eine boolesche Flagge; Wenn dies der 1Fall ist , bedeutet dies, dass Sie an globalen Zuordnungen interessiert sind, und wenn dies der Fall ist 0, an lokalen

Mit ihm können Sie die globalen Zuordnungen speichern mit den Tasten C-a, C-bund C-c, im normalen Modus, in einem Wörterbuch:

let your_saved_mappings = Save_mappings(['<C-a>', '<C-b>', '<C-c>'], 'n', 1)

Wenn Sie die Zuordnungen später wiederherstellen möchten, können Sie Folgendes aufrufen Restore_mappings()und das Wörterbuch mit allen Informationen als Argument übergeben:

call Restore_mappings(your_saved_mappings)

Beim Speichern / Wiederherstellen von pufferlokalen Zuordnungen kann ein drittes Problem auftreten. Denn zwischen dem Zeitpunkt, zu dem wir die Zuordnungen gespeichert haben, und dem Zeitpunkt, zu dem wir versuchen, sie wiederherzustellen, hat sich möglicherweise der aktuelle Puffer geändert.

In diesem Fall Save_mappings()könnte die Funktion möglicherweise durch Speichern der Nummer des aktuellen Puffers ( bufnr('%')) verbessert werden .

Und dann Restore_mappings()würde diese Informationen verwenden , um die Puffer-local - Zuordnungen im rechten Puffer wiederherzustellen. Wir könnten den :bufdoBefehl wahrscheinlich verwenden , dem letzteren eine Zählung voranstellen (die mit der zuvor gespeicherten Puffernummer übereinstimmt) und ihn mit dem Zuordnungsbefehl versehen.

Vielleicht so etwas wie:

:{original buffer number}bufdo {mapping command}

Mit der Funktion müssten wir zunächst prüfen, ob der Puffer noch vorhanden ist bufexists(), da er zwischenzeitlich gelöscht werden könnte.


Erstaunlich, genau das brauchte ich. Vielen Dank!
statox

2

In meinen Plugins sind temporäre Zuordnungen immer lokal gepuffert. Es ist mir wirklich egal, ob ich globale Zuordnungen speichere oder etwas Komplexes, das sie einbezieht. Daher meine lh#on#exit().restore_buffer_mapping()Helferfunktion - von lh-vim-lib .

Am Ende passiert Folgendes:

" excerpt from autoload/lh/on.vim
function! s:restore_buffer_mapping(key, mode) dict abort " {{{4
  let keybinding = maparg(a:key, a:mode, 0, 1)
  if get(keybinding, 'buffer', 0)
    let self.actions += [ 'silent! call lh#mapping#define('.string(keybinding).')']
  else
    let self.actions += [ 'silent! '.a:mode.'unmap <buffer> '.a:key ]
  endif
  return self
endfunction

" The action will be executed later on with:
" # finalizer methods {{{2
function! s:finalize() dict " {{{4
  " This function shall not fail!
  for l:Action in self.actions
    try
      if type(l:Action) == type(function('has'))
        call l:Action()
      elseif !empty(l:Action)
        exe l:Action
      endif
    catch /.*/
      call lh#log#this('Error occured when running action (%1)', l:Action)
      call lh#log#exception()
    finally
      unlet l:Action
    endtry
  endfor
endfunction


" excerpt from autoload/lh/mapping.vim
" Function: lh#mapping#_build_command(mapping_definition) {{{2
" @param mapping_definition is a dictionary witch the same keys than the ones
" filled by maparg()
function! lh#mapping#_build_command(mapping_definition)
  let cmd = a:mapping_definition.mode
  if has_key(a:mapping_definition, 'noremap') && a:mapping_definition.noremap
    let cmd .= 'nore'
  endif
  let cmd .= 'map'
  let specifiers = ['silent', 'expr', 'buffer']
  for specifier in specifiers
    if has_key(a:mapping_definition, specifier) && a:mapping_definition[specifier]
      let cmd .= ' <'.specifier.'>'
    endif
  endfor
  let cmd .= ' '.(a:mapping_definition.lhs)
  let rhs = substitute(a:mapping_definition.rhs, '<SID>', "\<SNR>".(a:mapping_definition.sid).'_', 'g')
  let cmd .= ' '.rhs
  return cmd
endfunction
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.