จะบันทึกและกู้คืนการแมปได้อย่างไร?


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. ผู้ใช้ใช้การแมปเพื่อเรียกใช้ฟังก์ชัน teardown
  5. ฟังก์ชั่น tear down ลบการแมปที่สร้างขึ้น:

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

ฉันไม่พอใจกับวิธีที่ฉันจัดการกับการแมปของฉัน: หากผู้ใช้แมปไปยังสิ่งอื่นแล้วเขาจะหลวมการแมปดั้งเดิมของเขา

ดังนั้นคำถามของฉันคือฉันจะบันทึกสิ่งที่<C-c>แมปไว้ (หากมีการจับคู่) และกู้คืนในฟังก์ชั่นการฉีกขาดได้อย่างไร มีคุณสมบัติในตัวที่จะทำเช่นนั้น? ฉันคิดเกี่ยวกับgrepผลลัพธ์:nmap <C-c>แต่ก็ไม่ได้รู้สึก "สะอาด" จริงๆ

หมายเหตุด้านข้างไม่กี่:

  • ฉันรู้ว่า LearnVimScriptTheHardWay มีส่วนที่เกี่ยวกับเรื่องนี้ แต่พวกเขาบอกว่าจะใช้ ftplugin ซึ่งเป็นไปไม่ได้ที่นี่: ปลั๊กอินไม่ได้ขึ้นอยู่กับประเภทของไฟล์
  • ฉันสามารถสร้างตัวแปรเพื่อให้ผู้ใช้เลือกปุ่มที่จะใช้: อาจเป็นสิ่งที่ฉันจะทำ แต่ฉันสนใจที่จะทำบันทึกและกู้คืนเป็นหลัก
  • ฉันสามารถใช้ผู้นำท้องถิ่น แต่ฉันคิดว่ามันเกินความจริงเล็กน้อยและฉันก็ยังอยากรู้เกี่ยวกับสิ่งที่บันทึกและกู้คืนส่วนใหญ่

คำตอบ:


24

คุณสามารถใช้maparg()ฟังก์ชั่น

ในการทดสอบว่าผู้ใช้ทำการแมปบางอย่างเป็น<C-c>ในโหมดปกติคุณจะต้องเขียน:

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

หากผู้ใช้แมปบางสิ่งเพื่อเก็บไว้{rhs}ในตัวแปรคุณจะเขียน:

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

หากคุณต้องการข้อมูลเพิ่มเติมเกี่ยวกับการทำแผนที่เช่น:

  • มันเงียบ ( <silent>โต้แย้ง)?
  • มันอยู่ในท้องถิ่นเพื่อบัฟเฟอร์ปัจจุบัน ( <buffer>โต้แย้ง)?
  • คือ{rhs}การประเมินผลของการแสดงออก (คน<expr>อาร์กิวเมนต์)?
  • มันทำการแมปใหม่{rhs}( nnoremapvs nmap) หรือไม่
  • หากผู้ใช้มีการแมปอื่นที่ขึ้นต้นด้วย<C-c>Vim จะรอให้พิมพ์อักขระเพิ่มเติม ( <nowait>อาร์กิวเมนต์) หรือไม่?
  • ...

จากนั้นคุณสามารถให้หนึ่งในสามและอาร์กิวเมนต์ที่สี่: และ0 เพราะคุณกำลังมองหาการทำแผนที่ไม่ใช่ตัวย่อและเพราะคุณต้องการพจนานุกรมที่มีข้อมูลสูงสุดไม่ใช่ค่า:1
01{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เป็นเพียงตัวระบุของสคริปต์ที่คำสั่งการจับคู่ของคุณจะถูกดำเนินการ อาจเป็นตัวเลขอื่นใดก็ได้ หากปลั๊กอินของคุณเป็นไฟล์ 42th 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)

อาจมีปัญหาที่ 3 เมื่อบันทึก / เรียกคืนการแมปบัฟเฟอร์ภายในเครื่อง เนื่องจากระหว่างช่วงเวลาที่เราบันทึกการแมปและช่วงเวลาที่เราพยายามกู้คืนบัฟเฟอร์ปัจจุบันอาจเปลี่ยนไป

ในกรณีนี้อาจปรับปรุงSave_mappings()ฟังก์ชันได้โดยบันทึกจำนวนบัฟเฟอร์ปัจจุบัน ( bufnr('%'))

จากนั้นRestore_mappings()จะใช้ข้อมูลนี้เพื่อกู้คืนการแม็พบัฟเฟอร์ในบัฟเฟอร์ที่ถูกต้อง เราอาจใช้:bufdoคำสั่งนำหน้าหลังด้วยการนับ (จับคู่หมายเลขบัฟเฟอร์ที่บันทึกไว้ก่อนหน้านี้) และต่อท้ายด้วยคำสั่งการจับคู่

อาจจะชอบ:

:{original buffer number}bufdo {mapping command}

เราจะต้องตรวจสอบก่อนว่าบัฟเฟอร์ยังคงมีอยู่โดยใช้bufexists()ฟังก์ชั่นเพราะมันอาจถูกลบในเวลาเดียวกัน


น่าทึ่งนั่นคือสิ่งที่ฉันต้องการ ขอบคุณ!
statox

2

ในปลั๊กอินของฉันเมื่อฉันมีการแมปชั่วคราวพวกเขามักจะบัฟเฟอร์ในพื้นที่ - ฉันไม่สนใจเกี่ยวกับการบันทึกการแมปทั่วโลกหรืออะไรที่ซับซ้อนที่เกี่ยวข้องกับพวกเขา ดังนั้นฉันlh#on#exit().restore_buffer_mapping()ฟังก์ชั่นผู้ช่วย - จากLH-เป็นกลุ่ม-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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.