매핑을 저장하고 복원하는 방법?


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>니까 ( 인수)?
  • ...

그런 다음 세 번째와 네 번째 인수를 줄 수 : 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 :

드로잉 플러그인에서 매핑을 저장하고 복원하는 방법과 동일한 문제에 직면하고 있습니다. 그리고 나는 그것을 작성할 당시 초기 답변에서 보지 못한 두 가지 문제를 발견했다고 생각합니다. 죄송합니다.

첫 번째 문제는 사용자가 <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. 버퍼 로컬 매핑을 복원

두 번째 문제는 다음과 같습니다. 사용자가에 아무것도 매핑하지 않았다고 가정 하면 빈 사전이 <C-c>출력 maparg()됩니다. 이 경우 복원 프로세스는 매핑 설치 ( :nnoremap)가 아니라 매핑 파괴 ( :nunmap)로 구성됩니다.

이 두 가지 새로운 문제를 해결하기 위해이 기능을 사용하여 매핑을 저장할 수 있습니다.

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()함수는 매핑을 저장하는 데 사용될 수 있습니다.
세 가지 주장이 필요합니다.

  1. 키 목록; 예:['<C-a>', '<C-b>', '<C-c>']
  2. 모드; 예 : n일반 모드 또는 x시각 모드
  3. 부울 플래그; 이 경우 1전역 매핑에 관심이 0있고 로컬 매핑에 관심이 있음을 의미 합니다.

그것으로, 당신은 키를 사용하여 글로벌 매핑을 절약 할 수 C-a, C-bC-c사전 내에서, 정상 모드에서 :

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

그런 다음 나중에 매핑을 복원하려는 경우 Restore_mappings()모든 정보를 포함하는 사전을 인수로 전달하여을 호출 할 수 있습니다 .

call Restore_mappings(your_saved_mappings)

버퍼 로컬 맵핑을 저장 / 복원 할 때 세 번째 문제점이있을 수 있습니다. 맵핑을 저장 한 시점과이를 복원하려고하는 시점 사이에 현재 버퍼가 변경되었을 수 있습니다.

이 경우 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.