Archive

Archive for November, 2016

Vim 里如何映射 CTRL-h 为 left ?

November 28th, 2016 No comments

很多人习惯在配置文件中映射 CTRL+HJKL 为光标移动,却碰到了一些问题:

inoremap <c-h> <left>
inoremap <c-j> <down>
inoremap <c-k> <up>
inoremap <c-l> <right>

映射后无效或者映射以后按 <BS>键不能删除,这是什么原因呢?

很简单,因为你的终端软件(Xshell, SecureCRT)有些老,默认配置是按下<BS>键以后
发送的是:^H (ASCII 码 0x08),而 ^H在你的 Vim 里被你 inoremap 成 了,所以你按了<BS>会被认为按下了左键。

早在 VT100终端时代,^H(ASCII码 0x08)表示<BS> 而 ^? (ASCII码 0x7f)表示<DEL>。过去 0x7f是留给 DELETE键使用的。而到了 VT220时代,DELETE已经变为 ^[[3~ (ASCII 码 0x1b, 0x5b, 0x33, 0x7e 共4个字节),而 ^? 的 0x7f 换给了我们的<BS>,有些老点版本的终端软件,默认 <BS>还是使用 VT100的 ^H,比如 Xshell 4 Build 0142以前的版本,默认<BS>是发送^H。SecureCRT直到6.x版本还在默认发送 VT100的 ^H。

你需要做的就是改一下终端默认配置而已,大部分不那么陈旧的终端软件,如今都是默认VT220的标准,比如 Xshell4 Build 0142及以后的 Xshell5,Putty,Mac下面的 Terminal.app, iTerm2,Ubuntu下面的 gnome-terminal,他们都是把 <BS> 发送成 ^?

你可以在你服务端下面查看下默认的键位设置:

$ stty -a

现在所有 Linux服务器的 erase (bs)基本都是 ^? 了(如果链接到非 Linux老操作系统 erase不是这个的话,需要改一下,可以在系统层改,也可以vim里面 set t_kb=…),Vim里面也是认可 ^?的,可老旧的终端软件却默认发送 ^H,不过好在他们都支持修改:

Read more…

Categories: 随笔 Tags:

Vim 中正确使用 Alt映射

November 19th, 2016 No comments

最简单的做法是:首先将终端软件的 “使用 Alt键作为 Meta键” 的功能打开,其次将 Alt的模式改为 ESC+字母,意思是如果你在终端下按下 ALT+X,那么终端软件将会发送 <ESC>x 两个字节过去,字节码为:0x27, 0x78。如果你使用过 NeoVim 或者 Emacs的话,这一步应该早就做过了。

XShell4 终端设置:

SecureCRT:终端设置

其他终端软件里:

  • Putty/MinTTY 默认ALT+X 就是发送 <ESC>x过去
  • Mac下面的 iTerm2/Terminal.app 需要跟 XShell / SecureCRT一样设置一下
  • Ubuntu 下面的 GnomeTerminal 默认也是发送 <ESC>x过去的
  • 任意平台下面的 xterm 可以配置 ~/.Xdefaults 来设置这个行为。

这样的话,不管是 NeoVim 还是 Emacs都识别了,Vim 的话,你可以简单这样:

noremap <ESC>x :echo "ALT-X pressed"<cr>

注意 ESC后面是小写 x,如果你是大写 X就变成 ALT+SHIFT+X了。于是你在 Vim 中,ALT+X就能看到后面输出的那句话了。看到这里你也许要问:这和我快速按下 ESC再马上按下 x键有什么区别?答案是没有区别,在终端里面这两个操作是一模一样的键盘码传送过去。

就像你不设置 ttimeoutttimeoutlen,然后快速在 VIM 里面按下 <ESC>OP,Vim 会以为你按下了 一样。因为 F1 的终端下字符串序列就是 <ESC>OP ,而你在 Insert 模式下面马上 <ESC> 退出并按下大写 O ,向上插入一行,Vim 将会等待一秒钟(默认 timeout ),确认后面没有一个 P,才会进一步确认不是F1,而是向上插行。

所以更好的做法是直接按照 <M-x> 进行映射,并且告诉 vim,<M-x>的键盘序列码是多少,然后再加上 ttimeoutlen超时:

noremap <M-x> :echo "ALT-X pressed"<cr>
exec "set <M-x>=\ex"
set ttimeout ttimeoutlen=100

这样做的好处是告诉 Vim, ESC+x是一个完整的按键码,并且需要在 100ms以内进行判断,即,如果收到 ESC,并且100ms以后没有后续的x,则是认为是一个单独的ESC键,退出 INSERT模式,否则认为是按下了 ALT+X,这和 Vim处理方向键,处理 F1, F2等功能键的原则是相同的,具体见 :h set-termcap:

                            *:set-termcap* *E522*
For {option} the form "t_xx" may be used to set a terminal option.  This will
override the value from the termcap.  You can then use it in a mapping.  If
the "xx" part contains special characters, use the <t_xx> form: >
    :set <t_#4>=^[Ot
This can also be used to translate a special code for a normal key.  For
example, if Alt-b produces <Esc>b, use this: >
    :set <M-b>=^[b
(the ^[ is a real <Esc> here, use CTRL-V <Esc> to enter it)
The advantage over a mapping is that it works in all situations.

You can define any key codes, e.g.: >
    :set t_xy=^[foo;
There is no warning for using a name that isn't recognized.  You can map these
codes as you like: >
    :map <t_xy> something

这是最完善的 ALT键解决方案了,网上有个流传很广的方式是 map <ESC>x <M-x> 然后你后面再映射 <M-x> 时就能被触发到,这是错误的方法,不能使用更短的 ttimeoutlen来识别键盘码,而会使用普通组合键的 timeoutlen来判断,后者一般设置为默认 1000毫秒,所以这样把 26个字母映射后,你 ESC退出 INSERT模式后,一秒内按了任何一个字母就会被当成 ALT+X来处理了,经常误操作。

如此,我们可以在 .vimrc中 for循环将 <M-0><M-9><M-a><M-z>等全部 set一遍,vim中即可正常使用。

早年的终端,处理ALT组合键时,是将单个字符的最高位设置成 1,这也是 vim的默认处理方式,如今 rxvt终端也支持这种模式(见上图 SecureCRT设置面板)。这种键盘码不是 ESC+x的模式,可以直接识别,不需要计算超时,缺点是支持终端较少,对终端编码格式有依赖。

如今基本上 <ESC>+的模式基本成为大部分终端的默认方式,主流操作了,详细可以看::h map-alt-keys 以及 :h set-termcap 两个文档有具体说明,关于超时部分可以见(:h esckeys

当然,如果你真能在100ms内连续按下 ESC和 X的话,那是另外一回事情了,你可以调短 ttimeoutlen到50ms解决,但是不建议该值低于 25ms,否则在低速网络情况下,你按功能键会被vim错误识别成几个单独的按键序列。

对于牛逼的打字员,也许 ttimeoutlen 设置的再短也没有用,当他瞬间快速 ESC退出插入模式并同时按下u时,他可能发现自己居然还呆插入模式中,因为被识别称 ALT+u了,而我们之前把 开头的A-Z全部都设置了一遍,这样放错的概率会大很多。

好了,下面是终极解决方法,重新定义终端软件的按键序列码:

Read more…

Categories: 随笔 Tags: