我碰到的neovim不好实现的几个功能

嗨大家好,我在使用neovim的过程中发现一些它目前不能、不容易做到的功能(即使是通过插件),发出来想着印证下我是不是错了,也借此了解下这些功能在emacs中的实现思路如果可以的话。

在显示一条消息(msg)到cmdline出现n秒后清除:我没有找到在msg出现时触发的事件,也没法全面接管vimscript的echo、echoerr及lua的api.nvim_echo。这样我觉得没办法实现定时清理这类消息的功能。当然,有通过vim.ui_attach({ext_cmdline, ext_messages})接管了cmdline和消息显示的插件,但对于我这个小需求来说也太overkill了。

librime的前端:我曾想找到方法接管neovim的用户输入,然后借助librime+luajit ffi配合floating window实现个rime的前端,但最终没有找到这样的接口。比较接近的是 vim.on_key,但它的工作方式类似于echo hello | tee rime-frontend > nvim,这样我也就不能拦截用户输入。 当然也有基于lsp的实现,不过我觉得延迟会高不少,我宁愿直接用fcitx5-rime。

方便地自定义mode:(n)vim虽然以其模式编辑为特色,但它却没有提供简单的方法来实现自定义mode。 虽然有hydra.nvim可用但看它的实现觉得超级复杂;作为对比,tmux提供了bind-key{ set-option key-table}、i3wm提供了bindsym mode。自己实现的话,我觉得可以通过getchar来读取用户输入加以处理,或者也可以通过临时修改keymaps在自定义模式结束后再复原keymaps,但都没有nnoremap my-mode lhs rhs这样省心

cmdline的popup menu不能显示针对补全项的详细信息:它只能显示补全--foo , 但不能额外显示关于foo 的额外信息。

3 秒后清除

 (message "Some message")
 (run-with-timer 3 nil (lambda () (message nil)))

Emacs RIME

Minor Mode

https://www.gnu.org/software/emacs/manual/html_node/elisp/Defining-Minor-Modes.html

(define-minor-mode hungry-mode
  "Toggle Hungry mode.
Interactively with no argument, this command toggles the mode.
A positive prefix argument enables the mode, any other prefix
argument disables it.  From Lisp, argument omitted or nil enables
the mode, `toggle' toggles the state.

When Hungry mode is enabled, the control delete key
gobbles all preceding whitespace except the last.
See the command \\[hungry-electric-delete]."
 ;; The initial value.
 nil
 ;; The indicator for the mode line.
 " Hungry"
 ;; The minor mode bindings.
 '(([C-backspace] . hungry-electric-delete)))

第一个,容易想到的是定时器 echo 个空字符串清除。但问题是,貌似容易把其它插件的消息给错误清掉?如果重度依赖消息回显,其实可以放到 statusbar 或 winbar。尤其是 cmdline 还要处理用户输入。

第二个想不到使用场景,Neovim 应该尽量保持简单。

自定义 mode 是要什么意思?你可以在某个 mode 下直接 set_keymap,没有比 nnoremap my-mode 复杂多少。

可以通过设置 set-message-functionclear-message-function 来控制 echo area 的行为

不过我猜楼主可能更想要的是没有任何操作 n 秒后清除 echo area,这个可以用 run-with-idle-timer 实现。

定时清空

function MyEcho(msg)
  vim.cmd.echo("\"" .. msg .. "\"")
  local timer = vim.loop.new_timer()
  timer:start(1000,0,vim.schedule_wrap(function()
    vim.cmd.echo([[""]])
  end))
end

不太清楚 emacs,emacs echo area 好像不需要处理用户输入?Neovim 下 cmdline 是用户重度输入区。

Emacs 一样,M-x 会进入 minibuffer,和 echo area 在同一个地方。 如果你想在 Emacs 里面临时在 echo area 显示某个信息,然后恢复之前显示的信息,可以用 with-temp-message,这样就不会覆盖原来的消息了。

挺好。可以把上一条消息存起来,然后稍后 restore。不过回显的地方很多,不必过分纠结。

vim没有emacs那种mode的概念,可以用localleader配合autocmd或者别的东西实现吧

谢谢各位回复!

简单地使用timer每n秒执行一次cmdline清空有点粗暴,它忽视了一些情况:

  • 在用户使用cmdline输入命令时不能执行,且timer应该在退出编辑cmdline时适当延时执行
  • 输出到cmdline的msg我们没办法知道具体时间,如果只是机械地执行n秒清空,很可能一条消息刚出现就被清空了,没机会被用户看到。而且消息出现时,nvim很可能是处于idle状态,即没有用户输入,更基本的是nvim中没有提供类似的事件。

第二个想不到使用场景,Neovim 应该尽量保持简单。

我不认同“nvim要尽量保持简单”这个论断。在neovimconf 2022上说“text editor as a platform”。我也刚巧嵌了个libmpv到nvim中放歌,以替换cmus。

自定义 mode 是要什么意思?

我特意举例了tmux的bind-key key-table跟i3wm的bindsym mode,我觉得是像它们这样的功能。

这个minor mode就是我期望的功能,妙啊。

cmdline 作为输入输出区,我想不到有什么办法可以避免把其它消息错误清除。Emacs 的 echo area 情况类似。

librime 的问题:是否提供了比原生方案更好的体验?tradeoff 是否值得?而后果几乎是立即的。与原生输入法的冲突,自动补全和 snippets 插件的适配…等等。lsp 的方案看起来也显得不那么糟糕了。不过,把 Vim/Emacs 作为 GUI 框架来用,这个现象仍然很有意思。

mode 的话,简单就用 localleader 或 buffer 绑定,过分纠结 mode 没意义。复杂的可能干扰其它插件的,最好自定义事件向全区广播,方便协作。

谢谢回复!我感觉跟你观念差挺多的,昨天看完就琢磨着回复,但最终不知道该怎么回复,也就不说啥了…

第一条回复那里不能改了,所以追加到这里,虽然不够集中。

overlay:我最近想在nvim中实现个tmux display-panes的来切换窗口,都找不到合适的方式,最后是用floating win + winblend(pseudo-transparency)来做,还由于我不用truecolor终端,整体高亮都乱了。类似地,实现个播放器的控制面板也会有类似的问题。

其中被排除的方式:extmark:它只能绑定到buffer上,多个splitwin会显示同一个buffer的同一块位置,因此没法用它来做;给每个splitwin造个floatwin:它比单个fullscreen floatwin的使用更多的floatwins,也没有解决winblend+notermguicolor带来的高亮失真问题;单个fullscreen floatwin+复制每个窗口的内容+高亮:实现成本太高,也不确定最后能不能成,我放弃了。

floatwin 应该是正确的选择。truecolor 与否不影响整体高亮的,除非你在全局空间修改高亮。

extmark 绑定 buffer 就是这样设计的,用来追踪文本变更的。Model 与 View 分开,完全没有问题。鉴于 Neovim 里一个 buffer 可能有多个显示窗口,显然你不应该用它。

这个需求应该早就有人实现了,你搜索一下。

BTW: I admire your negging tactic. :wink:

negging: 故意唱衰某事某物以获得关注的 mind manipulation 策略。

cmdline 的 popup menu 问题,Neovim 里有写没有实现。有些插件比如 cmp 自己实现了:

不过 github 上官方有人在开发原生支持这个功能了。

嗨,我实在难理解你在这个主题里的回复。我列举的这几个就是不好实现啊,我觉得你真的是在杠,死杠。有变通,有tradeoff的实现,我有提呀。

floatwin 应该是正确的选择

“正确”,连同你之前的“Neovim 应该尽量保持简单”,我觉得完全是个人喜好问题。干嘛要把这个设定套给所有人?

truecolor 与否不影响整体高亮的,除非你在全局空间修改高亮。

这个truecolor对应termguicolor,显然你并没有试过开启/关闭它以查看高亮的变化。

这个需求应该早就有人实现了,你搜索一下。

我有搜过。第一个上过twin,使用a-z单字母+floatwin;第二个第一次见,目前来看跟第一个实现基本一致。都包含在我说过的“被排除的方式”。

cmdline 的 popup menu 问题,Neovim 里有写没有实现。… 不过 github 上官方有人在开发原生支持这个功能了。

所以是不好实现吧,在用户这边来看?那个pr我看过,我并不觉得它能解决这个问题:从它的改动来看, 它并没有改变usercmd的定义中complete的数据,还是传给complete的函数只能返回string[],而不能包含对单个补缺项的详细说明。

BTW: I admire your negging tactic. :wink: negging: 故意唱衰某事某物以获得关注的 mind manipulation 策略。

呃,缺乏上下文,无法理解。但我觉得这是好词,感觉被恶意揣度了。

说说之前你回复中

是否提供了比原生方案更好的体验?tradeoff 是否值得?而后果几乎是立即的。与原生输入法的冲突,自动补全和 snippets 插件的适配…等等。lsp 的方案看起来也显得不那么糟糕了。

你没有实践过,也没有人之前做过类似的实现。你的结论“看起来也显得不那么糟糕了”没法推出来啊。我在提出对应问题时也提过lsp,显然我是了解过的,从你的视角来看我大概率是尝试过的。但我不明白你为啥要跟我这么掰扯。

把 Vim/Emacs 作为 GUI 框架来用,这个现象仍然很有意思。

nvim这里不是有好多替换vim.ui的插件吗?vim那里不是有quickui吗?这有啥让你突然发现惊讶觉得有意思的。

mode 的话,简单就用 localleader 或 buffer 绑定 … 复杂的可能干扰其它插件的,最好自定义事件向全区广播,方便协作。

你显然没有用过 nvim-hydra,我说的自定义mode跟它毫无关系。

过分纠结 mode 没意义

这句话太主观了。


这几个问题我都有亲自实现;但我肯定你一个都没有实现过,所以给出的回复全是表面的看法,在我看来。真的,没有调查就没有发言权。

floatwin 应该是正确的选择。你自己用的就是它啊,是说你做了正确的选择,是赞同的话。然后说 extmark 不是设计用来实现这个东西的。然后表达了我的看法,我以为你已经放弃这个。当然,你非要使用 extmark 来实现的话。。。

一个用来轻松活跃气氛的善意玩笑。特意加个 wink 表情,哈哈一笑而过就是了。无法理解,好吧。。。

我确实没写过什么插件,但是说我写不了插件我觉得。。。我回看了一下我之前的回答,确实有些居高临下的意味。让你不舒服的话,表示抱歉。

1 个赞

哈哈,nice!谢谢你的澄清,也抱歉我上个回复有些过火了,当时回复时也是觉得“以斗争求团结则团结存”。 我绝没有说你“写不了插件”的意思啊,况且现在lua插件写起来又很容易#1,只是在当时的语境下我觉得你并没有实践用过相应的api就想当然地给解决方案了。

#1容我跑个题,这对于我来说也是一直以来把玩nvim的乐趣:发现痛点、尝试解决痛点、交流改进实现。第二点在vimscript时代我一直没能觉醒,更没能力修改他人的插件,lua真是开天辟地般的存在,而且能用luajit的ffi来通过其他语言扩展能力。第三点,我之前一直在matrix的neovim room聊,但终归不如用母语交流顺畅达意。)

1 个赞

有人点赞什么鬼?:joy:

哈哈,使用“某某功能某软件是不是不好实现啊?”这样的 negging 方式吸引其拥护者点击,然后解决自己的疑惑/问题。其实是一种很聪明有效的策略(当然不要过度使用就是了),但是通过对讨论者的明显臆测并上升到身份攻击就过分了,有引发血战的风险。No one wants that. :sweat_smile:

Anyway, 如果你还对解决上一问题感兴趣的话:不用 truecolor 终端,那么是 256 色?色彩降级是一定的,但不至于说乱。因为 floatwin 是缩减版的 buffer/win,如果轻易乱掉那意味着 Neovim 完全没法工作。可能原因是使用的主题有问题,全局空间添加了自定义高亮,其它插件的影响等等。因为缺乏信息,我这里只能胡乱猜测。

至于实现方式,我认为 floatwin 是解决方案。extmark 因为与 buffer 绑定,这里并不适用,放弃它是正确的。可能你在 extmarks 上花费了大量时间,谁没有被坑过,谁没有走过弯路呢?说起来全是泪啊 :rofl: 好处是 extmark 上的经验能用到其它方面。