一名Clojurian的Emacs配置

我是一名熱衷于函數(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 vc-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 before after-init-hook'. Activation is not done ifuser-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進入它的操作指南润樱。重點查看EvaluationByte Compilation兩個章節(jié)渣触。不難發(fā)現(xiàn)lisp的解析器可以讀取解析兩種類型的lisp代碼,一種是適合人類閱讀的代碼壹若,以el作為后綴嗅钻;另一種是編譯字節(jié)碼,以elc作為后綴舌稀。編譯字節(jié)碼運行速度優(yōu)于前一種代碼啊犬,我們可以通過byte-compile-file把前一種代碼的文件編譯成字節(jié)碼文件。有趣的是壁查,如果我們使用package來安裝包觉至,對應(yīng)包的目錄下都存在配套的elelc兩類文件。
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语御、compilingexecuting值得留意一下。為了弄懂它們的含義席怪,我們需要了解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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末队贱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子萝勤,更是在濱河造成了極大的恐慌露筒,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敌卓,死亡現(xiàn)場離奇詭異,居然都是意外死亡伶氢,警方通過查閱死者的電腦和手機趟径,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來癣防,“玉大人蜗巧,你說我怎么就攤上這事±俣ⅲ” “怎么了幕屹?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長级遭。 經(jīng)常有香客問我望拖,道長,這世上最難降的妖魔是什么挫鸽? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任说敏,我火速辦了婚禮,結(jié)果婚禮上丢郊,老公的妹妹穿的比我還像新娘盔沫。我一直安慰自己,他們只是感情好枫匾,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布架诞。 她就那樣靜靜地躺著,像睡著了一般干茉。 火紅的嫁衣襯著肌膚如雪谴忧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天等脂,我揣著相機與錄音俏蛮,去河邊找鬼。 笑死上遥,一個胖子當(dāng)著我的面吹牛搏屑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播粉楚,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼辣恋,長吁一口氣:“原來是場噩夢啊……” “哼亮垫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起伟骨,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤饮潦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后携狭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體继蜡,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年逛腿,在試婚紗的時候發(fā)現(xiàn)自己被綠了稀并。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡单默,死狀恐怖碘举,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情搁廓,我是刑警寧澤引颈,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站境蜕,受9級特大地震影響蝙场,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜汽摹,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一李丰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逼泣,春花似錦趴泌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至氏仗,卻和暖如春吉捶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背皆尔。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工呐舔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人慷蠕。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓珊拼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親流炕。 傳聞我的和親對象是個殘疾皇子澎现,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容