Helix magic
2022-07-18
I’ve been using as daily driver the Helix text editor for 3 months now. Coming from 5 consistent years of Neovim usage, I switched fairly quickly. The kakoune-based modal editing intrigued me, and I wanted to give it a try. But what swayed me is the great out-of-the-box user experience. Going from 200 lines of fennel config and 20 dependencies to 0 config and 0 dependency is a big plus.
I’m still more comfortable with neovim than helix, but Helix has capabilities that I’ve yet to take advantage of.
Here is an example of what I mean.
The problem
I was asked to create a criterion report for changes I made to the bevy implementation of query iterator size hints.
After familiarizing myself with criterion and running the benchmarks, criterion generated a report in the form of a HTML file tree. Each benchmark has its own html file with a variety of plots, the reports also contain a bunch of json and csv files I’ve no idea how to use.
Criterion also generates automatically difference plots if you run it twice in a row. But again, does nothing else but embed them in the benchmark-specific html page.
However, what I needed was a summary of all the changes. It was impossible to tell what the changes I made entailed at a glance, since I would need to open each html benchmark report individually and look at the “Changes” section at the very end of the file.
How did Helix help me solve it
What I wanted was a unique page that displays a single plot per benchmark and a summary of the changes for it.
Looking at the file tree generated by criterion, I found that each report had a subdirectory called both with a few plots in them. The one I chose was pdf.svg (the probability density function), because it does both a good job of expressing the accuracy of the benchmark and the effects of the change.
I also needed the table of comparison from each html report files (as follow):
| Lower bound | Estimate | Upper bound | ||
|---|---|---|---|---|
| Change in time | -0.0149% | +0.0193% | +0.0543% | (p = 0.33 > 0.05) |
Ideally, I’d also make it clear which benchmark was an improvement and which one was a regression by having a different color for each comparison result.
How I did it
I first used the fd tool to list all pdf.svg files within a both directory. I then put the result in a file.
(btw I strongly recommend you use fd, it’s a better version of find, which paleolithic UI I absolutely never once remembered how to use, even at five minutes interval)
fd --full-path both/pdf.svg target/criterion > all_changes.html
I then opened all_changes.html with helix. The content of the all_changes.html file was as follow:
target/criterion/for_each_iter/report/both/pdf.svg
target/criterion/for_each_par_iter/threads/1/report/both/pdf.svg
target/criterion/for_each_par_iter/threads/16/report/both/pdf.svg
target/criterion/for_each_par_iter/threads/2/report/both/pdf.svg
target/criterion/for_each_par_iter/threads/32/report/both/pdf.svg
target/criterion/for_each_par_iter/threads/4/report/both/pdf.svg
...
Note that <ESC> means that I press the escape key.
I created a h2 html element by copying the head of the file name and copying it into a h2 tag. The command sequence would be:
111C(create a cursor per line in the file)gs2f/d(go to start of line, select all characters between it and the 2nd next slash and delete them)gl(go to line ending)3F/h(go back 3 slashes in the line + one more character back)v(enter selection extension mode)gs(go to start of the line)"iy(yank selection into registeri)O<ESC>(create a new line before this one, exit edit mode)_iP(insert in before the cursor the content of registeri)I<h2><ESC>(insert the string “<h2>” at the beginning of the line)A</h2>(finally, insert “</h2>” at the end of the line)
<h2>for_each_iter</h2>
for_each_iter/report/both/pdf.svg
<h2>for_each_par_iter/threads/1</h2>
for_each_par_iter/threads/1/report/both/pdf.svg
<h2>for_each_par_iter/threads/16</h2>
for_each_par_iter/threads/16/report/both/pdf.svg
I then embedded the image in the file by translating each line into a img element.
o<img src="<ESC>(start new line with img tag beginning)_iP(past content of registeribefore the cursor)i/report/both/pdf.svg"><ESC>(insert the end of the img tag)
<h2>for_each_iter</h2>
<img src="for_each_iter/report/both/pdf.svg">
for_each_iter/report/both/pdf.svg
<h2>for_each_par_iter/threads/1</h2>
<img src="for_each_par_iter/threads/1/report/both/pdf.svg">
for_each_par_iter/threads/1/report/both/pdf.svg
Now the difficult thing was embedding the html change report table from the benchmark page. For this, I used xidel, a xpath tool to read xml and html. It’s very similar to jq, but for xml rather than json.
FYI xidel is a single pascal file of 2000 lines.
I first tested in my shell how I would do it in pure bash. I came up with the following function:
function get-report-table {
file="${1%%both/pdf.svg}"
xidel ./target/criterion/$file/index.html \
--xpath3 '//*[@class="additional_stats"]' \
--printed-node-format=html
}
This returned (almost) the HTML I wanted in my final file:
<div class="additional_stats">
<!-- The report data for the last benchmark, which
we don't care about for the summary -->
</div>
<!-- What we want: the comparison table between the last benchmark and
the current one (with a diagnostic such as "Performance has improved.")
-->
<div class="additional_stats">
<h4>Additional Statistics:</h4>
<table>
<thead>
<tr>
<th></th>
<th title="0.95 confidence level" class="ci-bound">Lower bound</th>
<th>Estimate</th>
<th title="0.95 confidence level" class="ci-bound">Upper bound</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>Change in time</td>
<td class="ci-bound">-17.097%</td>
<td>-14.185%</td>
<td class="ci-bound">-11.391%</td>
<td>(p = 0.00 &lt; 0.05)</td>
</tr>
</tbody>
</table>
Performance has improved.
</div>
Helix has the “|” command to pass the currently selected lines to a shell command and replace them in place with the output of the command.
I had difficulties with this one, because xidel only accepts files as argument, also parameter expansion (such as ${1%%both/pdf.svg} used to remove the trailing both/pdf.svg from the input) does only work with variables, not standard input.
The trick was to:
- Replace the
$filein the command by$(cat /dev/stdin) - Remove in helix the trailing
both/pdf.svg(gl2F/d) - Now I can just press
|and copy the command.
And BANG! I had now the table in my html file for each benchmark.
I then selected the extraneous report div by using helix’s awesome tree-sitter based motions (I bound them to ( and ) on my keyboard), and pressed d to remove it.
Now for clarity, I just needed to wrap the “Performance has improved” bit into a <a style="color:red/green"> based on the message.
This is a classic usage of the helix editor.
- Select the whole document with
% - with
senter “select mode” - type
improved, then enter. You now have all the occurrences ofimproved - expand selection to parent syntax node (I set this to
]) - Add in front the anchor node opening with
i - Add at the back the node closing delimiter with
a - Repeat with
regressed.
Done.
I now had the full report, I uploaded it on my site and linked it in the PR.
EZ