Comment enregistrer et restaurer une cartographie?

12

Je suis en train de développer un plugin pour Vim et je voudrais définir un mapping qui ne serait disponible que pendant "l'exécution du plugin".

Jusqu'à présent, le flux de travail (simplifié) du plugin est le suivant:

  1. L'utilisateur appelle une commande du plugin
  2. La commande appelle la fonction de prétraitement:

    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. Une autre fonction est appelée qui change l'état du tampon ( Foo()ou Bar()dans les dernières lignes de la fonction précédente)

  4. L'utilisateur utilise le mappage pour appeler la fonction de démontage
  5. La fonction de suppression supprime le mappage créé:

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

Je ne suis pas satisfait de la façon dont je gère mon mappage: si l'utilisateur l'a déjà mappé à quelque chose d'autre, il perdra son mappage d'origine.

Ma question est donc la suivante: comment puis-je enregistrer ce qui <C-c>est mappé (s'il est mappé) et le restaurer dans ma fonction de suppression? Existe-t-il une fonction intégrée pour le faire? J'ai pensé au sujet grepdu résultat :nmap <C-c>mais cela ne me semble pas vraiment "propre".

Quelques notes annexes:

  • Je sais que LearnVimScriptTheHardWay a une section à ce sujet , mais ils disent d'utiliser un ftplugin qui n'est pas possible ici: le plugin ne dépend pas d'un type de fichier
  • Je pourrais créer une variable pour permettre à l'utilisateur de choisir les clés à utiliser: c'est probablement ce que je ferai mais je suis principalement intéressé par la façon de faire la sauvegarde et la restauration.
  • Je pourrais utiliser un leader local, mais je pense que c'est un peu exagéré et je suis toujours principalement curieux de la sauvegarde et de la restauration.
statox
la source

Réponses:

24

Vous pouvez utiliser la maparg()fonction.

Pour tester si l'utilisateur a mappé quelque chose <C-c>en mode normal, vous écririez:

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

Si l'utilisateur a mappé quelque chose, pour stocker le {rhs}dans une variable, vous écririez:

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

Si vous souhaitez plus d'informations sur le mappage, comme:

  • est-ce silencieux ( <silent>argument)?
  • est-il local au tampon actuel ( <buffer>argument)?
  • est l' {rhs}évaluation d'une expression ( <expr>argument)?
  • remappe-t-il le {rhs}( nnoremapvs nmap)?
  • si l'utilisateur a un autre mappage qui commence par <C-c>, Vim attend-il que plus de caractères soient saisis ( <nowait>argument)?
  • ...

Ensuite, vous pourriez donner un troisième et un quatrième argument: 0et 1.
0parce que vous cherchez une cartographie et non une abréviation, et 1parce que vous voulez un dictionnaire avec un maximum d'informations et pas seulement la {rhs}valeur:

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

En supposant que l'utilisateur n'a utilisé aucun argument spécial dans son mappage et qu'il ne remappe pas le {rhs}, pour le restaurer, vous pouvez simplement écrire:

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

" do some stuff which changes the mapping

exe 'nnoremap <C-c> ' . rhs_save

Ou pour être sûr et restaurer tous les arguments possibles:

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

Edit: Désolé, je viens de réaliser que cela ne fonctionnerait pas comme prévu si l'utilisateur appelle une fonction script locale dans {rhs}le mappage.

Supposons que l'utilisateur ait le mappage suivant dans son vimrc:

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

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

Quand il frappe <C-c>, il affiche le message hello world!.

Et dans votre plugin, vous enregistrez un dictionnaire avec toutes les informations, puis modifiez temporairement son mappage comme ceci:

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

Maintenant, il s'affichera bye all!. Votre plugin fonctionne, et une fois terminé, il essaie de restaurer le mappage avec la commande précédente.

Il échouera probablement avec un message ressemblant à ceci:

E117: Unknown function: <SNR>61_FuncA

61est juste l'identifiant du script dans lequel votre commande de mappage serait exécutée. Ce pourrait être n'importe quel autre numéro. Si votre plugin est le 42ème fichier provenant du système de l'utilisateur, il le sera 42.

Dans un script, lorsqu'une commande de mappage est exécutée, Vim traduit automatiquement la notation <SID>en code de clé spéciale <SNR>, suivi d'un nombre unique pour le script et d'un trait de soulignement. Il doit le faire, car lorsque l'utilisateur frappera <C-c>, le mappage sera exécuté en dehors du script, et donc il ne saura pas dans quel script FuncA()est défini.

Le problème est que le mappage d'origine provenait d'un script différent de celui de votre plugin, donc ici la traduction automatique est incorrecte. Il utilise l'identifiant de votre script, alors qu'il devrait utiliser l'identifiant de l'utilisateur vimrc.

Mais vous pouvez faire la traduction manuellement. Le dictionnaire map_savecontient une clé appelée 'sid'dont la valeur est l'identifiant correct.
Donc, pour rendre la commande de restauration précédente plus robuste, vous pouvez remplacer map_save.rhspar:

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

Si le {rhs}mappage d'origine contient <SID>, il doit être correctement traduit. Sinon, rien ne devrait être changé.

Et si vous voulez raccourcir un peu le code, vous pouvez remplacer les 4 lignes qui s'occupent des arguments spéciaux par:

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

La map()fonction doit convertir chaque élément de la liste ['buffer', 'expr', 'nowait', 'silent']en l'argument de mappage correspondant, mais uniquement si sa clé à l'intérieur map_saveest différente de zéro. Et join()devrait joindre tous les éléments dans une chaîne.

Ainsi, un moyen plus robuste d'enregistrer et de restaurer le mappage pourrait être:

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:

Je suis confronté au même problème que vous, comment enregistrer et restaurer un mappage dans un plugin de dessin. Et je pense que j'ai trouvé 2 problèmes que la réponse initiale ne voyait pas au moment où je l'ai écrit, désolé.

Premier problème, supposons que l'utilisateur utilise <C-c>dans un mappage global mais aussi dans un mappage tampon-local. Exemple:

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

Dans ce cas, maparg()donnera la priorité à la cartographie 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}

Ce qui est confirmé dans :h maparg():

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

Mais peut-être que vous n'êtes pas intéressé par le mappage tampon-local, peut-être que vous voulez le global.
Le seul moyen que j'ai trouvé pour obtenir de manière fiable les informations sur le mappage global, est d'essayer de démapper temporairement un mappage potentiel de tampon local avec la même clé.

Cela pourrait se faire en 4 étapes:

  1. enregistrer un mappage local (potentiel) du tampon à l'aide de la clé <C-c>
  2. exécuter :silent! nunmap <buffer> <C-c>pour supprimer un mappage local-tampon (potentiel)
  3. enregistrer la cartographie globale ( maparg('<C-c>', 'n', 0, 1))
  4. restaurer le mappage tampon-local

Le deuxième problème est le suivant. Supposons que l'utilisateur n'ait rien mappé <C-c>, la sortie de maparg()sera un dictionnaire vide. Et dans ce cas, le processus de restauration ne consiste pas à installer un mapping ( :nnoremap), mais à détruire un mapping ( :nunmap).

Pour essayer de résoudre ces 2 nouveaux problèmes, vous pouvez essayer cette fonction pour enregistrer les mappages:

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

... et celui-ci pour les restaurer:

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()fonction pourrait être utilisée pour enregistrer les mappages.
Il attend 3 arguments:

  1. une liste de clés; exemple:['<C-a>', '<C-b>', '<C-c>']
  2. un mode; exemple: npour le mode normal ou xpour le mode visuel
  3. un drapeau booléen; si c'est le cas 1, cela signifie que vous êtes intéressé par les mappages globaux, et si c'est le cas 0, par les mappages locaux

Avec elle, vous pouvez enregistrer les applications globales à l' aide des touches C-a, C-bet C-c, en mode normal, dans un dictionnaire:

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

Ensuite, plus tard, lorsque vous voudrez restaurer les mappages, vous pourrez appeler Restore_mappings(), en passant le dictionnaire contenant toutes les informations comme argument:

call Restore_mappings(your_saved_mappings)

Il pourrait y avoir un troisième problème lors de l'enregistrement / restauration des mappages locaux de tampon. Parce que, entre le moment où nous avons enregistré les mappages et le moment où nous essayons de les restaurer, le tampon actuel peut avoir changé.

Dans ce cas, la Save_mappings()fonction pourrait peut-être être améliorée en enregistrant le numéro du tampon actuel ( bufnr('%')).

Et puis, Restore_mappings()utiliserait ces informations pour restaurer les mappages de tampon local dans le bon tampon. Nous pourrions probablement utiliser la :bufdocommande, préfixer ce dernier avec un nombre (correspondant au numéro de tampon précédemment enregistré) et le suffixer avec la commande de mappage.

Peut-être quelque chose comme:

:{original buffer number}bufdo {mapping command}

Il faudrait d'abord vérifier si le tampon existe toujours, en utilisant la bufexists()fonction, car il aurait pu être supprimé entre-temps.

user9433424
la source
Incroyable, c'est exactement ce dont j'avais besoin. Merci!
statox
2

Dans mes plugins, lorsque j'ai des mappages temporaires, ils sont toujours en tampon local - je ne me soucie vraiment pas de sauvegarder les mappages globaux ni de quoi que ce soit de complexe qui les implique. D'où ma lh#on#exit().restore_buffer_mapping()fonction d'aide - de lh-vim-lib .

En fin de compte, ce qui se passe est le suivant:

" 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
Luc Hermitte
la source