Как сохранить и восстановить отображение?

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

Ответы:

24

Вы могли бы использовать 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команду, поставить перед последней префикс счетчиком (совпадающим с ранее сохраненным номером буфера) и добавить к суффиксу команду mapping.

Может быть что-то вроде:

:{original buffer number}bufdo {mapping command}

Мы должны были бы сначала проверить, если буфер все еще существует, используя bufexists()функцию, потому что он мог бы быть удален за это время.

user9433424
источник
Удивительно, это именно то, что мне было нужно. Благодарность!
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
Люк Эрмитт
источник