234 lines
6.4 KiB
VimL
234 lines
6.4 KiB
VimL
" 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<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
|