マッピングを保存および復元する方法は?


12

Vim用のプラグインを開発していますが、「プラグインの実行」中にのみ使用できるマッピングを定義したいと思います。

これまでのところ、プラグインの(簡略化された)ワークフローは次のとおりです。

  1. ユーザーがプラグインのコマンドを呼び出す
  2. このコマンドは、前処理機能を呼び出します。

    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. バッファの状態を変更する別の関数が呼び出されます(Foo()またはBar()前の関数の最後の行で)

  4. ユーザーはマッピングを使用してティアダウン関数を呼び出します
  5. 分解機能は、作成されたマッピングを削除します。

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

マッピングの処理方法に満足していません。ユーザーが既に別のマッピングを行っている場合、元のマッピングが失われます。

だから私の質問は次のとおりです:マップされたものを保存して<C-c>(マップされている場合)、ティアダウン機能で復元するにはどうすればよいですか? そのための組み込み機能はありますか?私grepはその結果を歌うことについてです:nmap <C-c>が、それは本当に「きれい」ではありません。

いくつかの補足事項:

  • LearnVimScriptTheHardWayにはそれに関するセクションあることは知っていますが彼らはここでは不可能なftpluginを使用すると言います:プラグインはファイルタイプに依存していません
  • ユーザーが使用するキーを選択できるように変数を作成できます。おそらくそれは私がすることですが、保存と復元を行う方法に主に興味があります。
  • 私は地元のリーダーを使うこともできますが、それは少しやり過ぎだと思い、今でも主に保存と復元のことに興味があります。

回答:


24

このmaparg()機能を使用できます。

ユーザー<C-c>が通常モードで何かをマップしたかどうかをテストするには、次のように記述します。

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

ユーザーが何かをマップした場合、{rhs}変数に保存するには、次のように記述します。

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

マッピングに関する詳細情報が必要な場合:

  • 沈黙している(<silent>議論)?
  • 現在のバッファ(<buffer>引数)に対してローカルですか?
  • ある{rhs}表現(の評価<expr>引数)?
  • {rhs}nnoremapvs nmap)を再マッピングしますか?
  • ユーザーがで始まる別のマッピングを持っている場合、<C-c>Vimはさらに文字が入力されるのを待ち<nowait>ますか(引数)?
  • ...

その後、あなたは、第3及び第4の引数を与えることができる:01
0略語ではなくマッピングを探している1ため、また、{rhs}値だけでなく最大の情報を持つ辞書が必要なためです。

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

ユーザーがマッピングで特別な引数を使用せず{rhs}、復元するためにを再マッピングしないと仮定すると、単純に次のように書くことができます。

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

" do some stuff which changes the mapping

exe 'nnoremap <C-c> ' . rhs_save

または、可能性のあるすべての引数を確認して復元するには:

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

編集:申し訳ありませんが、ユーザーが{rhs}マッピングのスクリプトローカル関数を呼び出すと、期待どおりに動作しないことに気付きました。

ユーザーの内部に次のマッピングがあるとしますvimrc

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

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

彼がヒット<C-c>すると、メッセージが表示されますhello world!

プラグインでは、すべての情報を含む辞書を保存し、一時的に次のようにマッピングを変更します。

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

これで、が表示されますbye all!。プラグインは何らかの作業を行い、終了すると、前のコマンドでマッピングを復元しようとします。

おそらく次のようなメッセージで失敗します:

E117: Unknown function: <SNR>61_FuncA

61マッピングコマンドが実行されるスクリプトの単なる識別子です。他の番号でも構いません。プラグインがユーザーのシステムをソースとする42番目のファイルである場合、それはになります42

スクリプト内で、マッピングコマンドが実行されると、Vimは表記<SID>を特別なキーコード<SNR>に自動的に変換し、その後にスクリプトに固有の番号とアンダースコアが続きます。ユーザーがを押す<C-c>と、マッピングがスクリプトの外部で実行されるため、どのスクリプトFuncA()で定義されているかわからないため、これを行う必要があります。

問題は、元のマッピングがプラグインとは異なるスクリプトをソースとしているため、自動翻訳が間違っていることです。スクリプトの識別子を使用しますが、ユーザーの識別子を使用する必要がありますvimrc

しかし、手動で翻訳を行うことができます。辞書にmap_saveは、'sid'値が正しい識別子であるというキーが含まれています。
したがって、以前の復元コマンドをより堅牢にするためにmap_save.rhs、次のものに置き換えることができます。

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

{rhs}元のマッピングにが含まれている場合、<SID>適切に翻訳する必要があります。それ以外の場合は、何も変更しないでください。

また、コードを少し短くしたい場合は、特別な引数を処理する4行を次のように置き換えることができます。

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

map()関数リストから各項目を変換する必要があり['buffer', 'expr', 'nowait', 'silent']、対応するマッピング引数にそのキー内部にいる場合にのみmap_save、非ゼロです。そしてjoin()、すべてのアイテムを文字列に結合する必要があります。

したがって、マッピングを保存および復元するより堅牢な方法は次のとおりです。

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')

編集2:

私はあなたと同じ問題に直面しています。描画プラグインでマッピングを保存および復元する方法です。そして、私はそれを書いた時点で最初の答えが見えなかった2つの問題を見つけたと思う、それについて申し訳ありません。

最初の問題は、ユーザーが<C-c>グローバルマッピングだけでなく、バ​​ッファローカルマッピングでも使用すると仮定します。例:

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

この場合、maparg()ローカルマッピングを優先します。

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

で確認され:h maparg()ます:

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

ただし、バッファローカルマッピングに興味がない場合や、グローバルマッピングが必要な場合があります。
グローバルマッピングに関する情報を確実に取得するために私が見つけた唯一の方法は、同じキーを使用して、潜在的なシャドウバッファローカルマッピングを一時的にマップ解除しようとすることです。

次の4つの手順で実行できます。

  1. キーを使用して(潜在的な)バッファローカルマッピングを保存する <C-c>
  2. :silent! nunmap <buffer> <C-c>(潜在的な)バッファローカルマッピングを削除するために実行します
  3. グローバルマッピングを保存(maparg('<C-c>', 'n', 0, 1)
  4. バッファローカルマッピングを復元する

2番目の問題は次のとおりです。ユーザーがに何もマップしなかったとすると、<C-c>の出力はmaparg()空の辞書になります。この場合、復元プロセスはマッピングのインストール(:nnoremap)ではなく、マッピングの破壊()で構成されます:nunmap

これら2つの新しい問題を解決するには、この関数を使用してマッピングを保存します。

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

...そしてこれを復元するために:

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

このSave_mappings()関数を使用して、マッピングを保存できます。
3つの引数が必要です。

  1. キーのリスト。例:['<C-a>', '<C-b>', '<C-c>']
  2. モード; 例:n通常モードまたはx視覚モード
  3. ブール値フラグ。の場合1、グローバルマッピングに関心があることを意味し0、ローカルのマッピングに関心がある場合、

これを使うと、キーを使用してグローバルマッピングを救うことができるC-aC-bC-c辞書内部で、通常モードでは、:

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

その後、後でマッピングを復元したいときにRestore_mappings()、を呼び出して、すべての情報を含む辞書を引数として渡すことができます。

call Restore_mappings(your_saved_mappings)

バッファローカルマッピングを保存/復元するときに、3番目の問題が発生する可能性があります。なぜなら、マッピングを保存した瞬間と、それらを復元しようとする瞬間の間に、現在のバッファが変更された可能性があるからです。

この場合、Save_mappings()現在のバッファの番号を保存することで機能を改善できる可能性があります(bufnr('%'))。

そして、Restore_mappings()この情報を使用して、適切なバッファのバッファローカルマッピングを復元します。おそらく:bufdoコマンドを使用し、前に保存されたバッファー番号に一致するカウントを前に付け、マッピングコマンドでサフィックスを付けることができます。

たぶん次のようなもの:

:{original buffer number}bufdo {mapping command}

bufexists()その間に削除された可能性があるため、関数を使用して、バッファがまだ存在するかどうかを最初に確認する必要があります。


それはまさに私が必要としたものです。ありがとう!
statox

2

私のプラグインでは、一時的なマッピングがある場合、それらは常にローカルバッファです。グローバルマッピングの保存や、それらを含む複雑なものについては本当に気にしません。したがって、私のlh#on#exit().restore_buffer_mapping()ヘルパー関数-lh-vim-libから

最終的に、次のことが起こります。

" 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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.