在 VSCode 的中文环境下使用 Vim

2023-03-02

VSCode 是微软推出的一款轻量级文本编辑器,主要特点为插件丰富,可自定义程度强。 Vim 是一种常见的编辑模式,受到几乎所有编辑器和 IDE 的支持;在熟悉键位之后有着非常不错的编辑效率。 另外,Vim(或者 Vi)几乎是你在 Linux 服务器环境中唯一可用的编辑器(除了 Nano)。

然而,Vim 对中文环境相当不友好。 在撰写中文文档时,由于 Normal 模式下的快捷键会被输入法所拦截,导致每在 Normal 和 Insert 模式之间切换一次就要开关一次输入法。 此外,大多数 Vim 不支持中文分词,导致原本非常好用的 bnw 这些快捷键会一下跳过整个句子。

幸运的是,VSCode 社区提供了丰富的插件,能够解决大多数问题。 不幸的是,一小部分问题仍然只能通过比较 Hack 的手段解决。

插件配置

这里推荐 VSCodeVim 这个插件,直接在插件窗口中搜索 vim 就可以找到。 这个插件无需配置就可以满足大多数人的使用需求。 如果你想进行更加个性化的配置,可以参考其 Github 仓库中的配置指南。

Jieba 插件提供了中文分词支持。 简单配置即可:

"vim.normalModeKeyBindings": [
    {
        "before": [
            "w"
        ],
        "commands": [
            "jieba.forwardWord"
        ]
    },
    {
        "before": [
            "b"
        ],
        "commands": [
            "jieba.backwardWord"
        ]
    },
    {
        "after": [
            "i"
        ],
        "before": [
            "c",
            "w"
        ],
        "commands": [
            "jieba.killWord"
        ]
    },
    {
        "after": [
            "i"
        ],
        "before": [
            "c",
            "b"
        ],
        "commands": [
            "jieba.backwardKillWord"
        ]
    },
    {
        "before": [
            "d",
            "w"
        ],
        "commands": [
            "jieba.killWord"
        ]
    },
    {
        "before": [
            "d",
            "b"
        ],
        "commands": [
            "jieba.backwardKillWord"
        ]
    }
],
上述配置允许 wbcwcbdwdb 在中文环境下正常运行,但 WBeE 指令不行,原因是 Jieba 插件没有提供相对应的指令。

中文输入法切换

这是最难的部分。 首先,我们必须有让 VSCode 识别当前输入法和自己切换输入法的方式。 如果你正在使用两种输入法(例如,搜狗拼音和英文输入法),那么可以使用 im-select 这个工具。 但是,如果你正在使用同一种输入法中的不同模式(例如,微软拼音的中文模式和英文模式),你就需要一些另外的方法。 这里是一种使用 AutoHotkey 的方法。 但我不想引入第三方软件,所以把它移植到了 C# 上:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

class Program
{
    private const int WM_IME_CONTROL = 0x283;

    private const int IMC_GETCONVERSIONMODE = 0x1;
    private const int IMC_SETCONVERSIONMODE = 0x2;

    [DllImport("imm32.dll")]
    private static extern IntPtr ImmGetDefaultIMEWnd(IntPtr hWnd);

    [DllImport("user32.dll")]
    private static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll")]
    private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);

    static void Main(string[] args)
    {
        IntPtr hIME = ImmGetDefaultIMEWnd(GetForegroundWindow());
        if (args.Length > 1)
            Console.Error.WriteLine("ImSwitch should not be used with more than one argument.");
        else if (args.Length == 0)
            Console.WriteLine(SendMessage(hIME, WM_IME_CONTROL, new IntPtr(IMC_GETCONVERSIONMODE), IntPtr.Zero));
        else if (args.Length == 1)
            SendMessage(hIME, WM_IME_CONTROL, new IntPtr(IMC_SETCONVERSIONMODE), new IntPtr(int.Parse(args[0])));
    }
}

注意该方法仅在 Windows 11 上测试过,并不保证对先前版本的 Windows 有效。 关于其中的魔法数值,可以参考 IMC_GETCONVERSIONMODEIMC_SETCONVERSIONMODE

编译之后,就可以在 VSCodeVim 中配置:

"vim.autoSwitchInputMethod.enable": true,
"vim.autoSwitchInputMethod.defaultIM": "0",
"vim.autoSwitchInputMethod.obtainIMCmd": "D:\\Drivers\\ImSwitch.exe",
"vim.autoSwitchInputMethod.switchIMCmd": "D:\\Drivers\\ImSwitch.exe {im}",

这样就能够在进入 Normal 模式时自动选择英文输入法,它还能够记忆 Insert 模式下的输入法选择。