<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>λ giuji</title>
    <subtitle><![CDATA[Feed for λ giuji blog]]></subtitle>
    <link href="http://giuji.codeberg.page/atom.xml" rel="self" />
    <link href="http://giuji.codeberg.page" />
    <id>http://giuji.codeberg.page/atom.xml</id>
    <author>
        <name>giuji</name>
        
        <email>giuji-git@proton.me</email>
        
    </author>
    <updated>2026-06-02T00:00:00Z</updated>
    <entry>
    <title>A little Vim inside your Emacs</title>
    <link href="http://giuji.codeberg.page/posts/2026-06-02-a-little-vim-inside-your-emacs.html" />
    <id>http://giuji.codeberg.page/posts/2026-06-02-a-little-vim-inside-your-emacs.html</id>
    <published>2026-06-02T00:00:00Z</published>
    <updated>2026-06-02T00:00:00Z</updated>
    <summary type="html"><![CDATA[<article>
    <section class="date">
        posted on June  2, 2026
        
    </section>
    <section class="tags">
      
          tags: <a title="All pages tagged &#39;emacs&#39;." href="/tags/emacs.html" rel="tag">emacs</a>
      
    </section>
    <section>
        <h1 id="semi-modal-editing">Semi modal editing</h1>
<p>Vanilla Emacs bindings often lack the snappiness of a modal editor like
Vim, especially when navigating, having to prefix each command with a
bunch of <code>C-x</code>, <code>C-c</code>, <code>C-@</code> and similar monstrosities can get quite
tiring (or downright <a href="http://xahlee.info/emacs/emacs/emacs_pinky.html">harmful</a>!). It would be nice to be able to switch to
some sort of a <em>temporary modal state</em>, where a single key press binds
to a command (just like in Vim), and to get back to good old vanilla
Emacs pinky workout by simply pressing any unbound key.</p>
<h1 id="implementation">Implementation</h1>
<p>Turns out that Emacs implements natively what I’ve been describing as
“temporary modal states”, they are called <em>transient-keymaps</em>, you can
think of a transient keymap as a temporary keymap that takes precedence
over all the others until an unbound key is pressed.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>(<span class="kw">defun</span><span class="fu"> transient-next/prev-line </span>()</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>  (interactive)</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>  (set-transient-map</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>   (<span class="kw">let</span> ((tmap (make-sparse-map)))</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>     (define-key (kbd <span class="st">&quot;n&quot;</span>) <span class="op">#&#39;</span>next-line)</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>     (define-key (kbd <span class="st">&quot;p&quot;</span>) <span class="op">#&#39;</span>prev-line)</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>     tmap)))</span></code></pre></div>
<p>Calling <code>transient-next/prev-line</code> sets up a transient keymap in which
key <code>n</code> is bound to <code>next-line</code> and key <code>p</code> to <code>prev-line</code>, pressing
anything else drops you out of the transient keymap. This is close to
the behavior we want but our <code>n</code> and <code>p</code> bindings are not sticky like
they would be in Vim, we want to be able to press <code>n</code> and <code>p</code> infinitely
many times without leaving the transient keymap.</p>
<p>To achieve that we use some mutual recursion magic:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>(<span class="kw">defun</span><span class="fu"> transient-next-line </span>()</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>(interactive)</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>(next-line)</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>(set-transient-map</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a> (<span class="kw">let</span> ((tmap (make-sparse-map)))</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>   (define-key (kbd <span class="st">&quot;n&quot;</span>) <span class="op">#&#39;</span>transient-next-line)</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>   (define-key (kbd <span class="st">&quot;p&quot;</span>) <span class="op">#&#39;</span>transient-prev-line)</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>   tmap)))</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>(<span class="kw">defun</span><span class="fu"> transient-prev-line </span>()</span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>  (interactive)</span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a>  (prev-line)</span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a>  (set-transient-map</span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a>   (<span class="kw">let</span> ((tmap (make-sparse-map)))</span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a>     (define-key (kbd <span class="st">&quot;n&quot;</span>) <span class="op">#&#39;</span>transient-next-line)</span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a>     (define-key (kbd <span class="st">&quot;p&quot;</span>) <span class="op">#&#39;</span>transient-prev-line)</span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a>     tmap)))</span></code></pre></div>
<p>Command <code>transient-next-line</code> moves point to the next line and sets up a
transient keymap, from there we can press <code>n</code> or <code>p</code> to to keep moving
between lines how many times we want. To wrap everything up we bind our
to commands to a common prefix:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a>(global-set-key (kbd <span class="st">&quot;C-c n&quot;</span>) <span class="op">#&#39;</span>transient-next-line)</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>(global-set-key (kbd <span class="st">&quot;C-c p&quot;</span>) <span class="op">#&#39;</span>transient-prev-line)</span></code></pre></div>
<p>Now after pressing either <code>C-c p</code> or <code>C-c n</code> we can keep pressing either
<code>n</code> or <code>p</code> to move point, pressing anything else kicks us out of the
transient. This is exactly the behavior we are looking for, mission
accomplished :D. This is pretty much how Emacs handles text resizing
inside a buffer via <code>C-x C-+</code> and <code>C-x C--</code>.</p>
<h2 id="tidying-up">Tidying up</h2>
<p>We can get rid of all the above boilerplate by writing a macro that
defines the mutually recursive commands and binds them to a common
prefix for us:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>(<span class="kw">defmacro</span><span class="fu"> bind-transient-keymap </span>(prefix &amp;<span class="kw">rest</span> keyalist)</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>  <span class="st">&quot;Build a transient keymap consisting of each (</span><span class="sc">\&quot;</span><span class="st">key</span><span class="sc">\&quot;</span><span class="st"> . command) pair in</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="st">KEYALIST and bind it to PREFIX.&quot;</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>  (cl-flet ((rename-transient (<span class="kw">symbol</span>)</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>          <span class="st">&quot;Add `--transient-wrapper` to SYMBOL&quot;</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>          (<span class="kw">intern</span> (concat (<span class="kw">symbol-name</span> <span class="kw">symbol</span>) <span class="st">&quot;--transient-wrapper&quot;</span>))))</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>    <span class="co">;; create a --transient-wrapper for each entry in KEYALIST</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>    `(<span class="kw">progn</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a>       ,@(<span class="kw">mapcar</span> (<span class="kw">lambda</span> (pair)</span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a>           (<span class="kw">let</span> ((key (<span class="kw">car</span> pair))</span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a>             (cmd (<span class="kw">cdr</span> pair)))</span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a>             `(<span class="kw">defun</span><span class="fu"> </span>,(rename-transient cmd) ()</span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a>            (interactive)</span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a>            (call-interactively <span class="op">#&#39;</span>,cmd)</span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a>            (set-transient-map</span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a>             (<span class="kw">let</span> ((tmap (make-sparse-keymap)))</span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a>               ,@(<span class="kw">mapcar</span> (<span class="kw">lambda</span> (p)</span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a>                       (<span class="kw">let</span> ((k (<span class="kw">car</span> p))</span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a>                         (c (<span class="kw">cdr</span> p)))</span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true" tabindex="-1"></a>                     `(define-key tmap</span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true" tabindex="-1"></a>                              (kbd ,k)</span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true" tabindex="-1"></a>                              <span class="op">#&#39;</span>,(rename-transient c))))</span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true" tabindex="-1"></a>                     keyalist)</span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true" tabindex="-1"></a>               tmap)))))</span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true" tabindex="-1"></a>         keyalist)</span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true" tabindex="-1"></a>       <span class="co">;; bind each --transiet-wrapper</span></span>
<span id="cb4-27"><a href="#cb4-27" aria-hidden="true" tabindex="-1"></a>       ,@(<span class="kw">mapcar</span> (<span class="kw">lambda</span> (pair)</span>
<span id="cb4-28"><a href="#cb4-28" aria-hidden="true" tabindex="-1"></a>           (<span class="kw">let</span> ((key (<span class="kw">car</span> pair))</span>
<span id="cb4-29"><a href="#cb4-29" aria-hidden="true" tabindex="-1"></a>             (cmd (<span class="kw">cdr</span> pair)))</span>
<span id="cb4-30"><a href="#cb4-30" aria-hidden="true" tabindex="-1"></a>             `(global-set-key (kbd ,(concat prefix <span class="st">&quot; &quot;</span> key))</span>
<span id="cb4-31"><a href="#cb4-31" aria-hidden="true" tabindex="-1"></a>                      <span class="op">#&#39;</span>,(rename-transient cmd))))</span>
<span id="cb4-32"><a href="#cb4-32" aria-hidden="true" tabindex="-1"></a>         keyalist))))</span></code></pre></div>
<p>Replicating the example from above using our newly defined macro is as
simple as:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode commonlisp"><code class="sourceCode commonlisp"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a>(bind-transient-keymap <span class="st">&quot;C-C&quot;</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>               (<span class="st">&quot;n&quot;</span> . next-line)</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>               (<span class="st">&quot;p&quot;</span> . prev-line))</span></code></pre></div>
    </section>
</article>
]]></summary>
</entry>

</feed>
