前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一名Clojurian的Emacs配置

一名Clojurian的Emacs配置

作者头像
lambeta
发布2018-08-17 11:44:22
2.7K0
发布2018-08-17 11:44:22
举报
文章被收录于专栏:编舟记编舟记

我是一名热衷于函数式编程的Clojurian(Clojure粉),网络ID是lambeta(λβ),读作/‘læmeitə/,个人的博客网站是https://lambeta.com。俗话说,工欲善其事必先利其器,完善开发工具与我而言是一件愉快的事情,所以想把经验集结成文字,便有了这篇文章。这篇文章不会介绍太多花式或有深度的emacs配置,更多是摸索学习的过程,其中充满了乐趣。

原因

网络上的.emacs.d/init.el配置数不胜数,各路lisp大神的dot file都已经放在github上了,而且前有牛人撰文推荐学习emacs配置的详实方法,看似确实没有什么必要自己折腾一份配置。这个说法对,也不对。

我在转向emacs之前,是一名忠实的vim党,从大学开始就不断折腾vim的配置,还花过一段时间专门学习了vimscript,曾经惊叹于vimscript的动态函数式风格的优美和强大。类似地,.vimrc配置文件在网络上也多如牛毛,华丽和酷炫的插件极大地提升了vim的操作性。尽管如此,我还是乐于一砖一瓦地打造自己的vim环境,竭力演化它变成我心目中的“编辑器之神”。这个过程一般会充满修改然后重启的重复性机械劳作,偶尔会遭遇无论怎么修改就是不生效、甚至遍寻google也一无所获的挫折,但是我就是无法厌倦它。

人天生好奇,探索未知事物本身就充满了乐趣,而且一旦配置奏效,便能获得满满的成就感。新事物对程序员具有极大的吸引力,但是程序员不会止步于使用新事物,而且会在惊奇之余,渴望控制那股背后主导它的力量本身,行使“上帝之力”。

话说回来,为什么我会从vim党摇身一变成为emacs党呢?这就不得不提起Clojure这门lisp方言,出于对lisp和函数式编程的痴迷,我选择了基于JVM的Clojure作为自己的偏好语言,而emacs天生为lisp而生。

有了这个充足的理由,我开始收集emacs的cheatsheet并打印出来,天天放在手边翻阅,甚至买了一本英文版的Learning GNU Emacs书籍,只要有机会就打开emacs开始刷4clojure上的编程题。由于emacs对lisp的亲和性,我几乎没花多少时间就掌握住了常用的操作技巧。

不过,emacs最负盛名的学习曲线确实让学习者绕过圈子,只要一段时间不用,就会忘记很多基本操作。另外,为了更好地在emacs中编写Clojure,还需要cider-mode和clojure-mode的支持,这时候就不得不编辑init.el文件,本着KISS (keep it simple, stupid)原则,我照着各种插件的说明文档中,把配置项复制粘贴到init.el文件当中,运行起来没有问题就好。随着自定义的内容变多,init.el文件也急剧膨胀起来。膨胀本来算不上问题,但我是个比较有操守的程序员,臃肿的代码是我极力避免的坏味道(bad smell)。

所以胸臆之中涌动一股浩然之气,决心学起emacs lisp,把emacs的配置从头来过。

从『头』开始

init.el文件位于~/.emacs.d目录之下,如果没有,自行创建一份即可。 首先,我们需要用到emacs的包管理工具package.el,因为emacs 24及其以上的版本都已经内置,所以无需下载到本地,直接通过require加载到emacs的运行时。

代码语言:javascript
复制
(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设置了5个包源,它们之间服从顺序的优先级,即先从第一个源中下载包,如果没有,到第二个源中寻找,以此类推。此外,这里("melpa" . "http://melpa.org/packages/")中的点号(dot)表示法也比较奇怪,其实这是lisp中的Dotted pair表示法,用法和普通的列表类似,但因为是pair的缘故,你可以使用(car )获取"melpa"(cdr )获取到的却不再是一个列表,而是"http://melpa.org/packages/"这个值本身。

emacs lisp不熟悉不要紧,先找个教程练习一下它的用法,比如learnxinyminutes就非常不错。完成这个教程,大体不会对elisp犯怵了。接下来,只需要使用c-h vc-h f查看elisp中定义的变量函数就能很快上手自行配置。 来个实际的例子,在大牛的配置文件中,经常能看到如下成对的配置:

代码语言:javascript
复制
(setq package-enable-at-startup nil)
(package-initialize)

开始我觉得这是一对矛盾的配置,package-enable-at-startup设置为nil,暗示emacs启动时不会启用package,而package-initialize明显表明在做package的初始化工作。这种时候,我心中就蹦跶出一句话“世界上本没有矛盾,如果出现了,检查你都有哪些前提条件,就会发现其中一个是错的”。这种非异常的知识点很难通过搜索引擎找到满意的答案,而阅读文档恰恰是最合适的解决方式。emacs对elisp文档的支持非常全面,只需将鼠标移到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的加载和初始化。所以,这两者之间并没有矛盾。当然,此时你可能会想把package-enable-at-startup设置为nil意欲何为?官方文档中有如下的解释:

This will automatically set package-enable-at-startup to nil, to avoid loading the packages again after processing the init file.

简单点说,就是防止在package-initialize之后重复加载包,因为可能会影响性能。

模块化

如果把什么东西都揉到init.el文件中,这个文件一定会很快变得臃肿不堪。为了解决这个问题,需要引入模块化的思想——把特定功能的配置放到独立的文件中,然后require进来。按照惯例,我在~/.emacs.d目录下建立一个lisp目录用于存放所有自定义的模块文件,随后在init.el中加入下面这句代码,意在把lisp目录加到emacs的加载路径列表里。

代码语言:javascript
复制
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))

看似,接下来就可以在每个独立的模块文件中编写各种功能的配置。但是由于package.el功能的局限,我们很快就会遇到包重复安装和配置漂移(configuration drift)的麻烦。package.el提供了package-install-p(p是predicate的意思)和package-install两个配套使用的函数,也就是说一般得先判断包在不在,才决定安不安装。幸运的是,有人已经很好地解决了这部分问题,use-package就是非常好用的包,它将包的配置和包的定义聚合到了一块,并且保证包一定会安装在你的系统当中。 在使用use-package之前,我们需要先安装它,如下:

代码语言:javascript
复制
(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函数,使用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不是动态语言吗,怎么还需要编译?这种时候,我们就要求助于elisp的文档了。在emacs中按下c-h i获取主话题(topic)的菜单,然后点击Elisp进入它的操作指南。重点查看EvaluationByte Compilation两个章节。不难发现lisp的解析器可以读取解析两种类型的lisp代码,一种是适合人类阅读的代码,以el作为后缀;另一种是编译字节码,以elc作为后缀。编译字节码运行速度优于前一种代码,我们可以通过byte-compile-file把前一种代码的文件编译成字节码文件。有趣的是,如果我们使用package来安装包,对应包的目录下都存在配套的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,

代码语言:javascript
复制
(eval-when-compile
   (require 'my-macro-package))

这里头有三个关键字loadcompilingexecuting值得留意一下。为了弄懂它们的含义,我们需要了解lisp解析器基本的工作原理:code text -[characters]-> load -[lisp object]-> evaluation/compiling -[bytecode]-> lisp interpretor。换句话说,除非你想编译包含上述代码的文件,否则它的作用和progn一模一样,顺序地求值包含其中的表达式。当你正在编译文件的时候,包中宏就会原地展开,然后被eval-when-compile宏加载进内存并被编译成字节码,供后续解析器执行。

Clojure相关

载入use-package之后,我需要开始配置自己强大的Clojure开发环境了。首先,引入几个包:

代码语言:javascript
复制
(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能够让括号变得如同彩虹一样绚丽(主要是易于区分forms),clj-refactor是重构Clojure程序的神器,company提供了强大的命令补全提示功能。

clojure mode

接下来,我们在~/.emacs.d/lisp目录下新建一个init-clojure.el文件,内容如下:

代码语言:javascript
复制
(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的配置项都统一放到:config中管理起来。配置完毕后,使用(provide 'init-clojure)将模块以这样的名字暴露给其它客户端调用。

CIDER mode

有了clojure-mode之后,我们还需要一个Clojure可交互式的开发工具,CIDER便是这么一款工具。同样地,我们在lisp目录下新建一个名为init-clojure-cider.el,内容如下:

代码语言:javascript
复制
(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本身进行一系列的配置。配置的详细信息可以通过CIDER github主页获取到,这里我就不再赘述。

最后,需要在init.el文件中添加入这么一句(require 'init-clojure-cider),重新启动emacs,找到一个Clojure项目,按下C-c M-j (hack-jack-in),就能获得一个Clojure的交互式开发环境。

小结

当然,我的emacs配置绝对不止这些,但是其余的过程大体类似。由于emacs速来有伪装成编辑器的操作系统的称号,所以我的探索是无止境的。如果大家对我的配置感兴趣,可以直接去我github上dotfiles上查看。

参考链接 [1] sriramkswamy dotemacs [2] purcell emacs.d

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.01.01 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 原因
  • 从『头』开始
  • 模块化
  • Clojure相关
    • clojure mode
      • CIDER mode
      • 小结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档