如何让 zsh 的 vi 模式表现得更像 bash 的 vi 模式?

Cha*_*ens 25 zsh command-line

我真的很喜欢 zsh 的一般速度,但是有两件事让我很恼火。

  1. 我必须在击中转义和击中斜线之间稍等片刻才能进入历史搜索(如果它击中斜线太快,它会说zsh: do you wish to see all 514 possibilities (172 lines)
  2. 由于点击a或进入插入模式后A,我无法退格超过进入插入模式的点。

我知道 2 就像经典的 vi,但我更喜欢 vim 风格。

Mar*_*nks 26

(1). 出于某种原因,当涉及到“/”时,bindkey 的行为很奇怪:<esc>紧随其后的/是被解释为<esc-/>. (我前几天观察到这种行为;不太确定是什么原因造成的。)我不知道这是一个错误还是一个功能,如果它可以被禁用,它是否是一个功能,但你可以很容易地解决它.

这个键组合可能绑定到_history-complete-older,这会产生不想要的结果 - 您可以使用它bindkey -L来查看是否是这种情况。

无论如何,如果您不介意牺牲实际 <esc-/>(压在一起,作为和弦)绑定,您可以将其重新绑定到 vi-mode 历史搜索命令,以便键入<esc>后跟/在任何键入时执行相同的操作速度。=)

由于这将被视为一个和弦,它不会具有首先进入 vi 命令模式的效果,因此我们必须确保首先发生这种情况。首先,你需要定义一个函数;fpath如果你使用它,把它放在你的某个地方,否则把它放在你的 .zshrc 中:

vi-search-fix() {
zle vi-cmd-mode
zle .vi-history-search-backward
}
Run Code Online (Sandbox Code Playgroud)

其余的都在你的 .zshrc 中:

autoload vi-search-fix
zle -N vi-search-fix
bindkey -M viins '\e/' vi-search-fix
Run Code Online (Sandbox Code Playgroud)

去应该不错。

(2). 您可以按如下方式修复退格键:

`bindkey "^?" backward-delete-char`
Run Code Online (Sandbox Code Playgroud)

此外,如果您希望其他 vi 样式命令具有类似的行为:

bindkey "^W" backward-kill-word 
bindkey "^H" backward-delete-char      # Control-h also deletes the previous char
bindkey "^U" backward-kill-line            
Run Code Online (Sandbox Code Playgroud)


wjv*_*wjv 13

我只打算解决问题(1)。

您的问题是 KEYTIMEOUT。我引自 zshzle(1):

当 ZLE 从终端读取命令时,它可能会读取一个绑定到某个命令的序列,并且也是一个较长绑定字符串的前缀。在这种情况下,ZLE 将等待一段时间来查看是否输入了更多字符,如果没有(或者它们不再匹配任何更长的字符串),它将执行绑定。此超时由 KEYTIMEOUT 参数定义;它的默认值为 0.4 秒。如果前缀字符串本身未绑定到命令,则不会超时。

0.4 秒是您按下 ESC 后遇到的延迟。解决方法是在 shell 启动文件之一中将 KEYTIMEOUT 设置为 0.01 秒:

export KEYTIMEOUT=1
Run Code Online (Sandbox Code Playgroud)

不幸的是,这会产生连锁反应:其他事情开始出错……

首先,现在 vi 命令模式存在一个问题:输入 ESC 会导致光标挂起,然后您接下来输入的任何字符都会被吞下。这是因为 ESC 在 vi​​ 命令模式下默认没有绑定到任何东西,但有以 ESC 开头的多字符小部件(光标键!)。因此,当您按下 ESC 时,ZLE 会等待下一个字符……然后将其消耗掉。

修复方法是在命令模式下将 ESC 绑定到某些内容,从而确保在 $KEYTIMEOUT 厘秒后将某些内容传递给 ZLE。现在我们可以在命令模式下保持以 ESC 开头的绑定,而不会产生这些不良影响。我将 ESC 绑定到钟形字符,我发现它比自插入更不具有侵入性(并且我的外壳被静音):

bindkey -sM vicmd '^[' '^G'
Run Code Online (Sandbox Code Playgroud)

2017 年更新:

从那以后,我找到了一个更好的绑定 ESC 的解决方案——undefined-key小部件。当我最初写这个答案时,我不确定这个小部件是否在 zsh 中可用。

bindkey -M vicmd '^[' undefined-key

下一个问题:在 vi 插入模式下,默认有一些以 ^X 开头的双键小部件;如果 $KEYTIMEOUT 一直设置为向下,这些将变得不可用。我所做的是在 vi 插入模式下解除 ^X 的绑定(默认情况下它是自插入的);这允许那些两键小部件继续工作。

bindkey -rM viins '^X'
Run Code Online (Sandbox Code Playgroud)

你失去了自我插入的绑定,但你当然可以将它绑定到其他东西。(我没有,因为我没有用它。)

最后一个问题(到目前为止我已经找到了):由于将 $KEYTIMEOUT 设置为正确,我们“丢失”了一些剩余的默认键绑定,即:那些在 vi 插入模式下以 ESC 开头的不是光标键。我个人重新绑定它们以 ^X 开头:

bindkey -M viins '^X,' _history-complete-newer \
                 '^X/' _history-complete-older \
                 '^X`' _bash_complete-word
Run Code Online (Sandbox Code Playgroud)

2018 年更新:

事实证明,不一定需要上面的整个部分(在“2017 年更新”之后)。可以使用以下方法将 META 键设置为与键盘映射中的 ESC 等效:

bindkey -mv

因此,可以解除 ^X 绑定,并通过按 META 作为引导键(现代键盘上的 ALT 或 OPT)来访问在 ESC 中开始的键绑定。

如果您可以阅读Kiddle 等人的《从 Bash 到 Z Shell》一书,则第 78-79 页的第 4 章侧边栏中讨论了键绑定中 ESC 和 META 的等效性。

  • 谢谢!我有点担心,毕竟过了这么久,我们仍然需要本质上是一种 hack 和一种解决方法来使 zsh 功能的核心部分可用! (2认同)