Files
vim-im-switch/vim-im-switch-select.vim
2026-07-15 11:04:39 +08:00

234 lines
6.4 KiB
VimL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

" 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 使用输入源 IDHammerspoon 主路径使用 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<Esc> 后,延迟 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