diff options
Diffstat (limited to 'docs/posts/rapid_refactoring_with_vim/index.html')
-rw-r--r-- | docs/posts/rapid_refactoring_with_vim/index.html | 312 |
1 files changed, 133 insertions, 179 deletions
diff --git a/docs/posts/rapid_refactoring_with_vim/index.html b/docs/posts/rapid_refactoring_with_vim/index.html index a309066..36d06fc 100644 --- a/docs/posts/rapid_refactoring_with_vim/index.html +++ b/docs/posts/rapid_refactoring_with_vim/index.html | |||
@@ -37,207 +37,161 @@ | |||
37 | Rapid Refactoring With Vim | 37 | Rapid Refactoring With Vim |
38 | </h1> | 38 | </h1> |
39 | <div class="post-text"> | 39 | <div class="post-text"> |
40 | <p>Last weekend, I was tasked with refactoring the 96 unit | 40 | <!DOCTYPE html> |
41 | tests on | 41 | <html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang=""> |
42 | <a href="https://github.com/ruma/ruma-events/pull/70">ruma-events</a> | 42 | <head> |
43 | to use strictly typed json objects using <code>serde_json::json!</code> | 43 | <meta charset="utf-8" /> |
44 | instead of raw strings. It was rather painless thanks to | 44 | <meta name="generator" content="pandoc" /> |
45 | vim :)</p> | 45 | <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> |
46 | 46 | <title>rapid_refactoring_with_vim</title> | |
47 | <p>Here's a small sample of what had to be done (note the lines | 47 | <style> |
48 | prefixed with the arrow):</p> | 48 | code{white-space: pre-wrap;} |
49 | 49 | span.smallcaps{font-variant: small-caps;} | |
50 | <pre><code>→ use serde_json::{from_str}; | 50 | span.underline{text-decoration: underline;} |
51 | 51 | div.column{display: inline-block; vertical-align: top; width: 50%;} | |
52 | #[test] | 52 | div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} |
53 | fn deserialize() { | 53 | ul.task-list{list-style: none;} |
54 | assert_eq!( | 54 | pre > code.sourceCode { white-space: pre; position: relative; } |
55 | → from_str::<Action>(r#"{"set_tweak": "highlight"}"#), | 55 | pre > code.sourceCode > span { display: inline-block; line-height: 1.25; } |
56 | Action::SetTweak(Tweak::Highlight { value: true }) | 56 | pre > code.sourceCode > span:empty { height: 1.2em; } |
57 | ); | 57 | code.sourceCode > span { color: inherit; text-decoration: inherit; } |
58 | } | 58 | div.sourceCode { margin: 1em 0; } |
59 | </code></pre> | 59 | pre.sourceCode { margin: 0; } |
60 | 60 | @media screen { | |
61 | div.sourceCode { overflow: auto; } | ||
62 | } | ||
63 | @media print { | ||
64 | pre > code.sourceCode { white-space: pre-wrap; } | ||
65 | pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; } | ||
66 | } | ||
67 | pre.numberSource code | ||
68 | { counter-reset: source-line 0; } | ||
69 | pre.numberSource code > span | ||
70 | { position: relative; left: -4em; counter-increment: source-line; } | ||
71 | pre.numberSource code > span > a:first-child::before | ||
72 | { content: counter(source-line); | ||
73 | position: relative; left: -1em; text-align: right; vertical-align: baseline; | ||
74 | border: none; display: inline-block; | ||
75 | -webkit-touch-callout: none; -webkit-user-select: none; | ||
76 | -khtml-user-select: none; -moz-user-select: none; | ||
77 | -ms-user-select: none; user-select: none; | ||
78 | padding: 0 4px; width: 4em; | ||
79 | } | ||
80 | pre.numberSource { margin-left: 3em; padding-left: 4px; } | ||
81 | div.sourceCode | ||
82 | { } | ||
83 | @media screen { | ||
84 | pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; } | ||
85 | } | ||
86 | code span.al { font-weight: bold; } /* Alert */ | ||
87 | code span.an { font-style: italic; } /* Annotation */ | ||
88 | code span.cf { font-weight: bold; } /* ControlFlow */ | ||
89 | code span.co { font-style: italic; } /* Comment */ | ||
90 | code span.cv { font-style: italic; } /* CommentVar */ | ||
91 | code span.do { font-style: italic; } /* Documentation */ | ||
92 | code span.dt { text-decoration: underline; } /* DataType */ | ||
93 | code span.er { font-weight: bold; } /* Error */ | ||
94 | code span.in { font-style: italic; } /* Information */ | ||
95 | code span.kw { font-weight: bold; } /* Keyword */ | ||
96 | code span.pp { font-weight: bold; } /* Preprocessor */ | ||
97 | code span.wa { font-style: italic; } /* Warning */ | ||
98 | </style> | ||
99 | <!--[if lt IE 9]> | ||
100 | <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script> | ||
101 | <![endif]--> | ||
102 | </head> | ||
103 | <body> | ||
104 | <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> | ||
105 | <p>Here’s a small sample of what had to be done (note the lines prefixed with the arrow):</p> | ||
106 | <div class="sourceCode" id="cb1"><pre class="sourceCode rust"><code class="sourceCode rust"><span id="cb1-1"><a href="#cb1-1"></a>→ <span class="kw">use</span> <span class="pp">serde_json::</span><span class="op">{</span>from_str<span class="op">};</span></span> | ||
107 | <span id="cb1-2"><a href="#cb1-2"></a> </span> | ||
108 | <span id="cb1-3"><a href="#cb1-3"></a> <span class="at">#[</span>test<span class="at">]</span></span> | ||
109 | <span id="cb1-4"><a href="#cb1-4"></a> <span class="kw">fn</span> deserialize() <span class="op">{</span></span> | ||
110 | <span id="cb1-5"><a href="#cb1-5"></a> <span class="pp">assert_eq!</span>(</span> | ||
111 | <span id="cb1-6"><a href="#cb1-6"></a>→ <span class="pp">from_str::</span><span class="op"><</span>Action<span class="op">></span>(<span class="st">r#"{"set_tweak": "highlight"}"#</span>)<span class="op">,</span></span> | ||
112 | <span id="cb1-7"><a href="#cb1-7"></a> <span class="pp">Action::</span>SetTweak(<span class="pp">Tweak::</span>Highlight <span class="op">{</span> value<span class="op">:</span> <span class="cn">true</span> <span class="op">}</span>)</span> | ||
113 | <span id="cb1-8"><a href="#cb1-8"></a> )<span class="op">;</span></span> | ||
114 | <span id="cb1-9"><a href="#cb1-9"></a> <span class="op">}</span></span></code></pre></div> | ||
61 | <p>had to be converted to:</p> | 115 | <p>had to be converted to:</p> |
62 | 116 | <div class="sourceCode" id="cb2"><pre class="sourceCode rust"><code class="sourceCode rust"><span id="cb2-1"><a href="#cb2-1"></a>→ <span class="kw">use</span> <span class="pp">serde_json::</span><span class="op">{</span>from_value<span class="op">};</span></span> | |
63 | <pre><code>→ use serde_json::{from_value}; | 117 | <span id="cb2-2"><a href="#cb2-2"></a> </span> |
64 | 118 | <span id="cb2-3"><a href="#cb2-3"></a> <span class="at">#[</span>test<span class="at">]</span></span> | |
65 | #[test] | 119 | <span id="cb2-4"><a href="#cb2-4"></a> <span class="kw">fn</span> deserialize() <span class="op">{</span></span> |
66 | fn deserialize() { | 120 | <span id="cb2-5"><a href="#cb2-5"></a> <span class="pp">assert_eq!</span>(</span> |
67 | assert_eq!( | 121 | <span id="cb2-6"><a href="#cb2-6"></a>→ <span class="pp">from_value::</span><span class="op"><</span>Action<span class="op">></span>(<span class="pp">json!</span>(<span class="op">{</span><span class="st">"set_tweak"</span><span class="op">:</span> <span class="st">"highlight"</span><span class="op">}</span>))<span class="op">,</span></span> |
68 | → from_value::<Action>(json!({"set_tweak": "highlight"})), | 122 | <span id="cb2-7"><a href="#cb2-7"></a> <span class="pp">Action::</span>SetTweak(<span class="pp">Tweak::</span>Highlight <span class="op">{</span> value<span class="op">:</span> <span class="cn">true</span> <span class="op">}</span>)</span> |
69 | Action::SetTweak(Tweak::Highlight { value: true }) | 123 | <span id="cb2-8"><a href="#cb2-8"></a> )<span class="op">;</span></span> |
70 | ); | 124 | <span id="cb2-9"><a href="#cb2-9"></a> <span class="op">}</span></span></code></pre></div> |
71 | } | 125 | <h3 id="the-arglist">The arglist</h3> |
72 | </code></pre> | 126 | <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 | 127 | <div class="sourceCode" id="cb3"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1"></a><span class="co"># `grep -l pattern files` lists all the files</span></span> | |
74 | <h3 id="The%20arglist">The arglist</h3> | 128 | <span id="cb3-2"><a href="#cb3-2"></a><span class="co"># matching the pattern</span></span> |
75 | 129 | <span id="cb3-3"><a href="#cb3-3"></a></span> | |
76 | <p>For the initial pass, I decided to handle imports, this was | 130 | <span id="cb3-4"><a href="#cb3-4"></a><span class="ex">vim</span> <span class="va">$(</span><span class="fu">grep</span> -l <span class="st">'cfg\(test\)'</span> ./**/*.rs<span class="va">)</span></span> |
77 | a simple find and replace operation, done to all the files | 131 | <span id="cb3-5"><a href="#cb3-5"></a></span> |
78 | containing tests. Luckily, modules (and therefore files) | 132 | <span id="cb3-6"><a href="#cb3-6"></a><span class="co"># expands to something like:</span></span> |
79 | containing tests in Rust are annotated with the | 133 | <span id="cb3-7"><a href="#cb3-7"></a><span class="ex">vim</span> push_rules.rs room/member.rs key/verification/lib.rs</span></code></pre></div> |
80 | <code>#[cfg(test)]</code> attribute. I opened all such files:</p> | 134 | <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> |
81 | 135 | <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> | |
82 | <pre><code># `grep -l pattern files` lists all the files | 136 | <pre><code>:argdo s/from_str/from_value/g</code></pre> |
83 | # matching the pattern | 137 | <h3 id="the-quickfix-list">The quickfix list</h3> |
84 | 138 | <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> | |
85 | vim $(grep -l 'cfg\(test\)' ./**/*.rs) | 139 | <pre><code>BUFFER: r#" ... "#; |
86 | |||
87 | # expands to something like: | ||
88 | vim push_rules.rs room/member.rs key/verification/lib.rs | ||
89 | </code></pre> | ||
90 | |||
91 | <p>Starting vim with more than one file at the shell prompt | ||
92 | populates the arglist. Hit <code>:args</code> to see the list of | ||
93 | files currently ready to edit. The square [brackets] | ||
94 | indicate the current file. Navigate through the arglist | ||
95 | with <code>:next</code> and <code>:prev</code>. I use tpope's vim-unimpaired | ||
96 | <sup id="fnref1"><a href="#fn1" rel="footnote">1</a></sup>, which adds <code>]a</code> and <code>[a</code>, mapped to <code>:next</code> and | ||
97 | <code>:prev</code>.</p> | ||
98 | |||
99 | <p>All that's left to do is the find and replace, for which we | ||
100 | will be using vim's <code>argdo</code>, applying a substitution to | ||
101 | every file in the arglist:</p> | ||
102 | |||
103 | <pre><code>:argdo s/from_str/from_value/g | ||
104 | </code></pre> | ||
105 | |||
106 | <h3 id="The%20quickfix%20list">The quickfix list</h3> | ||
107 | |||
108 | <p>Next up, replacing <code>r#" ... "#</code> with <code>json!( ... )</code>. I | ||
109 | couldn't search and replace that trivially, so I went with a | ||
110 | macro call <sup id="fnref2"><a href="#fn2" rel="footnote">2</a></sup> instead, starting with the cursor on | ||
111 | ‘r’, represented by the caret, in my attempt to breakdown | ||
112 | the process:</p> | ||
113 | |||
114 | <pre><code>BUFFER: r#" ... "#; | ||
115 | ^ | 140 | ^ |
116 | 141 | ||
117 | ACTION: vllsjson!( | 142 | ACTION: vllsjson!( |
118 | 143 | ||
119 | BUFFER json!( ... "#; | 144 | BUFFER json!( ... "#; |
120 | ^ | 145 | ^ |
121 | 146 | ||
122 | ACTION: <esc>$F# | 147 | ACTION: <esc>$F# |
123 | 148 | ||
124 | BUFFER: json!( ... "#; | 149 | BUFFER: json!( ... "#; |
125 | ^ | 150 | ^ |
126 | 151 | ||
127 | ACTION: vhs)<esc> | 152 | ACTION: vhs)<esc> |
128 | |||
129 | BUFFER: json!( ... ); | ||
130 | </code></pre> | ||
131 | |||
132 | <p>Here's the recorded <sup id="fnref3"><a href="#fn3" rel="footnote">3</a></sup> macro in all its glory: | ||
133 | <code>vllsjson!(<esc>$F#vhs)<esc></code>. </p> | ||
134 | |||
135 | <p>Great! So now we just go ahead, find every occurrence of | ||
136 | <code>r#</code> and apply the macro right? Unfortunately, there were | ||
137 | more than a few occurrences of raw strings that had to stay | ||
138 | raw strings. Enter, the quickfix list.</p> | ||
139 | |||
140 | <p>The idea behind the quickfix list is to jump from one | ||
141 | position in a file to another (maybe in a different file), | ||
142 | much like how the arglist lets you jump from one file to | ||
143 | another.</p> | ||
144 | |||
145 | <p>One of the easiest ways to populate this list with a bunch | ||
146 | of positions is to use <code>vimgrep</code>:</p> | ||
147 | 153 | ||
154 | BUFFER: json!( ... );</code></pre> | ||
155 | <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> | ||
156 | <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> | ||
157 | <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> | ||
158 | <p>One of the easiest ways to populate this list with a bunch of positions is to use <code>vimgrep</code>:</p> | ||
148 | <pre><code># basic usage | 159 | <pre><code># basic usage |
149 | :vimgrep pattern files | 160 | :vimgrep pattern files |
150 | 161 | ||
151 | # search for raw strings | 162 | # search for raw strings |
152 | :vimgrep 'r#' ./**/*.rs | 163 | :vimgrep 'r#' ./**/*.rs</code></pre> |
153 | </code></pre> | 164 | <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> |
154 | 165 | <pre><code>(1 of 131): r#"{"set_tweak": "highlight"}"#;</code></pre> | |
155 | <p>Like <code>:next</code> and <code>:prev</code>, you can navigate the quickfix list | 166 | <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> |
156 | with <code>:cnext</code> and <code>:cprev</code>. Every time you move up or down | 167 | <pre><code>:cdo norm! @q</code></pre> |
157 | the list, vim indicates your index:</p> | 168 | <p>But, I had to manually pick out matches, and it involved some button mashing.</p> |
158 | 169 | <h3 id="external-filtering">External Filtering</h3> | |
159 | <pre><code>(1 of 131): r#"{"set_tweak": "highlight"}"#; | 170 | <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> |
160 | </code></pre> | 171 | <pre><code>:'<,'></code></pre> |
161 | 172 | <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> | |
162 | <p>And just like <code>argdo</code>, you can <code>cdo</code> to apply commands to | ||
163 | <em>every</em> match in the quickfix list:</p> | ||
164 | |||
165 | <pre><code>:cdo norm! @q | ||
166 | </code></pre> | ||
167 | |||
168 | <p>But, I had to manually pick out matches, and it involved | ||
169 | some button mashing.</p> | ||
170 | |||
171 | <h3 id="External%20Filtering">External Filtering</h3> | ||
172 | |||
173 | <p>Some code reviews later, I was asked to format all the json | ||
174 | inside the <code>json!</code> macro. All you have to do is pass a | ||
175 | visual selection through a pretty json printer. Select the | ||
176 | range to be formatted in visual mode, and hit <code>:</code>, you will | ||
177 | notice the command line displaying what seems to be | ||
178 | gibberish:</p> | ||
179 | |||
180 | <pre><code>:'<,'> | ||
181 | </code></pre> | ||
182 | |||
183 | <p><code>'<</code> and <code>'></code> are <em>marks</em> <sup id="fnref4"><a href="#fn4" rel="footnote">4</a></sup>. More | ||
184 | specifically, they are marks that vim sets automatically | ||
185 | every time you make a visual selection, denoting the start | ||
186 | and end of the selection.</p> | ||
187 | |||
188 | <p>A range is one or more line specifiers separated by a <code>,</code>:</p> | 173 | <p>A range is one or more line specifiers separated by a <code>,</code>:</p> |
189 | |||
190 | <pre><code>:1,7 lines 1 through 7 | 174 | <pre><code>:1,7 lines 1 through 7 |
191 | :32 just line 32 | 175 | :32 just line 32 |
192 | :. the current line | 176 | :. the current line |
193 | :.,$ the current line to the last line | 177 | :.,$ the current line to the last line |
194 | :'a,'b mark 'a' to mark 'b' | 178 | :'a,'b mark 'a' to mark 'b'</code></pre> |
195 | </code></pre> | 179 | <p>Most <code>:</code> commands can be prefixed by ranges. <code>:help usr_10.txt</code> for more on that.</p> |
196 | 180 | <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> | |
197 | <p>Most <code>:</code> commands can be prefixed by ranges. <code>:help | 181 | <pre><code>:'<,'>!python -m json.tool</code></pre> |
198 | usr_10.txt</code> for more on that.</p> | 182 | <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> |
199 | 183 | <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> | |
200 | <p>Alright, lets pass json through <code>python -m json.tool</code>, a | 184 | <section class="footnotes" role="doc-endnotes"> |
201 | json formatter that accepts <code>stdin</code> (note the use of <code>!</code> to | 185 | <hr /> |
202 | make use of an external program):</p> | ||
203 | |||
204 | <pre><code>:'<,'>!python -m json.tool | ||
205 | </code></pre> | ||
206 | |||
207 | <p>Unfortunately that didn't quite work for me because the | ||
208 | range included some non-json text as well, a mix of regex | ||
209 | and macros helped fix that. I think you get the drift.</p> | ||
210 | |||
211 | <p>Another fun filter I use from time to time is <code>:!sort</code>, to | ||
212 | sort css attributes, or <code>:!uniq</code> to remove repeated imports.</p> | ||
213 | |||
214 | <div class="footnotes"> | ||
215 | <hr/> | ||
216 | <ol> | 186 | <ol> |
217 | 187 | <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> | |
218 | <li id="fn1"> | 188 | <li id="fn2" role="doc-endnote"><p><code>:help recording</code><a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li> |
219 | <p><a href="https://github.com/tpope/vim-unimpaired">https://github.com/tpope/vim-unimpaired</a> | 189 | <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> |
220 | It also handles various other mappings, <code>]q</code> and <code>[q</code> to | 190 | <li id="fn4" role="doc-endnote"><p><code>:help mark-motions</code><a href="#fnref4" class="footnote-back" role="doc-backlink">↩︎</a></p></li> |
221 | navigate the quickfix list for example <a href="#fnref1" rev="footnote">↩</a></p> | ||
222 | </li> | ||
223 | |||
224 | <li id="fn2"> | ||
225 | <p><code>:help recording</code> <a href="#fnref2" rev="footnote">↩</a></p> | ||
226 | </li> | ||
227 | |||
228 | <li id="fn3"> | ||
229 | <p>When I'm recording a macro, I prefer starting out by | ||
230 | storing it in register <code>q</code>, and then copying it over to | ||
231 | another register if it works as intended. I think of <code>qq</code> as | ||
232 | ‘quick record’. <a href="#fnref3" rev="footnote">↩</a></p> | ||
233 | </li> | ||
234 | |||
235 | <li id="fn4"> | ||
236 | <p><code>:help mark-motions</code> <a href="#fnref4" rev="footnote">↩</a></p> | ||
237 | </li> | ||
238 | |||
239 | </ol> | 191 | </ol> |
240 | </div> | 192 | </section> |
193 | </body> | ||
194 | </html> | ||
241 | 195 | ||
242 | </div> | 196 | </div> |
243 | 197 | ||