Jak zapisać i przywrócić mapowanie?


13

Tworzę wtyczkę dla Vima i chciałbym zdefiniować mapowanie, które byłoby dostępne tylko podczas „wykonywania wtyczki”.

Do tej pory (uproszczony) przepływ pracy wtyczki jest następujący:

  1. Użytkownik wywołuje polecenie wtyczki
  2. Polecenie wywołuje funkcję wstępnego leczenia:

    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. Wywoływana jest inna funkcja, która zmienia stan bufora ( Foo()lub Bar()w ostatnich wierszach poprzedniej funkcji)

  4. Użytkownik używa mapowania do wywołania funkcji porzucenia
  5. Funkcja odrywania usuwa utworzone mapowanie:

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

Nie jestem zadowolony ze sposobu, w jaki obsługuję moje mapowanie: jeśli użytkownik już go zmapował na coś innego, utraci swoje oryginalne mapowanie.

Więc moje pytanie brzmi: w jaki sposób mogę zapisać to, co <C-c>jest mapowane (jeśli jest mapowane) i przywrócić je w funkcji usuwania? Czy jest do tego wbudowana funkcja? Zastanawiałem grepsię nad rezultatem, :nmap <C-c>ale to nie wydaje się naprawdę „czyste”.

Kilka uwag dodatkowych:

  • Wiem, że LearnVimScriptTheHardWay ma sekcję na ten temat , ale mówią, że należy użyć ftplugin, co nie jest tutaj możliwe: wtyczka nie jest zależna od typu pliku
  • Mógłbym stworzyć zmienną, aby użytkownik mógł wybrać, jakich kluczy użyć: prawdopodobnie zrobię to, ale interesuje mnie głównie sposób zapisywania i przywracania.
  • Przydałby mi się lokalny lider, ale myślę, że to trochę przesada i nadal jestem głównie ciekawy, jak zapisać i przywrócić.

Odpowiedzi:


25

Możesz użyć tej maparg()funkcji.

Aby sprawdzić, czy użytkownik zmapował coś <C-c>w trybie normalnym, napiszesz:

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

Jeśli użytkownik coś zmapował, aby zapisać {rhs}w zmiennej, napiszesz:

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

Jeśli chcesz uzyskać więcej informacji o mapowaniu, na przykład:

  • czy to milczy ( <silent>argument)?
  • czy jest lokalny dla bieżącego bufora ( <buffer>argumentu)?
  • jest {rhs}ocena wyrażenia ( <expr>argumentu)?
  • czy odwzorowuje {rhs}( nnoremapvs nmap)?
  • jeśli użytkownik ma inne mapowanie, które zaczyna się od <C-c>, czy Vim czeka na wpisanie większej liczby znaków ( <nowait>argument)?
  • ...

Następnie możesz podać trzeci i czwarty argument: 0i 1.
0ponieważ szukasz odwzorowania, a nie skrótu, i 1ponieważ potrzebujesz słownika zawierającego maksimum informacji, a nie tylko {rhs}wartość:

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

Zakładając, że użytkownik nie użył żadnego specjalnego argumentu w swoim mapowaniu i że nie odwzorowuje on {rhs}, aby go przywrócić, możesz po prostu napisać:

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

" do some stuff which changes the mapping

exe 'nnoremap <C-c> ' . rhs_save

Lub, aby mieć pewność i przywrócić wszystkie możliwe argumenty:

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

Edycja: Przepraszam, właśnie zdałem sobie sprawę, że nie zadziałałoby zgodnie z oczekiwaniami, jeśli użytkownik wywoła funkcję lokalną dla skryptu w {rhs}odwzorowaniu.

Załóżmy, że użytkownik ma następujące mapowanie vimrc:

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

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

Kiedy uderza <C-c>, wyświetla komunikat hello world!.

A we wtyczce zapisujesz słownik ze wszystkimi informacjami, a następnie tymczasowo zmieniasz jego mapowanie w następujący sposób:

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

Teraz wyświetli się bye all!. Twoja wtyczka działa trochę, a kiedy się skończy, próbuje przywrócić mapowanie za pomocą poprzedniego polecenia.

Prawdopodobnie zakończy się niepowodzeniem z komunikatem wyglądającym tak:

E117: Unknown function: <SNR>61_FuncA

61jest tylko identyfikatorem skryptu, w którym zostanie wykonane polecenie mapowania. Może to być dowolny inny numer. Jeśli wtyczka jest 42 plikiem pochodzącym z systemu użytkownika, to tak będzie 42.

Wewnątrz skryptu, gdy wykonywane jest polecenie mapowania, Vim automatycznie tłumaczy notację <SID>na specjalny kod klucza <SNR>, po którym następuje unikalna dla skryptu liczba i podkreślenie. Musi to zrobić, ponieważ gdy użytkownik uderzy <C-c>, mapowanie zostanie wykonane poza skryptem, a zatem nie będzie wiedział, w którym skrypcie FuncA()jest zdefiniowany.

Problem polega na tym, że oryginalne mapowanie pochodziło z innego skryptu niż wtyczka, więc tutaj automatyczne tłumaczenie jest nieprawidłowe. Używa identyfikatora twojego skryptu, podczas gdy powinien używać identyfikatora użytkownika vimrc.

Ale możesz wykonać tłumaczenie ręcznie. Słownik map_savezawiera klucz o nazwie, 'sid'którego wartość jest poprawnym identyfikatorem.
Aby poprzednie polecenie przywracania było bardziej niezawodne, możesz zastąpić map_save.rhsje:

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

Jeśli {rhs}oryginalne mapowanie zawierało <SID>, należy je odpowiednio przetłumaczyć. W przeciwnym razie nic nie powinno zostać zmienione.

A jeśli chcesz nieco skrócić kod, możesz zastąpić 4 wiersze, które zajmują się specjalnymi argumentami:

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

map()Funkcja powinna przekształcić każdą pozycję z listy ['buffer', 'expr', 'nowait', 'silent']do odpowiedniego odwzorowania argumentu, ale tylko wtedy, gdy klucz wewnątrz map_savejest niezerowe. I join()powinien połączyć wszystkie elementy w ciąg.

Bardziej niezawodnym sposobem zapisywania i przywracania mapowania może być:

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

Edycja2:

Mam do czynienia z tym samym problemem co Ty, jak zapisać i przywrócić mapowanie we wtyczce do rysowania. I myślę, że znalazłem 2 problemy, których początkowa odpowiedź nie widziała w chwili, gdy ją pisałem, przepraszam za to.

Pierwszy problem, załóżmy, że użytkownik używa <C-c>w mapowaniu globalnym, ale także w mapowaniu lokalnym bufora. Przykład:

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

W takim przypadku maparg()pierwszeństwo będzie miało mapowanie lokalne:

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

Co potwierdza :h maparg():

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

Ale może nie interesuje Cię mapowanie lokalne bufora, może chcesz globalne.
Jedynym sposobem, w jaki udało mi się rzetelnie uzyskać informacje o mapowaniu globalnym, jest próba tymczasowego odwzorowania potencjalnego, cieniowania, mapowania lokalnego bufora przy użyciu tego samego klucza.

Można to zrobić w 4 krokach:

  1. zapisz (potencjalne) lokalne mapowanie bufora za pomocą klucza <C-c>
  2. wykonaj, :silent! nunmap <buffer> <C-c>aby usunąć (potencjalne) lokalne mapowanie bufora
  3. zapisz globalne mapowanie ( maparg('<C-c>', 'n', 0, 1))
  4. przywrócić lokalne mapowanie bufora

Drugi problem jest następujący. Załóżmy, że użytkownik niczego nie zamapował <C-c>, a wynikiem maparg()będzie pusty słownik. W tym przypadku proces przywracania nie polega na instalacji mapowania ( :nnoremap), ale na zniszczeniu mapowania ( :nunmap).

Aby spróbować rozwiązać te 2 nowe problemy, możesz wypróbować tę funkcję, aby zapisać mapowania:

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

... i ten, aby je przywrócić:

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()Funkcja może być używany do zapisywania mapowania.
Oczekuje 3 argumentów:

  1. lista kluczy; przykład:['<C-a>', '<C-b>', '<C-c>']
  2. tryb; przykład: nw trybie normalnym lub xw trybie wizualnym
  3. flaga logiczna; jeśli tak 1, oznacza to, że interesują Cię mapowania globalne, a jeśli tak 0, to lokalne

Dzięki niemu można zaoszczędzić globalnych mapowania za pomocą klawiszy C-a, C-bi C-c, w trybie normalnym, wewnątrz słownika:

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

Następnie, gdy będziesz chciał przywrócić mapowania, możesz zadzwonić Restore_mappings(), przekazując słownik zawierający wszystkie informacje jako argument:

call Restore_mappings(your_saved_mappings)

Może występować trzeci problem podczas zapisywania / przywracania lokalnych map buforów. Ponieważ między chwilą zapisania mapowań a chwilą próby ich przywrócenia bieżący bufor mógł ulec zmianie.

W takim przypadku Save_mappings()funkcja może być poprawiona poprzez zapisanie numeru bieżącego bufora ( bufnr('%')).

Następnie Restore_mappings()użyłby tych informacji do przywrócenia mapowań lokalnych buforów we właściwym buforze. Prawdopodobnie moglibyśmy użyć tej :bufdokomendy, poprzedzić ją drugą liczbą (odpowiadającą wcześniej zapisanemu numerowi bufora) i dodać do niej komendę mapowania.

Może coś takiego:

:{original buffer number}bufdo {mapping command}

Musielibyśmy najpierw sprawdzić, czy bufor nadal istnieje, używając bufexists()funkcji, ponieważ można go w międzyczasie usunąć.


Niesamowite, właśnie tego potrzebowałem. Dzięki!
statox

2

W moich wtyczkach, gdy mam tymczasowe mapowania, są one zawsze buforowane lokalnie - naprawdę nie dbam o zapisywanie globalnych mapowań ani o nic złożonego, które ich dotyczą. Stąd moja lh#on#exit().restore_buffer_mapping()funkcja pomocnika - z lh-vim-lib .

W końcu dzieje się tak:

" 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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.