Làm thế nào để lưu và khôi phục bản đồ?


12

Tôi đang phát triển một plugin cho Vim và tôi muốn xác định một ánh xạ chỉ có sẵn trong khi "thực thi plugin".

Cho đến nay quy trình làm việc (đơn giản hóa) của plugin là như sau:

  1. Người dùng gọi một lệnh của plugin
  2. Lệnh gọi chức năng tiền xử lý:

    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. Một hàm khác được gọi là thay đổi trạng thái của bộ đệm ( Foo()hoặc Bar()trong các dòng cuối cùng của chức năng trước đó)

  4. Người dùng sử dụng ánh xạ để gọi hàm xé
  5. Hàm xé bỏ loại bỏ ánh xạ đã tạo:

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

Tôi không hài lòng với cách tôi xử lý ánh xạ của mình: nếu người dùng đã ánh xạ nó sang một thứ khác, anh ta sẽ mất bản đồ gốc của mình.

Vì vậy, câu hỏi của tôi là: Làm thế nào tôi có thể lưu những gì <C-c>được ánh xạ (nếu nó được ánh xạ) và khôi phục nó trong chức năng phá bỏ của tôi? Có một tính năng tích hợp để làm như vậy? Tôi mặc dù về grepkết quả của :nmap <C-c>nhưng điều đó không cảm thấy thực sự "sạch".

Một vài lưu ý phụ:

  • Tôi biết rằng LearnVimScriptTheHardWay có một phần về điều đó , nhưng họ nói sẽ sử dụng một ftplugin không thể có ở đây: plugin không phụ thuộc vào loại tệp
  • Tôi có thể tạo một biến để cho phép người dùng chọn khóa nào sẽ sử dụng: Đó có thể là những gì tôi sẽ làm nhưng tôi chủ yếu quan tâm đến cách thực hiện lưu và khôi phục.
  • Tôi có thể sử dụng một nhà lãnh đạo địa phương nhưng tôi nghĩ đó là một chút quá mức cần thiết và tôi vẫn chủ yếu tò mò về điều lưu và khôi phục.

Câu trả lời:


24

Bạn có thể sử dụng maparg()chức năng.

Để kiểm tra xem người dùng có ánh xạ thứ gì đó sang <C-c>chế độ bình thường không, bạn sẽ viết:

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

Nếu người dùng ánh xạ một cái gì đó, để lưu trữ {rhs}trong một biến, bạn sẽ viết:

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

Nếu bạn muốn biết thêm thông tin về ánh xạ, như:

  • Là im lặng ( <silent>tranh luận)?
  • nó là cục bộ của bộ đệm hiện tại ( <buffer>đối số)?
  • là sự {rhs}đánh giá của một biểu thức ( <expr>đối số)?
  • nó ánh xạ lại {rhs}( nnoremapvs nmap)?
  • Nếu người dùng có một ánh xạ khác bắt đầu bằng <C-c>, Vim có chờ thêm ký tự để nhập ( <nowait>đối số) không?
  • ...

Sau đó, bạn có thể đưa ra một đối số thứ ba và thứ tư: 01.
0bởi vì bạn đang tìm kiếm một ánh xạ chứ không phải viết tắt và 1vì bạn muốn một từ điển có tối đa thông tin và không chỉ {rhs}giá trị:

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

Giả sử người dùng không sử dụng bất kỳ đối số đặc biệt nào trong ánh xạ của mình và rằng nó không ánh xạ lại {rhs}, để khôi phục nó, bạn chỉ cần viết:

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

" do some stuff which changes the mapping

exe 'nnoremap <C-c> ' . rhs_save

Hoặc để chắc chắn và khôi phục tất cả các đối số có thể:

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

Chỉnh sửa: Xin lỗi, tôi vừa nhận ra rằng nó sẽ không hoạt động như mong đợi nếu người dùng gọi một hàm script-local trong {rhs}ánh xạ.

Giả sử rằng người dùng có ánh xạ sau bên trong vimrc:

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

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

Khi anh ta nhấn <C-c>, nó sẽ hiển thị thông báo hello world!.

Và trong plugin của bạn, bạn lưu một từ điển với tất cả thông tin, sau đó tạm thời thay đổi ánh xạ của mình như thế này:

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

Bây giờ, nó sẽ hiển thị bye all!. Plugin của bạn thực hiện một số công việc và khi nó kết thúc, nó sẽ cố gắng khôi phục ánh xạ bằng lệnh trước đó.

Nó có thể sẽ thất bại với một tin nhắn trông như thế này:

E117: Unknown function: <SNR>61_FuncA

61chỉ là định danh của tập lệnh trong đó lệnh ánh xạ của bạn sẽ được thực thi. Nó có thể là bất kỳ số nào khác. Nếu plugin của bạn là tệp thứ 42 có nguồn gốc trên hệ thống của người dùng, thì nó sẽ như vậy 42.

Bên trong một tập lệnh, khi một lệnh ánh xạ được thực thi, Vim sẽ tự động dịch ký hiệu <SID>thành mã khóa đặc biệt <SNR>, theo sau là một số duy nhất cho tập lệnh và dấu gạch dưới. Nó phải làm điều này, bởi vì khi người dùng nhấn <C-c>, ánh xạ sẽ được thực thi bên ngoài tập lệnh và do đó nó sẽ không biết tập lệnh nào FuncA()được xác định.

Vấn đề là ánh xạ ban đầu có nguồn gốc từ một tập lệnh khác với plugin của bạn, vì vậy ở đây bản dịch tự động bị sai. Nó sử dụng định danh tập lệnh của bạn, trong khi nó nên sử dụng định danh của người dùng vimrc.

Nhưng bạn có thể làm bản dịch thủ công. Từ điển map_savechứa một khóa được gọi là 'sid'có giá trị là định danh chính xác.
Vì vậy, để làm cho lệnh khôi phục trước đó mạnh mẽ hơn, bạn có thể thay thế map_save.rhsbằng:

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

Nếu {rhs}ánh xạ gốc chứa <SID>, nó cần được dịch đúng. Nếu không, không có gì phải thay đổi.

Và nếu bạn muốn rút ngắn mã một chút, bạn có thể thay thế 4 dòng chăm sóc các đối số đặc biệt bằng:

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

Các map()chức năng nên chuyển đổi mỗi mục từ danh sách ['buffer', 'expr', 'nowait', 'silent']vào lập luận bản đồ tương ứng nhưng chỉ khi bên chủ chốt của nó map_savekhông phải là zero. Và join()nên tham gia tất cả các mục vào một chuỗi.

Vì vậy, một cách mạnh mẽ hơn để lưu và khôi phục ánh xạ có thể là:

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

Chỉnh sửa2:

Tôi đang đối mặt với cùng một vấn đề như bạn, cách lưu và khôi phục ánh xạ trong plugin vẽ. Và tôi nghĩ rằng tôi đã tìm thấy 2 vấn đề mà câu trả lời ban đầu không thấy vào lúc tôi viết nó, xin lỗi về điều đó.

Vấn đề đầu tiên, giả sử rằng người dùng sử dụng <C-c>trong ánh xạ toàn cục mà còn trong ánh xạ cục bộ đệm. Thí dụ:

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

Trong trường hợp này, maparg()sẽ ưu tiên ánh xạ cục bộ:

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

Điều này được xác nhận trong :h maparg():

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

Nhưng có thể bạn không quan tâm đến ánh xạ cục bộ đệm, có thể bạn muốn bản đồ toàn cầu.
Cách duy nhất mà tôi tìm thấy, đáng tin cậy, có được thông tin về ánh xạ toàn cầu, là cố gắng tạm thời hủy ánh xạ tiềm năng, tạo bóng, ánh xạ bộ đệm cục bộ bằng cách sử dụng cùng một khóa.

Nó có thể được thực hiện trong 4 bước:

  1. lưu một ánh xạ cục bộ (tiềm năng) bằng cách sử dụng khóa <C-c>
  2. thực hiện :silent! nunmap <buffer> <C-c>để xóa ánh xạ cục bộ đệm (tiềm năng)
  3. lưu bản đồ toàn cầu ( maparg('<C-c>', 'n', 0, 1))
  4. khôi phục ánh xạ cục bộ

Vấn đề thứ hai là như sau. Giả sử rằng người dùng không ánh xạ bất cứ thứ gì vào <C-c>thì đầu ra của maparg()sẽ là một từ điển trống. Và trong trường hợp này, quá trình khôi phục không bao gồm việc cài đặt ánh xạ ( :nnoremap), mà là phá hủy ánh xạ ( :nunmap).

Để cố gắng giải quyết 2 vấn đề mới này, bạn có thể thử chức năng này để lưu ánh xạ:

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

... Và cái này để khôi phục chúng:

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

Các Save_mappings()chức năng có thể được sử dụng để lưu ánh xạ.
Nó mong đợi 3 đối số:

  1. một danh sách các khóa; thí dụ:['<C-a>', '<C-b>', '<C-c>']
  2. Một chế độ; ví dụ: ncho chế độ bình thường hoặc xcho chế độ trực quan
  3. một lá cờ boolean; nếu có 1, điều đó có nghĩa là bạn quan tâm đến ánh xạ toàn cầu và nếu nó 0, ở địa phương

Với nó, bạn có thể tiết kiệm ánh xạ toàn cầu sử dụng các phím C-a, C-bC-c, trong chế độ bình thường, bên trong một cuốn từ điển:

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

Sau đó, sau này, khi bạn muốn khôi phục ánh xạ, bạn có thể gọi Restore_mappings(), chuyển từ điển chứa tất cả thông tin dưới dạng đối số:

call Restore_mappings(your_saved_mappings)

Có thể có vấn đề thứ 3, khi lưu / khôi phục ánh xạ bộ đệm cục bộ. Bởi vì, giữa thời điểm chúng ta lưu bản đồ và thời điểm chúng ta cố gắng khôi phục chúng, bộ đệm hiện tại có thể đã thay đổi.

Trong trường hợp này, có thể Save_mappings()chức năng có thể được cải thiện bằng cách lưu số lượng bộ đệm hiện tại ( bufnr('%')).

Và sau đó, Restore_mappings()sẽ sử dụng thông tin này để khôi phục ánh xạ bộ đệm cục bộ trong bộ đệm bên phải. Có lẽ chúng ta có thể sử dụng :bufdolệnh, tiền tố sau với số đếm (khớp với số bộ đệm đã lưu trước đó) và hậu tố nó với lệnh ánh xạ.

Có lẽ một cái gì đó như:

:{original buffer number}bufdo {mapping command}

Chúng ta sẽ phải kiểm tra trước nếu bộ đệm vẫn còn tồn tại, sử dụng bufexists()hàm, bởi vì nó có thể đã bị xóa trong thời gian đó.


Thật tuyệt vời chính xác là những gì tôi cần. Cảm ơn!
statox

2

Trong các plugin của tôi, khi tôi có ánh xạ tạm thời, chúng luôn được đệm cục bộ - tôi thực sự không quan tâm đến việc lưu bản đồ toàn cầu cũng như về bất kỳ thứ gì phức tạp liên quan đến chúng. Do đó lh#on#exit().restore_buffer_mapping()chức năng trợ giúp của tôi - từ lh-vim-lib .

Cuối cùng, những gì xảy ra là như sau:

" 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.