" vim-im-switch-select.vim - macOS Vim 输入法切换 " Author: Gavin Chan " Modified by: codefalling, enhanced with smart IM state memory " Version: 2.3.1 " Description: Normal 模式固定英文,Insert 模式恢复同进程内上次中/英状态 " --------------------------------------------------------------------- " Load Once: if exists('g:imselect_loaded') finish endif if &ttimeoutlen <= 0 || &ttimeoutlen > 50 set ttimeoutlen=50 endif if (has("win32") || has("win95") || has("win64") || has("win16")) " Windows 下不要载入 finish endif if exists('$SSH_TTY') finish endif let s:hs_cmd = executable('hs') ? exepath('hs') : '' let s:imselect_cmd = executable('im-select') ? exepath('im-select') : '' if s:hs_cmd ==# '' && s:imselect_cmd ==# '' finish endif let s:keepcpo = &cpo let g:imselect_loaded = 1 set cpo&vim " im-select fallback 使用输入源 ID;Hammerspoon 主路径使用 layout/method 名称。 if !exists('g:imselect_english_im') let g:imselect_english_im = 'com.apple.keylayout.ABC' endif if !exists('g:imselect_chinese_im') let g:imselect_chinese_im = 'com.apple.inputmethod.SCIM.ITABC' endif if !exists('g:imselect_hs_english_layout') let g:imselect_hs_english_layout = 'ABC' endif if !exists('g:imselect_hs_chinese_method') let g:imselect_hs_chinese_method = 'Pinyin – Simplified' endif if !exists('g:imselect_restore_delay') let g:imselect_restore_delay = 80 endif if !exists('g:imselect_confirm_delay') let g:imselect_confirm_delay = 200 endif let s:restore_timer = -1 let s:confirm_timer = -1 let s:target = '' " 只在当前 Vim 进程内记忆;退出 Vim 后会重新默认中文。 let s:last_insert_target = 'chinese' " --------------------------------------------------------------------- " Functions: function! IMSelectCurrentIM() abort if s:imselect_cmd ==# '' return '' endif return substitute(system(shellescape(s:imselect_cmd) . ' 2>/dev/null'), '[\r\n]', '', 'g') endfunction function! s:CancelTimer(timer) abort if a:timer != -1 silent! call timer_stop(a:timer) endif endfunction function! s:CancelInsertTimers() abort call s:CancelTimer(s:restore_timer) call s:CancelTimer(s:confirm_timer) let s:restore_timer = -1 let s:confirm_timer = -1 endfunction function! s:RunHammerspoon(lua) abort if s:hs_cmd ==# '' return 0 endif silent! call system(shellescape(s:hs_cmd) . ' -c ' . shellescape(a:lua) . ' >/dev/null 2>&1') return v:shell_error == 0 endfunction function! s:HammerspoonOutput(lua) abort if s:hs_cmd ==# '' return '' endif return substitute(system(shellescape(s:hs_cmd) . ' -c ' . shellescape(a:lua) . ' 2>/dev/null'), '[\r\n]', '', 'g') endfunction function! IMSelectSwitch(im) abort if a:im ==# '' || s:imselect_cmd ==# '' return endif silent! call system(shellescape(s:imselect_cmd) . ' ' . shellescape(a:im) . ' >/dev/null 2>&1') endfunction function! s:SwitchToEnglish() abort " Hammerspoon 能直接设置 layout;比只切 source ID 更贴近 macOS 实际输入状态。 if s:RunHammerspoon('hs.keycodes.setLayout(' . string(g:imselect_hs_english_layout) . ')') return endif call IMSelectSwitch(g:imselect_english_im) endfunction function! s:SwitchToChinese() abort " Apple 拼音可能出现 source ID 已切中文但 layout 仍为 ABC 的坏状态;setMethod 可避免该问题。 if s:RunHammerspoon('hs.keycodes.setMethod(' . string(g:imselect_hs_chinese_method) . ')') return endif call IMSelectSwitch(g:imselect_chinese_im) endfunction function! s:SwitchToTarget(target) abort if a:target ==# 'english' call s:SwitchToEnglish() else call s:SwitchToChinese() endif endfunction function! s:DetectInsertTarget() abort let layout = s:HammerspoonOutput('print(hs.keycodes.currentLayout() or "")') let method = s:HammerspoonOutput('print(hs.keycodes.currentMethod() or "")') if layout ==# g:imselect_hs_english_layout && method ==# '' let s:last_insert_target = 'english' elseif method ==# g:imselect_hs_chinese_method || layout ==# g:imselect_hs_chinese_method let s:last_insert_target = 'chinese' else let current_im = IMSelectCurrentIM() if current_im ==# g:imselect_english_im let s:last_insert_target = 'english' elseif current_im ==# g:imselect_chinese_im let s:last_insert_target = 'chinese' endif endif endfunction function! IMSelectStart() abort let s:target = 'english' call s:CancelInsertTimers() call s:SwitchToEnglish() endfunction function! s:ConfirmTarget(timer) abort let s:confirm_timer = -1 call s:SwitchToTarget(s:target) endfunction function! s:RestoreInsertTarget(timer) abort let s:restore_timer = -1 " 防止快速 i 后,延迟 timer 又把 Normal 模式切回中文。 if s:target !=# s:last_insert_target return endif call s:SwitchToTarget(s:last_insert_target) if g:imselect_confirm_delay > 0 let s:confirm_timer = timer_start(g:imselect_confirm_delay, function('s:ConfirmTarget')) endif endfunction " 必须在 InsertLeavePre 保存;InsertLeave 中会立即切到英文。 function! IMSelectRemember() abort call s:DetectInsertTarget() endfunction function! IMSelect2en() abort let s:target = 'english' call s:CancelInsertTimers() call s:SwitchToEnglish() endfunction function! IMSelect2zh() abort let s:target = s:last_insert_target call s:CancelInsertTimers() if g:imselect_restore_delay > 0 let s:restore_timer = timer_start(g:imselect_restore_delay, function('s:RestoreInsertTarget')) else call s:RestoreInsertTarget(-1) endif endfunction " --------------------------------------------------------------------- " Autocmds: function! BindIMSelectAu() augroup IMSelect autocmd! autocmd InsertLeavePre * call IMSelectRemember() autocmd InsertLeave * call IMSelect2en() autocmd InsertEnter * call IMSelect2zh() autocmd VimEnter * call timer_start(100, {-> IMSelectStart()}) augroup END endfunction function! UnBindIMSelectAu() augroup IMSelect autocmd! augroup END call s:CancelInsertTimers() endfunction call BindIMSelectAu() function! IMSelect_multiple_cursors_before() call UnBindIMSelectAu() endfunction function! IMSelect_multiple_cursors_after() call IMSelect2en() call BindIMSelectAu() endfunction " --------------------------------------------------------------------- " Restoration And Modelines: let &cpo=s:keepcpo unlet s:keepcpo let g:imselect_loaded = 1 " vim:fdm=expr:fde=getline(v\:lnum-1)=~'\\v"\\s*-{20,}'?'>1'\:1