@qqiseeu
2014-06-18 15:10
字数 6532
阅读 0
Emacs配置文件——新手攻略
原始链接戳这里:Emacs配置文件——新手攻略
引言
用Emacs好一段时间了,由于自己一向懒得折腾,以前用的配置文件直接就是从Steve Purcell那里fork过来的。但是由于Steve Purcell主要做前端,所以配置文件里各种针对HTML、JavaScript、Ruby之类的插件(虽然绝大部分都被我注释掉了),还有万恶的flymake等我完全用不上的东西。身为一个强迫症患者,这样存在大量冗余的配置文件我实在是忍无可忍了,最近又刚好有时间,遂决心自己从头配过一个简单点的版本:
- 必须保留Purcell的配置里我用的顺手的东西,包括广大群众喜闻乐见的auto-complete,yasnippet,以及极大提升生活质量的ido-ubiquitous和smex。
- 就整个结构来说,Purcell的配置可以称得上是清晰明了,尽管整个配置体积比较庞大,但不会让人晕头转向,各初始化文件的功能划分的很明确,耦合的地方少,文件的命名也很讲究,基本一看就知道是在干啥。作为新手,Purcell的配置文件是最好不过的模仿对象了。
- 根据我个人的需求,需要插件提供对C/C++、Matlab/Mathematica、Python、R、LaTeX/Markdown的增强支持。
本人的系统环境如下:
- Debian sid,x86_64,内核版本3.14
- GNU Emacs 24.3.1
- clang 3.2-11
整个配置文件目录的结构
首先,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
主要就是下面几句
;; 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)
init-utils.el
最主要的作用是提供了一个宏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)))
init-elpa.el
这个文件我也是从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)
之后)即可。
init-auto-complete.el
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)
init-yasnippet.el
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)
init-ac-source.el : clang
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 -
来得到(详细说明见这里)。
使用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的源里安装)绕过它。
具体为,开机的时候启动加载下述脚本:
#! /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它。