[关闭]
@qqiseeu 2014-06-18T07:10:19.000000Z 字数 6532 阅读 40399

Emacs配置文件——新手攻略

Emacs


引言

用Emacs好一段时间了,由于自己一向懒得折腾,以前用的配置文件直接就是从Steve Purcell那里fork过来的。但是由于Steve Purcell主要做前端,所以配置文件里各种针对HTML、JavaScript、Ruby之类的插件(虽然绝大部分都被我注释掉了),还有万恶的flymake等我完全用不上的东西。身为一个强迫症患者,这样存在大量冗余的配置文件我实在是忍无可忍了,最近又刚好有时间,遂决心自己从头配过一个简单点的版本:

  1. 必须保留Purcell的配置里我用的顺手的东西,包括广大群众喜闻乐见的auto-completeyasnippet,以及极大提升生活质量的ido-ubiquitoussmex
  2. 就整个结构来说,Purcell的配置可以称得上是清晰明了,尽管整个配置体积比较庞大,但不会让人晕头转向,各初始化文件的功能划分的很明确,耦合的地方少,文件的命名也很讲究,基本一看就知道是在干啥。作为新手,Purcell的配置文件是最好不过的模仿对象了
  3. 根据我个人的需求,需要插件提供对C/C++、Matlab/Mathematica、Python、R、LaTeX/Markdown的增强支持。

本人的系统环境如下:

整个配置文件目录的结构

首先,Emacs的初始化文件可以有两种设置方法

  1. 使用单个文件:~/.emacs。这种方法把所有初始化函数放在一个文件里,设置起来简单,但是一旦插件多了这个文件就会变得很长很乱。
  2. 使用目录:~/.emacs.d/。所有配置文件都放在该目录下,并且Emacs启动时会自动执行该目录下名为init.el的文件。虽说只有一个文件会被自动执行,但可以在init.el里执行其它的函数,所以init.el可以变得很简洁;使用Emacs的Feature机制,可以很方便地把具体的初始化工作按类别分在其余文件中。这也是我选择的方法

我自己的配置放在这里,目录结构如下(注意其中elpa/目录没有被push到github上):

  1. ~/.emacs.d/
  2. README.md #请无视该文件
  3. init.el #Emacs会自动从init.el开始执行
  4. snippets/ #yasnippet的自定义模板保存的位置,不重要
  5. elpa/ #通过ELPA下载的插件所保存的位置
  6. lisp/ #就是加载各个插件的初始化文件的位置啦
  7. init-xxx.el #某初始化文件
  8. editing-utils/ #文本编辑用的一些小工具
  9. custom-themes/ #自定义的主题,不重要
  10. custom-dicts/ #自定义的auto-complete词典,不重要

init.el

主要就是下面几句

  1. ;; init.el
  2. ;; 把目录lisp/添加到搜索路径中去
  3. (add-to-list
  4. 'load-path
  5. (expand-file-name "lisp" user-emacs-directory))
  6. ;; 下面每一个被require的feature都对应一个lisp/目录下的同名
  7. ;; elisp文件,例如init-utils.el、init-elpa.el
  8. (require 'init-utils) ;; 为加载初始化文件提供一些自定义的函数和宏
  9. (require 'init-elpa) ;; 加载ELPA,并定义了require-package函数
  10. (require 'init-fonts) ;; Server-Client模式启动时需额外设置字体
  11. (require 'init-editing-utils) ;; 一些顺手的小工具
  12. ...
  13. (require 'init-markdown)
  14. (require 'init-auctex)
  15. (provide 'init)

init-utils.el

最主要的作用是提供了一个宏after-load,供后续的各初始化函数使用。这个函数来自Purcell,目的是把一些相互依赖的feature的加载顺序理顺,例如feature A依赖于feature B,则可以写成(after-load 'B 'A),这样如果错误地在B之前require了A也不会影响正常启动。

  1. ;; after-load
  2. (defmacro after-load (feature &rest body)
  3. "After FEATURE is loaded, evaluate BODY."
  4. (declare (indent defun))
  5. `(eval-after-load ,feature
  6. '(progn ,@body)))

init-elpa.el

这个文件我也是从Purcell那里截取过来的,但是去掉了很多用不上的函数。该文件主要工作是初始化Emacs的包管理系统ELPA,你可以把ELPA类比为Emacs的软件源,就像Debian、Ubuntu之类的软件源一样。需要注意的是,仅从Emacs24开始默认支持ELPA,如果Emacs版本过低,建议升级一下;但如果使用的是Emacs23,也可以从这里下载package.el放在lisp/目录下,然后在init-elpa.el中加入如下代码:

  1. (require 'package)
  2. (add-to-list 'package-archives
  3. '("marmalade" .
  4. "http://marmalade-repo.org/packages/"))
  5. ....
  6. ;;这一句放在(provide 'init-elpa)之前
  7. (package-initialize)

init-elpa的关键内容如下:

  1. ;; init-elpa.el
  2. (require 'package)
  3. ;; 增加软件包仓库
  4. (add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/"))
  5. (when (< emacs-major-version 24)
  6. (add-to-list 'package-archives '("gnu" . "http://elpa.gnu.org/packages/")))
  7. (add-to-list 'package-archives '("melpa" . "http://melpa.milkbox.net/packages/"))
  8. (add-to-list 'package-archives '("melpa-stable" . "http://melpa-stable.milkbox.net/packages/"))
  9. ;; 定义require-package函数
  10. (defun require-package (package &optional min-version no-refresh)
  11. "Install given PACKAGE, optionally requiring MIN-VERSION.
  12. If NO-REFRESH is non-nil, the available package lists will not be
  13. re-downloaded in order to locate PACKAGE."
  14. (if (package-installed-p package min-version)
  15. t
  16. (if (or (assoc package package-archive-contents) no-refresh)
  17. (package-install package)
  18. (progn
  19. (package-refresh-contents)
  20. (require-package package min-version t)))))
  21. ;; 强行提前初始化ELPA。因为默认情况下Emacs在init.el加载完之后才开始初始化ELPA,
  22. ;; 而我们把大多数包的初始化函数都放在init.el中,如果不提前初始化ELPA会导致后面的
  23. ;; 初始化过程出错(对应的包文件还没有加载进来)。
  24. (package-initialize)
  25. (provide 'init-elpa)

require-package函数的作用是,判断某个包是否已经安装,如果没有则自动从ELPA中安装它(需要联网)。当然,对于自定义的插件或是没有被ELPA收录的插件,这个函数就不起作用了。

有了ELPA,给Emacs装插件就变的非常容易了。比方说你需要一个叫example的插件,那么可以在lisp/目录下增加一个文件init-example.el

  1. ;; init-example.el
  2. (require-package 'example)
  3. ;; ELPA中的插件一般都提供一个"autoloads"方法,可以帮用户自动加载插件并做相应
  4. ;; 的配置。不过如果涉及到细节的配置,那请自己看该插件的帮助文档。
  5. (require 'example-autoloads)
  6. (provide 'init-example)

然后在init.el中加入一句(require 'init-example)(注意这一句要放在(require 'init-elpa)之后)即可。

init-auto-complete.el

auto-complete是必不可少的Emacs插件之一,它的问题主要集中在设置触发补全动作的快捷键及添加ac-sources上。默认情况下补全动作是自动触发的,但是如果用了clang之类的扩展,第一次触发可能需要比较久的时间(在构造ac-source),于是给人一种卡出翔的错觉。所以需要将补全动作改为手动触发。ac-sourcesauto-complete在补全时自动搜索的模板空间,被补全的半单词会自动在ac-sources里面进行匹配。根据所编辑文件类型的不同,ac-sources最好也不同(例如我在补全C语言关键字的时候auto-complete就不应该去匹配Python的关键字),因此常见的做法是给每个主模式的hook上挂一个函数,由该函数来执行修改ac-sources的工作(具体可见init-ac-source.el中的函数ac-latex-mode-setup)。

  1. ;; init-auto-complete.el
  2. ...
  3. (require 'auto-complete-config)
  4. (global-auto-complete-mode t)
  5. ;; 把自定义的dict加到auto-complete的字典中去
  6. (add-to-list 'ac-dictionary-directories
  7. (expand-file-name "lisp/custom-dicts" user-emacs-directory))
  8. ;; 按下TAB时首先缩进所在行,然后尝试补全
  9. (setq tab-always-indent 'complete)
  10. ;; 阻止自动触发补全动作
  11. (setq-default ac-expand-on-auto-complete nil)
  12. (setq-default ac-auto-start nil)
  13. ;; 用TAB作为手动触发补全动作的快捷键
  14. (ac-set-trigger-key "TAB")
  15. ;; 使用after-load来确保ac-source-yasnippet已经完成加载
  16. (after-load 'init-yasnippet
  17. (set-default 'ac-sources
  18. '(ac-source-dictionary
  19. ac-source-words-in-buffer
  20. ac-source-words-in-same-mode-buffers
  21. ac-source-words-in-all-buffer
  22. ac-source-functions
  23. ac-source-yasnippet)))
  24. (require 'init-ac-source)
  25. (provide 'init-auto-complete)

init-yasnippet.el

yasnippetauto-complete的最好搭配。它的触发快捷键包括TAB,这和之前设置的auto-complete的键位冲突,所以做如下配置:

  1. (require-package 'yasnippet)
  2. (require 'yasnippet)
  3. ;; 使用Ctrl-c k作为唯一的触发快捷键
  4. (define-key yas-minor-mode-map (kbd "<tab>") nil)
  5. (define-key yas-minor-mode-map (kbd "TAB") nil)
  6. (define-key yas-minor-mode-map (kbd "C-c k") 'yas-expand)
  7. (yas-global-mode t)
  8. (provide 'init-yasnippet)

init-ac-source.el : clang

clang是一个C/C++、Obj-C/Obj-C++的编译器前端,利用它可以auto-complete能够补全C/C++的各种标准库函数。首先需要有auto-complete-clang,然后把系统中各头文件的路径告诉它:

  1. (require-package 'auto-complete-clang)
  2. (require 'auto-complete-clang)
  3. (setq ac-clang-flags
  4. (mapcar (lambda (item) (concat "-I" item))
  5. (split-string
  6. "
  7. /usr/include/c++/4.8
  8. /usr/include/x86_64-linux-gnu/c++/4.8
  9. /usr/include/c++/4.8/backward
  10. /usr/lib/gcc/x86_64-linux-gnu/4.8/include
  11. /usr/local/include
  12. /usr/lib/gcc/x86_64-linux-gnu/4.8/include-fixed
  13. /usr/include/x86_64-linux-gnu
  14. /usr/include
  15. "
  16. )))

上述路径可以通过命令echo "" | g++ -v -x c++ -E -来得到(详细说明见这里)。

使用Server-Client的方法来“加速”Emacs的启动

插件多了Emacs的启动速度就会很慢,为了掩盖(没错,就是掩盖,不是解决...)这个问题,可以用Server-Client的方法营造一种启动很快的错觉,用开机速度换Emacs的启动速度。

具体原理是,开机的时候以daemon模式启动emacs(这时就会进行插件的加载,并执行初始化文件),让它作为server运行在后台,之后再打开Emacs的时候就以client的形式调用,此时它会直接连接到在后台运行的server上,然后server给你一个窗口编辑文件。由于初始化过程已经在启动server时完成了,启动client的时候几乎是秒开。

需要注意的问题是,GTK版本的Emacs以daemon模式启动时,由于一个Gtk+的Bug,emacsclient可能会时不时地崩溃。解决这个问题的办法是换用emacs24-lucid(可以从Debian的源里安装)绕过它。

具体为,开机的时候启动加载下述脚本:

  1. #! /bin/sh
  2. LC_CTYPE=zh_CN.utf8 emacs24-lucid --daemon

然后给emacs命令加条alias:emacsclient -a "" -c即可。另外,根据这篇文章,由于启动server的时候是以daemon模式启动的,初始化文件中所有与X有关的设置都失效了。这是因为这些设置是在X下的frame创建时才有效的,而启动服务器的时候是没有创建frame的。在我的机子上,就表现为使用client创建frame后字体奇丑无比,因此使用下述函数来设置字体:

  1. ;; init-fonts.el
  2. (setq window-system-default-frame-alist
  3. '((x (font . "文泉驿等宽微米黑 11")) ;; 若frame在X下创建
  4. (nil))) ;; 若frame在terminal中创建
  5. (provide 'init-fonts)

总结

本文只是介绍了我认为的配置过程中几个比较重要的文件。全部配置文件可以在我的github repository中查看。如果你懒得配置,可以直接clone或fork它。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注