aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--posts/rapid_refactoring_with_vim.md198
1 files changed, 198 insertions, 0 deletions
diff --git a/posts/rapid_refactoring_with_vim.md b/posts/rapid_refactoring_with_vim.md
new file mode 100644
index 0000000..66ca0e3
--- /dev/null
+++ b/posts/rapid_refactoring_with_vim.md
@@ -0,0 +1,198 @@
1Last weekend, I was tasked with refactoring the 96 unit
2tests on
3[ruma-events](https://github.com/ruma/ruma-events/pull/70)
4to use strictly typed json objects using `serde_json::json!`
5instead of raw strings. It was rather painless thanks to
6vim :)
7
8Here's a small sample of what had to be done (note the lines
9prefixed with the arrow):
10
11```
12→ use serde_json::{from_str};
13
14 #[test]
15 fn deserialize() {
16 assert_eq!(
17→ from_str::<Action>(r#"{"set_tweak": "highlight"}"#),
18 Action::SetTweak(Tweak::Highlight { value: true })
19 );
20 }
21```
22
23had to be converted to:
24
25```
26→ use serde_json::{from_value};
27
28 #[test]
29 fn deserialize() {
30 assert_eq!(
31→ from_value::<Action>(json!({"set_tweak": "highlight"})),
32 Action::SetTweak(Tweak::Highlight { value: true })
33 );
34 }
35```
36
37## The arglist
38
39For the initial pass, I decided to handle imports, this was
40a simple find and replace operation, done to all the files
41containing tests. Luckily, modules (and therefore files)
42containing tests in Rust are annotated with the
43`#[cfg(test)]` attribute. I opened all such files:
44
45```
46# `grep -l pattern files` lists all the files
47# matching the pattern
48
49vim $(grep -l 'cfg\(test\)' ./**/*.rs)
50
51# expands to something like:
52vim push_rules.rs room/member.rs key/verification/lib.rs
53```
54
55Starting vim with more than one file at the shell prompt
56populates the arglist. Hit `:args` to see the list of
57files currently ready to edit. The square [brackets]
58indicate the current file. Navigate through the arglist
59with `:next` and `:prev`. I use tpope's vim-unimpaired
60[^un], which adds `]a` and `[a`, mapped to `:next` and
61`:prev`.
62
63[^un]: https://github.com/tpope/vim-unimpaired
64 It also handles various other mappings, `]q` and `[q` to
65 navigate the quickfix list for example
66
67All that's left to do is the find and replace, for which we
68will be using vim's `argdo`, applying a substitution to
69every file in the arglist:
70
71```
72:argdo s/from_str/from_value/g
73```
74
75## The quickfix list
76
77Next up, replacing `r#" ... "#` with `json!( ... )`. I
78couldn't search and replace that trivially, so I went with a
79macro call [^macro] instead, starting with the cursor on
80'r', represented by the caret, in my attempt to breakdown
81the process:
82
83[^macro]: `:help recording`
84
85```
86BUFFER: r#" ... "#;
87 ^
88
89ACTION: vllsjson!(
90
91BUFFER json!( ... "#;
92 ^
93
94ACTION: <esc>$F#
95
96BUFFER: json!( ... "#;
97 ^
98
99ACTION: vhs)<esc>
100
101BUFFER: json!( ... );
102```
103
104Here's the recorded [^rec] macro in all its glory:
105`vllsjson!(<esc>$F#vhs)<esc>`.
106
107[^rec]: When I'm recording a macro, I prefer starting out by
108 storing it in register `q`, and then copying it over to
109 another register if it works as intended. I think of `qq` as
110 'quick record'.
111
112Great! So now we just go ahead, find every occurrence of
113`r#` and apply the macro right? Unfortunately, there were
114more than a few occurrences of raw strings that had to stay
115raw strings. Enter, the quickfix list.
116
117The idea behind the quickfix list is to jump from one
118position in a file to another (maybe in a different file),
119much like how the arglist lets you jump from one file to
120another.
121
122One of the easiest ways to populate this list with a bunch
123of positions is to use `vimgrep`:
124
125```
126# basic usage
127:vimgrep pattern files
128
129# search for raw strings
130:vimgrep 'r#' ./**/*.rs
131```
132
133Like `:next` and `:prev`, you can navigate the quickfix list
134with `:cnext` and `:cprev`. Every time you move up or down
135the list, vim indicates your index:
136
137```
138(1 of 131): r#"{"set_tweak": "highlight"}"#;
139```
140
141And just like `argdo`, you can `cdo` to apply commands to
142*every* match in the quickfix list:
143
144```
145:cdo norm! @q
146```
147
148But, I had to manually pick out matches, and it involved
149some button mashing.
150
151## External Filtering
152
153Some code reviews later, I was asked to format all the json
154inside the `json!` macro. All you have to do is pass a
155visual selection through a pretty json printer. Select the
156range to be formatted in visual mode, and hit `:`, you will
157notice the command line displaying what seems to be
158gibberish:
159
160```
161:'<,'>
162```
163
164`'<` and `'>` are *marks* [^mark-motions]. More
165specifically, they are marks that vim sets automatically
166every time you make a visual selection, denoting the start
167and end of the selection.
168
169[^mark-motions]: `:help mark-motions`
170
171A range is one or more line specifiers separated by a `,`:
172
173```
174:1,7 lines 1 through 7
175:32 just line 32
176:. the current line
177:.,$ the current line to the last line
178:'a,'b mark 'a' to mark 'b'
179```
180
181Most `:` commands can be prefixed by ranges. `:help
182usr_10.txt` for more on that.
183
184Alright, lets pass json through `python -m json.tool`, a
185json formatter that accepts `stdin` (note the use of `!` to
186make use of an external program):
187
188```
189:'<,'>!python -m json.tool
190```
191
192Unfortunately that didn't quite work for me because the
193range included some non-json text as well, a mix of regex
194and macros helped fix that. I think you get the drift.
195
196Another fun filter I use from time to time is `:!sort`, to
197sort css attributes, or `:!uniq` to remove repeated imports.
198