update at 2026-07-06 14:20:29

This commit is contained in:
douboer
2026-07-06 14:20:29 +08:00
parent 58179424cf
commit 995d672f4a
3 changed files with 114 additions and 135 deletions

View File

@@ -20,9 +20,9 @@
## 功能特性
**自动切换输入法**:在 Normal 模式和 Insert 模式之间切换时自动切换输入法
**智能状态记忆**:记住上次 Insert 模式的输入法状态,下次自动恢复
**异步处理**:使用 `job_start()` 异步执行,避免 UI 阻塞
**中英混合友好**:完美支持中英文混合输入场景
**提前保存状态**:使用 `InsertLeavePre` 在离开 Insert 前保存输入法状态
**异步静默处理**:使用 `job_start()` 异步执行,并屏蔽命令输出,避免阻塞和乱码
**macOS 稳定恢复**:进入 Insert 后延迟恢复中文输入源,并进行一次兜底重试
## 安装要求
@@ -89,11 +89,9 @@ Plug 'yourusername/vim-im-switch', { 'rtp': 'vim-im-switch-select.vim' }
" 英文输入法 ID默认值
let g:imselect_english_im = 'com.apple.keylayout.ABC'
" 中文输入法 ID可选,插件会自动检测
" 例如:搜狗输入法
" let g:imselect_chinese_im = 'com.sogou.inputmethod.sogou'
" 例如:系统自带简体拼音
" let g:imselect_chinese_im = 'com.apple.inputmethod.SCIM.ITABC'
" 中文输入法 ID默认值:系统自带简体拼音
" macOS 更新后建议显式配置,不依赖自动检测
let g:imselect_chinese_im = 'com.apple.inputmethod.SCIM.ITABC'
```
### 如何获取输入法 ID
@@ -117,15 +115,17 @@ im-select
插件安装后会自动工作,无需手动操作:
1. **进入 Insert 模式** (`i`, `a`, `o` 等)
- 自动恢复上次保存的输入法状态
- 延迟切换到中文输入法
- 约 200ms 后再次确认中文输入法,降低 macOS 状态未刷新导致的偶发失败
2. **退出 Insert 模式** (`ESC`)
- 保存当前输入法状态
- 自动切换到英文输入法
- `InsertLeavePre` 提前保存当前输入法状态
-`InsertLeave` 自动切换到英文输入法
3. **中英文混合输入**
- 插件会记住你每次退出 Insert 模式时的输入法
- 下次进入时自动恢复
3. **macOS 行为说明**
- `im-select` 控制的是 macOS 输入源 ID
- 新版 macOS 中文输入法可能存在“图标为中文但内部仍输入英文”的状态
- 当前稳定版不模拟 `Ctrl-Space` / `Caps Lock`,避免快捷键被 Vim 接收导致退出 Insert 模式
## 测试
@@ -161,7 +161,8 @@ im-select
7. 按 `ESC` 退出到 Normal 模式
8. 再次按 `i` 进入 Insert 模式
- 观察输入法是否保持英文
- 当前稳定版会恢复到配置的中文输入法
- 如果需要保持英文,请临时禁用插件或调整 `g:imselect_chinese_im` 策略
## 调试
@@ -219,8 +220,21 @@ im-select
### 输入法切换延迟
- 这是正常的,因为使用了异步处理
- 通常延迟小于 100ms不影响使用
- 这是正常的,因为进入 Insert 后使用了 `timer_start()` 延迟恢复
- 当前稳定版会在进入 Insert 后约 80ms 切中文,并在约 200ms 后兜底确认
- 如果机器较慢或 macOS 状态刷新较慢,可以在插件中适当调大延迟值
### 输入法图标是中文但实际输入英文
这是新版 macOS 中文输入法可能出现的内部状态问题:`im-select` 已经把输入源切到中文,但中文输入法内部仍处于英文输入状态。
当前稳定版采取的处理方式:
1. 不模拟 `Ctrl-Space`、`Caps Lock`、`Shift` 等按键,避免这些按键被 Vim 接收后影响模式。
2. 进入 Insert 后延迟切换中文输入源。
3. 再进行一次中文输入源兜底确认。
如果仍偶发出现该问题,优先调整延迟时间,不建议重新加入系统快捷键模拟。
### 输入法 ID 不正确

View File

@@ -24,6 +24,7 @@ endif
if !executable("im-select")
finish
endif
let s:imselect_cmd = exepath('im-select')
let s:keepcpo = &cpo
let g:imselect_loaded = 1
set cpo&vim
@@ -38,96 +39,89 @@ if !exists('g:imselect_english_im')
endif
if !exists('g:imselect_chinese_im')
let g:imselect_chinese_im = 'auto-detect' " 将自动检测
let g:imselect_chinese_im = 'com.apple.inputmethod.SCIM.ITABC'
endif
" ---------------------------------------------------------------------
" Functions:
" 离开 Insert 模式:保存当前输入法名称,然后切换到英文
function! IMSelect2en()
" 保存当前输入法名称
let current_im = substitute(system("im-select 2>/dev/null"), '\n', '', 'g')
let g:imselect_last_insert_im_name = current_im
" 切换到英文输入法(使用后台任务并重定向所有输出)
function! IMSelectCurrentIM() abort
return substitute(system(shellescape(s:imselect_cmd) . ' 2>/dev/null'), '[\r\n]', '', 'g')
endfunction
function! IMSelectSwitch(im) abort
if has('job')
" Use nohup in a shell backgrounded process. Some Vim builds don't accept
" the 'detach' option; calling via sh -lc with nohup/& keeps process
" disassociated while avoiding unsupported job_start options.
call job_start(['sh', '-lc', 'nohup im-select ' . shellescape(g:imselect_english_im) . ' >/dev/null 2>&1 &'])
silent! call job_start([s:imselect_cmd, a:im], {'out_io': 'null', 'err_io': 'null'})
else
" Fallback: nohup in background
call system('nohup im-select ' . shellescape(g:imselect_english_im) . ' >/dev/null 2>&1 &')
silent! call system(shellescape(s:imselect_cmd) . ' ' . shellescape(a:im) . ' >/dev/null 2>&1 &')
endif
endfunction
" 进入 Insert 模式:根据保存的输入法名称恢复
function! IMSelect2zh()
" 如果上次保存的输入法名称为空,说明是第一次进入,保持英文
function! IMSelectStart() abort
call IMSelectSwitch(g:imselect_english_im)
endfunction
function! IMSelectEnsureTarget(timer) abort
if g:imselect_last_insert_im_name != ''
call IMSelectSwitch(g:imselect_last_insert_im_name)
endif
endfunction
" 离开 Insert 模式前:保存当前输入法名称
function! IMSelectRemember() abort
let current_im = IMSelectCurrentIM()
if current_im != ''
let g:imselect_last_insert_im_name = current_im
endif
endfunction
" 离开 Insert 模式:切换到英文
function! IMSelect2en() abort
call IMSelectSwitch(g:imselect_english_im)
endfunction
" 恢复 Insert 模式输入法
function! IMSelectRestore(timer) abort
if g:imselect_last_insert_im_name == ''
return
endif
" 如果上次是英文输入法,保持英文(不需要做任何事)
if g:imselect_last_insert_im_name == g:imselect_english_im
return
endif
" 上次是中文输入法,恢复到那个输入法(使用后台任务并重定向所有输出)
if has('job')
call job_start(['sh', '-lc', 'nohup im-select ' . shellescape(g:imselect_last_insert_im_name) . ' >/dev/null 2>&1 &'])
else
call system('nohup im-select ' . shellescape(g:imselect_last_insert_im_name) . ' >/dev/null 2>&1 &')
let g:imselect_last_insert_im_name = g:imselect_chinese_im
endif
call IMSelectSwitch(g:imselect_last_insert_im_name)
call timer_start(200, function('IMSelectEnsureTarget'))
endfunction
" 进入 Insert 模式:延迟恢复保存的输入法
function! IMSelect2zh() abort
call timer_start(80, function('IMSelectRestore'))
endfunction
" ---------------------------------------------------------------------
" Autocmds:
" 进入 Insert 模式一次后的处理
function! IMSelect2zhOnce()
call IMSelect2zh()
call UnBindIMSelectAu()
endfunction
function! BindIMSelectAu2zhOnce()
augroup IMSelect
au InsertEnter * call IMSelect2zhOnce()
augroup END
endfunction
" 绑定自动命令
function! BindIMSelectAu()
augroup IMSelect
" 离开 Insert 模式:保存输入法状态并切换到英文
au InsertLeave * call IMSelect2en()
autocmd!
" 离开 Insert 模式前:先保存输入法状态
autocmd InsertLeavePre * call IMSelectRemember()
" 离开 Insert 模式:切换到英文
autocmd InsertLeave * call IMSelect2en()
" 进入 Insert 模式:恢复上次的输入法状态
au InsertEnter * call IMSelect2zh()
" Vim 启动:切换到英文
au VimEnter * call IMSelect2en()
autocmd InsertEnter * call IMSelect2zh()
" Vim 启动:切换到英文
autocmd VimEnter * call timer_start(100, {-> IMSelectStart()})
augroup END
endfunction
function! UnBindIMSelectAu()
au! IMSelect InsertLeave *
au! IMSelect InsertEnter *
augroup IMSelect
autocmd!
augroup END
endfunction
" 延迟初始化:首次进入 Insert 模式时才绑定自动命令
let g:imselect_called_bind = 0
function! IMSelectEchoBind()
if (g:imselect_called_bind==0)
call BindIMSelectAu()
endif
let g:imselect_called_bind = 1
endfunction
autocmd InsertEnter * call IMSelectEchoBind()
call BindIMSelectAu()
" 多光标支持:在选择多个光标前后的处理
function! IMSelect_multiple_cursors_before()
call UnBindIMSelectAu()
call BindIMSelectAu2zhOnce()
endfunction
function! IMSelect_multiple_cursors_after()

View File

@@ -43,90 +43,61 @@ endif
" ---------------------------------------------------------------------
" Functions:
function! FcitxCurrentIM() abort
return substitute(system('fcitx-remote -n 2>/dev/null'), '[\r\n]', '', 'g')
endfunction
function! FcitxSwitch(im) abort
silent! call system('fcitx-remote -s ' . shellescape(a:im) . ' >/dev/null 2>&1')
endfunction
function! FcitxStart() abort
call FcitxSwitch(g:fcitx_english_im)
endfunction
" 离开 Insert 模式:保存当前输入法名称,然后切换到英文
function! Fcitx2en()
" 保存当前输入法名称
let current_im = substitute(system("fcitx-remote -n 2>/dev/null"), '\n', '', 'g')
let g:fcitx_last_insert_im_name = current_im
" 切换到英文输入法(使用后台任务并重定向所有输出)
if has('job')
" Use nohup in a shell backgrounded process. Some Vim builds don't accept
" the 'detach' option; calling via sh -lc with nohup/& keeps process
" disassociated while avoiding unsupported job_start options.
call job_start(['sh', '-lc', 'nohup fcitx-remote -s ' . shellescape(g:fcitx_english_im) . ' >/dev/null 2>&1 &'])
else
" Fallback: nohup in background
call system('nohup fcitx-remote -s ' . shellescape(g:fcitx_english_im) . ' >/dev/null 2>&1 &')
function! Fcitx2en() abort
let current_im = FcitxCurrentIM()
if current_im != ''
let g:fcitx_last_insert_im_name = current_im
endif
call FcitxSwitch(g:fcitx_english_im)
endfunction
" 进入 Insert 模式:根据保存的输入法名称恢复
function! Fcitx2zh()
" 如果上次保存的输入法名称为空,说明是第一次进入,保持英文
if g:fcitx_last_insert_im_name == ''
function! Fcitx2zh() abort
if g:fcitx_last_insert_im_name == '' || g:fcitx_last_insert_im_name == g:fcitx_english_im
return
endif
" 如果上次是英文输入法,保持英文(不需要做任何事)
if g:fcitx_last_insert_im_name == g:fcitx_english_im
return
endif
" 上次是中文输入法,恢复到那个输入法(使用后台任务并重定向所有输出)
if has('job')
call job_start(['sh', '-lc', 'nohup fcitx-remote -s ' . shellescape(g:fcitx_last_insert_im_name) . ' >/dev/null 2>&1 &'])
else
call system('nohup fcitx-remote -s ' . shellescape(g:fcitx_last_insert_im_name) . ' >/dev/null 2>&1 &')
endif
call FcitxSwitch(g:fcitx_last_insert_im_name)
endfunction
" ---------------------------------------------------------------------
" Autocmds:
" 进入 Insert 模式一次后的处理
function! Fcitx2zhOnce()
call Fcitx2zh()
call UnBindAu()
endfunction
function! BindAu2zhOnce()
augroup Fcitx
au InsertEnter * call Fcitx2zhOnce()
augroup END
endfunction
" 绑定自动命令
function! BindAu()
augroup Fcitx
autocmd!
" 离开 Insert 模式:保存输入法状态并切换到英文
au InsertLeave * call Fcitx2en()
autocmd InsertLeave * call Fcitx2en()
" 进入 Insert 模式:恢复上次的输入法状态
au InsertEnter * call Fcitx2zh()
" Vim 启动:切换到英文
au VimEnter * call Fcitx2en()
autocmd InsertEnter * call Fcitx2zh()
" Vim 启动:切换到英文
autocmd VimEnter * call timer_start(100, {-> FcitxStart()})
augroup END
endfunction
function! UnBindAu()
au! Fcitx InsertLeave *
au! Fcitx InsertEnter *
augroup Fcitx
autocmd!
augroup END
endfunction
" 延迟初始化:首次进入 Insert 模式时才绑定自动命令
let g:called_bind = 0
function! EchoBind()
if (g:called_bind==0)
call BindAu()
endif
let g:called_bind = 1
endfunction
autocmd InsertEnter * call EchoBind()
call BindAu()
" 多光标支持:在选择多个光标前后的处理
function! Multiple_cursors_before()
call UnBindAu()
call BindAu2zhOnce()
endfunction
function! Multiple_cursors_after()