diff --git a/README-im-select.md b/README-im-select.md deleted file mode 100644 index 28f8b98..0000000 --- a/README-im-select.md +++ /dev/null @@ -1,276 +0,0 @@ -# vim-im-switch-select.vim - -> 使用 `im-select` 而不是 `fcitx-remote` 的 Vim 输入法自动切换插件 - -## 概述 - -`vim-im-switch-select.vim` 是 `vim-im-switch.vim` 的变体版本,使用 `im-select` 工具而不是 `fcitx-remote` 来控制输入法切换。 - -## 主要区别 - -| 特性 | vim-im-switch.vim | vim-im-switch-select.vim | -|------|------------------|-------------------------| -| 输入法工具 | fcitx-remote | im-select | -| 适用平台 | macOS, Linux | 主要是 macOS | -| 获取当前输入法 | `fcitx-remote -n` | `im-select` | -| 切换输入法 | `fcitx-remote -s ` | `im-select ` | -| 全局变量前缀 | `g:fcitx_*` | `g:imselect_*` | -| 函数前缀 | `Fcitx*` | `IMSelect*` | - -## 功能特性 - -✅ **自动切换输入法**:在 Normal 模式和 Insert 模式之间切换时自动切换输入法 -✅ **提前保存状态**:使用 `InsertLeavePre` 在离开 Insert 前保存输入法状态 -✅ **异步静默处理**:使用 `job_start()` 异步执行,并屏蔽命令输出,避免阻塞和乱码 -✅ **macOS 稳定恢复**:进入 Insert 后延迟恢复中文输入源,并进行一次兜底重试 - -## 安装要求 - -### 1. 安装 im-select - -本项目已经包含了 `im-select` 可执行文件(适用于 macOS ARM64)。 - -如果你需要其他版本,可以从这里获取: -- [im-select GitHub](https://github.com/daipeihust/im-select) - -或者使用项目中的 `im-select` 文件: - -```bash -# 确保 im-select 有执行权限 -chmod +x /path/to/vim-im-switch/im-select - -# 将 im-select 添加到 PATH(可选) -sudo cp /path/to/vim-im-switch/im-select /usr/local/bin/ -``` - -### 2. 验证 im-select 安装 - -```bash -# 如果在项目目录中 -./im-select - -# 如果在 PATH 中 -im-select -``` - -应该输出当前的输入法名称,例如: -``` -com.apple.keylayout.ABC -``` - -## 安装插件 - -### 方法 1:复制到 Vim 插件目录 - -```bash -mkdir -p ~/.vim/plugin -cp vim-im-switch-select.vim ~/.vim/plugin/ -``` - -### 方法 2:在 .vimrc 中 source - -```vim -" 在 .vimrc 中添加 -source /Users/gavin/vim-im-switch/vim-im-switch-select.vim -``` - -### 方法 3:使用插件管理器 - -**vim-plug**: -```vim -Plug 'yourusername/vim-im-switch', { 'rtp': 'vim-im-switch-select.vim' } -``` - -## 配置 - -在 `.vimrc` 中添加配置(可选): - -```vim -" 英文输入法 ID(默认值) -let g:imselect_english_im = 'com.apple.keylayout.ABC' - -" 中文输入法 ID(默认值:系统自带简体拼音) -" macOS 更新后建议显式配置,不依赖自动检测 -let g:imselect_chinese_im = 'com.apple.inputmethod.SCIM.ITABC' -``` - -### 如何获取输入法 ID - -```bash -# 切换到你想要的输入法,然后运行: -./im-select -# 或 -im-select -``` - -常见的输入法 ID: -- 美国英文:`com.apple.keylayout.ABC` -- 简体拼音:`com.apple.inputmethod.SCIM.ITABC` -- 搜狗输入法:`com.sogou.inputmethod.sogou` -- 百度输入法:`com.baidu.inputmethod.BaiduIM.pinyin` -- 微信输入法:`com.tencent.inputmethod.wetype.pinyin` - -## 使用方法 - -插件安装后会自动工作,无需手动操作: - -1. **进入 Insert 模式** (`i`, `a`, `o` 等) - - 延迟切换到中文输入法 - - 约 200ms 后再次确认中文输入法,降低 macOS 状态未刷新导致的偶发失败 - -2. **退出 Insert 模式** (`ESC`) - - 在 `InsertLeavePre` 提前保存当前输入法状态 - - 在 `InsertLeave` 自动切换到英文输入法 - -3. **macOS 行为说明** - - `im-select` 控制的是 macOS 输入源 ID - - 新版 macOS 中文输入法可能存在“图标为中文但内部仍输入英文”的状态 - - 当前稳定版不模拟 `Ctrl-Space` / `Caps Lock`,避免快捷键被 Vim 接收导致退出 Insert 模式 - -## 测试 - -### 运行自动化测试 - -```bash -# 测试 im-select 功能 -./test-im-select.sh - -# 在 Vim 中测试插件 -./run-vim-test.sh -``` - -### 手动测试步骤 - -1. 启动 Vim 并打开测试文件: - ```bash - vim test-im-switch.txt - ``` - -2. 按 `i` 进入 Insert 模式 - -3. 切换到中文输入法,输入一些中文 - -4. 按 `ESC` 退出到 Normal 模式 - - 观察输入法是否切换到英文 - -5. 再次按 `i` 进入 Insert 模式 - - 观察输入法是否自动恢复到中文 - -6. 在 Insert 模式下,手动切换到英文输入法,输入一些英文 - -7. 按 `ESC` 退出到 Normal 模式 - -8. 再次按 `i` 进入 Insert 模式 - - 当前稳定版会恢复到配置的中文输入法 - - 如果需要保持英文,请临时禁用插件或调整 `g:imselect_chinese_im` 策略 - -## 调试 - -### 查看插件是否加载 - -```vim -:echo exists('g:imselect_loaded') -" 应该输出 1 -``` - -### 查看保存的输入法状态 - -```vim -:echo g:imselect_last_insert_im_name -" 输出上次 Insert 模式的输入法名称 -``` - -### 手动测试函数 - -```vim -" 切换到英文 -:call IMSelect2en() - -" 恢复输入法 -:call IMSelect2zh() -``` - -### 查看 Vim 消息 - -```vim -:messages -``` - -## 故障排除 - -### 插件没有效果 - -1. **检查 im-select 是否可执行**: - ```bash - which im-select - # 或 - ls -l ./im-select - ``` - -2. **检查 Vim 版本是否支持 job_start()**: - ```vim - :echo has('job') - " 应该输出 1 - ``` - -3. **检查是否有错误消息**: - ```vim - :messages - ``` - -### 输入法切换延迟 - -- 这是正常的,因为进入 Insert 后使用了 `timer_start()` 延迟恢复 -- 当前稳定版会在进入 Insert 后约 80ms 切中文,并在约 200ms 后兜底确认 -- 如果机器较慢或 macOS 状态刷新较慢,可以在插件中适当调大延迟值 - -### 输入法图标是中文但实际输入英文 - -这是新版 macOS 中文输入法可能出现的内部状态问题:`im-select` 已经把输入源切到中文,但中文输入法内部仍处于英文输入状态。 - -当前稳定版采取的处理方式: - -1. 不模拟 `Ctrl-Space`、`Caps Lock`、`Shift` 等按键,避免这些按键被 Vim 接收后影响模式。 -2. 进入 Insert 后延迟切换中文输入源。 -3. 再进行一次中文输入源兜底确认。 - -如果仍偶发出现该问题,优先调整延迟时间,不建议重新加入系统快捷键模拟。 - -### 输入法 ID 不正确 - -```bash -# 切换到你的中文输入法,然后运行: -./im-select - -# 将输出的 ID 配置到 .vimrc: -let g:imselect_chinese_im = '你的输入法ID' -``` - -## 与原版的兼容性 - -`vim-im-switch-select.vim` 和 `vim-im-switch.vim` 可以共存,但不建议同时使用。它们使用不同的: -- 全局变量名(`g:imselect_*` vs `g:fcitx_*`) -- 函数名(`IMSelect*` vs `Fcitx*`) -- 自动命令组名(`IMSelect` vs `Fcitx`) - -## 文件清单 - -- `vim-im-switch-select.vim` - 插件主文件 -- `im-select` - im-select 可执行文件(macOS ARM64) -- `test-im-select.sh` - 自动化测试脚本 -- `run-vim-test.sh` - Vim 交互测试脚本 -- `test-im-switch.txt` - 测试文件 -- `README-im-select.md` - 本说明文件 - -## 参考链接 - -- [im-select GitHub](https://github.com/daipeihust/im-select) -- [原版 vim-im-switch 项目](https://github.com/yourusername/vim-im-switch) - -## 许可证 - -MIT License - ---- - -**如有问题或建议,欢迎提交 Issue!** diff --git a/README.md b/README.md index dd0dfd1..c93c0ad 100644 --- a/README.md +++ b/README.md @@ -1,353 +1,286 @@ # Vim 输入法自动切换插件 -[![Version](https://img.shields.io/badge/version-2.0.3-blue.svg)](./CHANGELOG.md) -[![Platform](https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-lightgrey.svg)](#安装要求) -[![License](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE.txt) +面向 macOS 原生 Vim 的输入法自动切换方案: -[English](./README_en.md) | 中文 +- Normal 模式固定英文输入法 +- Insert 模式恢复同一 Vim 进程内上次使用的中/英状态 +- macOS 上优先使用 Hammerspoon `hs.keycodes.setLayout()` / `hs.keycodes.setMethod()` +- `im-select` 仅作为 fallback -[感谢 github 中的 vim-im-switch这个项目](https://github.com/yourusername/vim-im-switch) -这个项目不能满足需求,在这个项目基础上改进。 +> 说明:本次 Hammerspoon 方案和行为调整只针对原生 Vim 插件 `vim-im-switch-select.vim`。Obsidian 插件逻辑不变。 -作者:Gavin Chan +## Obsidian 插件说明 -> 🚀 **智能输入法管理工具** - 为 Vim 用户打造的无感知输入法切换体验 +本仓库仍包含 Obsidian 插件入口 `main.ts`,用于在 Obsidian 启用 Vim keymap 时切换输入法。 -这是为 **原生 Vim** 和 **Obsidian 编辑器** 的 Vim 模式设计的智能输入法自动切换插件,包含: -- 🎯 **Obsidian 插件**:适用于 Obsidian 编辑器的 Vim 模式 -- ⚡ **Vim 插件**:适用于原生 Vim/NeoVim 编辑器 -- 🌍 **跨平台支持**:macOS、Linux、Windows 全平台兼容 +当前 Obsidian 插件逻辑保持原样: -## ✨ 功能亮点 +- 通过 `fcitx-remote` 执行输入法查询和切换。 +- 监听 Obsidian / CodeMirror 的 Vim 模式变化。 +- Normal / Visual 模式切到英文输入法。 +- Insert / Replace 模式恢复上次 Insert 状态。 +- 配置项仍使用 `manifest.json` / 插件设置页中的 `fcitx-remote` 路径和输入法 ID。 -- 🔄 **自动切换输入法**:在 Vim 的 Normal 模式和 Insert 模式之间切换时,自动切换输入法 -- 🧠 **智能状态记忆**:记住上次 Insert 模式退出时的输入法状态,下次进入时自动恢复 -- 🎭 **无感知体验**:完全静默切换,无 UI 闪烁或延迟 -- 🌐 **中英混合友好**:完美支持中英文混合输入场景 -- ⚡ **多重保障机制**:三层检测确保切换的可靠性 +本次 Hammerspoon `hs.keycodes.setLayout()` / `hs.keycodes.setMethod()` 方案只应用在原生 Vim 插件 `vim-im-switch-select.vim`,没有改造 Obsidian 插件。 -## 核心特性 -### 1. 模式切换自动化 -- 进入 Normal 模式(按 ESC 或其他命令)→ 自动切换到英文输入法 -- 进入 Insert 模式(按 i, a, o 等)→ 自动恢复上次的输入法状态 +## 当前稳定策略 -### 2. 输入法状态记忆 -- 退出 Insert 模式时,自动检测并保存当前的输入法(中文/英文) -- 下次进入 Insert 模式时,自动恢复到上次保存的输入法状态 -- 支持中英文混合输入场景 +### 模式行为 -### 3. 智能检测机制 -插件采用**三重检测机制**确保可靠性: +| Vim 状态 | 行为 | +|---|---| +| Vim 启动后 | 延迟切到英文 `ABC` | +| 进入 Insert | 恢复本次 Vim 进程内上次 Insert 的中/英状态;首次默认中文 | +| 离开 Insert / `Esc` | 先记录 Insert 中最后的中/英状态,再切到英文 `ABC` | -| 检测方式 | 优先级 | 说明 | -|---------|--------|------| -| 🎯 **键盘事件监听** | 最高 | 使用事件捕获模式监听 ESC 和 Insert 按键,响应最快 | -| 🔧 **CodeMirror 事件** | 中等 | 监听 vim-mode-change 事件,处理非按键触发的模式切换 | -| 🔄 **定时轮询** | 兜底 | 100ms 轮询检测模式变化,作为最后保障机制 | +示例: -### 4. 技术特性 -- ⚡ **异步处理**:使用 `job_start()` 异步执行,避免 UI 阻塞 -- 🛡️ **防抖机制**:100ms 防抖避免重复处理 -- 🎯 **事件捕获优化**:使用 capture 模式确保最快响应 -- 🔧 **自动检测**:智能检测和配置中文输入法 +```text +第一次进入 Insert -> 中文 +Insert 中手动切到英文 -> Esc -> 再次 Insert -> 英文 +Insert 中手动切回中文 -> Esc -> 再次 Insert -> 中文 +退出 Vim 后重新打开 -> 记忆重置,第一次 Insert 默认中文 +``` + +### 为什么使用 Hammerspoon + +之前只用 `im-select` 切换 macOS input source ID 时,Apple 拼音可能出现: + +```text +当前输入源ID: com.apple.inputmethod.SCIM.ITABC +当前键盘布局: ABC +当前输入法Method: Pinyin – Simplified +``` + +表现为菜单栏显示中文输入法,但实际仍输入英文。 + +Hammerspoon 可以直接设置 layout/method: + +```lua +hs.keycodes.setLayout("ABC") +hs.keycodes.setMethod("Pinyin – Simplified") +``` + +这能避免只切 source ID 导致的 Apple 拼音内部状态不一致。 ## 安装要求 -#### macOS -1. 安装 [fcitx-remote-for-osx](https://github.com/xcodebuild/fcitx-remote-for-osx): - ```bash - brew install fcitx-remote-for-osx - ``` +### 1. Hammerspoon -2. 验证安装: - ```bash - fcitx-remote -n - # 应该输出当前输入法的名称,如:com.apple.keylayout.ABC - ``` +安装并运行 Hammerspoon。 + +在 `~/.hammerspoon/init.lua` 中启用 IPC: + +```lua +require("hs.ipc") +hs.ipc.cliInstall() +``` + +Reload Hammerspoon config 后,确认命令可用: -#### Linux -通过你的包管理器安装 `fcitx`: ```bash -# Ubuntu/Debian -sudo apt-get install fcitx - -# Fedora -sudo dnf install fcitx - -# Arch Linux -sudo pacman -S fcitx +hs -c 'hs.keycodes.setLayout("ABC")' +hs -c 'hs.keycodes.setMethod("Pinyin – Simplified")' ``` -#### Windows -使用项目内置的 AutoHotkey 脚本: -1. 安装 [AutoHotkey](https://www.autohotkey.com/) -2. 使用项目中的 `fcitx-remote.ahk` 脚本 -3. 或下载编译好的版本:[fcitx-remote.exe](https://github.com/yuanotes/obsidian-vim-im-switch-plugin/releases/download/1.0.3/fcitx-remote.exe) -4. 将 exe 文件放到系统 PATH 路径中 +两条命令都应返回: -## 安装插件 - -### Obsidian 插件安装 - -1. 下载插件文件到 Obsidian 插件目录: - ```bash - cd /path/to/your/vault/.obsidian/plugins/ - git clone https://github.com/yourusername/vim-im-switch.git - ``` - -2. 在 Obsidian 中启用插件: - - 打开设置 → 社区插件 → 浏览 - - 找到 "Vim Input Method Switch" - - 点击启用 - -3. 配置输入法(可选): - - 打开插件设置 - - 设置英文输入法(默认:`com.apple.keylayout.ABC`) - - 设置中文输入法(默认:自动检测) - -### Vim 插件安装 - -1. 复制插件文件到 Vim 配置目录: - ```bash - mkdir -p ~/.vim/plugin - cp vim-im-switch.vim ~/.vim/plugin/ - ``` - -2. 重启 Vim,插件会自动加载 - -3. 配置输入法(可选): - 在 `.vimrc` 中添加: - ```vim - " 英文输入法 ID(默认值) - let g:fcitx_english_im = 'com.apple.keylayout.ABC' - - " 中文输入法 ID(可选,插件会自动检测) - " let g:fcitx_chinese_im = 'com.tencent.inputmethod.wetype.pinyin' - ``` - -#### 使用插件管理器安装 - -**vim-plug**: -```vim -Plug 'yourusername/vim-im-switch' +```text +true ``` -**Vundle**: -```vim -Plugin 'yourusername/vim-im-switch' +如果出现: + +```text +can't access Hammerspoon message port Hammerspoon; is it running with the ipc module loaded? ``` -### 🚀 一键部署(推荐) +说明 Hammerspoon IPC 未启用或配置尚未 reload。 + +### 2. im-select fallback(可选但建议保留) + +当 `hs` 不可用时,插件会退回使用 `im-select`: -使用部署脚本同时安装 Obsidian 插件和 Vim 插件: ```bash -# 克隆项目 -git clone https://github.com/yourusername/vim-im-switch.git -cd vim-im-switch - -# 构建并部署 -npm install -./deploy.sh +im-select +im-select com.apple.keylayout.ABC +im-select com.apple.inputmethod.SCIM.ITABC ``` -> 💡 **提示**: v2.0.2 修复了终端 Vim 中的标题闪烁问题,详见 [更新日志](./CHANGELOG.md) +常见输入源 ID: -## 使用方法 +| 输入法 | ID | +|---|---| +| ABC | `com.apple.keylayout.ABC` | +| Apple 简体拼音 | `com.apple.inputmethod.SCIM.ITABC` | -### 基本使用场景 +## 安装 Vim 插件 -1. **中文输入**: - ``` - 按 i → 进入 Insert 模式 → 输入法切换到中文(如果上次是中文) - 输入中文内容 - 按 ESC → 退出到 Normal 模式 → 输入法切换到英文 - ``` +复制插件到 Vim plugin 目录: -2. **英文输入**: - ``` - 按 i → 进入 Insert 模式 → 输入法保持英文(如果上次是英文) - 输入英文内容 - 按 ESC → 退出到 Normal 模式 → 输入法保持英文 - ``` +```bash +mkdir -p ~/.vim/plugin +cp vim-im-switch-select.vim ~/.vim/plugin/ +``` -3. **中英混合**: - ``` - 按 i → 自动恢复上次的输入法 - 输入中文,然后手动切换到英文继续输入 - 按 ESC → 保存当前输入法状态(英文) - 按 i → 自动恢复英文输入法 - ``` +或在 `.vimrc` 中 source: -### 支持的 Vim 命令 +```vim +source /path/to/vim-im-switch-select.vim +``` -- **进入 Insert 模式**:`i`, `I`, `a`, `A`, `o`, `O`, `s`, `S`, `c`, `C` -- **退出 Insert 模式**:`ESC`, 以及其他触发 Normal 模式的命令 +## 配置 + +默认配置适用于 macOS ABC + Apple 简体拼音: + +```vim +" Hammerspoon 主路径:英文键盘布局名 +let g:imselect_hs_english_layout = 'ABC' + +" Hammerspoon 主路径:中文输入法 Method 名 +let g:imselect_hs_chinese_method = 'Pinyin – Simplified' + +" im-select fallback:英文输入源 ID +let g:imselect_english_im = 'com.apple.keylayout.ABC' + +" im-select fallback:中文输入源 ID +let g:imselect_chinese_im = 'com.apple.inputmethod.SCIM.ITABC' +``` + +延迟配置: + +```vim +" 进入 Insert 后延迟恢复,默认 80ms +let g:imselect_restore_delay = 80 + +" 恢复后再次确认目标状态,默认 200ms +let g:imselect_confirm_delay = 200 +``` ## 工作原理 -```mermaid -graph TD - A[Normal 模式
英文输入法] -->|按 i/a/o 等| B[检测模式切换] - B --> C[恢复上次保存的
输入法状态] - C --> D[Insert 模式
自动恢复的输入法:
中文/英文] - D -->|按 ESC| E[保存当前输入法
状态 中/英] - E --> F[切换到英文输入法] - F --> A +核心事件: - style A fill:#e1f5ff,stroke:#01579b,stroke-width:2px - style D fill:#fff9c4,stroke:#f57f17,stroke-width:2px - style E fill:#f3e5f5,stroke:#4a148c,stroke-width:2px - style F fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px -``` - -插件维护一个状态机,跟踪 Vim 模式和输入法状态,在模式转换时自动切换输入法,同时保留用户偏好设置。 - -## 🔧 故障排除 - -### 快速诊断 -使用内置诊断脚本: -```bash -./diagnose-im.sh -``` - -### 常见问题 - -#### 🚫 插件没有效果 -1. **检查依赖**:确认 fcitx-remote 是否正确安装 - ```bash - # macOS - fcitx-remote -n - - # Linux - which fcitx-remote - - # Windows - fcitx-remote.exe - ``` - -2. **检查日志**: - - **Obsidian**: 开发者控制台 (Ctrl+Shift+I) - - **Vim**: `:messages` 命令 - -3. **验证配置**:确认输入法名称与系统设置匹配 - -#### ⚠️ 输入法切换不正确 -1. **获取正确的输入法名称**: - ```bash - # 切换到中文输入法后执行 - fcitx-remote -n - ``` - -2. **手动测试**: - ```bash - # 切换到英文 - fcitx-remote -s com.apple.keylayout.ABC - - # 切换到中文(替换为你的输入法名称) - fcitx-remote -s com.tencent.inputmethod.wetype.pinyin - ``` - -3. **检查冲突**:暂时禁用其他 Vim 插件测试 - -#### 🐛 其他问题 -- **权限问题**:确保 fcitx-remote 有执行权限 -- **路径问题**:检查 fcitx-remote 是否在 PATH 中 -- **版本兼容**:确认 Vim 版本支持 `job_start()` (Vim 8+) - -## 🛠️ 开发指南 - -### 环境要求 -- Node.js 14+ -- TypeScript 4.2+ -- Rollup (构建工具) - -### 构建项目 -```bash -# 安装依赖 -npm install - -# 开发模式(监听文件变化) -npm run dev - -# 生产构建 -npm run build -``` - -### 项目结构 -``` -vim-im-switch/ -├── main.ts # Obsidian 插件主文件 -├── vim-im-switch.vim # Vim 插件文件 -├── fcitx-remote.ahk # Windows 支持脚本 -├── fcitx-remote-for-osx/ # macOS 支持工具 -├── deploy.sh # 一键部署脚本 -├── diagnose-im.sh # 诊断脚本 -└── vim-im-switch-plugin/ # 构建输出目录 -``` - -### 调试方法 - -#### Obsidian 插件调试 -插件会在控制台输出关键日志: -``` -🚀 [VimIMSwitch] Loading plugin... -🔤 [VimIMSwitch] ESC → English (saved Chinese) -🈳 [VimIMSwitch] → Chinese -❌ [VimIMSwitch] Error: ... -``` - -#### Vim 插件调试 ```vim -" 查看插件消息 -:messages - -" 检查插件是否加载 -:echo exists('g:fcitx_remote') - -" 手动测试函数 -:call Fcitx2en() -:call Fcitx2zh() +InsertLeavePre -> 记录 Insert 中最后的中/英状态 +InsertLeave -> 切到英文 ABC +InsertEnter -> 延迟恢复上次 Insert 中/英状态 +VimEnter -> 启动后延迟切到英文 ABC ``` -### 贡献指南 -1. Fork 项目 -2. 创建功能分支:`git checkout -b feature/amazing-feature` -3. 提交更改:`git commit -m 'Add amazing feature'` -4. 推送分支:`git push origin feature/amazing-feature` -5. 提交 Pull Request +关键点: -## 📋 版本历史 +- `InsertLeavePre` 必须早于 `InsertLeave` 保存状态,否则 `InsertLeave` 已经切到英文。 +- timer 回调会检查目标状态,避免快速 `i` 后延迟回调把 Normal 模式切回中文。 +- Hammerspoon 是主路径;`im-select` 只在 `hs` 不可用时 fallback。 +- 状态记忆是 script-local 内存,只在当前 Vim 进程内有效。 -| 版本 | 日期 | 主要更新 | -|------|------|----------| -| [v2.0.3](./CHANGELOG.md#203---2025-11-09) | 2025-11-09 | 修复 Normal 模式 ESC 键问题 | -| [v2.0.2](./CHANGELOG.md#202---2025-11-04) | 2025-11-04 | 修复终端兼容性问题 | -| [v2.0.0](./CHANGELOG.md#200---2025-11-04) | 2025-11-04 | 新增 Vim 原生插件支持 | -| [v1.0.8](./CHANGELOG.md#108---2025-01-04) | 2025-01-04 | 智能状态记忆功能 | -| [v1.0.0](./CHANGELOG.md#100---2024-06-01) | 2024-06-01 | 首次发布 | +## 手工测试 -查看 [完整更新日志](./CHANGELOG.md) 获取详细的版本更新历史。 +1. 打开 Vim: -## 🔗 相关链接 + ```bash + vim test-im-switch.txt + ``` -- 📖 [English Documentation](./README_en.md) -- 📝 [更新日志](./CHANGELOG.md) -- 🛠️ [fcitx-remote-for-osx](https://github.com/xcodebuild/fcitx-remote-for-osx) -- 🐛 [问题反馈](https://github.com/yourusername/vim-im-switch +2. 第一次按 `i` 进入 Insert:应切到中文。 +3. 输入中文,按 `Esc`:应切到英文 `ABC`。 +4. 再按 `i`:应恢复中文。 +5. Insert 中手动切到英文,输入英文,按 `Esc`。 +6. 再按 `i`:应恢复英文。 +7. Insert 中手动切回中文,按 `Esc`,再按 `i`:应恢复中文。 +8. 退出 Vim 后重新打开:第一次 Insert 默认中文,这是当前已知限制。 -## 🙏 致谢 +## 验证命令 -感谢以下项目和贡献者: -- [fcitx-remote-for-osx](https://github.com/xcodebuild/fcitx-remote-for-osx) - macOS 输入法控制工具 -- [Obsidian](https://obsidian.md/) - 强大的知识管理工具 -- [Vim](https://www.vim.org/) / [NeoVim](https://neovim.io/) - 经典的文本编辑器 +Vimscript 加载检查: ---- +```bash +vim -Nu NONE -n -es -S "/path/to/vim-im-switch-select.vim" -c 'qa' +``` -
+Hammerspoon 检查: -**如果这个项目对你有帮助,请给个 ⭐ Star!** +```bash +hs -c 'hs.keycodes.setLayout("ABC")' +hs -c 'hs.keycodes.setMethod("Pinyin – Simplified")' +``` -Made with ❤️ for Vim users +## 已知限制 -
+### 跨 Vim 进程不持久记忆 + +当前插件只在同一次 Vim 进程内记忆 Insert 中最后的中/英状态。 + +```text +Insert 中切到英文 -> Esc -> 再 Insert -> 英文 +退出 Vim -> 重新 vi -> 第一次 Insert 默认中文 +``` + +这是当前已知问题,暂不做文件持久化。 + +### 依赖 Hammerspoon 运行 + +稳定路径依赖: + +- Hammerspoon 正在运行 +- `hs` CLI 可用 +- `hs.ipc` 已启用 +- 输入法名称 `Pinyin – Simplified` 与系统一致 + +如果 Hammerspoon 不可用,插件会 fallback 到 `im-select`,但 fallback 只能切 input source ID,不能完全解决 Apple 拼音内部状态问题。 + +### 不模拟 CapsLock / Ctrl-Space + +当前实现不模拟 `Caps Lock`、`Ctrl-Space`、`Shift` 等系统快捷键。 + +原因:这些按键可能被 Vim 接收,导致退出 Insert 或产生额外副作用;之前调试也证明模拟 Caps 不稳定。 + +## 主要文件 + +| 文件 | 说明 | +|---|---| +| `vim-im-switch-select.vim` | 当前原生 Vim 稳定插件 | +| `README.md` | 当前说明文档 | +| `main.ts` | Obsidian 插件入口 | + +## 故障排除 + +### 进入 Insert 仍然输入英文 + +先确认 Hammerspoon method 设置有效: + +```bash +hs -c 'hs.keycodes.setMethod("Pinyin – Simplified")' +``` + +如果返回 `true` 后仍异常,检查 Hammerspoon 日志或系统输入法名称是否不是 `Pinyin – Simplified`。 + +### `hs -c` 报 IPC 错误 + +在 `~/.hammerspoon/init.lua` 加入: + +```lua +require("hs.ipc") +hs.ipc.cliInstall() +``` + +然后 reload Hammerspoon。 + +### fallback 输入法 ID 不正确 + +切到目标输入法后运行: + +```bash +im-select +``` + +将输出值配置到: + +```vim +let g:imselect_chinese_im = '你的输入法ID' +``` + +## License + +MIT License diff --git a/t.md b/t.md new file mode 100644 index 0000000..b66019c --- /dev/null +++ b/t.md @@ -0,0 +1,21 @@ + +jkfsd +及开发京东上看 +开接口饭店司机 +可激发卡的接口 + +福建省多孔菌科 +及开发水电接口 +激发开水大家 +空间饭店 +jkfd +kjfd +开娇娇饭店 +kjfdk +空间发快递 +jfdk +kj + +激发开水大家看 + + diff --git a/test-im-select.sh b/test-im-select.sh deleted file mode 100755 index bc7b123..0000000 --- a/test-im-select.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/bash -# 测试 vim-im-switch-select.vim 插件 - -echo "==========================================" -echo "测试 vim-im-switch-select.vim 插件" -echo "==========================================" -echo "" - -# 检查 im-select 是否可用 -echo "1. 检查 im-select 是否存在..." -if [ -x "./im-select" ]; then - echo "✓ 找到 im-select 可执行文件" - IM_SELECT_CMD="./im-select" -elif command -v im-select &> /dev/null; then - echo "✓ 系统中已安装 im-select" - IM_SELECT_CMD="im-select" -else - echo "✗ 未找到 im-select" - exit 1 -fi - -echo "" -echo "2. 获取当前输入法..." -CURRENT_IM=$($IM_SELECT_CMD) -echo " 当前输入法: $CURRENT_IM" - -echo "" -echo "3. 测试切换到英文输入法..." -$IM_SELECT_CMD com.apple.keylayout.ABC -sleep 0.5 -NEW_IM=$($IM_SELECT_CMD) -if [ "$NEW_IM" = "com.apple.keylayout.ABC" ]; then - echo "✓ 成功切换到英文: $NEW_IM" -else - echo "✗ 切换失败,当前: $NEW_IM" -fi - -echo "" -echo "4. 测试恢复原输入法..." -$IM_SELECT_CMD "$CURRENT_IM" -sleep 0.5 -RESTORED_IM=$($IM_SELECT_CMD) -if [ "$RESTORED_IM" = "$CURRENT_IM" ]; then - echo "✓ 成功恢复到原输入法: $RESTORED_IM" -else - echo "✗ 恢复失败,当前: $RESTORED_IM" -fi - -echo "" -echo "5. 检查 vim-im-switch-select.vim 文件..." -if [ -f "vim-im-switch-select.vim" ]; then - echo "✓ vim-im-switch-select.vim 文件存在" - echo "" - echo " 文件大小: $(wc -c < vim-im-switch-select.vim) 字节" - echo " 函数统计:" - grep -c "^function!" vim-im-switch-select.vim | xargs echo " 函数数量:" -else - echo "✗ vim-im-switch-select.vim 文件不存在" - exit 1 -fi - -echo "" -echo "==========================================" -echo "测试完成!" -echo "==========================================" -echo "" -echo "使用方法:" -echo "1. 复制插件到 Vim 配置目录:" -echo " cp vim-im-switch-select.vim ~/.vim/plugin/" -echo "" -echo "2. 或在 .vimrc 中添加:" -echo " source /Users/gavin/vim-im-switch/vim-im-switch-select.vim" -echo "" -echo "3. 可选配置(在 .vimrc 中):" -echo " let g:imselect_english_im = 'com.apple.keylayout.ABC'" -echo " let g:imselect_chinese_im = '$CURRENT_IM'" -echo "" -echo "4. 测试插件:" -echo " vim test-im-switch.txt" -echo "" diff --git a/test-im-switch.txt b/test-im-switch.txt deleted file mode 100644 index a768282..0000000 --- a/test-im-switch.txt +++ /dev/null @@ -1,11 +0,0 @@ -测试输入法切换功能 -Test IM Switch Functionality - -使用说明: -1. 在 Vim 中加载此文件 -2. 按 i 进入 Insert 模式 -3. 输入中文内容 -4. 按 ESC 退出到 Normal 模式(应该切换到英文输入法) -5. 再次按 i 进入 Insert 模式(应该恢复到中文输入法) - -测试记录: diff --git a/vim-im-switch-select.vim b/vim-im-switch-select.vim index a102a7b..6c847b5 100644 --- a/vim-im-switch-select.vim +++ b/vim-im-switch-select.vim @@ -1,9 +1,8 @@ -" vim-im-switch-select.vim - 智能记忆输入法状态 (使用 im-select) +" vim-im-switch-select.vim - macOS Vim 输入法切换 " Author: Gavin Chan " Modified by: codefalling, enhanced with smart IM state memory -" Version: 2.0.0 -" Description: 记住退出 Insert 模式时的输入法状态,下次进入时自动恢复 -" 使用 im-select 而不是 fcitx-remote 切换输入法 +" Version: 2.3.1 +" Description: Normal 模式固定英文,Insert 模式恢复同进程内上次中/英状态 " --------------------------------------------------------------------- " Load Once: if exists('g:imselect_loaded') @@ -21,19 +20,18 @@ endif if exists('$SSH_TTY') finish endif -if !executable("im-select") + +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:imselect_cmd = exepath('im-select') + let s:keepcpo = &cpo let g:imselect_loaded = 1 set cpo&vim -" --------------------------------------------------------------------- -" 全局变量:记住上次 Insert 模式的输入法名称 -let g:imselect_last_insert_im_name = '' - -" 英文和中文输入法的 ID(可以通过 im-select 获取) +" im-select fallback 使用输入源 ID;Hammerspoon 主路径使用 layout/method 名称。 if !exists('g:imselect_english_im') let g:imselect_english_im = 'com.apple.keylayout.ABC' endif @@ -42,71 +40,167 @@ 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 has('job') - silent! call job_start([s:imselect_cmd, a:im], {'out_io': 'null', 'err_io': 'null'}) + 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 - silent! call system(shellescape(s:imselect_cmd) . ' ' . shellescape(a:im) . ' >/dev/null 2>&1 &') + 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 - call IMSelectSwitch(g:imselect_english_im) + let s:target = 'english' + call s:CancelInsertTimers() + call s:SwitchToEnglish() endfunction -function! IMSelectEnsureTarget(timer) abort - if g:imselect_last_insert_im_name != '' - call IMSelectSwitch(g:imselect_last_insert_im_name) +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 -" 离开 Insert 模式前:保存当前输入法名称 +" 必须在 InsertLeavePre 保存;InsertLeave 中会立即切到英文。 function! IMSelectRemember() abort - let current_im = IMSelectCurrentIM() - if current_im != '' - let g:imselect_last_insert_im_name = current_im - endif + call s:DetectInsertTarget() endfunction -" 离开 Insert 模式:切换到英文 function! IMSelect2en() abort - call IMSelectSwitch(g:imselect_english_im) + let s:target = 'english' + call s:CancelInsertTimers() + call s:SwitchToEnglish() endfunction -" 恢复 Insert 模式输入法 -function! IMSelectRestore(timer) abort - if g:imselect_last_insert_im_name == '' - 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')) + 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! - " 离开 Insert 模式前:先保存输入法状态 autocmd InsertLeavePre * call IMSelectRemember() - " 离开 Insert 模式:切换到英文 autocmd InsertLeave * call IMSelect2en() - " 进入 Insert 模式:恢复上次的输入法状态 autocmd InsertEnter * call IMSelect2zh() - " Vim 启动后:切换到英文 autocmd VimEnter * call timer_start(100, {-> IMSelectStart()}) augroup END endfunction @@ -115,11 +209,11 @@ function! UnBindIMSelectAu() augroup IMSelect autocmd! augroup END + call s:CancelInsertTimers() endfunction call BindIMSelectAu() -" 多光标支持:在选择多个光标前后的处理 function! IMSelect_multiple_cursors_before() call UnBindIMSelectAu() endfunction diff --git a/vim-im-switch.vim.bk b/vim-im-switch.vim.bk deleted file mode 100644 index fbc37b6..0000000 --- a/vim-im-switch.vim.bk +++ /dev/null @@ -1,115 +0,0 @@ -" fcitx-osx.vim - 智能记忆输入法状态 -" Author: Gavin Chan -" Modified by: codefalling, enhanced with smart IM state memory -" Version: 2.0.0 -" Description: 记住退出 Insert 模式时的输入法状态,下次进入时自动恢复 -" --------------------------------------------------------------------- -" Load Once: -if exists('g:fcitx_remote') - 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 -if !executable("fcitx-remote") - finish -endif -let s:keepcpo = &cpo -let g:loaded_fcitx = 1 -set cpo&vim - -" --------------------------------------------------------------------- -" 全局变量:记住上次 Insert 模式的输入法名称 -let g:fcitx_last_insert_im_name = '' - -" 英文和中文输入法的 ID(可以通过 fcitx-remote -n 获取) -if !exists('g:fcitx_english_im') - let g:fcitx_english_im = 'com.apple.keylayout.ABC' -endif - -if !exists('g:fcitx_chinese_im') - let g:fcitx_chinese_im = 'auto-detect' " 将自动检测 -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() 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() abort - if g:fcitx_last_insert_im_name == '' || g:fcitx_last_insert_im_name == g:fcitx_english_im - return - endif - call FcitxSwitch(g:fcitx_last_insert_im_name) -endfunction -" --------------------------------------------------------------------- -" Autocmds: - -" 绑定自动命令 -function! BindAu() - augroup Fcitx - autocmd! - " 离开 Insert 模式:保存输入法状态并切换到英文 - autocmd InsertLeave * call Fcitx2en() - " 进入 Insert 模式:恢复上次的输入法状态 - autocmd InsertEnter * call Fcitx2zh() - " Vim 启动后:切换到英文 - autocmd VimEnter * call timer_start(100, {-> FcitxStart()}) - augroup END -endfunction - -function! UnBindAu() - augroup Fcitx - autocmd! - augroup END -endfunction - -call BindAu() - -" 多光标支持:在选择多个光标前后的处理 -function! Multiple_cursors_before() - call UnBindAu() -endfunction - -function! Multiple_cursors_after() - call Fcitx2en() - call BindAu() -endfunction - -" --------------------------------------------------------------------- -" Restoration And Modelines: -let &cpo=s:keepcpo -unlet s:keepcpo - -let g:fcitx_remote = 1 - -" vim:fdm=expr:fde=getline(v\:lnum-1)=~'\\v"\\s*-{20,}'?'>1'\:1