emacs/var/elfeed/db/data/f6/f605f8ba02e961521f3e71b7a33437c3f44a781b
2022-01-03 12:49:32 -06:00

141 lines
8.6 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<p>Emacs has multiple built-in libraries for folding code, as is the case for most things Emacs. The default interface it exposes for folding functions is unwieldy and cumbersome, as is the case for most things Emacs.</p>
<p>There is some overlap between Hideshow-mode and Outline-minor-mode. The latter is mainly for folding and navigating nested Org-like headings, but can be extended with the Foldout library. (Also included in Emacs!) The former works well to hide nested blocks of code. Both libraries use regular expressions and only support a few languages out of the box, so tree-sitter based folding is going to be a welcome addition whenever it arrives. But thats a story for another day. Todays problem is the user interface, which is independent of the folding backend.</p>
<p>Heres the keymap for folding-related functions in the two modes:</p>
<table>
<thead>
<tr>
<th>Key binding</th>
<th>Hideshow mode</th>
<th>Key binding</th>
<th>Outline minor mode</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>C-c @ C-a</code></td>
<td><code>hs-show-all</code></td>
<td><code>C-c @ TAB</code></td>
<td><code>outline-show-children</code></td>
</tr>
<tr>
<td><code>C-c @ C-c</code></td>
<td><code>hs-toggle-hiding</code></td>
<td><code>C-c @ C-k</code></td>
<td><code>outline-show-branches</code></td>
</tr>
<tr>
<td><code>C-c @ C-d</code></td>
<td><code>hs-hide-block</code></td>
<td><code>C-c @ C-o</code></td>
<td><code>outline-hide-other</code></td>
</tr>
<tr>
<td><code>C-c @ C-e</code></td>
<td><code>hs-toggle-hiding</code></td>
<td><code>C-c @ C-q</code></td>
<td><code>outline-hide-sublevels</code></td>
</tr>
<tr>
<td><code>C-c @ C-h</code></td>
<td><code>hs-hide-block</code></td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>C-c @ C-l</code></td>
<td><code>hs-hide-level</code></td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>C-c @ C-s</code></td>
<td><code>hs-show-block</code></td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>C-c @ C-t</code></td>
<td><code>hs-hide-all</code></td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>C-c @ ESC</code></td>
<td><code>Prefix Command</code></td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>C-c @ C-M-h</code></td>
<td><code>hs-hide-all</code></td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>C-c @ C-M-s</code></td>
<td><code>hs-show-all</code></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<p>This is irritating on two levels.</p>
<ul>
<li>The key bindings are on a difficult to use keymap.</li>
<li>Theres no easy entry point and there are too many commands to do simple tasks.</li>
</ul>
<p>The former is easily rectified by rebinding keys or defining a transient/hydra menu, but the latter takes more work. Designing a better interface to Outline modes folding functions was one of the original reasons for the creation of Org mode, which did a bang-up job: Theres no learning curve to cycling Org headings with <code>TAB</code>. It <strong>just works</strong> and theres nothing to look up or remember!</p>
<p>As of Emacs 28, Outline-mode and its minor-mode variant have acquired <code>outline-cycle</code>, a convenient fold cycling function inspired by Org. If youre on an older Emacs, there are packages for this: <a href="https://github.com/tarsius/bicycle">bicycle</a> and <a href="https://github.com/alphapapa/outshine">outshine</a>. For Hideshow mode theres <a href="https://melpa.org/#/hideshow-org">hideshow-org</a>, but this bugged out for me because it makes assumptions about the behavior of my already overloaded <code>TAB</code> key.</p>
<p>So I took a crack at making a simple Org-like one-key interface to Hideshow.</p>
<div style="width: 100%; height: 0px;"><p><a href="https://karthinks.com/img/hs-cycle.mp4">[HIDESHOW CYCLE DEMO]</a></p></div>
<p>(<a href="https://karthinks.com/img/hs-cycle.mp4">Direct link</a> to demo if the embed fails to load.)</p>
<p>Heres how it works (bind it to whatever, its <code>C-TAB</code> here):</p>
<ul>
<li><code>C-TAB</code> to cycle between showing unfolded, folded and showing children. (Same as Org)</li>
<li><code>C-TAB</code> with a prefix argument to show arg levels. <em>i.e.</em> <code>C-3 C-TAB</code> will show unfolded up to the third level.</li>
<li><code>C-S-TAB</code> to fold/unfold the whole buffer.</li>
</ul>
<p>I find myself calling <code>hs-cycle</code> with a numeric level as the prefix arg all the time to get a top-down overview of code at different levels of detail. Here are three views of the same function, folded and unfolded to levels 2 and 4:
<img alt="" src="https://karthinks.com/img/hs-cycle-level.png" /></p>
<p>Thats it. This combines <code>hs-show-all</code>, <code>hs-hide-all</code>, <code>hs-show-block</code>, <code>hs-hide-block</code>, <code>hs-toggle-hiding</code> and <code>hs-hide-level</code> into two commands with a hopefully familiar usage pattern. Its not much code either:</p>
<div class="highlight"><pre><code class="language-emacs-lisp">(<span style="color: #007020;">defun</span> <span style="color: #963;">hs-cycle</span> (<span style="color: #038; font-weight: bold;">&amp;optional</span> <span style="color: #963;">level</span>)
(<span style="color: #007020;">interactive</span> <span style="background-color: #fff0f0;">"p"</span>)
(<span style="color: #007020;">let</span> (<span style="color: #963;">message-log-max</span>
(<span style="color: #963;">inhibit-message</span> <span style="color: #036; font-weight: bold;">t</span>))
(<span style="color: #007020;">if</span> (<span style="color: #06b; font-weight: bold;">=</span> <span style="color: #963;">level</span> <span style="color: #00d; font-weight: bold;">1</span>)
(<span style="color: #007020;">pcase</span> <span style="color: #963;">last-command</span>
(<span style="color: #a60; background-color: #fff0f0;">'hs-cycle</span>
(<span style="color: #963;">hs-hide-level</span> <span style="color: #00d; font-weight: bold;">1</span>)
(<span style="color: #007020;">setq</span> <span style="color: #963;">this-command</span> <span style="color: #a60; background-color: #fff0f0;">'hs-cycle-children</span>))
(<span style="color: #a60; background-color: #fff0f0;">'hs-cycle-children</span>
<span style="color: #888;">;; TODO: Fix this case. `hs-show-block' needs to be</span>
<span style="color: #888;">;; called twice to open all folds of the parent</span>
<span style="color: #888;">;; block.</span>
(<span style="color: #007020;">save-excursion</span> (<span style="color: #963;">hs-show-block</span>))
(<span style="color: #963;">hs-show-block</span>)
(<span style="color: #007020;">setq</span> <span style="color: #963;">this-command</span> <span style="color: #a60; background-color: #fff0f0;">'hs-cycle-subtree</span>))
(<span style="color: #a60; background-color: #fff0f0;">'hs-cycle-subtree</span>
(<span style="color: #963;">hs-hide-block</span>))
(<span style="color: #963;">_</span>
(<span style="color: #007020;">if</span> (<span style="color: #963;">not</span> (<span style="color: #963;">hs-already-hidden-p</span>))
(<span style="color: #963;">hs-hide-block</span>)
(<span style="color: #963;">hs-hide-level</span> <span style="color: #00d; font-weight: bold;">1</span>)
(<span style="color: #007020;">setq</span> <span style="color: #963;">this-command</span> <span style="color: #a60; background-color: #fff0f0;">'hs-cycle-children</span>))))
(<span style="color: #963;">hs-hide-level</span> <span style="color: #963;">level</span>)
(<span style="color: #007020;">setq</span> <span style="color: #963;">this-command</span> <span style="color: #a60; background-color: #fff0f0;">'hs-hide-level</span>))))
(<span style="color: #007020;">defun</span> <span style="color: #963;">hs-global-cycle</span> ()
(<span style="color: #007020;">interactive</span>)
(<span style="color: #007020;">pcase</span> <span style="color: #963;">last-command</span>
(<span style="color: #a60; background-color: #fff0f0;">'hs-global-cycle</span>
(<span style="color: #007020;">save-excursion</span> (<span style="color: #963;">hs-show-all</span>))
(<span style="color: #007020;">setq</span> <span style="color: #963;">this-command</span> <span style="color: #a60; background-color: #fff0f0;">'hs-global-show</span>))
(<span style="color: #963;">_</span> (<span style="color: #963;">hs-hide-all</span>))))
</code></pre></div><details>
Note to future self
<div class="details">
<p>This code looks like it has some redundant clauses you can refactor using <code>hs-already-hidden-p</code>, and like you dont need to set <code>last-command</code> for all the clauses. Dont try this, it breaks in subtle ways.</p>
</div>
</details>