Open Zhniing opened 9 months ago
感谢你的 pr,我有两个点想详细了解一下:
还有一点我需要提前说一下,我觉得这个 pr 里的内容有点过于复杂和精细了,就我之前调研过的用户反馈来看,大部分用户可能完全不会需要这些精细的设置,他们也多半不了解 ModeChanged
这个参数(了解 VimEnter
、FocusGained
之类的其实都不多 ),所以后面我可能会跟你一起做一些修改,比如将新增的 ModeChanged
从 set_default_events
剥离出去,成为完全独立的设置,最好是跟 set_default_events
互相不影响。这样做的目的是保持在默认设置的情况下,现有用户的配置完全不需要改动,而对 ModeChanged
有需求的用户(比如你),可以通过自定义 ModeChanged
之类的配置实现自己的需求,我想这样对你和其他用户来说都会比较方便
(为了方便描述,我们用符号P
代表插件当前保存的上一个输入法状态)
Insert
模式,使用中文输入法(自动切换到:中文,不会更新P
)ESC
切换到Normal
模式(自动切换到:英文,P
更新为:中文):w
保存文件(不会切换,不会更新P
)Normal
模式(自动切换到:英文,P
更新为:英文)Insert
模式,继续输入中文(无法自动切换到中文,因为P
在第5步变成了英文)那么,只要不在命令行模式下保存输入法,就能解决我的问题,于是有了save_state_patterns
我使用packer.nvim
来管理插件,通过逐个禁用插件,发现问题来自coc.nvim
插件配置,我把回车键<cr>
映射为了一条命令。感谢您提供的思路。
那么,exclude_patterns
是否还有存在的必要?它让我们用通配符*
匹配ModeChanged
的同时,还能指定一些例外,而不是把所有情况全部写在配置中。如果您觉得不需要,我们可以把它去掉。
大部分用户可能完全不会需要这些精细的设置,他们也多半不了解
ModeChanged
这个参数
set_default_events
和set_previous_events
能够完全兼容之前的配置,原来的事件写法和ModeChanged
写法可以同时存在:
set_default_events = { "VimEnter", "FocusGained", ModeChanged = {"i:*", "c:*", "R:*"} },
也就是说,可以让默认配置与之前保持一致:
set_default_events = { "VimEnter", "FocusGained", "InsertLeave", "CmdlineLeave" },
set_previous_events = { "InsertEnter" },
这样就能确保在默认设置的情况下,现有用户的配置完全不需要改动
然后,需要ModeChanged
的配置默认为空:
save_state_patterns = {}, -- 为空时,与以前的行为一致,还需要修改一下代码
exclude_patterns = {},
(通过注释或其他形式)告知用户ModeChanged
的配置写法,让有需求的用户自行配置
如果需要,我可以更新一下pr,把默认配置改回原来的样子
我理解你的意思了,你只是需要在某些事件触发的时候,切换到默认输入法,但是不更新当前输入法的状态。
具体来说就是:在 CmdlineLeave
事件发生时,在 restore_default_im
函数里,只调用 https://github.com/keaising/im-select.nvim/blob/8cf35fae61dd777a453541e765b8a375270ae356/lua/im_select.lua#L159-L161 切换到默认输入法,不调用 https://github.com/keaising/im-select.nvim/blob/8cf35fae61dd777a453541e765b8a375270ae356/lua/im_select.lua#L156-L157 将缓存里保存的 im_select_saved_state
值改为默认输入法
那我们这样改可以吗?
增加一个参数 keep_state_when_set_default_events = { }
,默认是空的,你可以使用下面的配置
config = function()
require("im_select").setup({
set_previous_events = { "InsertEnter" },
set_default_events = { "VimEnter", "InsertLeave", "CmdlineLeave" },
keep_state_when_set_default_events = { "CmdlineLeave" },
})
end,
最终实现的效果就是在 "VimEnter", "InsertLeave", "CmdlineLeave"
三个事件发生时会还原成默认输入法,但是 CmdlineLeave
里还原成默认输入法时并不是把默认输入法当作一个状态存到缓存里,下次还原上一次保存的输入法状态时就不会是默认输入法了
你可以试试这个分支:https://github.com/keaising/im-select.nvim/tree/keep_state_event
我觉得这样简单点,不用引入 ModeChanged
,使用的参数和工具(依然是 vim 里的各种事件)跟现有的保持一致
感谢您提供的方案,但在使用过程中发现一个小问题:补全的时候,会出现模式转换,触发restore_default_im
函数,导致中文输入法丢失
具体复现步骤如下(已禁用除了im-select
之外的所有插件和设置):
初级
初
Ctrl
+n
选择补全这时,会触发一系列模式转换(ModeChanged
)事件,依次为:i:c
c:i
ic:i
i:ix
ix:i
(用上下方向键选择补全只会触发i:c
和c:i
事件)i:c
事件和c:i
事件之间,会触发restore_default_im
函数(应该是因为i:c
属于InsertLeave
事件)restore_previous_im
函数在~/.config/nvim/init.lua
中加入如下代码,可以监测到模式转换:
vim.api.nvim_create_autocmd("ModeChanged", {
pattern = {"*:*"},
callback = function (args)
os.execute("notify-send " .. args.match)
end,
})
当然,只输入中文几乎不会用到补全,但如果在中文穿插数字或字母,就有可能用到补全
所以,我还是建议引入ModeChanged
,然后对之前的pr做了一些改动:
exclude_patterns
选项autocmd
绑定不变,为ModeChanged
绑定新的autocmd
,代码实现在这里restore_default_im
函数中独立出来,由save_state_events
触发(默认与set_default_events
一致,以前的用户不用修改配置),这样就让用户能够自由配置您看这样是否满足了兼容性要求?
另外,我目前使用的配置如下:
set_default_events = { "VimEnter", ModeChanged = { "[iRc]*:[nvV\x16sS]*" } },
save_state_events = { ModeChanged = { "[iR]*:[nvV\x16sS]*" } },
set_previous_events = { ModeChanged = { "[nvV\x16sS]*:[iR]" } }, -- \x16 means Ctrl-V
在save_state_events
中少写一个c
就能实现我的需求
不好意思,前几天新冠了一直没上线
你可以给我一个最小的可复现的配置吗?我试了一下没有复现出来你说的“在补全的时候会触发 InsertLeave”的场景,按理说补全的时候摁 Ctrl+N 是不会脱离 Insert mode 的,也就不会触发 restore_default_im
函数
抱歉之前没看到消息
不好意思,之前是我测试做得有问题。
按照Lazy.nvim的Repro试了一下,确实不会脱离Insert模式,但是会触发CmdlineLeave事件,导致切换回默认的英文输入法。
我之前测试时只是把其他插件(包括coc.nvim)注释掉了,没有注意到coc.nvim还在生效。
执行:CocDisable
禁用coc.nvim后,补全时就不再触发InsertLeave和CmdlineLeave事件,一切正常。
最小的复现配置repro.lua
如下:
-- DO NOT change the paths and don't remove the colorscheme
local root = vim.fn.fnamemodify("./.repro", ":p")
-- set stdpaths to use .repro
for _, name in ipairs({ "config", "data", "state", "cache" }) do
vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
end
-- bootstrap lazy
local lazypath = root .. "/plugins/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", lazypath, })
end
vim.opt.runtimepath:prepend(lazypath)
-- install plugins
local plugins = {
"folke/tokyonight.nvim",
-- add any other plugins here
{
"keaising/im-select.nvim",
branch = "keep_state_event",
opts = {
set_previous_events = { "InsertEnter" },
set_default_events = {
"VimEnter",
"InsertLeave",
"CmdlineLeave" -- 注释这行就能防止补全时自动切回默认的英文输入法
},
keep_state_when_set_default_events = { "CmdlineLeave" },
},
},
{
'neoclide/coc.nvim',
branch = 'release',
}
}
require("lazy").setup(plugins, {
root = root .. "/plugins",
})
vim.cmd.colorscheme("tokyonight")
-- add anything else here
vim.api.nvim_create_autocmd("ModeChanged", {
pattern = {"*:*"},
callback = function (args)
os.execute("notify-send " .. args.match)
end,
})
vim.api.nvim_create_autocmd("CmdlineLeave", {
callback = function ()
os.execute("notify-send CmdlineLeave")
end,
})
vim.api.nvim_create_autocmd("InsertLeave", {
callback = function ()
os.execute("notify-send InsertLeave")
end,
})
通过nvim -u repro.lua
启动,然后复现所需的具体操作步骤和之前一样。
不知道怎么配置coc.nvim才能让它不触发CmdlineLeave(看了下coc.nvim的文档,感觉不大可能),目前还不打算放弃使用coc.nvim 可能还是只有ModeChanged才能实现我的需求 如果您觉得没有必要引入ModeChanged,那这个pr就算了吧,我先用着自己的fork
非常感谢您的积极维护
上面的 notify-send
是哪个应用?我尝试执行 nvim -u repro.lua repro.lua
的时候会弹出很多提示
sh: notify-send: command not found
notify-send
是一个发送桌面通知的程序,这里用来监听模式切换的事件。
我这里好像是Linux自带的。
在Ubuntu上可以使用命令安装:
apt-get install libnotify-bin
其他平台的安装可以参考这里
嗯嗯,我在 macOS 上用的这个 https://formulae.brew.sh/formula/terminal-notifier ,代码改成下面这样
我能复现你说的在 coc 里用 ctrl+p/n 选择补全的时候会触发 CmdlineLeave 的问题,我感觉有点奇怪,我再试试我自己的配置
-- DO NOT change the paths and don't remove the colorscheme
local root = vim.fn.fnamemodify("./.repro", ":p")
-- set stdpaths to use .repro
for _, name in ipairs({ "config", "data", "state", "cache" }) do
vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
end
-- bootstrap lazy
local lazypath = root .. "/plugins/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", lazypath, })
end
vim.opt.runtimepath:prepend(lazypath)
-- install plugins
local plugins = {
{
"keaising/im-select.nvim",
branch = "keep_state_event",
opts = {
set_previous_events = { "InsertEnter" },
set_default_events = {
"VimEnter",
"InsertLeave",
"CmdlineLeave" -- 注释这行就能防止补全时自动切回默认的英文输入法
},
keep_state_when_set_default_events = { "CmdlineLeave" },
},
},
{
'neoclide/coc.nvim',
branch = 'release',
}
}
local cmd_count = 0
local insert_count = 0
require("lazy").setup(plugins, {
root = root .. "/plugins",
})
-- vim.api.nvim_create_autocmd("ModeChanged", {
-- pattern = {"*:*"},
-- callback = function (args)
-- os.execute("terminal-notifier -title ModeChanged -message " .. args.match)
-- end,
-- })
vim.api.nvim_create_autocmd("CmdlineLeave", {
callback = function ()
os.execute("terminal-notifier -title Cmd -message CmdlineLeave" .. cmd_count)
cmd_count = cmd_count + 1
end,
})
vim.api.nvim_create_autocmd("InsertLeave", {
callback = function ()
os.execute("terminal-notifier -title Cmd -message InsertLeave" .. insert_count)
insert_count = insert_count + 1
end,
})
好像还真是 Coc 的一个问题,之前也有人提到过类似的问题:https://github.com/neoclide/coc.nvim/issues/3290#issuecomment-1010969259 ,在他的注释里提到了一句:
" Make <S-CR> auto-select the first completion item and notify coc.nvim to
" format on shift-enter, <S-CR> could be remapped by other vim plugin
" because it will trigger CmdLineLeave to affect the fcitx-remote, so I
" only use it when I type codes
嗯嗯,我试过按照他README给的配置,改用TAB
来选择补全,也会触发CmdlineLeave
;另外,如果按照这个配置使用<CR>
来确认补全的话,正常按回车键都会触发CmdlineLeave
repro.lua
如下:
-- DO NOT change the paths and don't remove the colorscheme
local root = vim.fn.fnamemodify("./.repro", ":p")
-- set stdpaths to use .repro
for _, name in ipairs({ "config", "data", "state", "cache" }) do
vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
end
-- bootstrap lazy
local lazypath = root .. "/plugins/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", lazypath, })
end
vim.opt.runtimepath:prepend(lazypath)
-- install plugins
local plugins = {
"folke/tokyonight.nvim",
-- add any other plugins here
{
"keaising/im-select.nvim",
branch = "keep_state_event",
opts = {
set_previous_events = { "InsertEnter" },
set_default_events = {
"VimEnter",
"InsertLeave",
"CmdlineLeave" -- 注释这行就能防止补全时自动切回默认的英文输入法
},
keep_state_when_set_default_events = { "CmdlineLeave" },
},
},
{
'neoclide/coc.nvim',
branch = 'release',
config = function ()
-- Some servers have issues with backup files, see #649
vim.opt.backup = false
vim.opt.writebackup = false
-- Having longer updatetime (default is 4000 ms = 4s) leads to noticeable
-- delays and poor user experience
vim.opt.updatetime = 300
-- Always show the signcolumn, otherwise it would shift the text each time
-- diagnostics appeared/became resolved
vim.opt.signcolumn = "yes"
local keyset = vim.keymap.set
-- Autocomplete
function _G.check_back_space()
local col = vim.fn.col('.') - 1
return col == 0 or vim.fn.getline('.'):sub(col, col):match('%s') ~= nil
end
-- Use Tab for trigger completion with characters ahead and navigate
-- NOTE: There's always a completion item selected by default, you may want to enable
-- no select by setting `"suggest.noselect": true` in your configuration file
-- NOTE: Use command ':verbose imap <tab>' to make sure Tab is not mapped by
-- other plugins before putting this into your config
local opts = {silent = true, noremap = true, expr = true, replace_keycodes = false}
keyset("i", "<TAB>", 'coc#pum#visible() ? coc#pum#next(1) : v:lua.check_back_space() ? "<TAB>" : coc#refresh()', opts)
keyset("i", "<S-TAB>", [[coc#pum#visible() ? coc#pum#prev(1) : "\<C-h>"]], opts)
-- Make <CR> to accept selected completion item or notify coc.nvim to format
-- <C-g>u breaks current undo, please make your own choice
keyset("i", "<cr>", [[coc#pum#visible() ? coc#pum#confirm() : "\<C-g>u\<CR>\<c-r>=coc#on_enter()\<CR>"]], opts)
end
}
}
require("lazy").setup(plugins, {
root = root .. "/plugins",
})
vim.cmd.colorscheme("tokyonight")
-- add anything else here
vim.api.nvim_create_autocmd("ModeChanged", {
pattern = {"*:*"},
callback = function (args)
os.execute("notify-send " .. args.match)
end,
})
vim.api.nvim_create_autocmd("CmdlineLeave", {
callback = function ()
os.execute("notify-send CmdlineLeave")
end,
})
vim.api.nvim_create_autocmd("InsertLeave", {
callback = function ()
os.execute("notify-send InsertLeave")
end,
})
我也去翻了一下 Coc 的文档,好像没有提这个的,具体为什么会在补全的时候触发 CmdlineLeave 就不得而知了,可能得读一下 Coc 的代码才知道能不能改,估计不太好弄
说实话我一直不想引入 ModeChanged 的原因是我觉得这玩意太复杂了,我读了好久才弄明白它是干什么的以及该如何用,至于用起来还是直接抄的你的代码,我想尽量让这个插件简单点,让新来的用户一眼就能看明白里面的逻辑然后在有需求的时候自己修改它(就像你现在做的一样)。所以我不打算合并这个 PR(并不是说你的 PR 有问题,只是单纯我对功能复杂性的偏好导致的)
不过你发现的的确是个兼容性问题,稍后我会把我们的对话和找到的问题翻译一下放在后面,然后在 README 里提示一下如果用 Coc 的话需要把 CmdlineLeave 排除掉
嗯嗯,可以。这个插件的代码确实写得不错,我改代码的时候也觉得读起来很舒服,写这个PR的过程中也学到了很多,还是非常感谢您
结论: Coc 的 bug/feature 导致在 insert mode 里每次用 Coc 补全时切换了候选项,都会导致输入法被切换到默认输入法
通过后面附上的脚本配置可以复现,在使用 Coc 补全的时候(不管快捷键是使用Ctrl+n/p 还是 Tab,应该是更底层的命令触发的),每一次切换补全项都会触发一次 CmdlineEnter 和 CmdlineLeave,这就导致在下列两种情况下会遇到这个问题:
im-select.nvim
的默认配置,set_default_events
里有 CmdlineLeave
但 set_previous_events
里没有 CmdlineEnter
这是一个不兼容的情况,但是目前本插件无法解决,一个可用的解决方案是使用 https://github.com/Zhniing/im-select.nvim ,这个 fork 使用了 https://neovim.io/doc/user/autocmd.html#ModeChanged 来精确匹配切换模式,可以避开 Coc 的这个问题,配置需要修改为
set_default_events = { "VimEnter", ModeChanged = { "[iRc]*:[nvV\x16sS]*" } },
save_state_events = { ModeChanged = { "[iR]*:[nvV\x16sS]*" } },
set_previous_events = { ModeChanged = { "[nvV\x16sS]*:[iR]" } }, -- \x16 means Ctrl-V
复现 Coc 切换问题的 repro.lua
Ubuntu: https://github.com/keaising/im-select.nvim/pull/19#issuecomment-1871800455 macOS: https://github.com/keaising/im-select.nvim/pull/19#issuecomment-1875165784
Conclusion: A bug/feature in Coc causes the input method to be switched to the default input method every time a candidate is switched when using Coc Completion in insert mode.
This can be reproduced by the following script configuration, when using Coc complement (regardless of whether the shortcut is Ctrl+n/p or Tab, it should be triggered by a lower level command), every time you switch between the complement items, it triggers CmdlineEnter
and CmdlineLeave
once, which results in the problem in the following two scenarios:
im-select.nvim
is used.CmdlineLeave
in set_default_events
but not CmdlineEnter
in set_previous_events
.This is an incompatibility, but currently this plugin can't solve it. An available solution is to use https://github.com/Zhniing/im-select.nvim, this fork uses https://neovim.io/doc/user/autocmd.html#ModeChanged to accurately match the switching mode, you can bypass this problem in Coc, the configuration needs to be changed to
set_default_events = { "VimEnter", ModeChanged = { "[iRc]*:[nvV\x16sS]*" } },
save_state_events = { ModeChanged = { "[iR]*:[nvV\x16sS]*" } },
set_previous_events = { ModeChanged = { "[nvV\x16sS]*:[iR]" } }, -- \x16 means Ctrl-V
You can reproduce this issue by
Ubuntu: https://github.com/keaising/im-select.nvim/pull/19#issuecomment-1871800455 macOS: https://github.com/keaising/im-select.nvim/pull/19#issuecomment-1875165784
I have been using this plugin for a while now, and enjoying the convenience of automatically switching IM, thanks for your work.
However, some specific needs cannot be met:
CmdlineLeave
is valuable, because we may search for some text in the command line. But, in most cases, we just want to execute some commands (e.g.:w
), and only the default IM will be used. So, we may not expect the default IM to override the previous IM.CmdlineEnter
(patterni:c
) andCmdlineLeave
(patternc:i
) events will be triggered when I type theEnter
key in the insert mode, which may cause unexpected IM state switching ifCmdlineLeave
is included inset_default_events
.Workaround:
The ModeChanged event supports the pattern match to identify events precisely:
i:n
,R:n
), which could be configured insave_state_patterns
.i:c
,c:i
), which could be configured inexclude_patterns
.这个插件非常棒,非常方便,感谢作者的积极维护。
用一段时间后,发现了一些问题:
CmdlineEnter
(i:c
模式)和CmdlineLeave
(c:i
模式)事件,如果在set_default_events
中配置了CmdlineLeave
就会自动切换为英文输入法。解决方案:
ModeChanged支持使用模式匹配来精确识别事件:
save_state_patterns
里面配置exclude_patterns
里面配置