Emacs
用Emacs好一段时间了,由于自己一向懒得折腾,以前用的配置文件直接就是从 Steve Purcell那里 fork过来的。但是由于Steve Purcell主要做前端,所以配置文件里各种针对HTML、JavaScript、Ruby之类的插件(虽然绝大部分都被我注释掉了),还有万恶的flymake等我完全用不上的东西。身为一个强迫症患者,这样存在大量冗余的配置文件我实在是忍无可忍了,最近又刚好有时间,遂决心自己从头配过一个简单点的版本:
本人的系统环境如下:
首先,Emacs的初始化文件可以有 两种设置方法 :
~/.emacs
。这种方法把所有初始化函数放在一个文件里,设置起来简单,但是一旦插件多了这个文件就会变得很长很乱。 ~/.emacs.d/
。所有配置文件都放在该目录下,并且Emacs启动时会自动执行该目录下名为 init.el
的文件。虽说只有一个文件会被自动执行,但可以在 init.el
里执行其它的函数,所以 init.el
可以变得很简洁;使用Emacs的 Feature 机制,可以很方便地把具体的初始化工作按类别分在其余文件中。 这也是我选择的方法 我自己的配置放在 这里 ,目录结构如下(注意其中 elpa/
目录没有被push到github上):
~/.emacs.d/
README.md #请无视该文件
init.el #Emacs会自动从init.el开始执行
snippets/ #yasnippet的自定义模板保存的位置,不重要
elpa/ #通过ELPA下载的插件所保存的位置
lisp/ #就是加载各个插件的初始化文件的位置啦
init-xxx.el #某初始化文件
editing-utils/ #文本编辑用的一些小工具
custom-themes/ #自定义的主题,不重要
custom-dicts/ #自定义的auto-complete词典,不重要
主要就是下面几句
;; init.el
;; 把目录lisp/添加到搜索路径中去
(add-to-list
'load-path
(expand-file-name "lisp" user-emacs-directory))
;; 下面每一个被require的feature都对应一个lisp/目录下的同名
;; elisp文件,例如init-utils.el、init-elpa.el
(require 'init-utils) ;; 为加载初始化文件提供一些自定义的函数和宏
(require 'init-elpa) ;; 加载ELPA,并定义了require-package函数
(require 'init-fonts) ;; 以Server-Client模式启动时需额外设置字体
(require 'init-editing-utils) ;; 一些顺手的小工具
...
(require 'init-markdown)
(require 'init-auctex)
(provide 'init)
最主要的作用是提供了一个宏 after-load
,供后续的各初始化函数使用。这个函数来自 Purcell ,目的是把一些相互依赖的feature的加载顺序理顺,例如feature A依赖于feature B,则可以写成 (after-load 'B 'A)
,这样如果错误地在B之前require了A也不会影响正常启动。
;; after-load
(defmacro after-load (feature &rest body)
"After FEATURE is loaded, evaluate BODY."
(declare (indent defun))
`(eval-after-load ,feature
'(progn ,@body)))
这个文件我也是从 Purcell 那里截取过来的,但是去掉了很多用不上的函数。该文件主要工作是初始化Emacs的包管理系统 ELPA ,你可以把ELPA类比为Emacs的软件源,就像Debian、Ubuntu之类的软件源一样。需要注意的是, 仅从Emacs24开始默认支持ELPA ,如果Emacs版本过低,建议升级一下;但如果使用的是Emacs23,也可以从 这里 下载 package.el
放在 lisp/
目录下,然后在 init-elpa.el
中加入如下代码:
(require 'package)
(add-to-list 'package-archives
'("marmalade" .
"http://marmalade-repo.org/packages/"))
....
;;这一句放在(provide 'init-elpa)之前
(package-initialize)
init-elpa
的关键内容如下:
;; init-elpa.el
(require 'package)
;; 增加软件包仓库
(add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/"))
(when (< emacs-major-version 24)
(add-to-list 'package-archives '("gnu" . "http://elpa.gnu.org/packages/")))
(add-to-list 'package-archives '("melpa" . "http://melpa.milkbox.net/packages/"))
(add-to-list 'package-archives '("melpa-stable" . "http://melpa-stable.milkbox.net/packages/"))
;; 定义require-package函数
(defun require-package (package &optional min-version no-refresh)
"Install given PACKAGE, optionally requiring MIN-VERSION.
If NO-REFRESH is non-nil, the available package lists will not be
re-downloaded in order to locate PACKAGE."
(if (package-installed-p package min-version)
t
(if (or (assoc package package-archive-contents) no-refresh)
(package-install package)
(progn
(package-refresh-contents)
(require-package package min-version t)))))
;; 强行提前初始化ELPA。因为默认情况下Emacs在init.el加载完之后才开始初始化ELPA,
;; 而我们把大多数包的初始化函数都放在init.el中,如果不提前初始化ELPA会导致后面的
;; 初始化过程出错(对应的包文件还没有加载进来)。
(package-initialize)
(provide 'init-elpa)
require-package
函数的作用是,判断某个包是否已经安装,如果没有则自动从ELPA中安装它(需要联网)。当然,对于自定义的插件或是没有被ELPA收录的插件,这个函数就不起作用了。
有了ELPA,给Emacs装插件就变的非常容易了。比方说你需要一个叫 example
的插件,那么可以在 lisp/
目录下增加一个文件 init-example.el
:
;; init-example.el
(require-package 'example)
;; ELPA中的插件一般都提供一个"autoloads"方法,可以帮用户自动加载插件并做相应
;; 的配置。不过如果涉及到细节的配置,那请自己看该插件的帮助文档。
(require 'example-autoloads)
(provide 'init-example)
然后在 init.el
中加入一句 (require 'init-example)
(注意这一句要放在 (require 'init-elpa)
之后)即可。
auto-complete 是必不可少的Emacs插件之一,它的问题主要集中在设置触发补全动作的快捷键及添加 ac-sources
上。默认情况下补全动作是自动触发的,但是如果用了clang之类的扩展,第一次触发可能需要比较久的时间(在构造ac-source),于是给人一种卡出翔的错觉。所以需要将补全动作改为手动触发。 ac-sources
是 auto-complete
在补全时自动搜索的模板空间,被补全的半单词会自动在 ac-sources
里面进行匹配。根据所编辑文件类型的不同, ac-sources
最好也不同(例如我在补全C语言关键字的时候 auto-complete
就不应该去匹配Python的关键字),因此常见的做法是给每个主模式的hook上挂一个函数,由该函数来执行修改 ac-sources
的工作(具体可见init-ac-source.el中的函数 ac-latex-mode-setup
)。
;; init-auto-complete.el
...
(require 'auto-complete-config)
(global-auto-complete-mode t)
;; 把自定义的dict加到auto-complete的字典中去
(add-to-list 'ac-dictionary-directories
(expand-file-name "lisp/custom-dicts" user-emacs-directory))
;; 按下TAB时首先缩进所在行,然后尝试补全
(setq tab-always-indent 'complete)
;; 阻止自动触发补全动作
(setq-default ac-expand-on-auto-complete nil)
(setq-default ac-auto-start nil)
;; 用TAB作为手动触发补全动作的快捷键
(ac-set-trigger-key "TAB")
;; 使用after-load来确保ac-source-yasnippet已经完成加载
(after-load 'init-yasnippet
(set-default 'ac-sources
'(ac-source-dictionary
ac-source-words-in-buffer
ac-source-words-in-same-mode-buffers
ac-source-words-in-all-buffer
ac-source-functions
ac-source-yasnippet)))
(require 'init-ac-source)
(provide 'init-auto-complete)
yasnippet 是 auto-complete
的最好搭配。它的触发快捷键包括TAB,这和之前设置的 auto-complete
的键位冲突,所以做如下配置:
(require-package 'yasnippet)
(require 'yasnippet)
;; 使用Ctrl-c k作为唯一的触发快捷键
(define-key yas-minor-mode-map (kbd "<tab>") nil)
(define-key yas-minor-mode-map (kbd "TAB") nil)
(define-key yas-minor-mode-map (kbd "C-c k") 'yas-expand)
(yas-global-mode t)
(provide 'init-yasnippet)
clang 是一个C/C++、Obj-C/Obj-C++的编译器前端,利用它可以 auto-complete
能够补全C/C++的各种标准库函数。首先需要有 auto-complete-clang ,然后把系统中各头文件的路径告诉它:
(require-package 'auto-complete-clang)
(require 'auto-complete-clang)
(setq ac-clang-flags
(mapcar (lambda (item) (concat "-I" item))
(split-string
/usr/include/c++/4.8
/usr/include/x86_64-linux-gnu/c++/4.8
/usr/include/c++/4.8/backward
/usr/lib/gcc/x86_64-linux-gnu/4.8/include
/usr/local/include
/usr/lib/gcc/x86_64-linux-gnu/4.8/include-fixed
/usr/include/x86_64-linux-gnu
/usr/include
上述路径可以通过命令 echo "" | g++ -v -x c++ -E -
来得到(详细说明见 这里 )。
插件多了Emacs的启动速度就会很慢,为了掩盖(没错,就是掩盖,不是解决...)这个问题,可以用Server-Client的方法营造一种启动很快的错觉,用开机速度换Emacs的启动速度。
具体原理是,开机的时候以daemon模式启动emacs(这时就会进行插件的加载,并执行初始化文件),让它作为server运行在后台,之后再打开Emacs的时候就以client的形式调用,此时它会直接连接到在后台运行的server上,然后server给你一个窗口编辑文件。由于初始化过程已经在启动server时完成了,启动client的时候几乎是秒开。
需要注意的问题是,GTK版本的Emacs以daemon模式启动时,由于一个 Gtk+的Bug ,emacsclient可能会时不时地崩溃。解决这个问题的办法是换用 emacs24-lucid (可以从Debian的源里安装)绕过它。
具体为,开机的时候启动加载下述脚本:
#! /bin/sh
LC_CTYPE=zh_CN.utf8 emacs24-lucid --daemon
然后给 emacs
命令加条alias: emacsclient -a "" -c
即可。另外,根据 这篇文章 ,由于启动server的时候是以daemon模式启动的,初始化文件中所有与X有关的设置都失效了。 这是因为这些设置是在X下的frame创建时才有效的,而启动服务器的时候是没有创建frame的 。在我的机子上,就表现为使用client创建frame后字体奇丑无比,因此使用下述函数来设置字体:
;; init-fonts.el
(setq window-system-default-frame-alist
'((x (font . "文泉驿等宽微米黑 11")) ;; 若frame在X下创建
(nil))) ;; 若frame在terminal中创建
(provide 'init-fonts)
本文只是介绍了我认为的配置过程中几个比较重要的文件。全部配置文件可以在 我的github repository 中查看。如果你懒得配置,可以直接clone或fork它。