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它。