(or emacs irrelevant)

My "refactoring" workflow

Well, "refactoring" is for the Java people, I simply rename things. Just today, I had to do a big rename operation related to the release of lispy 0.22.0. Below, I'll share some functions and packages that I used for that.

My Github setup

Here's it (tree -da -L 1) is:

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

I've cloned my gh-pages branch into its own git repository inside the original lispy repository. This way, I can rename functions in the code and the documentation simultaneously.

Declaring functions obsolete in Elisp

It can be done like this:

(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")

After version "0.21.0" hits, any time you call lispy-left by its now obsolete alias lispy-out-backward, you'll get a message:

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

So now, since I'm releasing version "0.22.0", I can remove even the alias declarations. I gave people one week of warnings to adjust (just rename to the new name) any of their code that's calling the currently obsolete functions.

Renaming obsolete functions in the documentation

Taking advantage of the repository setup, I can:

Step 1: call rgrep

rgrep is a fine function, I wonder why it's not bound by default; I bind it to C-<, taking advantage of my weird key mappings (I'm actually pressing the C-;-l physical keys).

It requires 3 inputs:

  1. Symbol to search for: I call it with the point positioned on the symbol that I want to rename in the code, in the middle of the define-obsolete-function-alias tag, so rgrep picks up the symbol name as the default and I just type RET to select it.
  2. File pattern: *.el, the current file extension is the default. I type * RET, since I want to match the org and html files as well.
  3. Base directory: this directory will be will be recursively searched for files that match the file pattern; the default ~/git/lispy/ is fine here, RET.

Step 2: call wgrep

wgrep is a fine package, I wonder why it's not more popular. Since it's so similar to wdired (one of the best things since sliced bread, btw), I like to bind the starter to C-x C-q and the finisher to C-c C-c as well:

(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))

Step 3: call iedit

iedit is an amazing package, it's crazy-good. Here's how I bind it, since once iedit-mode is on, you can move to the next occurrence with C-i:

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

In order to change really every occurrence in the buffer, I need to mark the thing that I want to change, before C-M-i. Otherwise, iedit will automatically add symbol bounds (a nice feature, actually), so that e.g. =lispy-out-forward= will not match lispy-out-forward.

Finally, I interactively, char-by-char, rename e.g. lispy-out-forward to lispy-right. The experience is similar to the popular multiple-cursors, which I also like to use, just for different purposes.

Step 4: exiting

When I'm done:

  • I exit iedit-mode with C-M-i.
  • I exit wgrep with C-c C-c
  • I save all affected files if I want, since they aren't saved yet and it's still possible to revert everything.

Outro

Woah, that's a lot of steps!

True, but do note that all three tools can be used on their own for various other tasks. See for instance my other "refactoring" demo, that uses iedit-mode to unbind a let-bound variable in Elisp (should also work for Common Lisp, since the syntax is the same).

There's beauty and utility in having such composable tools. A lot of the time, it's better than to just have one "Rename" button. For instance, when only one buffer is involved, the rgrep-wgrep step can be skipped and I can rename stuff with iedit-mode only.

Or, when the playground for renaming is less than a buffer, I can:

  1. C-x nd - narrow-to-defun or C-x nn - narrow-to-region (both are equivalent to N in lispy)
  2. iedit-mode
  3. C-x nw - widen (W in lispy)