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