EMACS-DOCUMENT

=============>集思广益

Org-mode进行文学编程的最佳配置

我一度热衷于用Emacs和Org-mode以文学编程的方式开发来开发Clojure. 我也写过了很多关于文学编程的文章,比如它的基础理念, 文学编程的好处, . 这一次我想告诉你们我是怎么来实践文学编程的,我在实践过程中总结出来的经验有哪些,我写程序时一般是怎样的结构,等等内容.

不过在我开始之前,我想我有必要先解释一下为了用文学编程开发应用时有一个流畅的体验,我是怎样配置Org-mode的. 再然后才会写一系列的文章来解释我是怎么组织Clojure项目的,我的开发流程等内容.

如果你没有配置过Clojure/Cider, 那我推荐你看看这篇文章, 它会告诉你如何在Emacs中配置Clojure开发环境.

1 Packages

Emacs安装Cider之后, 你要做的第一件事就是安装下面这些package. 这些package,有些是使用Org-mode必须的,有些能够优化Org-mode的使用体验.

  1. org
  2. adaptive-wrap
  3. htmlize

安装过程也很容易,运行下面这些命令就行:

M-x package-install [RET] org [RET]
M-x package-install [RET] adaptive-wrap [RET]
M-x package-install [RET] htmlize [RET]

你也可以先运行 M-x package-list-packages, 然后将光标移动到要安装的package那一行. 按下 i (意思是install), 等待所有要安装的package都选中后,再按下 x (意思是execute) 就可以一次性安装所有的package了.

2 Configure Org-mode Editor Behaviors

Org-mode安装之后有很多默认的设置,但是我觉得它的默认设置不适于进行文学编程. 首先我要隐藏掉一些Org-mode的标志,而代之以显示为它真正要显示的字体样式. 例如,我不想看到 /test/ 这样的内容,而应该看到的是斜体的 test. 同样的,粗体,链接也是一样的显示方式. 为了做到这一点,我们需要设置一下 org-hide-emphasis-markers 变量.

然后我会为Org-mode设置开启Emacs visual line mode. 这样就会自动折行而无需手工调整.

(require 'org)

;; Remove the markup characters, i.e., "/text/" becomes (italized) "text"
(setq org-hide-emphasis-markers t)

;; Turn on visual-line-mode for Org-mode only
;; Also install "adaptive-wrap" from elpa
(add-hook 'org-mode-hook 'turn-on-visual-line-mode)

3 Configure Cider for Org-mode

由于我们想用文学编程的方式来开发Clojure,因此我们需要配置一下Org-mode让它使用Cider作为Clojure的后端. Org-mode的code block功能支持许多的编程语言. 然而除了像Elisp(Elisp本身使用Emacs作为后端)这样的语言外,绝大部分的语言都要有一个后端作支持. 我们要做的就是为Clojure设置一个后端.

这里我们首先要做的是告诉Org-mode要支持哪些语言的code blocks. 我这里暂时只定义了4种语言: clojure, sh, dot 和 elisp.

然后我们要设置 org-babel-clojure-backend 为cider.

;; Configure org-mode with Cider

;; Configure Org-mode supported languages
(org-babel-do-load-languages
 'org-babel-load-languages
 '((clojure . t)
   (sh . t)
   (dot . t)
   (emacs-lisp . t)))

;; Use cider as the Clojure execution backend
(setq org-babel-clojure-backend 'cider)
(require 'cider)

3.1 Useful Key Bindings

Cider本身有一些快捷键如果能直接在Org-mode触发那就很方便了. 要做到这样也不难,只要像下面这样设置就行了. 我这里设置了两个快捷键,一个用来运行上一个Clojure表达式的,快捷键为 C-x C-e. 还有一个是获取symbol的文档说明的,快捷键是 C-c C-d

你可能会觉得奇怪为什么要重新设置一下快捷键呢? 这是因为Org-mode的code block本身不是处于Clojure major mode下的, 因此Clojure major mode的快捷键并不能在Org文件中直接使用. 这也就是为什么我们需要在org-mode下重用映射一次快捷键的原因了.

;; Useful keybindings when using Clojure from Org
(org-defkey org-mode-map "\C-x\C-e" 'cider-eval-last-sexp)
(org-defkey org-mode-map "\C-c\C-d" 'cider-doc)

3.2 Configuration of Custom Features

在我的Using Clojure in Org-mode and Implementing Asynchronous Processing 一文中,我提到了 org-babel-clojure 实现的一些限制,也展示了越过这些限制的方法. 这里与那篇文章中设置唯一不同的地方在于我需要去除nrepl中间层的超时设置. 方法如下:

;; No timeout when executing calls on Cider via nrepl
(setq org-babel-clojure-sync-nrepl-timeout nil)

请确保你只有在已经按照上面那篇博文中所提到的方法修改了 org-bable-clojure 的情况下才做上面的设置.

4 Configure Code blocks Behaviors

Org-mode中的code block有它们独特的行为规范,例如,缩进的空格数量,是否能使用shift+方向键选中文本,是否使用编程语言所属mode的原生tab行为以及语法高亮等等. 对于这些行为规范,我们也能做出改变.

;; Let's have pretty source code blocks
(setq org-edit-src-content-indentation 0
      org-src-tab-acts-natively t
      org-src-fontify-natively t
      org-confirm-babel-evaluate nil
      org-support-shift-select 'always)

5 Change Behaviors On Save

将Org-mode配置成文学编程环境最主要的是配置org-mode文档保存时的行为.

使用文学编程的目的是在记录软件开发过程,软件的作用,软件的实现等内容的同时将软件编写出来. 我希望每次保存文件时,都能自动抽取出文件中的代码并保存到相应的Clojure源文件中去. 要做到这一点,需要为 after-save-hook 添加一个新的函数, 这个新的函数先判断当前保存的buffer是否是Org-mode的buffer,若是的话,则调用Org-mode的tangle过程.

此外,在实际用文学编程开发Clojure时,你通常还有在另一个buffer中打开tangled的源文件. 这时若你修改了org文件并保存,则buffer中的内容与tangled文件的实际内容就不同步了. 要解决这个问题需要开启 global-auto-revert-mode. 它会在buffer所属文件被修改后,自动重新加载文件的内容.

最后,Org-mode可不仅仅是用来写那些code blocks. 我常常会在我的Org文件中设置待办任务,这样就能知道还有哪些事情待完成. 然而你可能用Org-mode同时开发多个项目,也可能因为Org-mode的其他功能而在别处创建了Org文件,在这种情况下,通过搜索整个计算机来追踪你有哪些待办事项是不现实的. 因此Org-mode提供了agenda功能,能够创建一个所有的待办事项列表. 你只需要运行 M-x org-todo-list 就能看到所有Org文件中的所有待办事项的列表了. 但前提是你需要手工将Org文件推送到它agenda系统中. agenda本身没有什么问题,问题是是我们很容易忘记把相关的Org文件推送到agenda中. 因此,我选择每次保存Org文件时都自动推送该Org文件到Org agenda中. 这样就不怕遗漏了什么待办事项没有完成了.

;; Tangle Org files when we save them
(defun tangle-on-save-org-mode-file()
  (when (string= (message "%s" major-mode) "org-mode")
    (org-babel-tangle)))

(add-hook 'after-save-hook 'tangle-on-save-org-mode-file)

;; Enable the auto-revert mode globally. This is quite useful when you have 
;; multiple buffers opened that Org-mode can update after tangling.
;; All the buffers will be updated with what changed on the disk.
(global-auto-revert-mode)  

;; Add Org files to the agenda when we save them
(defun to-agenda-on-save-org-mode-file()
  (when (string= (message "%s" major-mode) "org-mode")
    (org-agenda-file-to-front)))

(add-hook 'after-save-hook 'to-agenda-on-save-org-mode-file)

6 Export Configurations

你也可以设置导出og文件的方式. 我就将 org-html-htmlize-output-type 设置成了 css (默认是inline-css) 这样导出的HTML文件中就不会包含CSS了. 我比较喜欢用我使用的HTML theme的CSS来渲染导出的HTML. 但是有些时候我也确实需要用到inline CSS (比如我想为我的博客导出HTML时)时,因此我也定义了一个简单的elisp代码块,在其中将 org-html-htmlize-output-type 的值设置回 inline-css, 这样就能恢复导出的默认方式了.

;; make sure that when we export in HTML, that we don't export with inline css.
;; that way the CSS of the HTML theme will be used instead which is better
(setq org-html-htmlize-output-type 'css)

6.1 Enable External Exporters

存在有大量的Org-mode的导出插件,然而大部分默认情况下都不会启用. 为了使用那些第三方的插件你需要从这个Git仓库 中的 contrib/lisp 目录中抽取出来放到 [home]/.emacs.d/elpa/[org-20160623]/ 下.

下面这段代码能够启用导出为Confulence的插件,随后你可以通过 M-x org-confulence-export-as-confluence 将org文件导出成Confulence格式.

;; Enable Confluence export
(require 'ox-confluence)

7 Dired Configuration

我个人经常使用Dired. 然而它默认会显示所有的文件,包括那些自动产生的auto-save文件. 我倾向于把这些文件在Dired buffer中过滤掉.

; Remove autosave and other unnecessary files to see in Dire
(require 'dired-x)
(setq-default dired-omit-files-p t) ; Buffer-local variable
(setq dired-omit-files "^\\.?#")

8 Spell checker

若能在Org-mode中开启拼写检查,那想是极好的. 目前我搭配着 ispell 以及 flyspell 来做到拼写检查. 目前来说它们工作的挺好的,但是美中不足的是,aspell的windows版本以及有14年没有更新了! 好希望有人能解决一下这个问题啊.

要开启拼写检查,第一步是去下载一个GNU Aspell (我用的是Windows版). 然后我们要告诉Emacs aspell的目录地址. 再然后是为text-mode及其子mode开启Flyspell功能. 最后要记得确保已经下载了合适的语言包.

(custom-set-variables
 '(ispell-program-name "c:\\Program Files (x86)\\Aspell\\bin\\aspell.exe"))

;; Enable Flyspell for text modes
(add-hook 'text-mode-hook 'flyspell-mode)

9 DOT support

DOT 是一门用来描述图形的标记语言. 它用法简单并且能够很容易的将产生的图片插入到Org-mode文件中.

第一步是安装Graphviz. 它能将DOT说明转换成图片. 你所要的的就只是将Graphviz的bin目录放到 Path 环境变量中就行了.

安装并配置好Graphviz后, 重启Emacs就能用了,无需其他的配置过程. 下面是一个用DOT创建的类继承关系图的例子:

#+BEGIN_SRC dot
  digraph {
    soloist -> "musical performer";
    "musical performer" -> musician;
    musician -> artist;
    artist -> person;
    person -> human;
    author -> artist;
    "scifi writer" -> author;
    journalist -> author;
    correspondent -> journalist;
  }
#+END_SRC

actors-authors-humans-e1466538581874.jpeg

10 Inline Images Display

要将Org-mode改造成为一款好用的Notebook应用,还需要能够直接在Emacs中显示内嵌的图片(这些图片可能是通过code-block生成的,也可能是一个指向其他文件的一个链接). 根据你的Emacs发行版,你可以还需要下载并安装一些库才能正确地显示图片(至少对于windows版的Emacs来说是需要的)

首先你要明白Org-mode默认情况下是会显示内嵌图片的. 若你不希望这样,你可以按下 C-c C-x C-v 来切换这项功能. 若你希望确保Emacs进入Org-mode后一定开启显示内置图片的功能,则你可以将下面这段代码放到你的 .emacs 文件中

;; Enable inline image when entering org-mode
;; Make sure you have all the necessary DLL for image display
;; Windows ones can be downloaded from: https://sourceforge.net/projects/ezwinports/files/
(defun turn-on-org-show-all-inline-images ()
  (org-display-inline-images t t))

(add-hook 'org-mode-hook 'turn-on-org-show-all-inline-images)

有可能你在按下 C-c C-x C-v 后会在minibuffer中显示错误信息 no images to display inline

这可能是因为你缺少显示这种图片的库. 你需要运行下面这段elisp代码来看哪个格式的图片需要哪个对应的库文件:

(print image-library-alist)
((xpm "libxpm.dll" "xpm4.dll" "libXpm-nox4.dll") (png "libpng16.dll" "libpng16-16.dll") (tiff "libtiff-5.dll" "libtiff3.dll" "libtiff.dll") (jpeg "libjpeg-9.dll") (gif "libgif-7.dll") (svg "librsvg-2-2.dll") (gdk-pixbuf "libgdk_pixbuf-2.0-0.dll") (glib "libglib-2.0-0.dll") (gobject "libgobject-2.0-0.dll") (gnutls "libgnutls-28.dll" "libgnutls-26.dll") (libxml2 "libxml2-2.dll" "libxml2.dll") (zlib "zlib1.dll" "libz-1.dll"))

然后针对你想要支持的文件格式下载对应的库文件并放在你的 [...]/emacs/bin/ 目录中. 对于window用户,可以在EzWinPorts 中找到所需的DLL文件.

11 Language Specific Libraries

我会使用外部库来完成某些任务,而不直接使用Emacs/Org-mode特定的插件/功能. 这里我会介绍一些Clojure的外部库,但其他语言应该也有类似的库.

假设我想在Org-mode中输出表格形式的信息, 我一般会用Clojure Table 这款应用. 它能将Clojure各种类型的数据结构都转换成排版好的表格形式. 真的是太方便了.

此外,我也经常使用Incanter 来生成PNG格式的各种图表以及点阵图,然后在Org-mode中显示这些图片. 然而,如果我要生成像流程图这样的图片,则我会使用DOT插件,因为它与Org-mode搭配起来真的很方便.

基本上你可以在Org-mode中使用任何能够输出文本或图片的库,但是就我开发过的软件以及接触过的数据分析任务来说,这两个库是目前看来最好用的.

12 Helpful Keys for Working With Org-mode

Org-mode中有一些快捷键能够帮助你进行文学式编程.

若你在Org文件中写Clojure程序,则你首先要做的事情就是开启Cider. 我将 cider-jack-in 的快捷键设置为 F9, 开启了Cider后,你就能够直接在Org中运行Clojure代码了.

最常用的快捷键应该就是 C-x C-s 了,它不仅会保存Org文件,还会作我们上面说过的那些动作.

另一个常用的快捷键是 C-c C-c, 它会执行光标所在的的code block(包括头部和尾部),然后显示执行的结果.

若你想要一次性运行org文件中的所有的code block,可以按下 C-c C-v t (译者注:但是这个快捷键触发的应该是 org-babel-tangle 它的作用是抽取出文件中的所有code block中的代码然后写到源文件中去)

你要时刻记住,在Org文件中我们是出于Org-mode而不是Clojure major mode下. 然而若能切换到Clojure的major mode下编辑代码那就方便的多了. 方法也很简单,只要在code block上按下 C-c ' 就会新开一个以及填充好了代码的buffer,且该buffer是处于clojure的major mode下的. 你这这个buffer中修改代码后,按下 C-c C-x 后也会自动更新Org文件中的代码内容. 要退出到org文件中,只需要再次按下 C-c ' 就行了.

还有一些用来与文档结构有关的快捷键. 我们经常要写一些大型的Org文件,里面充满了大量各级的标题. 若能在编辑时只关注Org大纲的某一部分那就好了. 你只需要先选好要关注的区域然后按下 C-x n s ,就会发现只有选中的那部分内容才会显示在buffer中. 再按下 C-x n w, 其他的内容又会在buffer中恢复显示.

你在用Org-mode进行文学编程时还会用到其他的快捷键,但是上面这些是我最常用的快捷键了.

13 Conclusion

如你所见,Org-mode中有太多的东西可以配置了. 这里说的只是冰山一角而已. 不过这些都是我进行文学编程和数据分析笔记时最常用的功能了.

现在我们的Emacs已经配置好了, 我门的Org-mode也配置好了, 我的下一步计划就该写一些我是如何组织Clojure应用来进行文学编程了.