Emacs中的 return, RET, Enter, Ctrl-m解析

在Emacs修改键绑定的时候, 大多数非常简单, 但是有些则不那么简单, 有几个键是非常难搞的, 这次关注的重点是Ctrl-m.

ASCII中有两个换行符: newline 和 Carriage Return, 也写作 \n 和 \r, ASCII分别是10和13也就是常说的0A和0D. Emacs里面按Enter键的时候产生的是"<return>", 这是 Emacs内部对Enter键的记法. 那么我们似乎可以认为这个就是Carriage Return. 因为很多时候Enter键就叫做Carriage Return.

但是在往下看, Emacs还有一个记法"RET", 这个对应Ctrl-m. 实际上这个才是ASCII码为13的那个换行, 就是Carriage Return, 即\r. 那么"<return>"应该是newline了, 即\n.

默认配置下按Enter键的时候实际产生的是"RET", 是通过translate得到的. 可以通过帮助来证实这一点: 按F1然后输入?然后输入c, 此时会在mini buffer中提示你输入按键组合, 此时按Enter键. mini buffer中会提示输入的是RET, (translated from <return>).

RET和<return>都有换行的效果, 但是Emacs对待两者的态度不同, 在默认配置下, <return>只是一个摆设, 当接受到<return>的时候, 总是先转换为RET. 除了单纯的换行, RET被 赋予了提交命令的角色, 例如mini buffer里面输入命令之后按Enter键执行, 或者ielm, ELISP的REPL当中也是用RET来执行表达式的, 还有在Emacs自带的教程当中在链接之间进行跳转的时候全部是用RET的实现的.

绝大多数时候我们是使用Enter键的, 但是实际起作用的是Ctrl-m, Ctrl-m和RET的绑定已经硬编码到Emacs的内置ELISP代码库中了. 在emacs根目录的lisp目录执行如下搜索就可以知道

 
grep "\"\\C-m\"" -r *
或者
 
grep "\"\\r\"" -r *
 

这样导致的结果是改写Ctrl-m的键绑定的时候将会非常麻烦. 因为系统中已经有太多东西和他绑定在一起了. 例如我想用Ctrl-m来代替Backspace, 即删除字符. 如果这样写

 
(global-set-key "\C-m" 'delete-backward-char)
 

那么Enter键同时被改了, 因为Enter键总是translate为RET, 而RET和Ctrl-m是等价的.

所以我们会加一句

 
(global-set-key (kbd "<return>") 'newline)
 

这样Enter键换行, Ctrl-m删除字符. 这样改了之后Enter仅仅是换行, 没有了RET的语义, 而mini buffer, IELM, 还有很多其他地方都需要RET来提交命令或者跳转. 此时在mini buffer中输入命令后按Enter, 会在mini buffer里面换行, 而不会执行命令.

原因就在于Emacs内置将提交命令的操作和Ctrl+m也即RET绑定在一起了. 这个定义在minibuffer.el中.

 
  (define-key map "\r" 'exit-minibuffer)
 

修改的方法

 
(define-key minibuffer-local-map (kbd "<return>") 'exit-minibuffer)
 

同理下面的地方也要改

帮助文档中跳转操作:

 
(eval-after-load 'help-mode
  '(define-key help-mode-map (kbd "<return>") 'help-follow-symbol)
)
 

Emacs自带的教程文档中的超链接

 
(define-key button-map (kbd "<return>") 'push-button)
 

IELM模式下

 
(add-hook 'ielm-mode-hook
  (lambda ()
     (define-key ielm-map (kbd "<return>") 'ielm-return)))