# -*- org -*- # Published by `wl-org-export-as-latex' #+TITLE: Emacs定制与扩展 #+AUTHOR: 王亮 #+EMAIL: netcasper AT gmail DOT com * 版权 本文采用[[http://creativecommons.org/licenses/by-nc-sa/2.5/cn/][创作共用署名-非商业性使用-相同方式共享 2.5 中国大陆版许可证]] (Creative Commons Attribution-Noncommercial-Share Alike 2.5 China Mainland) 如需查看许可证全文,请访问如下网址: http://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 。 * 前言 在使用 [[http://www.gnu.org/software/emacs/][GNU Emacs]] 的第六个年头上,仍然发现有很多新鲜的东西需要学习,仍 然有这样那样的配置或者扩展可以提高工作效率。在 Emacs 的世界里,真可谓 学无止境啊! 曾经对手册有很强烈的排斥心理,是在学习 Emacs (以及 Perl)的过程中,渐渐 感觉到阅读手册的必要性和重要性,也慢慢明白,并未只有印刷在书上的文字才 充满智慧,屏幕上的手册也可以妙趣横生、引人入胜。 当有一天,手册也不能满足学习的饥渴,便只好在网络上收集、整理、加工乃至 升华。过程虽然枯燥,但是当发现新的工具学会新的用法时,宛如攀上山的顶 峰,一路的艰辛早已抛之脑后,只有无尽的愉悦在心中荡漾。怎一个爽字了得! 写一本书,是我的一个梦想。一度以为,也只能想想而已,因为没啥好写的。最 近几个月的积累,让我有了尝试的冲动。即便写不成上百页的著作,弄个几十页 甚至十几页的小册子也好啊。只要对他人有帮助。 于是,便有了以下的内容。 这本小册子并非是 Emacs 的入门读物,它是接受了 Emacs 的设计理念,认真阅 读了操作手册,能够熟练运用其基本技巧之后的进阶读物。本文大部分内容留给 了我钟爱的 [[http://orgmode.org/][Org Mode]] 和适合程序员使用的专有工具。 * emacs 和 emacsclient ** CVS Emacs 使用CVS Emacs的理由很简单,它具有一些已经发布的 GNU Emacs 不具备的功能。就目前来讲,一个是 Emacs daemon,另一个就是可以享受漂亮的字体。 访问CVS Emacs的方法参见 http://savannah.gnu.org/cvs/?group=emacs 。 *** Daemon 从前只有Emacs server的概念,即可以让已经启动的Emacs进程作为server,然后使用emacsclient连接它;而 Emacs daemon 则是让Emacs进程作为daemon存在。 两者的区别是,Emacs server至少具有一个frame,即使不使用emacsclient程序,我们仍然可以使用这个Emacs进程;而daemon没有任何交互界面存在,必须通过emacsclient创建frame才能使用它。 使用Emacs daemon的好处之一是在重新启动 X Window 的时候无须关闭Emacs进程;另外就是可以远程启动,Emacs daemon可以在登录退出时仍然运行在服务器端。 CVS里面的emacsclient程序拥有两个新的选项: =-c= 和 =-t= ,分别用于创建X frame和终端上的frame。 *** 字体 CVS Emacs的另一个特性是可以使用更加漂亮的字体,正是这一特性,使我一直坚持使用 CVS Emacs,而不是正式发布的稳定版本,屏幕截图参见 [[http://emacs-fu.blogspot.com/2009/01/emacs-23.html]] 。 编译方法如下: : cd emacs : ./configure --prefix=/usr --enable-font-backend : make bootstrap && make : sudo make install 设置字体方法是运行 M-x customize-face RET default RET ,我基本都是使用 Liberation Mono 。 *** 问题 因为是开发版,CVS Emacs 难免有这样那样的问题,尤其是对于刚刚开发出来的新特性,但是对于基本特性来说,质量还是相当过硬的,这也是为什么我使用 CVS Emacs 一年多来再也没有回头使用已经发布的稳定版本的原因。如果在使用过程中确信是 CVS Emacs 的问题,建议报到[[http://savannah.gnu.org/mail/?group=emacs][emacs-devel邮件列表]]上,以便开发者及时发现并解决问题。 在同时使用 Emacs daemon 和漂亮字体的时候我曾遇到过一个问题,至今不得要领,解决方法是很偶然发现的,即将自己的配置全都放在 custom-set-faces 语句之后。我的 =.emacs= 文件是如下的样子: #+BEGIN_SRC emacs-lisp (custom-set-variables ;; custom-set-variables was added by Custom. ;; If you edit it by hand, you could mess it up, so be careful. ;; Your init file should contain only one such instance. ;; If there is more than one, they won't work right. ) (custom-set-faces ;; custom-set-faces was added by Custom. ;; If you edit it by hand, you could mess it up, so be careful. ;; Your init file should contain only one such instance. ;; If there is more than one, they won't work right. '(default ((t (:inherit nil :stipple nil :background "white" :foreground "black" :inverse-video nil :box nil :strike-through nil :overline nil :underline nil :slant normal :weight normal :height 140 :width normal :foundry "unknown" :family "Liberation Mono"))))) (add-to-list 'load-path "~/elisp") (require 'wl-fedora-init) #+END_SRC ** 辅助脚本 作为一名 GNU Emacs 重度用户,却一度使用vi做些系统管理的工作,因为vi的启动速度快。命令行上输入vi加文件名,修改三五字符,便存盘退出,就这层意义上说,Emacs慢了。当然也可以使用 Emacs server,但是那就复杂了些,如果是远程登录,就更复杂了,搞不好便要启动多个Emacs进程。 这个问题随着multi-tty乃至 Emacs daemon 的出现,便迎刃而解了。首先,我 们可以在命令行上使用 =emacsclient -t= 的用法,在终端上开启一个frame, 速度堪比vi。如果喜欢图形界面,请使用 =emacsclient -c= 。不过,敲这么一长串字符,比起vi两个字符而言,还是繁琐了不少。 解决这个问题也很简单,要么做一个alias,要么创建一个短名字的脚本,为了能够用在更多的场合,我选择创建两个bash脚本,名字分别为[[http://www.wanglianghome.org/svn/elisp/scripts/ect][ect]]和[[http://www.wanglianghome.org/svn/elisp/scripts/ecc][ecc]]。 所谓更多的场合,目前来讲也只有一个,就是配合firefox的扩展 [[https://addons.mozilla.org/zh-CN/firefox/addon/4125][It's All Text!]] 使用。通过这一套工具的组合,便可以使用Emacs编辑原本需要在textarea里面编辑的内容。 ** 能获得 root 权限的辅助脚本 前面提到曾使用vi做些系统管理的工作,速度快是原因之一,根本原因还是如何 获得root权限,就这一点来说, =sudo emacs= 根本没法和 =sudo vi= 相提并论。幸好,我们有TRAMP。 TRAMP本是为了编辑远程文件而设计的,仔细阅读它的文档会发现,它还提供了 使用root权限打开本地文件的功能,方法是 =C-x C-f /sudo::= ,后面加上本 地文件名,如 =C-x C-f /sudo::/etc/X11/xorg.conf= ,此外,对文件名还提 供 =TAB= 键补全功能,第一次按 =TAB= 键会要求输入密码。 为了方便在命令行上使用,我创建了两个bash脚本——[[http://www.wanglianghome.org/svn/elisp/scripts/sudoect][sudoect]]和[[http://www.wanglianghome.org/svn/elisp/scripts/sudoecc][sudoecc]],以及一个 Emacs Lisp 辅助函数,如下: #+BEGIN_SRC emacs-lisp (defun wl-sudo-find-file (file dir) (find-file (concat "/sudo:localhost:" (expand-file-name file dir)))) #+END_SRC * Emacs lisp 定制、扩展 Emacs 难免要写一些 Emacs Lisp 代码,下面简单介绍几个后面会 用到的特性,Emacs Lisp 手册参见 http://www.gnu.org/software/emacs/manual/elisp.html 。 - `autoload'. 很多 Emacs 功能模块都可以通过 =autoload= 动态加载,即 第一次使用的时候,这样可以减少 Emacs 的启动时间。 - `require'. 对于没有提供 autoload 特性的模块,就要使用 =require= 在启动时加载。 - `eval-after-load'. 可以用于模块加载之后对其进行定制和扩展,这样可 以避免重复加载一个模块导致定制或扩展丢失的现象。 - `hook'. 许多模块会在特定的时机调用 =hook= ,给用户一个机会做一些 个性化的设置。 - `advice'. 在没有 =hook= 可以使用的情况下,编写 =advice= 可以把个 性化设置塞进模块。 当找不到相应模块时,使用一个参数的 =require= 会报错,从而导致 Emacs 启 动失败,使用如下的宏(引自[[http://emacs-fu.blogspot.com/2008/12/using-packages-functions-only-if-they.html][using packages/functions only if they are available]] ),可以避免这种情况。 #+BEGIN_SRC emacs-lisp (defmacro require-maybe (feature &optional file) "*Try to require FEATURE, but don't signal an error if `require' fails." `(require ,feature ,file 'noerror)) #+END_SRC ** eldoc, paredit, 以及 find-func 这三个模块使得浏览和编写 Emacs Lisp 程序的过程更加流畅和便捷。 *** eldoc eldoc能够在echo area显示函数的参数列表,它对系统自带函数和用户自定义函数一视同仁。例如当输入 : (dolist 的时候,eldoc显示如下帮助信息: : dolist: ((VAR LIST [[RESULT]]) BODY...) 其中各个参数部分还会随着用户的输入而逐个高亮显示。 *** paredit paredit是为了对付 Lisp 程序里无处不在的括号,下面列出它的部分功能: - 在用户输入左括号后,自动输入相应的右括号 - 如果光标在左括号前,那么 =C-k= 删除整个括号表达式,即使它跨越多行;否则删除到当前一级括号表达式的最后,但保留括号 - =M-s= 删除当前括号表达式的左右括号 - =C-S-)= 将当前一级的右括号向右扩展一位,即将下一个表达式包含进来 更多功能参见[[http://mumble.net/~campbell/emacs/paredit.el][paredit.el]]中的变量 =paredit-commands= 。 *** find-func 使用 =find-func= ,可以快速定位emacs lisp函数定义,配置如下: #+BEGIN_SRC emacs-lisp (require 'find-func) (find-function-setup-keys) #+END_SRC 快捷键如下(以下函数由find-func提供,无需自行定义): #+BEGIN_SRC emacs-lisp (defun find-function-setup-keys () "Define some key bindings for the find-function family of functions." (define-key ctl-x-map "F" 'find-function) (define-key ctl-x-4-map "F" 'find-function-other-window) (define-key ctl-x-5-map "F" 'find-function-other-frame) (define-key ctl-x-map "K" 'find-function-on-key) (define-key ctl-x-map "V" 'find-variable) (define-key ctl-x-4-map "V" 'find-variable-other-window) (define-key ctl-x-5-map "V" 'find-variable-other-frame)) #+END_SRC 以前都是傻傻地使用 =C-h f= ,然后把光标移到 =*Help*= buffer里面的相应链接上,最后按回 车。有了 =find-func= ,无需移动光标了。摘一段注释 : The funniest thing about this is that I can't imagine why a package so : obviously useful as this hasn't been written before!! * 通用工具 ** 加密 EasyPG把用gnupg加密解密的过程集成的Emacs里面,CVS版Emacs自带EasyPG,如果是 Emacs 22的话,要自己到http://www.easypg.org/ 下载。 使用EasyPG很简单,只需在.emacs里添加如下语句: #+BEGIN_SRC emacs-lisp (require 'epa) #+END_SRC 如果是单独下载的EasyPG,还需要添加一条: #+BEGIN_SRC emacs-lisp (require 'epa-setup) #+END_SRC 这条语句的主要目的就是调用 =(epa-file-enable)= 使得Emacs遇到后缀名为gpg的文件会自 动解密。 如果希望使用minibuffer输入passphrase,而不是弹出对话框的话,可以将环境变量 =GPG_AGENT_INFO= 清空。 #+BEGIN_SRC emacs-lisp (setenv "GPG_AGENT_INFO" nil) #+END_SRC 然后就可以在Emacs里面直接使用加密文件了,比如使用加密过的bbdb文件数据库: #+BEGIN_SRC emacs-lisp (require 'bbdb) (setq bbdb-file "~/bbdb.gpg") #+END_SRC ** 上网 [[http://emacs-w3m.namazu.org/][Emacs-w3m]] 可以使你用 Emacs 浏览网页,它使用 w3m 程序把网页抓下来,显示 在 Emacs buffer 里面。Emacs-w3m 虽然不支持 CSS 和 javascript,但是对于 大多数只有文字和图片的静态网页,它足够用了;或者,可以访问网站的手机 版,如 http://m.delicious.com/ 。我的配置很简单,主要就是显示图片和记 录 cookie (很多网站用cookie记录登录信息)。 #+BEGIN_SRC emacs-lisp (or (require-maybe 'w3m-load) (require-maybe 'w3m)) (eval-after-load 'w3m '(progn (setq w3m-default-display-inline-images t) (setq browse-url-browser-function 'w3m-browse-url) (setq w3m-use-cookies t) (setq w3m-use-title-buffer-name t))) #+END_SRC 如果使用 CVS Emacs 的话,也要相应地从 Emacs-w3m 的 CVS 服务器上拿开发 版才能使用。 ** install-elisp [[http://www.emacswiki.org/][EmacsWiki]]上有好多强大的工具可以下载,甚至有一个专门的工具用于下载elisp,这就是 [[http://www.emacswiki.org/emacs/install-elisp.el][install-elisp.el]]。如果你还没有下载过,那么需要手工下载、编译、安装和加载,之后 就可以利用它的强大功能完成自动化操作。具体配置参见该文件里面的注释。 如果你像我一样,曾经安装过多个elisp工具,那么批量更新就是个问题,又或者有了一 台新机器,重新安装一遍也很麻烦。使用如下一段小程序,可以解决这个问题。 #+BEGIN_SRC emacs-lisp (defun wl-install-elisp-from-emacswiki () (interactive) (let ((install-elisp-confirm-flag nil) (emacs-lisp-mode-hook nil)) (dolist (m '(anything anything-config auto-complete browse-kill-ring htmlize install-elisp)) (install-elisp-from-emacswiki (concat (symbol-name m) ".el"))) (dolist (u '("http://www.davep.org/emacs/boxquote.el" "http://code.jblevins.org/markdown-mode/markdown-mode.el" "http://mumble.net/~campbell/emacs/paredit.el" "http://homepage1.nifty.com/bmonkey/emacs/elisp/cldoc.el" "http://www.xsteve.at/prg/emacs/psvn.el")) (install-elisp u)))) #+END_SRC 在批处理过程中不希望用户使用 =C-c C-c= 逐个确认,所以暂时把 =install-elisp-confirm-flag= 设为 =nil= ,当然这样做可能会有很严重的安 全问题,因为根本不知道下载的是什么就运行了。 另外,加载这些模块可能导致以前的配置被覆盖,所以关于这几个模块的配置都 会使用 =eval-after-load= 保护起来,使得这些配置在模块被重新加载之后仍 然有机会运行。 ** Org Mode *** 简单的 todo (Org Mode) 就我个人来看,Org Mode是近年来最震撼人心的 Emacs Mode 之一,它的出现,使得大量用户更加紧密地团结在Emacs周围。 主页: http://orgmode.org/ 代码: git://repo.or.cz/orgmode.git 管理代办事项是Org Mode的核心功能之一,使用起来非常简单。在 Outline Mode 的基础上,Org Mode提供了 =C-c C-t= 切换任务状态。第一次将人物切换 到 =TODO= 状态,第二次切换到 =DONE= 表示完成。下面是一个简单的例子。 : * TODO 写一篇关于Emacs的blog : * DONE 确认测试全部通过 另外还可以通过 =C-c C-s= 设置任务开始日期, =C-c C-d= 设置截止日期。更多功能参见Org Mode手册。 *** remember Org Mode 最简单的功能不是管理代办事项,而是记笔记,不需任何配置,就想使 用 Outline Mode 那样即可。除此之外,如果想记录每次记笔记的时间,可以使 用快捷键 =C-c != 来插入一个日期。 Org Mode功能很强大,只是在开始记录的时候稍嫌麻烦,比如要先打开相应的 org文件,选择合适的位置,才能插入内容。如果配合remember使用Org Mode,则 对于那些上下文无关的内容,可以大大减轻辅助工作量。更花哨地,可以配合使 用remember,不仅可以记录日期,还能插入链接。下面是一些配置: #+BEGIN_SRC emacs-lisp (autoload 'remember "remember" nil t) (autoload 'remember-region "remember" nil t) (setq org-reverse-note-order t) (when (file-exists-p "~/gtd/") (define-key global-map [(f8)] 'remember) (setq remember-annotation-functions '(org-remember-annotation)) (setq remember-handler-functions '(org-remember-handler)) (add-hook 'remember-mode-hook 'org-remember-apply-template) (setq org-directory "~/gtd/") (setq org-remember-templates `((?t "* TODO %?\n %i" ,(expand-file-name "todo.org" org-directory) "Tasks") (?m "* %U\n\n %?%i\n %a" ,(expand-file-name "notes.org" org-directory) "Notes"))) (let ((todo (expand-file-name "todo.org" org-directory))) (when (file-exists-p todo) (add-to-list 'org-agenda-files todo)))) #+END_SRC 想要创建任务,先按 =F8= 键,然后按 =t= ,之后输入任务标题、时间、标签或 者更详细的描述,输入完毕之后按 =C-c C-c= ,将这个任务保存在 =~/gtd/todo.org= 文件的 =Tasks= 大类下面。 *** 周期性的任务 (Org Mode) 只要对任务开始日期稍加修改,Org Mode 就能够管理周期性代办事项。比如周四要开会,可以设置如下代办事项: : * TODO 开会 : SCHEDULED: <2009-01-22 四> 如果是每周四都开会,就改写成如下的样子: : * TODO 开会 : SCHEDULED: <2009-01-22 四 +1w> 1w表示每周,另外1d表示每天,1m表示每月。对于周期性的任务, =C-c C-t= 每次将开始日期修改为相应的下一次开始日期,并保持 =TODO= 状态不变。 通常情况下,任务开始日期总是严格地按照预定间隔变动,但是当我们需要忽略 掉已经过期的日期时,就可以使用 =++= 或者 =.+= 来修饰时间间隔,如 : <2009-01-22 四 ++1w> 的下一次日期一定是今天之后的第一个星期四,而 : <2009-01-22 四 .+1w> 的下一次日期是按今天算起的下一个星期,也就是说,不一定是星期四;如果今天是星期二,那么下一次开始日期就是星期二。 *** 提醒 (Org Mode) Org Mode本身并没有提供提醒功能,需要配合appt使用。下面是一个简单的配置: #+BEGIN_SRC emacs-lisp (defun wl-org-agenda-to-appt () ;; Dangerous!!! This might remove entries added by `appt-add' manually. (org-agenda-to-appt t "TODO")) (wl-org-agenda-to-appt) (defadvice org-agenda-redo (after org-agenda-redo-add-appts) "Pressing `r' on the agenda will also add appointments." (progn (let ((config (current-window-configuration))) (appt-check t) (set-window-configuration config)) (wl-org-agenda-to-appt))) (ad-activate 'org-agenda-redo) #+END_SRC 在 Org Mode 的 Agenda View 下,按 =r= 或者 =g= ,就可以把有具体时间的 任务添加到appt的任务提醒列表里面。需要注意的是,手工使用 =appt-add= 添 加的提醒将被清除,无法恢复。所以,当使用本节的配置时,请将任务添加到相 应的org文件里,而不是使用 =appt-add= 。 * 程序员的工具 ** filecache 和 anything *** filecache 经过配置,filecache 能够根据用户输入的文件名,找到该文件的实际位置,省 去了用户回忆和查找的过程。下面是我的配置(忽略git相关目录及文件): #+BEGIN_SRC emacs-lisp (require 'filecache) (add-to-list 'file-cache-filter-regexps "\\.git\\>") (file-cache-add-directory-recursively "/path/to/project") #+END_SRC filecache 没有独立的文档,用法记录在源文件头上的注释里,不过已经足够 了,本来也不是很复杂的东西。使用时有一个小窍门,在使用 =C-x C-f= 打开文 件时,不用管前面的目录名是什么,直接在后面输入文件名,然后用 =C-TAB= 补 全,目录名会自动被替换,无需手工修改。 *** anything 使用filecache可以快速打开项目里的某个文件,但是它的文件名补全功能有一个小小的 局限,就是必须从头开始匹配,不像iswitchb那样可以匹配buffer名的任意部分。配合使 用anything可以解决这个问题。 anything不仅仅可以配合filecache使用,之所以叫anything,就是因为它可以快速打开 anything,而且高度可配置、可扩展。在anything模式下有几个快捷键,左右方向键在不 同分类之间切换; =C-n= 和 =C-p= 在不同条目之间切换; =C-v= 和 =M-v= 上下翻页。下面是我的配置, 使用 =F9= 作为快捷键启动anything模式。 #+BEGIN_SRC emacs-lisp (eval-after-load 'anything '(progn (setq anything-enable-digit-shortcuts t) (global-set-key (kbd "") 'anything))) (eval-after-load 'anything-config '(add-to-list 'anything-sources anything-c-source-file-cache)) (require-maybe 'anything) (require-maybe 'anything-config) #+END_SRC ** etags 和 cscope etags是GNU Emacs的标配,程序员通常用它来定位函数、变量或其它实体的定义。 对于每个名字之只有一处定义的项目,etags足够用了;如果某个名字对应多出定 义,那么etags会根据用户请求逐个遍历这些定义位置,直到用户找到自己想要的 东西而终止遍历。 [[http://cscope.sourceforge.net/][cscope]]提供的功能比etags更强大,它的使用很简单,只需在项目根目录下运行 : find . -name "*.[hc]" -type f >cscope.files : cscope -b -q -k 即可。 而在GNU Emacs里面也只需一行代码 #+BEGIN_SRC emacs-lisp (require-maybe 'xcscope) #+END_SRC 在GNU Emacs里面可以使用绝大多数cscope的功能,然而要想显示函数调用关系 的话,还需要另外的程序,如[[http://cbrowser.sourceforge.net/][Cbrowser]]或[[http://kscope.sourceforge.net/][KScope]]。 然而 etags 也有它的优点——速度快,所以,我们不妨同时使用 etags 和 cscope。有了 =cscope.files= ,生成 =TAGS= 文件也简单了,如下: : cat cscope.files | etags - ** hippie-expand 和 auto-complete *** hippie-expand hippie-expand是个非常强大的补全工具,虽然其中的补全文件或路径功能很少用 到,但是在添加 =load-path= 的时候就很方便了,感觉就像在mini-buffer里输 入路径一样。当然用得最多的还是在写程序的时候了。使用下面的配置可以通过 =M-/= 快捷键调用该补全功能。: #+BEGIN_SRC emacs-lisp (global-set-key (kbd "M-/") 'hippie-expand) (setq hippie-expand-try-functions-list '(try-expand-all-abbrevs try-expand-dabbrev try-expand-dabbrev-all-buffers try-expand-dabbrev-from-kill try-complete-lisp-symbol-partially try-complete-lisp-symbol try-complete-file-name-partially try-complete-file-name)) #+END_SRC *** auto-complete [[http://www.emacswiki.org/emacs/AutoComplete][auto-complete.el]]提供了与hippie-expand完全不同的补全方式,通过弹出菜单的 形式让用户在候选列表中选择,它的作者用一段[[http://www.cx4a.org/pub/ac-demo/ac-demo.html][视频]]展示了auto complete提供怎 样的功能。 以下配置,使用 =F1= 键打开自动补全功能,补全过程中候选列表随着输入的变 化随之更新,选择或取消后,自动补全功能关闭。: #+BEGIN_SRC emacs-lisp (require-maybe 'auto-complete) (eval-after-load 'auto-complete '(progn (global-auto-complete-mode t) (define-key ac-complete-mode-map "\C-n" 'ac-next) (define-key ac-complete-mode-map "\C-p" 'ac-previous) (setq ac-auto-start nil) (defun wl-ac-start () (interactive) (setq ac-auto-start 6) (ac-start)) (defadvice ac-cleanup (after wl-ac-cleanup ()) (setq ac-auto-start nil)) (ad-activate 'ac-cleanup) (define-key global-map (kbd "") 'wl-ac-start) (add-hook 'emacs-lisp-mode-hook (lambda () (make-local-variable 'ac-sources) (setq ac-sources '(ac-source-words-in-buffer ac-source-symbols)))) (defvar ac-source-etags '((candidates . (lambda () (all-completions ac-target (tags-completion-table)))))) (defun wl-add-ac-source-etags () (make-local-variable 'ac-sources) (add-to-list 'ac-sources 'ac-source-etags)) (add-hook 'c-mode-common-hook 'wl-add-ac-source-etags))) #+END_SRC ** repository 书签 [[http://www.xsteve.at/prg/emacs/psvn.el][psvn]] 提供了一个书签功能,可以快速定位 subversion repository,很强大。 模仿它的实现,可以将该书签功能扩充到支持 CVS 和 Git 。 #+BEGIN_SRC emacs-lisp (defvar wl-vc-bookmark-list nil) (defvar wl-vc-status-completing-read-function 'wl-iswitchb-completing-read) (defun wl-vc-status-via-bookmark (bookmark) (interactive (list (let ((completion-ignore-case t)) (funcall wl-vc-status-completing-read-function "VC status bookmark: " wl-vc-bookmark-list)))) (unless bookmark (error "No bookmark specified")) (let ((directory (cdr (assoc bookmark wl-vc-bookmark-list)))) (if (file-directory-p directory) (cond ((file-exists-p (expand-file-name "CVS" directory)) (cvs-examine directory nil)) ((file-exists-p (expand-file-name ".svn" directory)) (svn-status directory)) ((file-exists-p (expand-file-name ".git" directory)) (magit-status directory)) (t (dired-x-find-file directory))) (error "%s is not a directory" directory)))) (define-key global-map (kbd "") 'wl-vc-status-via-bookmark) (defun wl-vc-add-to-bookmark-list (bookmark &rest repos) (dolist (repo repos) (when (and (file-exists-p repo) (file-directory-p repo)) (when (= 0 (length (file-name-nondirectory repo))) ;; remove trailing slash to get the last directory name (setq repo (substring repo 0 -1))) (add-to-list bookmark (cons (file-name-nondirectory repo) (file-name-as-directory repo)))))) #+END_SRC 接下来使用 =wl-vc-add-to-bookmark-list= 添加 repository 。如: #+BEGIN_SRC emacs-lisp (wl-vc-add-to-bookmark-list 'wl-vc-bookmark-list "~/project/proj1" "~/project/proj2") #+END_SRC 之后,就可以通过 =F7= 快捷键管理注册过的 repository 了。 ** yasnippet 主页: http://code.google.com/p/yasnippet/ 演示:[[http://www.youtube.com/watch?v=vOj7btx3ATg][YouTube]],下载[[http://yasnippet.googlecode.com/files/yasnippet.avi][高清晰度版本]] 以下配置选择性的在某些 major mode 下打开该功能,由于 =org-mode= 使用了 =TAB= 键,所以在 =org-mode= 里面使用 =F4= 展开模板。: #+BEGIN_SRC emacs-lisp (when (file-exists-p "~/elisp/3rd-party-lib/yasnippet") (add-to-list 'load-path "~/elisp/3rd-party-lib/yasnippet") ;;; Workaround: do not show menu until I find a way to show menu only ;;; on certain window. (setq yas/use-menu nil) (require-maybe 'yasnippet) (eval-after-load 'yasnippet '(progn (yas/initialize) (yas/load-directory "~/elisp/3rd-party-lib/yasnippet/snippets/") (remove-hook 'after-change-major-mode-hook 'yas/minor-mode-auto-on) (add-hook 'cperl-mode-hook 'yas/minor-mode-auto-on) (add-hook 'perl-mode-hook 'yas/minor-mode-auto-on) (add-hook 'c-mode-common-hook 'yas/minor-mode-auto-on) (add-hook 'html-mode-hook 'yas/minor-mode-auto-on) (add-hook 'org-mode-hook (lambda () (make-local-variable 'yas/trigger-key) (make-local-variable 'yas/next-field-key) (setq yas/trigger-key (kbd "") yas/next-field-key (kbd "")) (yas/minor-mode-auto-on)))))) #+END_SRC 下面的模板用于在 =org-mode= 下引用 =Emacs Lisp= 代码。 #+BEGIN_SRC emacs-lisp (eval-after-load 'yasnippet '(yas/define-snippets 'org-mode '(("elisp" "#+BEGIN_SRC emacs-lisp $0\n#+END_SRC" "#+BEGIN_SRC emacs-lisp ... #+END_SRC")))) #+END_SRC 接下来的模板用于 GCC 开发时编写遍历 basic block 和 edge 的循环。 #+BEGIN_SRC emacs-lisp (eval-after-load 'yasnippet '(progn (yas/define-snippets 'c-mode '(("bb" "basic_block ${1:bb}; FOR_EACH_BB (${1:bb}) { $0 }" "FOR_EACH_BB (...) { ... }") ("bsi" "for (${1:si} = bsi_start (${2:bb}); !bsi_end_p (${1:si}); bsi_next (&${1:si})) { $0 }" "bsi_start (...) { ... }") ("ee" "edge ${1:e}; edge_iterator ${2:ei}; FOR_EACH_EDGE (${1:e}, ${2:ei}, ${3:bb->succs}) { $0 }" "FOR_EACH_EDGE (...) { ... }"))))) #+END_SRC 编写模板的方法参见 http://code.google.com/p/yasnippet/wiki/HowtoDefineSnippet 或者 [[http://yasnippet.googlecode.com/svn/trunk/doc/define_snippet.html][How to define a snippet]] 。 * 学习资源 ** Emacs 手册 Emacs 手册是学习 Emacs 最主要的资源,对于初学者尤其如此。手册内容很多, 可以先从感兴趣的部分开始。通读一遍是不够的,要反复阅读,而且要边读边实 践。更重要的是,要反思自己的操作方式是否合理,有无可能的改进。 ** Emacs Lisp 参考手册 基本的语法和概念要掌握,否则没有办法去配置和扩展 Emacs 。幸运的是, Emacs Lisp 语言的核心部分非常简单,无需花费多大功夫。不要被 Lisp 吓倒 了,其实 Lisp 属于易学难精的语言,上手还是很容易的。 ** EmacsWiki [[http://www.emacswiki.org/]] 是各种 Emacs 工具的网上聚集地。 ** 邮件列表和新闻组 提问可以选择邮件列表或新闻组。 订阅邮件列表参见 [[http://savannah.gnu.org/mail/?group=emacs]] 。 新闻组可以在 [[http://groups.google.com/][Google Groups]] 查看;或者用 Gnus 通过 nntp 方式订阅,地址可 以在 [[http://dir.gmane.org/index.php?prefix=gmane.emacs]] 上找到。 * 后记 本文在 Emacs 里完成写作,使用 Org Mode 管理组织结构、样式风格,以及写 作进度,并使用版本控制工具 Subversion 管理原稿。有 HTML 和 PDF 两种文 件格式可供阅读,其中 PDF 使用文泉驿正黑字体。 获得原稿请安装 Subversion ,并运行如下命令: : svn co http://www.wanglianghome.org/svn/emacsbook/trunk emacsbook 欢迎一同探讨、学习与 Emacs 相关的知识、工具。对于本文的内容如有任何意 见和建议,也非常欢迎提出。 Patches welcome! 相对于文字,我更善于写代码,因此本文的内容,尤其是其中涉及到的配置,可 能与我实际使用的有出入。如需获得我的最新配置,可以使用如下命令: : svn co http://www.wanglianghome.org/svn/elisp