EMACS-DOCUMENT

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

我是怎么在Emacs中进行重构的

好吧,只有写Java的人才会重构,我只是重命名而已. 今天,由于要发布lispy 0.22.0 ,我不得不进行大量的重命名操作. 下面我会分享我所用到的那些函数和package.

1 My Github setup

我的仓库结构如下(tree -da -L 1):

.
├── .cask
├── gh-pages
├── .git
└── images

我将gh-pages分支也作为独立的git仓库克隆到了原始的lispy仓库中了. 这样我就可以同时重命名代码和文档中的函数了.

2 声明废弃的函数

可以用类似下面的方法来声明废弃的函数:

(define-obsolete-function-alias 'lispy-out-forward
  'lispy-right "0.21.0")
(define-obsolete-function-alias 'lispy-out-backward
  'lispy-left "0.21.0")
(define-obsolete-function-alias 'lispy-out-forward-nostring
  'lispy-right-nostring "0.21.0")

这样在"0.21.0"及其以后的版本中, lispy-out-backward 作为 lispy-left 被废弃的别名,在调用时会有以下提醒:

`lispy-out-forward' is an obsolete command (as of 0.21.0); use `lispy-right' instead.

现在,在发布"0.22.0"版本时,我完全可以删掉这些别名的声明了. 毕竟我已经給他们予警告并预留了一个星期的时间来让他们把代码中的废弃函数名改为新的名字了.

3 修改文档中的废弃函数名

给予仓库的结果,我可以通过下面几步来进行:

3.1 Step 1: call rgrep

rgrep 是一个很好用的函数, 我都觉得好奇怪,为什么默认没有为它分配一个快捷键呢; 考虑到我的键映射十分古怪,我将它的快捷键设置为 C-<,这样实际上我要按下的键是 C-;-l

rgrep 函数接受三个参数:

  1. 要搜索的符号: 我将光标定位到 define-obsolete-function-alias 语句中要重命名的那个符号上,然后调用该 rgrep. 这样 rgrep 会将该symbol名来作为搜索的默认正则表达式的值,而我要作的仅仅是按下回车就行了.
  2. 文件模式: 默认情况下使用的是当前文件的扩展名,即 *.el. 我输入的是 * RET,因为我还要搜索org和html文件.
  3. 基线目录: rgrep会在该目录下递归地搜索符合文件模式的文件; 这里的默认值为 ~/git/lispy/, 没问题,直接按 RET.

3.2 Step 2: call wgrep

wgrep 是一个很棒的package,它本应该更加的流行的. 由于它与wdired这个package及其类似,因此我也将它的启动热键设为 C-x C-q,退出热键设为 C-c C-c:

(eval-after-load 'grep
  '(define-key grep-mode-map
     (kbd "C-x C-q") 'wgrep-change-to-wgrep-mode))

(eval-after-load 'wgrep
  '(define-key grep-mode-map
     (kbd "C-c C-c") 'wgrep-finish-edit))

3.3 Step 3: call iedit

iedit 是一个令人震惊的package,它太TMD好用了. 我为它设置快捷键的配置如下所示,在开启了iedit-mode的情况下,你可以用 C-i 移动到下一个同名symbol出现的地方.

(global-set-key (kbd "C-M-i") 'iedit-mode)

为了更改buffer中所有搜索出的同名symbol,我需要在按下 C-M-i 之前mark好所有我想修改的地方. 否则的话,iedit会自动为搜索的symbol加上分隔标示(事实上,这本是一个很棒的功能), 这样的话=lispy-out-forward=和lispy-out-forward就不被iedit认为是匹配的了.

最终,我可以交互式的,一个字一个字的将所有的 lispy-out-forward 改为 lispy-right. 这个体验类似于multiple-cursors, (我也挺喜欢这个package的), 只是目的不一样而已.

3.4 Step 4: exiting

我做完这些修改之后:

  • 按下 C-M-i 退出iedit-mode
  • 按下 C-c C-c 退出wgrep
  • 检查无误后保持被修改的文件, 在没有保存前,你还可以将文件回退回原来的内容.

4 Outro

我靠,这么多步骤!

没错,但是请注意,这三个工具可以分开来完成各自不同的任务. 例如在我的另一个"refactoring" demo中, 我使用iedit-mode来为一个Elisp中的let-boud变量解绑(该操作为Common Lisp也应该生效,毕竟这两者的符号都是一样的).

拥有这种可以相互配合的工具是一件很棒的事情. 在大多数情况下,这笔只有一个"Rename"功能要好. 例如若你只想在一个buffe的范围内重命名,则可以跳过rgrep和wgrep的步骤,而只使用iedit-mode就行了. 而若修改的范围只是bufer的一部分,则我只需要:

  1. 按下 C-x nd 调用 narrow-to-defun 函数 或者 按下 C-x nn 调用 narrow-to-region 函数(两个都对应lispy中的N键)
  2. 调用 iedit-mode
  3. 按下 C-x nw 调用 widen (在lispy中按下W键)