My "refactoring" workflow
27 Jan 2015Well, "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:
- 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, sorgrep
picks up the symbol name as the default and I just type RET to select it. - File pattern:
*.el
, the current file extension is the default. I type*
RET, since I want to match theorg
andhtml
files as well. - 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:
- C-x nd -
narrow-to-defun
or C-x nn -narrow-to-region
(both are equivalent to N inlispy
) iedit-mode
- C-x nw -
widen
(W inlispy
)