我是一名熱衷于函數(shù)式編程的Clojurian(Clojure粉)峡懈,網(wǎng)絡(luò)ID是lambeta(λβ),讀作/‘l?meit?/脱拼,個人的博客網(wǎng)站是https://lambeta.name艇潭。俗話說,工欲善其事必先利其器务荆,完善開發(fā)工具與我而言是一件愉快的事情,所以想把經(jīng)驗集結(jié)成文字穷遂,便有了這篇文章函匕。這篇文章不會介紹太多花式或有深度的emacs配置,更多是摸索學(xué)習(xí)的過程蚪黑,其中充滿了樂趣盅惜。
原因
網(wǎng)絡(luò)上的.emacs.d/init.el配置數(shù)不勝數(shù),各路lisp大神的dot file都已經(jīng)放在github上了忌穿,而且前有牛人撰文推薦學(xué)習(xí)emacs配置的詳實方法抒寂,看似確實沒有什么必要自己折騰一份配置。這個說法對掠剑,也不對屈芜。
我在轉(zhuǎn)向emacs之前,是一名忠實的vim黨朴译,從大學(xué)開始就不斷折騰vim的配置井佑,還花過一段時間專門學(xué)習(xí)了vimscript,曾經(jīng)驚嘆于vimscript的動態(tài)函數(shù)式風(fēng)格的優(yōu)美和強大眠寿。類似地躬翁,.vimrc配置文件在網(wǎng)絡(luò)上也多如牛毛,華麗和酷炫的插件極大地提升了vim的操作性盯拱。盡管如此盒发,我還是樂于一磚一瓦地打造自己的vim環(huán)境,竭力演化它變成我心目中的“編輯器之神”狡逢。這個過程一般會充滿修改然后重啟的重復(fù)性機械勞作宁舰,偶爾會遭遇無論怎么修改就是不生效、甚至遍尋google也一無所獲的挫折奢浑,但是我就是無法厭倦它蛮艰。
人天生好奇,探索未知事物本身就充滿了樂趣殷费,而且一旦配置奏效印荔,便能獲得滿滿的成就感低葫。新事物對程序員具有極大的吸引力,但是程序員不會止步于使用新事物仍律,而且會在驚奇之余嘿悬,渴望控制那股背后主導(dǎo)它的力量本身,行使“上帝之力”水泉。
話說回來善涨,為什么我會從vim黨搖身一變成為emacs黨呢?這就不得不提起Clojure這門lisp方言草则,出于對lisp和函數(shù)式編程的癡迷钢拧,我選擇了基于JVM的Clojure作為自己的偏好語言,而emacs天生為lisp而生炕横。
有了這個充足的理由源内,我開始收集emacs的cheatsheet并打印出來,天天放在手邊翻閱份殿,甚至買了一本英文版的Learning GNU Emacs書籍膜钓,只要有機會就打開emacs開始刷4clojure上的編程題。由于emacs對lisp的親和性卿嘲,我?guī)缀鯖]花多少時間就掌握住了常用的操作技巧颂斜。
不過,emacs最負(fù)盛名的學(xué)習(xí)曲線確實讓學(xué)習(xí)者繞過圈子拾枣,只要一段時間不用沃疮,就會忘記很多基本操作。另外梅肤,為了更好地在emacs中編寫Clojure司蔬,還需要cider-mode和clojure-mode的支持,這時候就不得不編輯init.el文件凭语,本著KISS (keep it simple, stupid)原則葱她,我照著各種插件的說明文檔中,把配置項復(fù)制粘貼到init.el文件當(dāng)中似扔,運行起來沒有問題就好。隨著自定義的內(nèi)容變多搓谆,init.el文件也急劇膨脹起來炒辉。膨脹本來算不上問題,但我是個比較有操守的程序員泉手,臃腫的代碼是我極力避免的壞味道(bad smell)黔寇。
所以胸臆之中涌動一股浩然之氣,決心學(xué)起emacs lisp斩萌,把emacs的配置從頭來過缝裤。
從『頭』開始
init.el文件位于~/.emacs.d目錄之下屏轰,如果沒有,自行創(chuàng)建一份即可憋飞。
首先霎苗,我們需要用到emacs的包管理工具package.el,因為emacs 24及其以上的版本都已經(jīng)內(nèi)置榛做,所以無需下載到本地唁盏,直接通過require
加載到emacs的運行時。
(require 'package)
(setq package-archives '(("melpa" . "http://melpa.org/packages/")
("melpa-stable" . "https://stable.melpa.org/packages/")
("marmalade" . "http://marmalade-repo.org/packages/")
("elpy" . "http://jorgenschaefer.github.io/packages/")
("gnu" . "http://elpa.gnu.org/packages/"))
package-enable-at-startup nil)
上面的代碼涉及到setq
(變量賦值)的操作检眯,package-archives
厘擂,顧名思義,多個包的下載源锰瘸,我給package-archives
設(shè)置了5個包源刽严,它們之間服從順序的優(yōu)先級,即先從第一個源中下載包避凝,如果沒有舞萄,到第二個源中尋找,以此類推恕曲。
此外鹏氧,這里("melpa" . "http://melpa.org/packages/")
中的點號(dot)表示法也比較奇怪,其實這是lisp中的Dotted pair表示法佩谣,用法和普通的列表類似把还,但因為是pair的緣故,你可以使用(car )
獲取"melpa"
茸俭,(cdr )
獲取到的卻不再是一個列表吊履,而是"http://melpa.org/packages/"
這個值本身。
由于國情緣故调鬓,所以我推薦使用清華大學(xué)的ELPA鏡像源艇炎。
(setq package-archives '(("gnu" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/gnu/")
("melpa-stable" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/melpa-stable/")
("melpa" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/"))
package-enable-at-startup nil)
熟悉lisp語法
對emacs lisp不熟悉不要緊,先找個教程練習(xí)一下它的用法腾窝,比如learnxinyminutes就非常不錯缀踪。完成這個教程,大體不會對elisp犯怵了虹脯。接下來驴娃,只需要使用c-h v
和c-h f
查看elisp中定義的變量函數(shù)就能很快上手自行配置。
來個實際的例子循集,在大牛的配置文件中唇敞,經(jīng)常能看到如下成對的配置:
(setq package-enable-at-startup nil)
(package-initialize)
開始我覺得這是一對矛盾的配置,package-enable-at-startup
設(shè)置為nil,暗示emacs啟動時不會啟用package
疆柔,而package-initialize
明顯表明在做package
的初始化工作咒精。這種時候,我心中就蹦跶出一句話“世界上本沒有矛盾旷档,如果出現(xiàn)了模叙,檢查你都有哪些前提條件,就會發(fā)現(xiàn)其中一個是錯的”彬犯。這種非異常的知識點很難通過搜索引擎找到滿意的答案向楼,而閱讀文檔恰恰是最合適的解決方式。emacs對elisp
文檔的支持非常全面谐区,只需將鼠標(biāo)移到package-enable-at-startup
變量上湖蜕,按下c-h v
(control + h, v) 組合鍵,就能在其它窗口(window) 看到文檔描述:
Whether to activate installed packages when Emacs starts.
If non-nil, packages are activated after reading the init file
and beforeafter-init-hook'. Activation is not done if
user-init-file' is nil (e.g. Emacs was started with "-q").
意思是在讀入init.el之后宋列,這個變量才會生效昭抒。換句話說,在讀取init.el的過程中炼杖,該變量不論是nil或是non-nil都不會影響package
的加載和初始化灭返。所以,這兩者之間并沒有矛盾坤邪。當(dāng)然熙含,此時你可能會想把package-enable-at-startup
設(shè)置為nil意欲何為?官方文檔中有如下的解釋:
This will automatically set package-enable-at-startup to nil, to avoid loading the packages again after processing the init file.
簡單點說艇纺,就是防止在package-initialize
之后重復(fù)加載包怎静,因為可能會影響性能。
模塊化
如果把什么東西都揉到init.el文件中黔衡,這個文件一定會很快變得臃腫不堪蚓聘。為了解決這個問題,需要引入模塊化的思想——把特定功能的配置放到獨立的文件中盟劫,然后require
進來夜牡。按照慣例,我在~/.emacs.d目錄下建立一個lisp目錄用于存放所有自定義的模塊文件侣签,隨后在init.el中加入下面這句代碼塘装,意在把lisp目錄加到emacs的加載路徑列表里。
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))
看似影所,接下來就可以在每個獨立的模塊文件中編寫各種功能的配置氢哮。但是由于package.el
功能的局限,我們很快就會遇到包重復(fù)安裝和配置漂移(configuration drift)的麻煩型檀。package.el
提供了package-install-p
(p是predicate的意思)和package-install
兩個配套使用的函數(shù),也就是說一般得先判斷包在不在听盖,才決定安不安裝胀溺。幸運的是裂七,有人已經(jīng)很好地解決了這部分問題,use-package就是非常好用的包仓坞,它將包的配置和包的定義聚合到了一塊背零,并且保證包一定會安裝在你的系統(tǒng)當(dāng)中。
在使用use-package
之前无埃,我們需要先安裝它徙瓶,如下:
(unless (package-installed-p 'use-package)
(package-refresh-contents)
(package-install 'use-package))
(eval-when-compile
(require 'use-package))
由于use-package
本身就是一個包,所以可以使用package-install
安裝到本地嫉称,然后require
到emacs的運行時侦镇,值得一提的是這個eval-when-compile
函數(shù),使用c-h f
查看它的定義:
Like ‘progn’, but evaluates the body at compile time if you're compiling.
Thus, the result of the body appears to the compiler as a quoted constant.
In interpreted code, this is entirely equivalent to `progn'.
初次看到compile time织阅,心中難免會有疑問:lisp不是動態(tài)語言嗎壳繁,怎么還需要編譯?這種時候荔棉,我們就要求助于elisp的文檔了闹炉。在emacs中按下c-h i
獲取主話題(topic)的菜單,然后點擊Elisp進入它的操作指南润樱。重點查看Evaluation和Byte Compilation兩個章節(jié)渣触。不難發(fā)現(xiàn)lisp的解析器可以讀取解析兩種類型的lisp代碼,一種是適合人類閱讀的代碼壹若,以el作為后綴嗅钻;另一種是編譯字節(jié)碼,以elc作為后綴舌稀。編譯字節(jié)碼運行速度優(yōu)于前一種代碼啊犬,我們可以通過byte-compile-file
把前一種代碼的文件編譯成字節(jié)碼文件。有趣的是壁查,如果我們使用package來安裝包觉至,對應(yīng)包的目錄下都存在配套的el和elc兩類文件。
在Byte Compilation條目下睡腿,有eval-when-compile
的完整描述:
If you’re using another package, but only need macros from it (the byte compiler will expand those), then ‘eval-when-compile’ can be used to load it for compiling, but not executing. For example,
(eval-when-compile
(require 'my-macro-package))
這里頭有三個關(guān)鍵字load语御、compiling和executing值得留意一下。為了弄懂它們的含義席怪,我們需要了解lisp解析器基本的工作原理:code text -[characters]-> load -[lisp object]-> evaluation/compiling -[bytecode]-> lisp interpretor
应闯。換句話說,除非你想編譯包含上述代碼的文件挂捻,否則它的作用和progn
一模一樣碉纺,順序地求值包含其中的表達(dá)式。當(dāng)你正在編譯文件的時候,包中宏就會原地展開骨田,然后被eval-when-compile
宏加載進內(nèi)存并被編譯成字節(jié)碼耿导,供后續(xù)解析器執(zhí)行。
Clojure相關(guān)
載入use-package之后态贤,我需要開始配置自己強大的Clojure開發(fā)環(huán)境了舱呻。首先,引入幾個包:
(use-package rainbow-delimiters
:ensure t)
(use-package clj-refactor
:ensure t)
(use-package company
:ensure t
:defer t
:config (global-company-mode))
rainbow-delimiters能夠讓括號變得如同彩虹一樣絢麗(主要是易于區(qū)分forms)悠汽,clj-refactor是重構(gòu)Clojure程序的神器箱吕,company提供了強大的命令補全提示功能。
clojure mode
接下來柿冲,我們在~/.emacs.d/lisp目錄下新建一個init-clojure.el文件茬高,內(nèi)容如下:
(require 'clj-refactor)
(require 'rainbow-delimiters)
(use-package midje-mode
:ensure t)
(defun my-clj-refactor-mode-hook ()
(clj-refactor-mode 1)
(yas-minor-mode 1) ; for adding require/use/import
(cljr-add-keybindings-with-prefix "C-c C-m"))
(use-package clojure-mode
:ensure t
:config
(add-hook 'clojure-mode-hook #'rainbow-delimiters-mode)
(add-hook 'clojure-mode-hook #'subword-mode)
(add-hook 'clojure-mode-hook #'midje-mode)
(add-hook 'clojure-mode-hook #'my-clj-refactor-mode-hook)
(add-hook 'clojure-mode-hook #'enable-paredit-mode))
(provide 'init-clojure)
這里就能看出use-package的好處來了,針對clojure-mode的配置項都統(tǒng)一放到:config
中管理起來姻采。配置完畢后雅采,使用(provide 'init-clojure)
將模塊以這樣的名字暴露給其它客戶端調(diào)用。
CIDER mode
有了clojure-mode之后慨亲,我們還需要一個Clojure可交互式的開發(fā)工具婚瓜,CIDER便是這么一款工具。同樣地刑棵,我們在lisp目錄下新建一個名為init-clojure-cider.el巴刻,內(nèi)容如下:
(require 'init-clojure)
(require 'company)
(use-package cider
:ensure t
:config
(setq nrepl-popup-stacktraces nil)
(add-hook 'cider-mode-hook 'eldoc-mode)
(add-hook 'cider-mode-hook #'rainbow-delimiters-mode)
;; Replace return key with newline-and-indent when in cider mode.
(add-hook 'cider-mode-hook '(lambda () (local-set-key (kbd "RET") 'newline-and-indent)))
(add-hook 'cider-mode-hook #'company-mode)
(add-hook 'cider-repl-mode-hook 'subword-mode)
(add-hook 'cider-repl-mode-hook 'paredit-mode)
(add-hook 'cider-repl-mode-hook #'company-mode)
(add-hook 'cider-repl-mode-hook #'rainbow-delimiters-mode))
(provide 'init-clojure-cider)
配置的首部,我使用(require 'init-clojure)
先加載init-clojure蛉签,然后對CIDER本身進行一系列的配置胡陪。配置的詳細(xì)信息可以通過CIDER github主頁獲取到,這里我就不再贅述碍舍。
最后柠座,需要在init.el文件中添加入這么一句(require 'init-clojure-cider)
,重新啟動emacs片橡,找到一個Clojure項目妈经,按下C-c M-j
(hack-jack-in),就能獲得一個Clojure的交互式開發(fā)環(huán)境捧书。
小結(jié)
當(dāng)然吹泡,我的emacs配置絕對不止這些,但是其余的過程大體類似经瓷。由于emacs速來有偽裝成編輯器的操作系統(tǒng)的稱號爆哑,所以我的探索是無止境的。如果大家對我的配置感興趣舆吮,可以直接去我github上dotfiles上查看揭朝。
—
參考鏈接
[1] sriramkswamy dotemacs
[2] purcell emacs.d