My "refactoring" workflow27 Jan 2015
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
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")
"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
Renaming obsolete functions in the documentation
Taking advantage of the repository setup, I can:
Step 1: call
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
rgreppicks 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 the
htmlfiles 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 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
(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 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.
iedit will automatically add symbol bounds (a nice
feature, actually), so that e.g.
=lispy-out-forward= will not match
Finally, I interactively, char-by-char, rename e.g.
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
- I exit
wgrepwith 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.
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
can be skipped and I can rename stuff with
Or, when the playground for renaming is less than a buffer, I can:
- C-x nd -
narrow-to-defunor C-x nn -
narrow-to-region(both are equivalent to N in
- C-x nw -