Bagaimana cara menyimpan dan mengembalikan pemetaan?


12

Saya sedang mengembangkan plugin untuk Vim dan saya ingin mendefinisikan pemetaan yang hanya akan tersedia saat "eksekusi plugin".

Sejauh ini alur kerja plugin yang disederhanakan adalah sebagai berikut:

  1. Pengguna memanggil perintah plugin
  2. Perintah memanggil fungsi pra-perawatan:

    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. Fungsi lain disebut yang mengubah status buffer ( Foo()atau Bar()di baris terakhir dari fungsi sebelumnya)

  4. Pengguna menggunakan pemetaan untuk memanggil fungsi teardown
  5. Fungsi teardown menghapus pemetaan yang dibuat:

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

Saya tidak puas dengan cara saya menangani pemetaan saya: jika pengguna sudah memetakannya ke hal lain, ia akan kehilangan pemetaan aslinya.

Jadi pertanyaan saya adalah: Bagaimana saya bisa menyimpan apa <C-c>yang dipetakan (jika dipetakan) dan mengembalikannya dalam fungsi merobohkan saya? Apakah ada fitur bawaan untuk melakukannya? Saya berpikir tentang grephasil :nmap <C-c>tetapi itu tidak terasa benar-benar "bersih".

Beberapa catatan:

  • Saya tahu bahwa LearnVimScriptTheHardWay memiliki bagian tentang itu , tetapi mereka mengatakan untuk menggunakan ftplugin yang tidak mungkin dilakukan di sini: plugin tidak bergantung pada tipe file
  • Saya bisa membuat variabel untuk membiarkan pengguna memilih kunci apa yang akan digunakan: Ini mungkin apa yang akan saya lakukan, tetapi saya terutama tertarik pada bagaimana melakukan save dan restore.
  • Saya bisa menggunakan pemimpin lokal tapi saya pikir itu sedikit berlebihan dan saya masih penasaran tentang menyimpan dan memulihkan.

Jawaban:


24

Anda bisa menggunakan maparg()fungsinya.

Untuk menguji apakah pengguna memetakan sesuatu ke <C-c>dalam mode normal, Anda akan menulis:

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

Jika pengguna memetakan sesuatu, untuk menyimpan {rhs}dalam variabel, Anda akan menulis:

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

Jika Anda ingin informasi lebih lanjut tentang pemetaan, seperti:

  • apakah itu diam ( <silent>argumen)?
  • apakah ini bersifat lokal untuk buffer ( <buffer>argumen) saat ini?
  • adalah {rhs}evaluasi ekspresi ( <expr>argumen)?
  • apakah itu memetakan kembali {rhs}( nnoremapvs nmap)?
  • jika pengguna memiliki pemetaan lain yang dimulai dengan <C-c>, apakah Vim menunggu lebih banyak karakter untuk diketik ( <nowait>argumen)?
  • ...

Lalu, Anda bisa memberikan argumen ketiga dan keempat: 0dan 1.
0karena Anda mencari pemetaan dan bukan singkatan, dan 1karena Anda menginginkan kamus dengan informasi maksimal dan bukan hanya {rhs}nilainya:

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

Dengan asumsi pengguna tidak menggunakan argumen khusus dalam pemetaannya, dan itu tidak memetakan kembali {rhs}, untuk mengembalikannya, Anda cukup menulis:

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

" do some stuff which changes the mapping

exe 'nnoremap <C-c> ' . rhs_save

Atau untuk memastikan dan mengembalikan semua argumen yang mungkin:

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

Sunting: Maaf, saya baru menyadari itu tidak akan berfungsi seperti yang diharapkan jika pengguna memanggil fungsi skrip-lokal di {rhs}pemetaan.

Misalkan pengguna memiliki pemetaan berikut di dalam miliknya vimrc:

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

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

Ketika dia memukul <C-c>, itu menampilkan pesan hello world!.

Dan di plugin Anda, Anda menyimpan kamus dengan semua informasi, lalu untuk sementara mengubah pemetaannya seperti ini:

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

Sekarang, itu akan ditampilkan bye all!. Plugin Anda berfungsi, dan ketika selesai, ia mencoba mengembalikan pemetaan dengan perintah sebelumnya.

Mungkin akan gagal dengan pesan yang tampak seperti ini:

E117: Unknown function: <SNR>61_FuncA

61hanyalah pengidentifikasi dari skrip di mana perintah pemetaan Anda akan dieksekusi. Bisa jadi nomor lainnya. Jika plugin Anda adalah file ke-42 yang bersumber pada sistem pengguna, itu akan menjadi 42.

Di dalam skrip, ketika perintah pemetaan dijalankan, Vim secara otomatis menerjemahkan notasi <SID>ke kode kunci khusus <SNR>, diikuti oleh angka yang unik untuk skrip, dan garis bawah. Itu harus melakukan ini, karena ketika pengguna akan menekan <C-c>, pemetaan akan dieksekusi di luar skrip, dan dengan demikian ia tidak akan tahu di mana skrip FuncA()didefinisikan.

Masalahnya adalah pemetaan asli bersumber dalam skrip yang berbeda dari plugin Anda, jadi di sini terjemahan otomatisnya salah. Ini menggunakan pengidentifikasi skrip Anda, sementara itu harus menggunakan pengidentifikasi pengguna vimrc.

Tetapi Anda bisa melakukan terjemahan secara manual. Kamus map_saveberisi kunci yang disebut 'sid'nilainya sebagai pengidentifikasi yang benar.
Jadi, untuk membuat perintah restorasi sebelumnya lebih kuat, Anda bisa menggantinya map_save.rhsdengan:

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

Jika {rhs}pemetaan asli berisi <SID>, itu harus diterjemahkan dengan benar. Kalau tidak, tidak ada yang harus diubah.

Dan jika Anda ingin mempersingkat kodenya sedikit, Anda bisa mengganti 4 baris yang menangani argumen khusus dengan:

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

The map()Fungsi harus mengkonversi setiap item dari daftar ['buffer', 'expr', 'nowait', 'silent']ke argumen pemetaan yang sesuai tetapi hanya jika dalam kuncinya map_saveadalah non-nol. Dan join()harus menggabungkan semua item menjadi string.

Jadi, cara yang lebih kuat untuk menyimpan dan memulihkan pemetaan bisa menjadi:

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

Sunting2:

Saya menghadapi masalah yang sama seperti Anda, bagaimana cara menyimpan dan mengembalikan pemetaan dalam plugin menggambar. Dan saya pikir saya menemukan 2 masalah yang tidak dilihat oleh jawaban awal pada saat saya menulisnya, maaf soal itu.

Masalah pertama, anggaplah bahwa pengguna menggunakan <C-c>pemetaan global tetapi juga dalam pemetaan buffer-lokal. Contoh:

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

Dalam hal ini, maparg()akan memberikan prioritas pada pemetaan lokal:

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

Yang dikonfirmasi di :h maparg():

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

Tapi mungkin Anda tidak tertarik pada pemetaan buffer-lokal, mungkin Anda ingin yang global.
Satu-satunya cara yang saya temukan untuk, secara andal, mendapatkan informasi tentang pemetaan global, adalah mencoba untuk sementara waktu memetakan pemetaan potensi, bayangan, buffer-lokal menggunakan kunci yang sama.

Itu bisa dilakukan dalam 4 langkah:

  1. menyimpan (potensi) pemetaan buffer-lokal menggunakan kunci <C-c>
  2. mengeksekusi :silent! nunmap <buffer> <C-c>untuk menghapus (potensi) pemetaan buffer-lokal
  3. simpan pemetaan global ( maparg('<C-c>', 'n', 0, 1))
  4. pulihkan pemetaan buffer-local

Masalah kedua adalah sebagai berikut. Misalkan pengguna tidak memetakan apa pun untuk <C-c>, maka output dari maparg()akan menjadi kamus kosong. Dan dalam hal ini, proses restorasi tidak terdiri dari pemasangan pemetaan ( :nnoremap), tetapi dalam penghancuran pemetaan ( :nunmap).

Untuk mencoba menyelesaikan 2 masalah baru ini, Anda dapat mencoba fungsi ini untuk menyimpan pemetaan:

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

... dan yang ini untuk mengembalikannya:

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

The Save_mappings()Fungsi dapat digunakan untuk menyimpan pemetaan.
Ia mengharapkan 3 argumen:

  1. daftar kunci; contoh:['<C-a>', '<C-b>', '<C-c>']
  2. sebuah mode; contoh: nuntuk mode normal atau xuntuk mode visual
  3. sebuah bendera boolean; jika itu 1, itu berarti Anda tertarik pada pemetaan global, dan jika itu 0, dalam pemetaan lokal

Dengannya, Anda bisa menyimpan pemetaan global menggunakan tombol C-a, C-bdan C-c, dalam mode normal, di dalam kamus:

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

Kemudian, nanti, ketika Anda ingin mengembalikan pemetaan, Anda bisa menelepon Restore_mappings(), melewati kamus berisi semua info sebagai argumen:

call Restore_mappings(your_saved_mappings)

Mungkin ada masalah ke-3, saat menyimpan / mengembalikan pemetaan penyangga-lokal. Karena, antara saat ketika kami menyimpan pemetaan, dan saat ketika kami mencoba mengembalikannya, buffer saat ini mungkin telah berubah.

Dalam hal ini, mungkin Save_mappings()fungsinya dapat ditingkatkan dengan menyimpan jumlah buffer saat ini ( bufnr('%')).

Dan kemudian, Restore_mappings()akan menggunakan info ini untuk mengembalikan pemetaan buffer-lokal di buffer yang tepat. Kita mungkin bisa menggunakan :bufdoperintah, awali yang terakhir dengan hitungan (cocok dengan nomor buffer yang disimpan sebelumnya), dan sufikskan dengan perintah pemetaan.

Mungkin sesuatu seperti:

:{original buffer number}bufdo {mapping command}

Kami harus memeriksa dulu apakah buffer masih ada, menggunakan bufexists()fungsi, karena itu bisa saja dihapus sementara itu.


Luar biasa itulah yang saya butuhkan. Terima kasih!
statox

2

Di plugin saya, ketika saya memiliki pemetaan sementara, mereka selalu menjadi penyangga lokal - saya benar-benar tidak peduli tentang menyimpan pemetaan global atau tentang hal-hal rumit yang melibatkan mereka. Karenanya lh#on#exit().restore_buffer_mapping()fungsi pembantu saya - dari lh-vim-lib .

Pada akhirnya, yang terjadi adalah sebagai berikut:

" 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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.