Blog

用 Emacs LISP 写程序

Mac OS X 下我常用的终端程序中,Terminal.app 和 iTerm 都有一个 command+k 的快捷键,作用是清除终端里的字符,回到只显示一个 shell 提示符的状态。这个操作是终端软件自己的,而不是执行 clear 命令,因此效率上会高一些,在远程终端里尤其明显。而且按两个键远比输入五个字符再按回车要舒服,因此我很快就习惯了这个快捷键了。

我平时喜欢用 emacs 来编辑东西,因此 emacs 经常开着。有时需要使用终端时,我回直接在 emacs 执行 M-x shell 打开一个终端来用。这样一方面方便快捷,另一方面可以像操作 emacs buffer 那样来用 emacs 快捷键移动光标,编辑命令,用起来反而更顺。

但在 emacs 中就没有了 command+k 快捷键可以用了。不仅如此,在 emacs 的shell 里执行 clear 命令也不会清空 shell buffer。所以唯一的办法就是选中全部的文字,然后执行 yank,不过这样也太麻烦了一点。今天我终于觉得忍无可忍,于是就像给 emacs 添加这么个功能。

之前我看过 Xah Lee 写的关于 emacs lisp 的一些文章,Xah 对 elisp 比较推崇,说是用它来做字处理等操作十分方便。原话我找不到了,但 Xah 的意思好像是 elisp 比 Perl 还来得好用。我也看过一些相关的文档,但我自己没有实际用 elisp 写过东西。

我要的功能其实很好实现,在 .emacs 里面加一个 shell-mode 的 hook,设定一个快捷键 M-k,绑定一个清空 buffer 的函数就好了。然后就是这个函数了。我本来以为 emacs 应该有个类似 clear-buffer 之类的函数,但我没有找到,于是就上网找了一个,也放在 .emacs 文件里面。我找的那个函数只是把 buffer 内容都清空了,勉强符合要求。我希望在清空了 buffer 之后还要显示 shell 的提示符。虽然清空后按一下回车键或者直接输入命令也无所谓,但我还是希望稍微完美一些。

在 emacs 中写操作 elisp 的程序很方便。emacs 的操作是由一个一个的 elisp 函数组成的,因此只要把相关的函数给找出来加到 clear-buffer 函数中就行。要找按键对应的函数,用 C-h k 就可以办到了。很容易的,我就得到了最后的结果,也就是在 .emacs 中加入如下代码就达到要求了:


;; M-k clear buffer of shell-mode like Terminal.app
(defun clear-buffer()
  "Kill all of the text in the current buffer."
  (interactive)
  (clipboard-kill-region 1 (point-max))
  (comint-send-input)
  (previous-line)
  (kill-line)
  (end-of-buffer))

(add-hook 'shell-mode-hook
          (lambda ()
            (local-set-key (kbd "M-k") 'clear-buffer)))

我感觉这个 clear-buffer 函数有些琐碎,像为了要实现清空 buffer 后显示 shell 的提示符,我让函数执行一次回车,然后回到上一行,删除这一行,再回到 buffer 的结尾,感觉上做了很多琐碎的工作似的,不知道对于效率有没有影响,不过我测试了之后感觉可以接受,而且我也不知道有没有更好的方式来解决这个问题。

几个星期前我看 RubyConf 2010 的时候,有个视频探讨了用 Ruby 来写一个 Ruby 编辑器的,当中提到用一门语言来写这种语言的编辑器的好处。emacs 就是一个典型的例子,用 elisp 来完成各种操作,用 elisp 很简单的就可以完成各种配置,而且不用重启编辑器,写完了代码后 eval 一下就好了。估计也只有 LISP 可以做到这种效果了吧。

4 comments

  1. Sylveter Sylveter

    😆 😆 😆 写得不错。哈哈

    沙发
  2. Denny Denny

    erase-buffer函数即可。

    我在emacs的配置文件中, 添加了如下配置。 (put ‘erase-buffer ‘disabled nil) (defalias ‘clear ‘erase-buffer)

    板凳
    • Feng Feng

      erase-buffer 清除所有的文字,包括 bash 的提示符,这不符合我的要求。我试验了一下,它其实相当于我的代码中 (clipboard-kill-region 1 (point-max)) 这句。

      • Denny Denny

        是的, erase-buffer不太好。

        如果是shell的话, 直接用comint的方法就好了; 如果是eshell的话, 那是不行的。

        我刚写了一个elisp方法, 希望对你有用。

        (defun clear-shell () “Remove content of shell/eshell, with the prompt lines reserved” (interactive) (cond ((string-equal mode-name “Shell”) ;; In shell buffer, leverage comint (let ((comint-buffer-maximum-size 0)) (comint-truncate-buffer))) ((string-equal mode-name “EShell”) ;; In eshell buffer, simply delete content of region (let ((inhibit-read-only t)) (goto-line 3) (eshell-bol) (kill-region (point) (point-max))) ) ))

        by Denny(markfilebat@126.com).

Leave a Reply