Come salvare e ripristinare una mappatura?


12

Sto sviluppando un plug-in per Vim e vorrei definire un mapping che sarebbe disponibile solo durante "l'esecuzione del plug-in".

Finora il flusso di lavoro (semplificato) del plugin è il seguente:

  1. L'utente chiama un comando del plugin
  2. Il comando chiama la funzione di pre-trattamento:

    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. Viene chiamata un'altra funzione che modifica lo stato del buffer ( Foo()o Bar()nelle ultime righe della funzione precedente)

  4. L'utente utilizza la mappatura per chiamare la funzione di smontaggio
  5. La funzione tear down rimuove la mappatura creata:

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

Non sono soddisfatto del modo in cui gestisco la mia mappatura: se l'utente l'ha già mappata su qualcos'altro, perderà la sua mappatura originale.

Quindi la mia domanda è: come posso salvare ciò che <C-c>è mappato (se è mappato) e ripristinarlo nella mia funzione di abbattimento? Esiste una funzione integrata per farlo? Ho greppensato al risultato :nmap <C-c>ma non mi sembra davvero "pulito".

Alcune note a margine:

  • So che LearnVimScriptTheHardWay ha una sezione a riguardo , ma dicono di usare un ftplugin che non è possibile qui: il plugin non dipende da un tipo di file
  • Potrei creare una variabile per consentire all'utente di scegliere quali chiavi utilizzare: è probabilmente ciò che farò, ma sono principalmente interessato a come eseguire il salvataggio e il ripristino.
  • Potrei usare un leader locale, ma penso che sia un po 'eccessivo e sono ancora principalmente curioso di sapere cosa salvare e ripristinare.

Risposte:


24

È possibile utilizzare la maparg()funzione.

Per verificare se l'utente ha mappato qualcosa <C-c>in modalità normale, scrivere:

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

Se l'utente ha mappato qualcosa, per archiviare {rhs}in una variabile, dovresti scrivere:

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

Se vuoi maggiori informazioni sulla mappatura, come:

  • è silenzioso ( <silent>argomento)?
  • è locale al buffer corrente ( <buffer>argomento)?
  • è la {rhs}valutazione di un'espressione ( <expr>argomento)?
  • rimappa il {rhs}( nnoremapvs nmap)?
  • se l'utente ha un'altra mappatura che inizia con <C-c>, Vim attende che vengano digitati più caratteri ( <nowait>argomento)?
  • ...

Quindi, potresti dare un terzo e un quarto argomento: 0e 1.
0perché stai cercando una mappatura e non un'abbreviazione, e 1perché vuoi un dizionario con un massimo di informazioni e non solo il {rhs}valore:

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

Supponendo che l'utente non abbia usato alcun argomento speciale nella sua mappatura e che non rimappa {rhs}, per ripristinarlo, potresti semplicemente scrivere:

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

" do some stuff which changes the mapping

exe 'nnoremap <C-c> ' . rhs_save

O per essere sicuri e ripristinare tutti i possibili argomenti:

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

Modifica: mi dispiace, ho appena realizzato che non funzionerebbe come previsto se l'utente chiama una funzione script-local nella {rhs}mappa.

Supponiamo che l'utente abbia la seguente mappatura all'interno della sua vimrc:

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

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

Quando colpisce <C-c>, visualizza il messaggio hello world!.

E nel tuo plugin, salvi un dizionario con tutte le informazioni, quindi modifichi temporaneamente la sua mappatura in questo modo:

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

Ora verrà visualizzato bye all!. Il tuo plugin funziona un po ', e quando è finito, prova a ripristinare la mappatura con il comando precedente.

Probabilmente fallirà con un messaggio simile al seguente:

E117: Unknown function: <SNR>61_FuncA

61è solo l'identificatore dello script in cui verrà eseguito il comando di mappatura. Potrebbe essere qualsiasi altro numero. Se il plug-in è il 42 ° file proveniente dal sistema dell'utente, lo sarà 42.

All'interno di uno script, quando viene eseguito un comando di mappatura, Vim traduce automaticamente la notazione <SID>nello speciale codice chiave <SNR>, seguito da un numero univoco per lo script e da un carattere di sottolineatura. Deve farlo, perché quando l'utente colpirà <C-c>, la mappatura verrà eseguita al di fuori dello script, e quindi non saprà in quale script FuncA()è definito.

Il problema è che la mappatura originale è stata fornita in uno script diverso rispetto al tuo plugin, quindi qui la traduzione automatica è sbagliata. Utilizza l'identificatore del tuo script, mentre dovrebbe usare l'identificatore dell'utente vimrc.

Ma potresti fare la traduzione manualmente. Il dizionario map_savecontiene una chiave chiamata il 'sid'cui valore è l'identificatore corretto.
Pertanto, per rendere più robusto il comando di ripristino precedente, è possibile sostituire map_save.rhscon:

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

Se il {rhs}contenuto del mapping originale è contenuto <SID>, deve essere tradotto correttamente. Altrimenti, nulla dovrebbe essere cambiato.

E se vuoi abbreviare un po 'il codice, potresti sostituire le 4 righe che si occupano degli argomenti speciali con:

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

La map()funzione dovrebbe convertire ciascun elemento dall'elenco ['buffer', 'expr', 'nowait', 'silent']nel corrispondente argomento di mappatura, ma solo se la sua chiave interna map_saveè diversa da zero. E join()dovrebbe unire tutti gli elementi in una stringa.

Pertanto, un modo più efficace per salvare e ripristinare la mappatura potrebbe essere:

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:

Sto affrontando lo stesso problema, come salvare e ripristinare una mappatura in un plug-in di disegno. E penso di aver trovato 2 problemi che la risposta iniziale non ha visto al momento in cui l'ho scritta, mi dispiace per quello.

Primo problema, supponiamo che l'utente usi <C-c>in una mappatura globale ma anche in una mappatura buffer-locale. Esempio:

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

In questo caso, maparg()darà priorità alla mappatura locale:

: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}

Che è confermato in :h maparg():

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

Ma forse non sei interessato al mapping buffer-local, forse vuoi quello globale.
L'unico modo in cui ho scoperto, in modo affidabile, di ottenere le informazioni sulla mappatura globale, è cercare di annullare la mappatura temporanea di una potenziale mappatura buffer-local usando una stessa chiave.

Potrebbe essere fatto in 4 passaggi:

  1. salva una (potenziale) mappatura buffer-locale usando la chiave <C-c>
  2. eseguire :silent! nunmap <buffer> <C-c>per eliminare una (potenziale) mappatura buffer-locale
  3. salva la mappatura globale ( maparg('<C-c>', 'n', 0, 1))
  4. ripristinare la mappatura buffer-locale

Il secondo problema è il seguente. Supponiamo che l'utente non abbia mappato nulla <C-c>, quindi l'output di maparg()sarà un dizionario vuoto. E in questo caso, il processo di ripristino non consiste nell'installazione di un mapping ( :nnoremap), ma nella distruzione di un mapping ( :nunmap).

Per provare a risolvere questi 2 nuovi problemi, puoi provare questa funzione per salvare i mapping:

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

... e questo per ripristinarli:

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

La Save_mappings()funzione potrebbe essere utilizzata per salvare i mapping.
Si aspetta 3 argomenti:

  1. un elenco di chiavi; esempio:['<C-a>', '<C-b>', '<C-c>']
  2. una modalità; esempio: nper la modalità normale o xper la modalità visiva
  3. una bandiera booleana; se lo è 1, significa che sei interessato ai mapping globali e, se lo è 0, a quelli locali

Con esso, si potrebbe risparmiare le mappature globali utilizzando i tasti C-a, C-be C-c, in modalità normale, all'interno di un dizionario:

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

Quindi, in seguito, quando vorrai ripristinare i mapping, puoi chiamare Restore_mappings(), passando il dizionario contenente tutte le informazioni come argomento:

call Restore_mappings(your_saved_mappings)

Potrebbe esserci un terzo problema, durante il salvataggio / ripristino delle mappature buffer-local. Perché, tra il momento in cui abbiamo salvato i mapping e il momento in cui proviamo a ripristinarli, il buffer corrente potrebbe essere cambiato.

In questo caso, forse la Save_mappings()funzione potrebbe essere migliorata salvando il numero del buffer corrente ( bufnr('%')).

Quindi, Restore_mappings()utilizzare queste informazioni per ripristinare i mapping buffer-local nel buffer corretto. Probabilmente potremmo usare il :bufdocomando, aggiungere il prefisso a quest'ultimo con un conteggio (corrispondente al numero di buffer precedentemente salvato) e suffissarlo con il comando mapping.

Forse qualcosa del tipo:

:{original buffer number}bufdo {mapping command}

Dovremmo prima verificare se il buffer esiste ancora, usando la bufexists()funzione, perché potrebbe essere stato eliminato nel frattempo.


Incredibile è esattamente quello di cui avevo bisogno. Grazie!
statox

2

Nei miei plugin, quando ho mappature temporanee, sono sempre buffer locali - non mi interessa davvero salvare le mappature globali né qualsiasi cosa complessa che le coinvolga. Da qui la mia lh#on#exit().restore_buffer_mapping()funzione di aiuto - da lh-vim-lib .

Alla fine, ciò che accade è il seguente:

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