aboutsummaryrefslogtreecommitdiff
path: root/docs/posts/rapid_refactoring_with_vim/index.html
blob: ac375b3182ea0cfe9491d9831cfbf8350819bb00 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="/style.css">
    <link rel="stylesheet" href="/syntax.css">
    <meta charset="UTF-8">
    <meta name="viewport" content="initial-scale=1">
    <meta content="#ffffff" name="theme-color">
    <meta name="HandheldFriendly" content="true">
    <meta property="og:title" content="Rapid Refactoring With Vim">
    <meta property="og:type" content="website">
    <meta property="og:description" content="a static site {for, by, about} me ">
    <meta property="og:url" content="https://peppe.rs">
    <link rel="icon" type="image/x-icon" href="/favicon.png">
    <title>Rapid Refactoring With Vim · peppe.rs</title>
    <body>
      <div class="posts">
        <div class="post">
          <a href="/" class="post-end-link">Home</a>
          <span>/</span>
          <a href="/posts" class="post-end-link">Posts</a>
          <span>/</span>
          <a class="post-end-link">Rapid Refactoring With Vim</a>
          <a class="stats post-end-link" href="https://git.peppe.rs/web/site/plain/posts/rapid_refactoring_with_vim.md
">View Raw</a>
          <div class="separator"></div>
          <div class="date">
            31/03 — 2020
            <div class="stats">
              <span class="stats-number">
                79.12
              </span>
              <span class="stats-unit">cm</span>
              &nbsp
              <span class="stats-number">
                5.4
              </span>
              <span class="stats-unit">min</span>
            </div>
          </div>
          <h1>
            Rapid Refactoring With Vim
          </h1>
          <div class="post-text">
            <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>
<p>Here’s a small sample of what had to be done (note the lines prefixed
with the arrow):</p>
<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>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>  </span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>  <span class="at">#[</span>test<span class="at">]</span></span>
<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>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>    <span class="pp">assert_eq!</span>(</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="pp">from_str::</span><span class="op">&lt;</span>Action<span class="op">&gt;</span>(<span class="st">r#&quot;{&quot;set_tweak&quot;: &quot;highlight&quot;}&quot;#</span>)<span class="op">,</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></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>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>        )<span class="op">;</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span></code></pre></div>
<p>had to be converted to:</p>
<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>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>  </span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>  <span class="at">#[</span>test<span class="at">]</span></span>
<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>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>    <span class="pp">assert_eq!</span>(</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="pp">from_value::</span><span class="op">&lt;</span>Action<span class="op">&gt;</span>(<span class="pp">json!</span>(<span class="op">{</span><span class="st">&quot;set_tweak&quot;</span><span class="op">:</span> <span class="st">&quot;highlight&quot;</span><span class="op">}</span>))<span class="op">,</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></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>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>        )<span class="op">;</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span></code></pre></div>
<h3 id="the-arglist">The arglist</h3>
<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>
<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>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="co">#  matching the pattern</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a></span>
<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">&#39;cfg\(test\)&#39;</span> ./<span class="pp">**</span>/<span class="pp">*</span>.rs<span class="va">)</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="co"># expands to something like:</span></span>
<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>
<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>
<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>
<pre><code>:argdo s/from_str/from_value/g</code></pre>
<h3 id="the-quickfix-list">The quickfix list</h3>
<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>
<pre><code>BUFFER:    r#&quot; ... &quot;#;
           ^

ACTION:    vllsjson!(

BUFFER     json!( ... &quot;#;
                ^

ACTION:    &lt;esc&gt;$F#

BUFFER:    json!( ... &quot;#;
                       ^

ACTION:    vhs)&lt;esc&gt;

BUFFER:    json!( ... );</code></pre>
<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!(&lt;esc&gt;$F#vhs)&lt;esc&gt;</code>.</p>
<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>
<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>
<p>One of the easiest ways to populate this list with a bunch of
positions is to use <code>vimgrep</code>:</p>
<pre><code># basic usage
:vimgrep pattern files

# search for raw strings
:vimgrep &#39;r#&#39; ./**/*.rs</code></pre>
<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>
<pre><code>(1 of 131): r#&quot;{&quot;set_tweak&quot;: &quot;highlight&quot;}&quot;#;</code></pre>
<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>
<pre><code>:cdo norm! @q</code></pre>
<p>But, I had to manually pick out matches, and it involved some button
mashing.</p>
<h3 id="external-filtering">External Filtering</h3>
<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>
<pre><code>:&#39;&lt;,&#39;&gt;</code></pre>
<p><code>'&lt;</code> and <code>'&gt;</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>
<p>A range is one or more line specifiers separated by a
<code>,</code>:</p>
<pre><code>:1,7       lines 1 through 7
:32        just line 32
:.         the current line
:.,$       the current line to the last line
:&#39;a,&#39;b     mark &#39;a&#39; to mark &#39;b&#39;</code></pre>
<p>Most <code>:</code> commands can be prefixed by ranges.
<code>:help usr_10.txt</code> for more on that.</p>
<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>
<pre><code>:&#39;&lt;,&#39;&gt;!python -m json.tool</code></pre>
<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>
<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>
<section id="footnotes" class="footnotes footnotes-end-of-document"
role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><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>
<li id="fn2"><p><code>:help recording</code><a href="#fnref2"
class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn3"><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>
<li id="fn4"><p><code>:help mark-motions</code><a href="#fnref4"
class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>

          </div>
          
    <div class="intro">
        Hi. 
        <div class="hot-links">
            <a href="https://peppe.rs/index.xml" class="feed-button">Subscribe</a>
        </div>
        <p>I'm Akshay, programmer and pixel-artist.</p>
        <p>
        I write <a href="https://git.peppe.rs">open-source stuff</a> to pass time. 
        I also design fonts: 
        <a href="https://git.peppe.rs/fonts/scientifica/about">scientifica</a>, 
        <a href="https://git.peppe.rs/fonts/curie/about">curie</a>.
        </p>
        <p>Send me a mail at [email protected] or a message at [email protected].</p>
    </div>
    
          <a href="/" class="post-end-link">Home</a>
          <span>/</span>
          <a href="/posts" class="post-end-link">Posts</a>
          <span>/</span>
          <a class="post-end-link">Rapid Refactoring With Vim</a>
          <a class="stats post-end-link" href="https://git.peppe.rs/web/site/plain/posts/rapid_refactoring_with_vim.md
">View Raw</a>
        </div>
      </div>
    </body>
</html>