diff --git a/README-im-select.md b/README-im-select.md index 3e35486..28f8b98 100644 --- a/README-im-select.md +++ b/README-im-select.md @@ -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 不正确 diff --git a/vim-im-switch-select.vim b/vim-im-switch-select.vim index 52451ce..a102a7b 100644 --- a/vim-im-switch-select.vim +++ b/vim-im-switch-select.vim @@ -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() diff --git a/vim-im-switch.vim b/vim-im-switch.vim.bk similarity index 52% rename from vim-im-switch.vim rename to vim-im-switch.vim.bk index 34acc40..fbc37b6 100644 --- a/vim-im-switch.vim +++ b/vim-im-switch.vim.bk @@ -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()