Як зберегти та відновити відображення?


13

Я розробляю плагін для 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, який тут неможливий: плагін не залежить від типу файлу
  • Я міг би створити змінну, щоб дозволити користувачеві вибирати, які ключі використовувати: Це, мабуть, те, що я буду робити, але мене в основному цікавить, як зробити збереження та відновлення.
  • Я міг би скористатися місцевим лідером, але я думаю, що це трохи непосильно, і я все ще цікавлюсь питаннями збереження та відновлення.

Відповіді:


25

Ви можете використовувати maparg()функцію.

Щоб перевірити, чи користувач щось відмітив <C-c>у звичайному режимі, напишете:

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

Якщо користувач щось відобразив для зберігання {rhs}змінної, ви напишете:

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

Якщо ви хочете отримати більше інформації про картографування, наприклад:

  • це мовчить ( <silent>аргумент)?
  • вона локальна для поточного буфера ( <buffer>аргументу)?
  • чи є {rhs}оцінка виразу ( <expr>аргументу)?
  • чи перезаписує {rhs}( nnoremapпроти nmap)?
  • якщо у користувача є інше відображення, яке починається з того <C-c>, чи чекає Vim, щоб ввести більше символів ( <nowait>аргумент)?
  • ...

Тоді ви можете дати третій та четвертий аргументи: 0і 1.
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')

Edit2:

Я зіткнувся з тією ж проблемою, що і ви, як зберегти та відновити відображення у плагіні для малювання. І я думаю, що я знайшов 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).

Щоб спробувати вирішити ці 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-a, C-bа C-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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.