<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" >
  <generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator>
  <link href="https://www.bounga.org/feed/emacs.xml" rel="self" type="application/atom+xml" />
  <link href="https://www.bounga.org/" rel="alternate" type="text/html" />
  <updated>2026-07-06T19:51:25+02:00</updated>
  <id>https://www.bounga.org/feed/emacs.xml</id>

  
  <title type="html">Bounga’s Home | Emacs</title>
  
    <subtitle>Thoughts about Ruby, Rails, Hanami, Elixir, Phoenix, Emacs and Unix.</subtitle>
  
  
    <author>
        <name>Nicolas Cavigneaux</name>
      <email>nico@bounga.org</email>
      
    </author>
  

  
  
  
  
  
  
  
    <entry>
      
      <title type="html">emacs-mailto: Open macOS mailto: Links in Emacs</title>
      <link href="https://www.bounga.org/emacs/2026/07/02/emacs-mailto-open-macos-mailto-links-in-emacs/" rel="alternate" type="text/html" title="emacs-mailto: Open macOS mailto: Links in Emacs" />
      <published>2026-07-02T00:00:00+02:00</published>
      <updated>2026-07-02T00:00:00+02:00</updated>
      <id>https://www.bounga.org/emacs/2026/07/02/emacs-mailto-open-macos-mailto-links-in-emacs</id>
      <content type="html" xml:base="https://www.bounga.org/emacs/2026/07/02/emacs-mailto-open-macos-mailto-links-in-emacs/"><![CDATA[<h2 id="what-it-is">What it is</h2>

<p><code class="language-plaintext highlighter-rouge">emacs-mailto</code> makes macOS <code class="language-plaintext highlighter-rouge">mailto:</code> links open in Emacs, using whatever
<code class="language-plaintext highlighter-rouge">mail-user-agent</code> you already have configured: mu4e, notmuch, Gnus, Rmail,
or plain <code class="language-plaintext highlighter-rouge">message-mode</code>. Click a <code class="language-plaintext highlighter-rouge">mailto:</code> link in your browser, and Emacs
comes to the front with a compose buffer already addressed.</p>

<p>The project lives at
<a href="https://sr.ht/~bounga/emacs-mailto">sr.ht/~bounga/emacs-mailto</a>, where
you’ll find the source and the ticket tracker.</p>

<h2 id="the-problem-it-solves">The problem it solves</h2>

<p>The GNU Emacs NS port (as shipped by <code class="language-plaintext highlighter-rouge">emacs-plus</code>, <code class="language-plaintext highlighter-rouge">emacs-mac</code>, or the
official build) does <strong>not</strong> handle <code class="language-plaintext highlighter-rouge">mailto:</code> URLs.</p>

<p>When Emacs is already running, clicking a <code class="language-plaintext highlighter-rouge">mailto:</code> link activates Emacs but
the URL never arrives: macOS brings the app to the front and drops the URL
Apple-event before it ever reaches Emacs Lisp. So nothing happens: no compose
buffer, and no way to fix it from your config, because the event never gets
there in the first place.</p>

<p>That’s the whole frustration <code class="language-plaintext highlighter-rouge">emacs-mailto</code> exists to remove.</p>

<h2 id="how-it-works">How it works</h2>

<p><code class="language-plaintext highlighter-rouge">emacs-mailto</code> sidesteps the dropped Apple-event with a tiny relay app
registered as your default mail application. On a <code class="language-plaintext highlighter-rouge">mailto:</code> click, it brings
Emacs to the front and hands the URL to <code class="language-plaintext highlighter-rouge">emacsclient</code>:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>open <span class="nt">-a</span> Emacs
emacsclient <span class="nt">-n</span> <span class="nt">-e</span> <span class="s2">"(run-at-time 0 nil (lambda () (browse-url-mail </span><span class="se">\"</span><span class="s2">&lt;url&gt;</span><span class="se">\"</span><span class="s2">)))"</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">browse-url-mail</code> parses the <code class="language-plaintext highlighter-rouge">mailto:</code> URL (recipient, subject, body) and
opens a compose buffer through your <code class="language-plaintext highlighter-rouge">mail-user-agent</code>. The <code class="language-plaintext highlighter-rouge">run-at-time</code>
wrapper lets <code class="language-plaintext highlighter-rouge">emacsclient</code> return immediately instead of blocking on an
interactive prompt, such as a From-address picker.</p>

<p>A few details make it painless in practice:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">emacsclient</code> is located automatically at runtime (Homebrew on Apple
Silicon or Intel, MacPorts, <code class="language-plaintext highlighter-rouge">~/.local/bin</code>, or <code class="language-plaintext highlighter-rouge">/usr/bin</code>), so no path is
hard-coded.</li>
  <li>The app is built and ad-hoc signed locally by the installer, so there is
no Gatekeeper quarantine and no pre-built binary to trust.</li>
</ul>

<h2 id="requirements">Requirements</h2>

<ul>
  <li>macOS</li>
  <li>Emacs running with a server (<code class="language-plaintext highlighter-rouge">M-x server-start</code>, or the Doom/Spacemacs
defaults), so <code class="language-plaintext highlighter-rouge">emacsclient</code> can reach it</li>
  <li>A configured <code class="language-plaintext highlighter-rouge">mail-user-agent</code> (this is what actually opens the compose
buffer)</li>
</ul>

<h2 id="installation">Installation</h2>

<p>Clone the repository and run the installer:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://git.sr.ht/~bounga/emacs-mailto
<span class="nb">cd </span>emacs-mailto
./install.sh
</code></pre></div></div>

<p>This compiles <code class="language-plaintext highlighter-rouge">Emacs Mailto.app</code> into <code class="language-plaintext highlighter-rouge">~/Applications</code>, registers it, and
sets it as the default <code class="language-plaintext highlighter-rouge">mailto:</code> handler.</p>

<p>Test it:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>open <span class="s2">"mailto:test@example.com?subject=Hi"</span>
</code></pre></div></div>

<p>Emacs should come to the front with a compose buffer addressed to
<code class="language-plaintext highlighter-rouge">test@example.com</code>.</p>

<p>If your Emacs app has a different name, or you want a custom bundle
identifier, override them with environment variables:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">EMACS_APP</span><span class="o">=</span><span class="s2">"Emacs"</span> <span class="nv">BUNDLE_ID</span><span class="o">=</span><span class="s2">"org.nongnu.emacs-mailto"</span> ./install.sh
</code></pre></div></div>

<h2 id="a-caveat-about-the-default-mail-app">A caveat about the default mail app</h2>

<p>The relay runs as a background agent (<code class="language-plaintext highlighter-rouge">LSUIElement</code>), so it does <strong>not</strong>
show up in <em>Mail → Settings → General → “Default email application”</em>. That
menu may keep displaying another app; ignore it. To check the real handler,
ask LaunchServices directly:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>swift <span class="nt">-e</span> <span class="s1">'import AppKit; print(NSWorkspace.shared.urlForApplication(toOpen: URL(string:"mailto:x@y.z")!)!.path)'</span>
</code></pre></div></div>

<p>It should print <code class="language-plaintext highlighter-rouge">.../Emacs Mailto.app</code>.</p>

<h2 id="uninstall">Uninstall</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./uninstall.sh
</code></pre></div></div>

<p>This removes the app and resets the <code class="language-plaintext highlighter-rouge">mailto:</code> handler to Apple Mail.</p>

<h2 id="wrapping-up">Wrapping up</h2>

<p>The whole thing is one small relay that removes a long-standing papercut of
running Emacs as a mail client on macOS. It’s MIT licensed. If you hit a
rough edge with your particular Emacs build or macOS version, the tracker is
on the project page at
<a href="https://sr.ht/~bounga/emacs-mailto">sr.ht/~bounga/emacs-mailto</a>.</p>]]></content>

      
      
      <author>
          <name>Nicolas Cavigneaux</name>
        <email>nico@bounga.org</email>
        
      </author>

      
        <category term="Emacs" />
      
      <category term="Emacs" /><category term="macOS" /><category term="mailto" /><category term="Open Source" />

      
      
        <summary type="html"><![CDATA[The GNU Emacs NS port doesn't handle mailto: links on macOS: click one while Emacs is running and nothing happens. emacs-mailto is a tiny relay app that fixes that, opening a compose buffer through whatever mail-user-agent you already use.]]></summary>
      
    </entry>
  
    <entry>
      
      <title type="html">shpaste: a paste.sr.ht Client for Emacs</title>
      <link href="https://www.bounga.org/emacs/2026/07/01/shpaste-a-paste-sr-ht-client-for-emacs/" rel="alternate" type="text/html" title="shpaste: a paste.sr.ht Client for Emacs" />
      <published>2026-07-01T00:00:00+02:00</published>
      <updated>2026-07-02T00:00:00+02:00</updated>
      <id>https://www.bounga.org/emacs/2026/07/01/shpaste-a-paste-sr-ht-client-for-emacs</id>
      <content type="html" xml:base="https://www.bounga.org/emacs/2026/07/01/shpaste-a-paste-sr-ht-client-for-emacs/"><![CDATA[<h2 id="what-it-is">What it is</h2>

<p><code class="language-plaintext highlighter-rouge">shpaste</code> is a small Emacs client for <a href="https://paste.sr.ht">paste.sr.ht</a>,
the pastebin service from SourceHut. It lets me share a snippet without
leaving the editor: select some text, run a command, and the paste URL is
already in my kill-ring, ready to paste into a chat or an email.</p>

<p>It is built on the SourceHut GraphQL API and does just enough to be useful:
create pastes, list them, open them, delete them. Nothing more.</p>

<p>The project lives at
<a href="https://sr.ht/~bounga/shpaste">sr.ht/~bounga/shpaste</a>, where you’ll find
the source, the ticket tracker, and the mailing lists.</p>

<h2 id="what-it-does">What it does</h2>

<ul>
  <li><strong>Create a paste from the region</strong> or from the <strong>whole buffer</strong>. The
resulting URL is pushed to the kill-ring and shown in the echo area.</li>
  <li><strong>Browse your pastes</strong> in a dedicated list buffer, sorted and tabulated.</li>
  <li>From that buffer: <strong>open</strong> a paste, <strong>copy</strong> its URL, <strong>delete</strong> it, or
<strong>refresh</strong> the list.</li>
  <li>Choose a <strong>visibility</strong> per paste: <code class="language-plaintext highlighter-rouge">public</code>, <code class="language-plaintext highlighter-rouge">unlisted</code>, or <code class="language-plaintext highlighter-rouge">private</code>.</li>
  <li>Point it at a <strong>self-hosted</strong> SourceHut instance if you don’t use the
public one.</li>
</ul>

<p>The list buffer looks like this:</p>

<p><img src="/assets/images/shpaste-list.png" alt="The shpaste-list buffer" /></p>

<h2 id="requirements">Requirements</h2>

<ul>
  <li>Emacs 29.1 or later</li>
  <li><a href="https://elpa.gnu.org/packages/plz.html"><code class="language-plaintext highlighter-rouge">plz</code></a> (GNU ELPA) and <code class="language-plaintext highlighter-rouge">curl</code></li>
</ul>

<h2 id="installation">Installation</h2>

<p><code class="language-plaintext highlighter-rouge">shpaste</code> is not on MELPA yet; the package is only a few weeks old. MELPA
Stable support will come later, built from the version tags. In the
meantime, install it straight from the repository.</p>

<p>On vanilla Emacs 29.1+, <code class="language-plaintext highlighter-rouge">package-vc-install</code> clones and builds it from
source:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nv">package-vc-install</span> <span class="s">"https://git.sr.ht/~bounga/shpaste"</span><span class="p">)</span>
</code></pre></div></div>

<p>On Emacs 30+, you can let <code class="language-plaintext highlighter-rouge">use-package</code> do the same with <code class="language-plaintext highlighter-rouge">:vc</code>:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nb">use-package</span> <span class="nv">shpaste</span>
  <span class="ss">:vc</span> <span class="p">(</span><span class="ss">:url</span> <span class="s">"https://git.sr.ht/~bounga/shpaste"</span> <span class="ss">:rev</span> <span class="ss">:newest</span><span class="p">))</span>
</code></pre></div></div>

<p>In Doom Emacs, declare the recipe in <code class="language-plaintext highlighter-rouge">packages.el</code>:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nv">package!</span> <span class="nv">shpaste</span>
  <span class="ss">:recipe</span> <span class="p">(</span><span class="ss">:type</span> <span class="nv">git</span>
           <span class="ss">:host</span> <span class="no">nil</span>
           <span class="ss">:repo</span> <span class="s">"https://git.sr.ht/~bounga/shpaste"</span>
           <span class="ss">:files</span> <span class="p">(</span><span class="s">"*.el"</span><span class="p">)))</span>
</code></pre></div></div>

<p>Then configure it in <code class="language-plaintext highlighter-rouge">config.el</code>. This is the setup I use, with a <code class="language-plaintext highlighter-rouge">paste</code>
leader menu and the list-buffer bindings:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nv">use-package!</span> <span class="nv">shpaste</span>
  <span class="ss">:commands</span> <span class="p">(</span><span class="nv">shpaste-list</span>
             <span class="nv">shpaste-create-from-region</span>
             <span class="nv">shpaste-create-from-buffer</span><span class="p">)</span>
  <span class="ss">:init</span>
  <span class="p">(</span><span class="k">setq</span> <span class="nv">shpaste-default-visibility</span> <span class="ss">'unlisted</span><span class="p">)</span>
  <span class="p">(</span><span class="nv">map!</span> <span class="ss">:leader</span>
        <span class="p">(</span><span class="ss">:prefix</span> <span class="p">(</span><span class="s">"P"</span> <span class="o">.</span> <span class="s">"paste"</span><span class="p">)</span>
         <span class="ss">:desc</span> <span class="s">"List my pastes"</span>      <span class="s">"l"</span> <span class="nf">#'</span><span class="nv">shpaste-list</span>
         <span class="ss">:desc</span> <span class="s">"Paste buffer"</span>        <span class="s">"p"</span> <span class="nf">#'</span><span class="nv">shpaste-create-from-buffer</span>
         <span class="ss">:desc</span> <span class="s">"Paste region"</span>        <span class="s">"r"</span> <span class="nf">#'</span><span class="nv">shpaste-create-from-region</span><span class="p">))</span>
  <span class="ss">:config</span>
  <span class="p">(</span><span class="nv">map!</span> <span class="ss">:map</span> <span class="nv">shpaste-list-mode-map</span>
        <span class="ss">:n</span> <span class="s">"RET"</span> <span class="nf">#'</span><span class="nv">shpaste-list-open</span>
        <span class="ss">:n</span> <span class="s">"w"</span>   <span class="nf">#'</span><span class="nv">shpaste-list-copy-url</span>
        <span class="ss">:n</span> <span class="s">"d"</span>   <span class="nf">#'</span><span class="nv">shpaste-list-delete</span>
        <span class="ss">:n</span> <span class="s">"gr"</span>  <span class="nf">#'</span><span class="nv">shpaste-list-refresh</span><span class="p">))</span>
</code></pre></div></div>

<h2 id="getting-a-token">Getting a token</h2>

<p><code class="language-plaintext highlighter-rouge">shpaste</code> authenticates with a SourceHut OAuth2 <strong>personal access token</strong>.
Generate one at <code class="language-plaintext highlighter-rouge">meta.sr.ht/oauth2</code> with the <code class="language-plaintext highlighter-rouge">PASTES</code> grant in <strong>read-write</strong>
mode, then store it in <code class="language-plaintext highlighter-rouge">auth-source</code> with the host set to your instance —
for example in <code class="language-plaintext highlighter-rouge">~/.authinfo.gpg</code>:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>machine paste.sr.ht password &lt;YOUR_TOKEN&gt;
</code></pre></div></div>

<p>There is no <code class="language-plaintext highlighter-rouge">shpaste-token</code> variable: the token is only ever read from
<code class="language-plaintext highlighter-rouge">auth-source</code>. If you want the details of how that lookup works and why I
chose it, I wrote a whole post about it:
<a href="/emacs/2026/06/11/using-auth-source-in-a-real-emacs-package/">Using auth-source in a Real Emacs Package</a>.</p>

<h2 id="usage">Usage</h2>

<table>
  <thead>
    <tr>
      <th>Command</th>
      <th>Action</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">shpaste-create-from-region</code></td>
      <td>Create a paste from the region; URL to kill-ring</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">shpaste-create-from-buffer</code></td>
      <td>Create a paste from the whole buffer</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">shpaste-list</code></td>
      <td>Browse your pastes in a list buffer</td>
    </tr>
  </tbody>
</table>

<p>Inside the <code class="language-plaintext highlighter-rouge">shpaste-list</code> buffer:</p>

<table>
  <thead>
    <tr>
      <th>Key</th>
      <th>Action</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">RET</code></td>
      <td>Open the paste</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">w</code></td>
      <td>Copy its URL</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">d</code></td>
      <td>Delete it</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">g</code></td>
      <td>Refresh the list</td>
    </tr>
  </tbody>
</table>

<h2 id="configuration">Configuration</h2>

<p>Two options, both optional:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">shpaste-instance</code> (default <code class="language-plaintext highlighter-rouge">"paste.sr.ht"</code>) — set it to a self-hosted
host. It is used both to build the API endpoint and as the <code class="language-plaintext highlighter-rouge">auth-source</code>
lookup key, so the <code class="language-plaintext highlighter-rouge">machine</code> field above must match it.</li>
  <li><code class="language-plaintext highlighter-rouge">shpaste-default-visibility</code> (default <code class="language-plaintext highlighter-rouge">unlisted</code>) — one of <code class="language-plaintext highlighter-rouge">public</code>,
<code class="language-plaintext highlighter-rouge">unlisted</code>, or <code class="language-plaintext highlighter-rouge">private</code>.</li>
</ul>

<h2 id="wrapping-up">Wrapping up</h2>

<p>That’s the whole tool. <code class="language-plaintext highlighter-rouge">shpaste</code> is young (currently 0.1.0) and MIT
licensed. If you live in Emacs and use SourceHut, give it a try — and if you
hit a rough edge or have an idea, the project page is at
<a href="https://sr.ht/~bounga/shpaste">sr.ht/~bounga/shpaste</a>.</p>]]></content>

      
      
      <author>
          <name>Nicolas Cavigneaux</name>
        <email>nico@bounga.org</email>
        
      </author>

      
        <category term="Emacs" />
      
      <category term="Emacs" /><category term="shpaste" /><category term="SourceHut" /><category term="Open Source" />

      
      
        <summary type="html"><![CDATA[shpaste is a small Emacs client for paste.sr.ht. Create a paste from a region or a whole buffer, get the URL straight in your kill-ring, and browse or delete your existing pastes from a list buffer — without leaving Emacs.]]></summary>
      
    </entry>
  
    <entry>
      
      <title type="html">Sign Always, Encrypt When Possible: Automatic GPG in mu4e</title>
      <link href="https://www.bounga.org/emacs/2026/06/30/sign-always-encrypt-when-possible-in-mu4e/" rel="alternate" type="text/html" title="Sign Always, Encrypt When Possible: Automatic GPG in mu4e" />
      <published>2026-06-30T00:00:00+02:00</published>
      <updated>2026-07-02T00:00:00+02:00</updated>
      <id>https://www.bounga.org/emacs/2026/06/30/sign-always-encrypt-when-possible-in-mu4e</id>
      <content type="html" xml:base="https://www.bounga.org/emacs/2026/06/30/sign-always-encrypt-when-possible-in-mu4e/"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>GPG is only useful for the emails you actually remember to protect.</p>

<p>In <code class="language-plaintext highlighter-rouge">mu4e</code>, signing and encrypting a message is a manual gesture. Before
sending, I hit <code class="language-plaintext highlighter-rouge">C-c C-m s p</code> to sign, or <code class="language-plaintext highlighter-rouge">C-c C-m c p</code> to sign and
encrypt. It works, but it relies on me thinking about it every single
time.</p>

<p>I don’t. Nobody does.</p>

<p>So in practice, the manual approach means most messages go out
unsigned, and encryption only happens on the rare occasion I stop to
remember it exists.</p>

<p>I wanted the opposite default. Something with no friction, that follows a
clear policy:</p>

<ul>
  <li><strong>Sign every message I send.</strong> Always.</li>
  <li><strong>Encrypt a message when, and only when, I have a public key for every
recipient.</strong></li>
</ul>

<p>The second rule matters. Encryption is all-or-nothing: if a single
recipient is missing from my keyring, encrypting would lock them out of
their own email. So encryption has to be opportunistic — on when it can
be, silently off when it can’t.</p>

<p>Here is how I wired that into <code class="language-plaintext highlighter-rouge">mu4e</code>.</p>

<h2 id="the-building-blocks">The Building Blocks</h2>

<p>Three pieces do the work.</p>

<p><strong>MML secure tags.</strong> When you compose a message, Emacs doesn’t encrypt
anything itself. It inserts an MML tag that tells the message layer what
to do at send time. Two functions add those tags for me:
<code class="language-plaintext highlighter-rouge">mml-secure-message-sign</code> and <code class="language-plaintext highlighter-rouge">mml-secure-message-sign-encrypt</code>.</p>

<p><strong><code class="language-plaintext highlighter-rouge">message-send-hook</code>.</strong> This hook runs right before a message is sent,
when the headers are filled in and the recipients are known. That’s the
perfect moment to decide whether to sign or to sign and encrypt.</p>

<p><strong><code class="language-plaintext highlighter-rouge">epg</code>.</strong> The Emacs interface to GnuPG. I use it to ask a simple
question: do I have a public key for this address?</p>

<aside class="notice">
  <h4 id="where-this-code-lives">Where this code lives</h4>

  <p>Everything that follows sits inside an <code class="language-plaintext highlighter-rouge">(after! mu4e …)</code> block in my Doom
config. On vanilla Emacs, wrap it in <code class="language-plaintext highlighter-rouge">(with-eval-after-load 'mu4e …)</code>
instead.</p>
</aside>

<p>Two settings configure the signing context:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="k">setq</span> <span class="nv">mml-secure-openpgp-sign-with-sender</span> <span class="no">t</span>
      <span class="nv">mml-secure-openpgp-encrypt-to-self</span> <span class="no">t</span><span class="p">)</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">sign-with-sender</code> tells the OpenPGP layer to pick the signing key based
on the <code class="language-plaintext highlighter-rouge">From</code> address, which is exactly what I want when juggling several
accounts.</p>

<p><code class="language-plaintext highlighter-rouge">encrypt-to-self</code> is the one that saves you from a classic mistake. When
you encrypt a message to someone else’s key, you can no longer read it —
not even in your own <code class="language-plaintext highlighter-rouge">Sent</code> folder. Encrypting to yourself as well keeps
your sent copies readable.</p>

<h2 id="collecting-the-recipients">Collecting the Recipients</h2>

<p>To decide whether encryption is possible, I first need the full list of
people the message is going to.</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nb">defun</span> <span class="nv">bounga/message-recipients</span> <span class="p">()</span>
  <span class="s">"Return a list of all recipients in the message, looking at TO, CC and BCC.
Each recipient is in the format of `mail-extract-address-components'."</span>
  <span class="p">(</span><span class="nb">mapcan</span> <span class="p">(</span><span class="k">lambda</span> <span class="p">(</span><span class="nv">header</span><span class="p">)</span>
            <span class="p">(</span><span class="k">let</span> <span class="p">((</span><span class="nv">header-value</span> <span class="p">(</span><span class="nv">message-fetch-field</span> <span class="nv">header</span><span class="p">)))</span>
              <span class="p">(</span><span class="nb">and</span>
               <span class="nv">header-value</span>
               <span class="p">(</span><span class="nv">mail-extract-address-components</span> <span class="nv">header-value</span> <span class="no">t</span><span class="p">))))</span>
          <span class="o">'</span><span class="p">(</span><span class="s">"To"</span> <span class="s">"Cc"</span> <span class="s">"Bcc"</span><span class="p">)))</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">bounga/</code> prefix is my personal namespace. Emacs Lisp has a single
global namespace, so prefixing your own functions is the convention that
keeps them from colliding with built-ins or package code — never define a
bare <code class="language-plaintext highlighter-rouge">message-recipients</code> that looks like it belongs to <code class="language-plaintext highlighter-rouge">message.el</code>. Pick
whatever prefix you like; just be consistent.</p>

<p>I walk the <code class="language-plaintext highlighter-rouge">To</code>, <code class="language-plaintext highlighter-rouge">Cc</code> and <code class="language-plaintext highlighter-rouge">Bcc</code> headers, read each one with
<code class="language-plaintext highlighter-rouge">message-fetch-field</code>, and parse it with <code class="language-plaintext highlighter-rouge">mail-extract-address-components</code>.
The trailing <code class="language-plaintext highlighter-rouge">t</code> asks for <em>all</em> addresses in a header, not just the first,
so a <code class="language-plaintext highlighter-rouge">To</code> line with five people returns five entries.</p>

<p><code class="language-plaintext highlighter-rouge">Bcc</code> is in the list on purpose. A blind-carbon recipient still has to be
able to read the message — if I can’t encrypt to them, I can’t encrypt at
all.</p>

<p><code class="language-plaintext highlighter-rouge">mapcan</code> concatenates the per-header results into a single flat list.</p>

<h2 id="do-i-have-everyones-key">Do I Have Everyone’s Key?</h2>

<p>Now the actual question: is there a public key in my keyring for every
recipient?</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nb">defun</span> <span class="nv">bounga/message-all-epg-keys-available-p</span> <span class="p">()</span>
  <span class="s">"Return non-nil if the pgp keyring has a public key for each recipient."</span>
  <span class="p">(</span><span class="nb">require</span> <span class="ss">'epa</span><span class="p">)</span>
  <span class="p">(</span><span class="k">let</span> <span class="p">((</span><span class="nv">context</span> <span class="p">(</span><span class="nv">epg-make-context</span> <span class="nv">epa-protocol</span><span class="p">)))</span>
    <span class="p">(</span><span class="k">catch</span> <span class="ss">'break</span>
      <span class="p">(</span><span class="nb">dolist</span> <span class="p">(</span><span class="nv">recipient</span> <span class="p">(</span><span class="nv">bounga/message-recipients</span><span class="p">))</span>
        <span class="p">(</span><span class="k">let</span> <span class="p">((</span><span class="nv">recipient-email</span> <span class="p">(</span><span class="nb">cadr</span> <span class="nv">recipient</span><span class="p">)))</span>
          <span class="p">(</span><span class="nb">when</span> <span class="p">(</span><span class="nb">and</span> <span class="nv">recipient-email</span> <span class="p">(</span><span class="nb">not</span> <span class="p">(</span><span class="nv">epg-list-keys</span> <span class="nv">context</span> <span class="nv">recipient-email</span><span class="p">)))</span>
            <span class="p">(</span><span class="k">throw</span> <span class="ss">'break</span> <span class="no">nil</span><span class="p">))))</span>
      <span class="no">t</span><span class="p">)))</span>
</code></pre></div></div>

<p>I create an <code class="language-plaintext highlighter-rouge">epg</code> context, then loop over the recipients. For each one I
ask <code class="language-plaintext highlighter-rouge">epg-list-keys</code> whether the keyring holds a key for that address. The
email is the second element of each parsed recipient, hence <code class="language-plaintext highlighter-rouge">cadr</code>.</p>

<p>The logic is deliberately strict. The moment I find a single recipient
with no key, I <code class="language-plaintext highlighter-rouge">throw</code> out of the loop and return <code class="language-plaintext highlighter-rouge">nil</code>: encryption is off
the table. Only if the loop completes — every recipient covered — does the
function return <code class="language-plaintext highlighter-rouge">t</code>.</p>

<p>The <code class="language-plaintext highlighter-rouge">catch</code>/<code class="language-plaintext highlighter-rouge">throw</code> pair is just an early exit. There is no point checking
the remaining addresses once one is missing.</p>

<h2 id="tying-it-to-send">Tying It to Send</h2>

<p>The decision function is tiny, because the two helpers did the hard part:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nb">defun</span> <span class="nv">bounga/message-sign-encrypt-if-all-keys-available</span> <span class="p">()</span>
  <span class="s">"Add MML tag to encrypt message when there is a key for each recipient,
sign it otherwise.
Consider adding this function to `message-send-hook' to
systematically send signed / encrypted emails when possible."</span>
  <span class="p">(</span><span class="k">if</span> <span class="p">(</span><span class="nv">bounga/message-all-epg-keys-available-p</span><span class="p">)</span>
      <span class="p">(</span><span class="nv">mml-secure-message-sign-encrypt</span><span class="p">)</span>
    <span class="p">(</span><span class="nv">mml-secure-message-sign</span><span class="p">)))</span>
</code></pre></div></div>

<p>If I have everyone’s key, sign <em>and</em> encrypt. Otherwise, just sign. Either
way, the message is signed — that part is never optional.</p>

<p>The last step is to run this before every send:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nv">add-hook</span> <span class="ss">'message-send-hook</span> <span class="ss">'bounga/message-sign-encrypt-if-all-keys-available</span><span class="p">)</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">message-send-hook</code> fires once the message is complete and about to leave.
By then the recipients are settled, so the keyring check reflects exactly
who will receive the message.</p>

<h2 id="the-result">The Result</h2>

<p>With those few lines in place, I no longer have to remember GPG at all.</p>

<p>Every email I send is signed. Whenever I happen to have public keys for
all the recipients (which, over time, covers more and more of my
correspondence), the message is encrypted too, without a single keystroke
on my part.</p>

<p>A couple of honest caveats.</p>

<p>Signing everything means every recipient can tell I use GPG, and gets a
signature attachment whether they care about it or not. For me that’s a
feature, not a cost.</p>

<p>And the policy is only as good as my keyring. Encryption depends on having
imported the right public keys; nothing here fetches them for me. When a
key is missing, the message silently falls back to signing only — which is
the safe failure mode, but a silent one. If you’d rather be told, this is
the spot to add a notice.</p>

<p>None of that changes the day-to-day feel, though: I write email, I send
it, and the right cryptography happens on its own.</p>

<p>BTW, my public PGP key is available here:
<a href="https://www.cavigneaux.net/pgp.asc">DB19B66E</a>.</p>]]></content>

      
      
      <author>
          <name>Nicolas Cavigneaux</name>
        <email>nico@bounga.org</email>
        
      </author>

      
        <category term="Emacs" />
      
      <category term="Emacs" /><category term="mu4e" /><category term="Security" /><category term="GPG" />

      
      
        <summary type="html"><![CDATA[GPG only protects the emails you actually remember to protect. Instead of toggling signing and encryption by hand before every send, mu4e can do it for me: sign every message, and encrypt automatically whenever I hold a public key for every recipient.]]></summary>
      
    </entry>
  
    <entry>
      
      <title type="html">Not Quite Vim: Switching to Evil in Doom Emacs</title>
      <link href="https://www.bounga.org/emacs/2026/06/26/not-quite-vim-switching-to-evil-in-doom-emacs/" rel="alternate" type="text/html" title="Not Quite Vim: Switching to Evil in Doom Emacs" />
      <published>2026-06-26T00:00:00+02:00</published>
      <updated>2026-06-26T00:00:00+02:00</updated>
      <id>https://www.bounga.org/emacs/2026/06/26/not-quite-vim-switching-to-evil-in-doom-emacs</id>
      <content type="html" xml:base="https://www.bounga.org/emacs/2026/06/26/not-quite-vim-switching-to-evil-in-doom-emacs/"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>I’ve used Emacs for many years.</p>

<p>My fingers knew the native bindings by heart. <code class="language-plaintext highlighter-rouge">C-a</code>, <code class="language-plaintext highlighter-rouge">C-e</code>, <code class="language-plaintext highlighter-rouge">C-k</code>, <code class="language-plaintext highlighter-rouge">M-f</code>,
the whole choreography of chords. I never really questioned it.</p>

<p>Then I moved to Doom Emacs.</p>

<p>Doom is built around Evil, the Vim emulation layer. Vim keybindings are the
default, on purpose. And the first thing I did was turn them off.</p>

<p>It was pure habit. I had years of native muscle memory and no intention of
throwing it away to relearn how to move a cursor.</p>

<p>So for a while I ran Doom the “wrong” way: a Vim-first distribution with Vim
disabled.</p>

<p>Later, I changed my mind.</p>

<h2 id="giving-evil-a-real-chance">Giving Evil a Real Chance</h2>

<p>At some point it felt silly to run Doom against its own grain.</p>

<p>The whole project is designed around Evil. Most of its polish, its defaults,
its bindings assume you’re editing the Vim way. By keeping native bindings I
was fighting the tool instead of using it.</p>

<p>So I flipped the switch and committed to it.</p>

<p>The beginning was rough.</p>

<p>There’s a whole grammar to learn. Modes. Operators. Motions. Text objects. A
language where pressing keys in the wrong order does nothing useful, and where
the “obvious” Emacs reflex is suddenly wrong.</p>

<p>For a few days I was slower at everything.</p>

<p>Then, slowly, it started to make sense. And once it did, I kept it. This is
still how I edit today.</p>

<p>One thing matters before the bindings, though.</p>

<p>What I switched to isn’t really Evil. It’s <strong>Doom in Evil mode</strong>, which is a
very different, much friendlier thing.</p>

<h2 id="its-doom-not-raw-vim">It’s Doom, Not Raw Vim</h2>

<p>This distinction took me a while to appreciate.</p>

<p>Evil on its own is the engine: it teaches Emacs to speak Vim. But raw Evil is
spartan. Doom wraps it in a coherent set of defaults, a leader key,
discoverable menus, and dozens of small integrations that make the modal
model pleasant instead of merely functional.</p>

<p><code class="language-plaintext highlighter-rouge">which-key</code> is the clearest example. Start a key sequence, pause, and Doom
shows you every possible continuation in a popup. You’re never stranded
wondering what comes next.</p>

<p>So when I say I switched to Evil, I really mean I switched to Doom’s curated
take on it. I don’t think I would have stuck with bare Evil. The polish is
what kept me.</p>

<h2 id="the-mental-model-that-made-it-click">The Mental Model That Made It Click</h2>

<p>The breakthrough wasn’t memorising bindings.</p>

<p>It was understanding that Vim editing is a small language: a verb, then
something to act on.</p>

<p>You combine an <strong>operator</strong> (the verb) with a <strong>motion</strong> or a <strong>text object</strong>
(what to act on):</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">d</code> — delete</li>
  <li><code class="language-plaintext highlighter-rouge">c</code> — change (delete, then drop into insert mode)</li>
  <li><code class="language-plaintext highlighter-rouge">y</code> — yank (copy)</li>
</ul>

<p>And the things to act on:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">w</code> — to the next word</li>
  <li><code class="language-plaintext highlighter-rouge">iw</code> / <code class="language-plaintext highlighter-rouge">aw</code> — “inner word” / “a word” (with surrounding space)</li>
  <li><code class="language-plaintext highlighter-rouge">i)</code> / <code class="language-plaintext highlighter-rouge">a)</code> — inside / around parentheses</li>
  <li><code class="language-plaintext highlighter-rouge">ip</code> / <code class="language-plaintext highlighter-rouge">ap</code> — inside / around a paragraph</li>
</ul>

<p>Once that clicks, you stop memorising commands and start composing them. <code class="language-plaintext highlighter-rouge">dw</code>,
<code class="language-plaintext highlighter-rouge">ci)</code>, <code class="language-plaintext highlighter-rouge">yap</code> — none of these needed to be learned individually. They fall out
of the grammar.</p>

<p>The other revelation was the dot command.</p>

<p>Pressing <code class="language-plaintext highlighter-rouge">.</code> repeats your last change. Edit one word, jump to the next
occurrence, press <code class="language-plaintext highlighter-rouge">.</code>, and you’ve made the same edit again. It sounds trivial.
It’s one of the things I’d miss the most.</p>

<h2 id="the-editing-bindings-i-reach-for-daily">The Editing Bindings I Reach For Daily</h2>

<p>These are the ones that earned a permanent place in my hands.</p>

<p>Acting on the word under the cursor:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">daw</code> — delete a word (and its surrounding space)</li>
  <li><code class="language-plaintext highlighter-rouge">caw</code> — change a word (delete it and land in insert mode)</li>
  <li><code class="language-plaintext highlighter-rouge">diw</code> — delete the inner word, leaving the spaces</li>
  <li><code class="language-plaintext highlighter-rouge">ciw</code> — change the whole inner word</li>
  <li><code class="language-plaintext highlighter-rouge">cw</code> — change from the cursor to the end of the word</li>
</ul>

<p>Working with indentation, which is wonderful in indentation-heavy languages:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">dii</code> — delete everything at the current indentation level</li>
  <li><code class="language-plaintext highlighter-rouge">cii</code> — change that whole block</li>
</ul>

<p>Doom ships the indentation text object (<code class="language-plaintext highlighter-rouge">ii</code> / <code class="language-plaintext highlighter-rouge">ai</code>), so these compose just
like the word objects.</p>

<p>Commenting:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">gcc</code> — toggle the comment on the current line</li>
  <li><code class="language-plaintext highlighter-rouge">gc</code> + a motion — comment a region, e.g. <code class="language-plaintext highlighter-rouge">gcap</code> to comment a paragraph</li>
</ul>

<p>And for shuffling things around, there’s a surprising amount you can do with
operators and the <code class="language-plaintext highlighter-rouge">p</code> (paste) register. The Vim wiki has a
<a href="https://vim.fandom.com/wiki/Swapping_characters,_words_and_lines">good reference on swapping characters, words and lines</a>
that I still go back to.</p>

<h2 id="getting-around">Getting Around</h2>

<p>Motion is where the modal model really pays off, because once you’re in
normal mode the whole keyboard is navigation.</p>

<p>Jumping to a line is just the line number followed by <code class="language-plaintext highlighter-rouge">G</code>:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>30G
</code></pre></div></div>

<p>That puts the cursor on line 30. (<code class="language-plaintext highlighter-rouge">:30</code> followed by Enter does the same.)</p>

<p>Within a line, <code class="language-plaintext highlighter-rouge">f</code> and <code class="language-plaintext highlighter-rouge">t</code> are indispensable:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">f(</code> — jump forward to the next <code class="language-plaintext highlighter-rouge">(</code></li>
  <li><code class="language-plaintext highlighter-rouge">t(</code> — jump just before it</li>
  <li><code class="language-plaintext highlighter-rouge">;</code> and <code class="language-plaintext highlighter-rouge">,</code> — repeat that jump forward and backward</li>
</ul>

<p>But the binding that genuinely changed how I move is Doom’s avy integration,
hiding under the <code class="language-plaintext highlighter-rouge">gs</code> prefix:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">gss</code> — type a couple of characters and jump to anything visible on screen</li>
  <li><code class="language-plaintext highlighter-rouge">gsj</code> / <code class="language-plaintext highlighter-rouge">gsk</code> — jump visually to a line below / above</li>
  <li><code class="language-plaintext highlighter-rouge">gs SPC</code> — jump to any character, anywhere in the buffer</li>
</ul>

<p>After a while, reaching for the mouse or hammering arrow keys feels slow.</p>

<p>For code, Doom’s LSP layer adds the usual suspects:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">gd</code> — go to definition</li>
  <li><code class="language-plaintext highlighter-rouge">gr</code> — find references</li>
  <li><code class="language-plaintext highlighter-rouge">K</code> — show documentation for the symbol under the cursor</li>
</ul>

<h2 id="dooms-polish-the-leader-key">Doom’s Polish: The Leader Key</h2>

<p>This is the part that has very little to do with Vim, and everything to do
with why Doom is comfortable.</p>

<p>Almost every non-editing action lives under the <code class="language-plaintext highlighter-rouge">SPC</code> leader key:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">SPC f f</code> — find a file</li>
  <li><code class="language-plaintext highlighter-rouge">SPC b b</code> — switch buffer</li>
  <li><code class="language-plaintext highlighter-rouge">SPC p p</code> — switch project</li>
  <li><code class="language-plaintext highlighter-rouge">SPC w</code> — window management</li>
  <li><code class="language-plaintext highlighter-rouge">SPC s</code> — search (in buffer, project, etc.)</li>
  <li><code class="language-plaintext highlighter-rouge">SPC g g</code> — open Magit</li>
  <li><code class="language-plaintext highlighter-rouge">SPC :</code> — run a command (the old <code class="language-plaintext highlighter-rouge">M-x</code>)</li>
</ul>

<p>Because of <code class="language-plaintext highlighter-rouge">which-key</code>, I never had to memorise these up front. I’d press
<code class="language-plaintext highlighter-rouge">SPC</code>, read the menu, and drill down. The bindings stuck on their own through
repetition.</p>

<p>And when I want to know what a key does in the current mode, the bindings help
is right there under <code class="language-plaintext highlighter-rouge">SPC h b</code> — a searchable list of everything currently
bound. It’s how I discovered half of what I now use.</p>

<h2 id="what-i-didnt-fully-convert">What I Didn’t Fully Convert</h2>

<p>I’m not a purist about any of this.</p>

<p>A few Emacs reflexes survived the switch, simply because my fingers refused to
let them go.</p>

<p>The big one is <code class="language-plaintext highlighter-rouge">C-g</code>. It’s my universal “get me out of here”, and it works
everywhere in Doom, so I never replaced it. Leaving insert mode is the clearest
case: I still do it three different ways depending on where my hands are —
<code class="language-plaintext highlighter-rouge">Esc</code>, <code class="language-plaintext highlighter-rouge">jk</code>, or <code class="language-plaintext highlighter-rouge">C-g</code>. I never forced myself to settle on one.</p>

<p>In insert mode I also kept <code class="language-plaintext highlighter-rouge">C-a</code> and <code class="language-plaintext highlighter-rouge">C-e</code> to jump to the start and end of the
line. That’s an Emacs-ism rather than a Vim one — Doom keeps those motions
alive in insert state — and it’s so wired into my hands that switching to a
normal-mode motion just to reach the end of a line never felt worth it.</p>

<p>And every so often I still reach for <code class="language-plaintext highlighter-rouge">M-x</code> instead of <code class="language-plaintext highlighter-rouge">SPC :</code> to run a command.
Old habits.</p>

<p>On top of that I keep a handful of small custom bindings, and they fall into
two telling camps.</p>

<p>Some of them duplicate what Evil already does. I bound my own commands to
duplicate a line and to change the case of a word, even though Evil has
perfectly good answers — <code class="language-plaintext highlighter-rouge">yyp</code> to duplicate a line, <code class="language-plaintext highlighter-rouge">gUiw</code> / <code class="language-plaintext highlighter-rouge">guiw</code> to upper-
or lower-case a word. I kept the Emacs versions purely out of reflex.</p>

<p>Others have no Vim equivalent at all, and those I would never give up.
Narrowing is the clearest example: focusing the buffer on a single region or
function is a pure Emacs power, so I gave it a proper home under the leader.</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nv">map!</span> <span class="ss">:leader</span>
      <span class="ss">:desc</span> <span class="s">"Narrow to region"</span> <span class="s">"n r"</span> <span class="nf">#'</span><span class="nv">narrow-to-region</span>
      <span class="ss">:desc</span> <span class="s">"Narrow to defun"</span>  <span class="s">"n f"</span> <span class="nf">#'</span><span class="nv">narrow-to-defun</span>
      <span class="ss">:desc</span> <span class="s">"Widen"</span>            <span class="s">"n w"</span> <span class="nf">#'</span><span class="nv">widen</span><span class="p">)</span>
</code></pre></div></div>

<p>That’s the honest part of the story. I didn’t replace one orthodoxy with
another. I took Doom’s Evil as a strong default and bent the last few percent
to fit my hands.</p>

<p>If anything, that’s the lesson: the goal isn’t to edit like Vim, or like
Emacs. It’s to edit like you, on top of sane defaults.</p>

<h2 id="resources">Resources</h2>

<p>A few things that helped me far more than any single cheat sheet:</p>

<ul>
  <li><a href="https://noelwelsh.com/posts/doom-emacs/">Noel Welsh — Doom Emacs Workflows</a>
covers the basics of finding text, moving text, workspaces and multiple
cursors really well.</li>
  <li><a href="https://github.com/niyumard/Doom-Emacs-Cheat-Sheet">niyumard/Doom-Emacs-Cheat-Sheet</a>
for a quick binding reference.</li>
  <li>The built-in <code class="language-plaintext highlighter-rouge">SPC h b</code> bindings help, which is the resource you’ll actually
reach for once you’re inside Doom.</li>
</ul>

<h2 id="was-it-worth-it">Was It Worth It?</h2>

<p>Yes — with an asterisk.</p>

<p>It was worth it because, after the rough first days, modal editing genuinely
made me faster: the verb-plus-object grammar turns most common edits into a
couple of deliberate keystrokes.</p>

<p>The asterisk is that “switching to Evil” undersells what actually happened. I
didn’t move to Vim. I moved to Doom, resisted its way of working for a while,
then finally let it do what it was built to do.</p>

<p>The grammar fought me. Then it made sense. And it stuck.</p>]]></content>

      
      
      <author>
          <name>Nicolas Cavigneaux</name>
        <email>nico@bounga.org</email>
        
      </author>

      
        <category term="Emacs" />
      
      <category term="Emacs" /><category term="Doom Emacs" /><category term="Evil" /><category term="Vim" />

      
      
        <summary type="html"><![CDATA[I moved to Doom Emacs and disabled Evil out of habit. Later I gave its Vim bindings a real chance. The grammar fought me for a while, then it clicked, and it stuck.]]></summary>
      
    </entry>
  
    <entry>
      
      <title type="html">Using auth-source in a Real Emacs Package</title>
      <link href="https://www.bounga.org/emacs/2026/06/11/using-auth-source-in-a-real-emacs-package/" rel="alternate" type="text/html" title="Using auth-source in a Real Emacs Package" />
      <published>2026-06-11T00:00:00+02:00</published>
      <updated>2026-06-11T00:00:00+02:00</updated>
      <id>https://www.bounga.org/emacs/2026/06/11/using-auth-source-in-a-real-emacs-package</id>
      <content type="html" xml:base="https://www.bounga.org/emacs/2026/06/11/using-auth-source-in-a-real-emacs-package/"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>When an Emacs package talks to an external service, it often needs some
kind of credential.</p>

<p>In the case of <code class="language-plaintext highlighter-rouge">shpaste</code>, the package talks to <code class="language-plaintext highlighter-rouge">paste.sr.ht</code> through the
SourceHut GraphQL API. To create, list, open, and delete pastes, it needs
a SourceHut OAuth2 personal access token.</p>

<p>The full source code is available at
<a href="https://git.sr.ht/~bounga/shpaste">git.sr.ht/~bounga/shpaste</a>.</p>

<p>The boring solution would be to ask users to put a token in a variable.</p>

<p>Something like:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="k">setq</span> <span class="nv">some-token</span> <span class="s">"..."</span><span class="p">)</span>
</code></pre></div></div>

<p>I don’t like that approach.</p>

<p>It works, but it moves secret management into the package configuration.
It also encourages users to store sensitive values directly in their Emacs
config, sometimes in plain text, sometimes in a repository, and sometimes
in multiple places.</p>

<p>For <code class="language-plaintext highlighter-rouge">shpaste</code>, I wanted something simpler and more standard.</p>

<p>So the package uses <code class="language-plaintext highlighter-rouge">auth-source</code>.</p>

<p>Not as an optional extra.</p>

<p>Not as a nice-to-have.</p>

<p>As the only place where the token is looked up.</p>

<h2 id="the-setup-from-the-user-point-of-view">The Setup From the User Point of View</h2>

<p>The README keeps the setup intentionally small.</p>

<p>First, generate an OAuth2 personal access token on SourceHut with the
<code class="language-plaintext highlighter-rouge">PASTES</code> grant in read-write mode.</p>

<p>Then store it in <code class="language-plaintext highlighter-rouge">auth-source</code>:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>machine paste.sr.ht password &lt;YOUR_TOKEN&gt;
</code></pre></div></div>

<p>That’s the important part.</p>

<p>There is no <code class="language-plaintext highlighter-rouge">shpaste-token</code> variable.</p>

<p>There is no token stored in the package configuration.</p>

<p>There is no custom secret storage layer.</p>

<p>The <code class="language-plaintext highlighter-rouge">machine</code> field is also not arbitrary. It must match the configured
SourceHut instance.</p>

<p>The default instance is defined in <code class="language-plaintext highlighter-rouge">shpaste-api.el</code>:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nv">defcustom</span> <span class="nv">shpaste-instance</span> <span class="s">"paste.sr.ht"</span>
  <span class="s">"Host of the sourcehut paste instance.
Defaults to the official instance; set to a self-hosted host if needed."</span>
  <span class="ss">:type</span> <span class="ss">'string</span>
  <span class="ss">:group</span> <span class="ss">'shpaste</span><span class="p">)</span>
</code></pre></div></div>

<p>This small choice matters more than it may seem.</p>

<p>The host is used for two different things:</p>

<ul>
  <li>building the API endpoint</li>
  <li>looking up the token in <code class="language-plaintext highlighter-rouge">auth-source</code></li>
</ul>

<p>So when a user changes <code class="language-plaintext highlighter-rouge">shpaste-instance</code> to target a self-hosted instance,
the same value is used consistently everywhere.</p>

<h2 id="looking-up-the-token">Looking Up the Token</h2>

<p>The whole token lookup lives in <code class="language-plaintext highlighter-rouge">shpaste--token</code>.</p>

<p>Here is the actual function:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nb">defun</span> <span class="nv">shpaste--token</span> <span class="p">()</span>
  <span class="s">"Return the sourcehut personal access token for `shpaste-instance'.
The token is looked up in auth-source with the host set to the instance.
Signal a `user-error' when no token is configured."</span>
  <span class="p">(</span><span class="k">let</span> <span class="p">((</span><span class="nv">entry</span> <span class="p">(</span><span class="nb">car</span> <span class="p">(</span><span class="nv">auth-source-search</span> <span class="ss">:host</span> <span class="nv">shpaste-instance</span>
                                         <span class="ss">:require</span> <span class="o">'</span><span class="p">(</span><span class="ss">:secret</span><span class="p">)</span>
                                         <span class="ss">:max</span> <span class="mi">1</span><span class="p">))))</span>
    <span class="p">(</span><span class="nb">unless</span> <span class="nv">entry</span>
      <span class="p">(</span><span class="nv">user-error</span> <span class="s">"No sourcehut token for host %s; add one to auth-source (OAuth2 personal access token, scope PASTES, read-write)"</span>
                  <span class="nv">shpaste-instance</span><span class="p">))</span>
    <span class="p">(</span><span class="k">let</span> <span class="p">((</span><span class="nv">secret</span> <span class="p">(</span><span class="nv">plist-get</span> <span class="nv">entry</span> <span class="ss">:secret</span><span class="p">)))</span>
      <span class="p">(</span><span class="k">if</span> <span class="p">(</span><span class="nb">functionp</span> <span class="nv">secret</span><span class="p">)</span> <span class="p">(</span><span class="nb">funcall</span> <span class="nv">secret</span><span class="p">)</span> <span class="nv">secret</span><span class="p">))))</span>
</code></pre></div></div>

<p>There are a few interesting details here.</p>

<p>First, the lookup uses <code class="language-plaintext highlighter-rouge">shpaste-instance</code> as the host:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nv">auth-source-search</span> <span class="ss">:host</span> <span class="nv">shpaste-instance</span>
                    <span class="ss">:require</span> <span class="o">'</span><span class="p">(</span><span class="ss">:secret</span><span class="p">)</span>
                    <span class="ss">:max</span> <span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>

<p>That means the package does not need a separate configuration value for
credentials.</p>

<p>If the instance is <code class="language-plaintext highlighter-rouge">paste.sr.ht</code>, the token is looked up for
<code class="language-plaintext highlighter-rouge">paste.sr.ht</code>.</p>

<p>If the instance is changed to a self-hosted host, the token lookup follows
that host automatically.</p>

<p>This keeps the mental model simple:</p>

<blockquote>
  <p>configure the instance once, then store the matching token in auth-source.</p>
</blockquote>

<p>That’s usually the kind of configuration I prefer in Emacs packages. One
concept, one value, no duplicated settings.</p>

<h2 id="requiring-a-secret">Requiring a Secret</h2>

<p>The lookup also uses:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="ss">:require</span> <span class="o">'</span><span class="p">(</span><span class="ss">:secret</span><span class="p">)</span>
</code></pre></div></div>

<p>This is small but important.</p>

<p>The package is not interested in any random auth-source entry matching the
host.</p>

<p>It needs a secret.</p>

<p>If no usable entry exists, <code class="language-plaintext highlighter-rouge">auth-source-search</code> won’t return something that
pretends to be good enough.</p>

<p>This keeps the rest of the code simpler because <code class="language-plaintext highlighter-rouge">shpaste--token</code> has a
clear contract:</p>

<ul>
  <li>it returns a token</li>
  <li>or it signals a user-facing error</li>
</ul>

<p>There is no half-configured state leaking into the HTTP layer.</p>

<h2 id="failing-early">Failing Early</h2>

<p>Missing credentials are not handled later by the API client.</p>

<p>They are handled immediately:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nb">unless</span> <span class="nv">entry</span>
  <span class="p">(</span><span class="nv">user-error</span> <span class="s">"No sourcehut token for host %s; add one to auth-source (OAuth2 personal access token, scope PASTES, read-write)"</span>
              <span class="nv">shpaste-instance</span><span class="p">))</span>
</code></pre></div></div>

<p>I like this for two reasons.</p>

<p>First, the error is explicit.</p>

<p>The user is not left with a random <code class="language-plaintext highlighter-rouge">401</code>, a GraphQL error, or a failed HTTP
request. The package tells exactly what is missing and which host was used
for the lookup.</p>

<p>Second, it fails before building the request.</p>

<p>This makes debugging much easier. If authentication is not configured, the
problem is reported at the boundary where credentials are read, not five
functions later.</p>

<p>That may sound obvious, but it is the kind of detail that makes a package
feel nicer to use.</p>

<h2 id="secrets-are-not-always-strings">Secrets Are Not Always Strings</h2>

<p>One easy mistake with <code class="language-plaintext highlighter-rouge">auth-source</code> is assuming that <code class="language-plaintext highlighter-rouge">:secret</code> is always the
final string.</p>

<p><code class="language-plaintext highlighter-rouge">shpaste</code> does not make that assumption:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="k">let</span> <span class="p">((</span><span class="nv">secret</span> <span class="p">(</span><span class="nv">plist-get</span> <span class="nv">entry</span> <span class="ss">:secret</span><span class="p">)))</span>
  <span class="p">(</span><span class="k">if</span> <span class="p">(</span><span class="nb">functionp</span> <span class="nv">secret</span><span class="p">)</span> <span class="p">(</span><span class="nb">funcall</span> <span class="nv">secret</span><span class="p">)</span> <span class="nv">secret</span><span class="p">))</span>
</code></pre></div></div>

<p>This is one of those small pieces of code that is easy to miss if you only
test with one local setup.</p>

<p>Depending on the backend, <code class="language-plaintext highlighter-rouge">auth-source</code> may return the secret directly, or
it may return a function that has to be called to obtain the secret.</p>

<p>By handling both cases, the package avoids coupling itself too tightly to
one specific auth-source backend.</p>

<p>The important part here is not that the code is complicated.</p>

<p>It is the opposite.</p>

<p>The implementation is tiny, but it accounts for a real behavior of the API.</p>

<p>That’s usually a good sign.</p>

<h2 id="building-the-authorization-header">Building the Authorization Header</h2>

<p>Once the token retrieval is isolated, the Authorization header becomes a
small helper:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nb">defun</span> <span class="nv">shpaste--auth-header</span> <span class="p">()</span>
  <span class="s">"Return the Authorization header entry carrying the bearer token."</span>
  <span class="p">(</span><span class="nb">cons</span> <span class="s">"Authorization"</span> <span class="p">(</span><span class="nv">concat</span> <span class="s">"Bearer "</span> <span class="p">(</span><span class="nv">shpaste--token</span><span class="p">))))</span>
</code></pre></div></div>

<p>This keeps the rest of the transport code readable.</p>

<p>For normal GraphQL queries, <code class="language-plaintext highlighter-rouge">shpaste--query</code> uses it here:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nv">raw</span> <span class="p">(</span><span class="nv">plz</span> <span class="ss">'post</span> <span class="p">(</span><span class="nv">shpaste--endpoint</span><span class="p">)</span>
       <span class="ss">:headers</span> <span class="p">(</span><span class="nb">list</span> <span class="p">(</span><span class="nv">shpaste--auth-header</span><span class="p">)</span>
                      <span class="o">'</span><span class="p">(</span><span class="s">"Content-Type"</span> <span class="o">.</span> <span class="s">"application/json"</span><span class="p">))</span>
       <span class="ss">:body</span> <span class="p">(</span><span class="nv">json-encode</span> <span class="nv">payload</span><span class="p">)</span>
       <span class="ss">:as</span> <span class="ss">'string</span><span class="p">))</span>
</code></pre></div></div>

<p>For blob fetching, the same helper is reused:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nv">plz</span> <span class="ss">'get</span> <span class="nv">url</span>
  <span class="ss">:headers</span> <span class="p">(</span><span class="nb">list</span> <span class="p">(</span><span class="nv">shpaste--auth-header</span><span class="p">))</span>
  <span class="ss">:as</span> <span class="ss">'string</span><span class="p">)</span>
</code></pre></div></div>

<p>There is no repeated token lookup logic scattered around the codebase.</p>

<p>The package has one way to build authenticated requests.</p>

<p>That’s boring, but it is exactly what I want in this kind of code.</p>

<h2 id="the-endpoint-uses-the-same-instance">The Endpoint Uses the Same Instance</h2>

<p>The token lookup is not the only place where <code class="language-plaintext highlighter-rouge">shpaste-instance</code> is used.</p>

<p>The GraphQL endpoint is also derived from it:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nb">defun</span> <span class="nv">shpaste--endpoint</span> <span class="p">()</span>
  <span class="s">"Return the GraphQL endpoint URL for `shpaste-instance'."</span>
  <span class="p">(</span><span class="nb">format</span> <span class="s">"https://%s/query"</span> <span class="nv">shpaste-instance</span><span class="p">))</span>
</code></pre></div></div>

<p>This gives <code class="language-plaintext highlighter-rouge">shpaste-instance</code> a clear role:</p>

<ul>
  <li>it identifies the SourceHut paste host</li>
  <li>it selects the matching auth-source entry</li>
  <li>it builds the GraphQL endpoint</li>
</ul>

<p>Again, the interesting part is not the code itself.</p>

<p>The interesting part is that the package avoids inventing three different
configuration knobs for what is really one concept.</p>

<h2 id="testing-the-token-lookup">Testing the Token Lookup</h2>

<p>Credential code is easy to ignore in tests because it often depends on the
developer’s local machine.</p>

<p><code class="language-plaintext highlighter-rouge">shpaste</code> avoids that by stubbing <code class="language-plaintext highlighter-rouge">auth-source-search</code>.</p>

<p>The first test checks that a function secret is called:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nv">ert-deftest</span> <span class="nv">shpaste-test-token-found</span> <span class="p">()</span>
  <span class="s">"Token is read from auth-source; a function secret is funcalled."</span>
  <span class="p">(</span><span class="nv">cl-letf</span> <span class="p">(((</span><span class="nb">symbol-function</span> <span class="ss">'auth-source-search</span><span class="p">)</span>
             <span class="p">(</span><span class="k">lambda</span> <span class="p">(</span><span class="k">&amp;rest</span> <span class="nv">_</span><span class="p">)</span> <span class="p">(</span><span class="nb">list</span> <span class="p">(</span><span class="nb">list</span> <span class="ss">:secret</span> <span class="p">(</span><span class="k">lambda</span> <span class="p">()</span> <span class="s">"tok123"</span><span class="p">))))))</span>
    <span class="p">(</span><span class="nv">should</span> <span class="p">(</span><span class="nb">equal</span> <span class="p">(</span><span class="nv">shpaste--token</span><span class="p">)</span> <span class="s">"tok123"</span><span class="p">))))</span>
</code></pre></div></div>

<p>This test is useful because it verifies the non-obvious case.</p>

<p>If <code class="language-plaintext highlighter-rouge">:secret</code> is a function, <code class="language-plaintext highlighter-rouge">shpaste--token</code> must call it.</p>

<p>A simpler implementation returning <code class="language-plaintext highlighter-rouge">(plist-get entry :secret)</code> would pass
some manual tests, but it would fail this one.</p>

<p>The second test checks the missing-token path:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nv">ert-deftest</span> <span class="nv">shpaste-test-token-missing</span> <span class="p">()</span>
  <span class="s">"A missing token raises a user-error."</span>
  <span class="p">(</span><span class="nv">cl-letf</span> <span class="p">(((</span><span class="nb">symbol-function</span> <span class="ss">'auth-source-search</span><span class="p">)</span> <span class="p">(</span><span class="k">lambda</span> <span class="p">(</span><span class="k">&amp;rest</span> <span class="nv">_</span><span class="p">)</span> <span class="no">nil</span><span class="p">)))</span>
    <span class="p">(</span><span class="nv">should-error</span> <span class="p">(</span><span class="nv">shpaste--token</span><span class="p">)</span> <span class="ss">:type</span> <span class="ss">'user-error</span><span class="p">)))</span>
</code></pre></div></div>

<p>This is just as important.</p>

<p>The behavior is not only documented in the function docstring. It is tested.</p>

<p>No token means <code class="language-plaintext highlighter-rouge">user-error</code>.</p>

<p>Not <code class="language-plaintext highlighter-rouge">nil</code>.</p>

<p>Not a later HTTP failure.</p>

<p>Not a cryptic exception from another layer.</p>

<p>A clear user-facing error.</p>

<h2 id="testing-the-header">Testing the Header</h2>

<p>The tests also verify that the token actually ends up in the HTTP request.</p>

<p>In <code class="language-plaintext highlighter-rouge">shpaste-test-query</code>, the token function is stubbed:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nv">cl-letf</span> <span class="p">(((</span><span class="nb">symbol-function</span> <span class="ss">'shpaste--token</span><span class="p">)</span> <span class="p">(</span><span class="k">lambda</span> <span class="p">()</span> <span class="s">"tok"</span><span class="p">))</span>
          <span class="p">((</span><span class="nb">symbol-function</span> <span class="ss">'plz</span><span class="p">)</span>
           <span class="p">(</span><span class="k">lambda</span> <span class="p">(</span><span class="k">&amp;rest</span> <span class="nv">args</span><span class="p">)</span>
             <span class="p">(</span><span class="k">setq</span> <span class="nv">captured-args</span> <span class="nv">args</span><span class="p">)</span>
             <span class="s">"{"</span><span class="nv">data</span><span class="s">":{"</span><span class="nv">ping</span><span class="s">":"</span><span class="nv">pong</span><span class="s">"}}"</span><span class="p">)))</span>
</code></pre></div></div>

<p>Then the test checks the Authorization header:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="k">let</span> <span class="p">((</span><span class="nv">auth</span> <span class="p">(</span><span class="nv">alist-get</span> <span class="s">"Authorization"</span> <span class="p">(</span><span class="nv">plist-get</span> <span class="p">(</span><span class="nb">cddr</span> <span class="nv">captured-args</span><span class="p">)</span> <span class="ss">:headers</span><span class="p">)</span>
                       <span class="no">nil</span> <span class="no">nil</span> <span class="nf">#'</span><span class="nb">equal</span><span class="p">)))</span>
  <span class="p">(</span><span class="nv">should</span> <span class="p">(</span><span class="nb">equal</span> <span class="nv">auth</span> <span class="s">"Bearer tok"</span><span class="p">)))</span>
</code></pre></div></div>

<p>This may look like a small assertion, but it protects the integration point.</p>

<p>The test does not only check that <code class="language-plaintext highlighter-rouge">shpaste--token</code> works in isolation.</p>

<p>It also checks that <code class="language-plaintext highlighter-rouge">shpaste--query</code> builds authenticated requests properly.</p>

<p>That’s the useful level of testing for a small package:</p>

<ul>
  <li>test the unit</li>
  <li>test the boundary where the unit is consumed</li>
  <li>avoid hitting the real network</li>
</ul>

<h2 id="testing-blob-fetching-too">Testing Blob Fetching Too</h2>

<p>The same idea appears in the blob-fetching test.</p>

<p><code class="language-plaintext highlighter-rouge">shpaste--fetch-blob</code> performs an authenticated <code class="language-plaintext highlighter-rouge">GET</code>:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nb">defun</span> <span class="nv">shpaste--fetch-blob</span> <span class="p">(</span><span class="nv">url</span><span class="p">)</span>
  <span class="s">"Fetch and return the raw text content at URL (authenticated)."</span>
  <span class="p">(</span><span class="nv">plz</span> <span class="ss">'get</span> <span class="nv">url</span>
    <span class="ss">:headers</span> <span class="p">(</span><span class="nb">list</span> <span class="p">(</span><span class="nv">shpaste--auth-header</span><span class="p">))</span>
    <span class="ss">:as</span> <span class="ss">'string</span><span class="p">))</span>
</code></pre></div></div>

<p>The corresponding test checks that the request also carries the bearer
token:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="k">let</span> <span class="p">((</span><span class="nv">auth</span> <span class="p">(</span><span class="nv">alist-get</span> <span class="s">"Authorization"</span> <span class="p">(</span><span class="nv">plist-get</span> <span class="p">(</span><span class="nb">cddr</span> <span class="nv">captured</span><span class="p">)</span> <span class="ss">:headers</span><span class="p">)</span>
                       <span class="no">nil</span> <span class="no">nil</span> <span class="nf">#'</span><span class="nb">equal</span><span class="p">)))</span>
  <span class="p">(</span><span class="nv">should</span> <span class="p">(</span><span class="nb">equal</span> <span class="nv">auth</span> <span class="s">"Bearer tok"</span><span class="p">)))</span>
</code></pre></div></div>

<p>This matters because fetching the paste metadata and fetching the paste
content are not the same operation.</p>

<p>Both need authentication.</p>

<p>Both should use the same mechanism.</p>

<p>Both are tested.</p>

<h2 id="what-i-like-about-this-design">What I Like About This Design</h2>

<p>The whole authentication layer is small.</p>

<p>There is no framework.</p>

<p>There is no custom credential store.</p>

<p>There is no complicated abstraction.</p>

<p>The flow is simply:</p>

<ol>
  <li>read the token from <code class="language-plaintext highlighter-rouge">auth-source</code></li>
  <li>normalize the secret if needed</li>
  <li>build a bearer header</li>
  <li>reuse that header everywhere HTTP authentication is needed</li>
  <li>test both success and failure cases</li>
</ol>

<p>That is enough.</p>

<p>And honestly, that’s what I like about it.</p>

<p>The package does not try to be clever. It uses an Emacs mechanism that
already exists and keeps the project-specific code focused on SourceHut.</p>

<h2 id="things-worth-keeping-in-mind">Things Worth Keeping in Mind</h2>

<p>This design is not magic.</p>

<p>Users still need to configure their token correctly.</p>

<p>For the default instance, the README shows:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>machine paste.sr.ht password &lt;YOUR_TOKEN&gt;
</code></pre></div></div>

<p>If <code class="language-plaintext highlighter-rouge">shpaste-instance</code> is changed, the <code class="language-plaintext highlighter-rouge">machine</code> field must match the new
host.</p>

<p>That coupling is intentional, but it is also something users need to know.</p>

<p>The package helps by making the missing-token error explicit:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nv">user-error</span> <span class="s">"No sourcehut token for host %s; add one to auth-source (OAuth2 personal access token, scope PASTES, read-write)"</span>
            <span class="nv">shpaste-instance</span><span class="p">)</span>
</code></pre></div></div>

<p>Could this message be shorter?</p>

<p>Maybe.</p>

<p>But for a configuration error, I prefer being slightly verbose and useful.</p>

<p>A short error like <code class="language-plaintext highlighter-rouge">"No token"</code> would be less helpful.</p>

<h2 id="my-take">My Take</h2>

<p>For small Emacs packages, I think the best configuration is often the one
that does not introduce a new concept.</p>

<p><code class="language-plaintext highlighter-rouge">shpaste</code> already needs to know the SourceHut paste instance.</p>

<p>So it reuses that value:</p>

<ul>
  <li>as the API host</li>
  <li>as the auth-source lookup key</li>
</ul>

<p>That keeps the user-facing setup compact.</p>

<p>It also keeps the implementation easy to follow.</p>

<p>The full credential retrieval logic fits in one function, and the tests
cover the important paths.</p>

<p>That’s a good trade-off.</p>

<p>Not because it is fancy.</p>

<p>Because it is boring in the right way.</p>

<h2 id="wrapping-up">Wrapping Up</h2>

<p>If your Emacs package needs an API token, storing it in a custom variable is
tempting.</p>

<p>It is easy to implement.</p>

<p>It is also usually not the best user experience.</p>

<p>With <code class="language-plaintext highlighter-rouge">auth-source</code>, Emacs already gives us a standard place to retrieve
credentials.</p>

<p>In <code class="language-plaintext highlighter-rouge">shpaste</code>, that leads to a small and clear design:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">shpaste-instance</code> identifies the host</li>
  <li><code class="language-plaintext highlighter-rouge">auth-source-search</code> retrieves the token</li>
  <li>function secrets are handled properly</li>
  <li>missing credentials raise a <code class="language-plaintext highlighter-rouge">user-error</code></li>
  <li>HTTP requests reuse a single Authorization header helper</li>
  <li>tests verify both lookup and request integration</li>
</ul>

<p>There is nothing spectacular here.</p>

<p>And that’s the point.</p>

<p>Good authentication plumbing should be boring, explicit, and easy to test.</p>]]></content>

      
      
      <author>
          <name>Nicolas Cavigneaux</name>
        <email>nico@bounga.org</email>
        
      </author>

      
        <category term="Emacs" />
      
      <category term="Emacs" /><category term="Security" /><category term="auth-source" />

      
      
        <summary type="html"><![CDATA[When an Emacs package needs an API token, the easy path is to introduce a custom variable and let users store the secret in their configuration. shpaste takes a different route: it relies on auth-source, fails early when the token is missing, and tests the behavior explicitly.]]></summary>
      
    </entry>
  
    <entry>
      
      <title type="html">Setting up multiple IMAP and SMTP accounts in Gnus</title>
      <link href="https://www.bounga.org/tips/2020/05/03/multiple-smtp-accounts-in-gnus-without-external-tools/" rel="alternate" type="text/html" title="Setting up multiple IMAP and SMTP accounts in Gnus" />
      <published>2020-05-03T00:00:00+02:00</published>
      <updated>2020-05-03T00:00:00+02:00</updated>
      <id>https://www.bounga.org/tips/2020/05/03/multiple-smtp-accounts-in-gnus-without-external-tools</id>
      <content type="html" xml:base="https://www.bounga.org/tips/2020/05/03/multiple-smtp-accounts-in-gnus-without-external-tools/"><![CDATA[<p>Emacs’ users tend to like to do everything in it as I do. A frequent
desire is to be able to read emails in it. A popular option is
<a href="https://gnus.org">Gnus</a>.</p>

<p>Gnus is a beast it can do a lot of things from newsgroups, to RSS to
emails. So configuring and using it can be a bit scary at first.</p>

<p>There’s a really widespread misinformation about Gnus. I thought for long
it was true but it comes out it wasn’t.</p>

<p>This is about using multiple SMTP servers seamlessly for sending
emails. I always read you have to use external tools such as
<a href="https://marlam.de/msmtp/">MSMTP</a> with specific configuration to make
it acts as a proxy between your software and your multiple remote
SMTP. Some other advice you to use a homemade function to play with
message fields and hook it via <code class="language-plaintext highlighter-rouge">message-send-hook</code>.</p>

<p>Some will even tell you to hack your <code class="language-plaintext highlighter-rouge">/etc/hosts</code>.</p>

<p>But hey, Gnus is there since the nineties. Someone must have think of
a solution and it must be built-in!</p>

<p>I stopped searching blog posts and Stack Overflow and read the <a href="https://www.gnu.org/software/emacs/manual/html_node/message/Mail-Variables.html">manual
entry</a>
which clearly states that you <strong>can</strong> set up a complex workflow using
multiple SMTP servers.</p>

<p>This works by using <a href="https://www.gnu.org/software/emacs/manual/html_node/gnus/Posting-Styles.html">posting
styles</a>
which is a way to instruct Gnus of how you want to prepare your email
(headers, body, signature) according to the context.</p>

<p>Before digging into it I’d like to explain my IMAP settings because
it’s tightly related to how we’re going to setup SMTP.</p>

<p>So let’s take a look at the “select-method” definitions. This is where
we tell Gnus about our IMAP servers, their local name and how they
must behave:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="k">setq</span> <span class="nv">gnus-select-method</span> <span class="o">'</span><span class="p">(</span><span class="nv">nnnil</span> <span class="no">nil</span><span class="p">))</span>
<span class="p">(</span><span class="k">setq</span> <span class="nv">gnus-secondary-select-methods</span>
      <span class="o">'</span><span class="p">((</span><span class="nv">nnimap</span> <span class="s">"home"</span>
                <span class="p">(</span><span class="nv">nnimap-address</span> <span class="s">"imap.gmail.com"</span><span class="p">)</span>
                <span class="p">(</span><span class="nv">nnimap-server-port</span> <span class="s">"imaps"</span><span class="p">)</span>
                <span class="p">(</span><span class="nv">nnimap-stream</span> <span class="nv">ssl</span><span class="p">)</span>
                <span class="p">(</span><span class="nv">nnir-search-engine</span> <span class="nv">imap</span><span class="p">)</span>
                <span class="p">(</span><span class="nv">nnmail-expiry-target</span> <span class="s">"nnimap+home:[Gmail]/Trash"</span><span class="p">)</span>
                <span class="p">(</span><span class="nv">nnmail-expiry-wait</span> <span class="ss">'immediate</span><span class="p">))</span>
        <span class="p">(</span><span class="nv">nnimap</span> <span class="s">"work"</span>
                <span class="p">(</span><span class="nv">nnimap-address</span> <span class="s">"imap.gmail.com"</span><span class="p">)</span>
                <span class="p">(</span><span class="nv">nnimap-server-port</span> <span class="s">"imaps"</span><span class="p">)</span>
                <span class="p">(</span><span class="nv">nnimap-stream</span> <span class="nv">ssl</span><span class="p">)</span>
                <span class="p">(</span><span class="nv">nnir-search-engine</span> <span class="nv">imap</span><span class="p">)</span>
                <span class="p">(</span><span class="nv">nnmail-expiry-target</span> <span class="s">"nnimap+work:[Gmail]/Trash"</span><span class="p">)</span>
                <span class="p">(</span><span class="nv">nnmail-expiry-wait</span> <span class="ss">'immediate</span><span class="p">))))</span>
</code></pre></div></div>

<p>We set <code class="language-plaintext highlighter-rouge">gnus-select-method</code> to <code class="language-plaintext highlighter-rouge">nnnil</code> which is a NOOP back-end. I
prefer to set all the accounts in the same place
<code class="language-plaintext highlighter-rouge">gnus-secondary-select-methods</code>.</p>

<p>In this variable I have declared two IMAP servers. The first one will
be known locally as <code class="language-plaintext highlighter-rouge">home</code> and the second one as <code class="language-plaintext highlighter-rouge">work</code>.</p>

<p>Both are using Gmail, so we have to find a way to distinguish these to
account to provide credentials.</p>

<p>The standard Unix way to share credentials across software is to
store them in <code class="language-plaintext highlighter-rouge">~/.authinfo</code> file. In my case I use <code class="language-plaintext highlighter-rouge">~/.authinfo.gpg</code>
so my credential are encrypted with GPG and no one but me can read it.</p>

<p>Here is the content of <code class="language-plaintext highlighter-rouge">~/.authinfo.gpg</code>:</p>

<div class="language-config highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">machine</span> <span class="n">home</span> <span class="n">login</span> <span class="n">home</span>@<span class="n">gmail</span>.<span class="n">com</span> <span class="n">password</span> <span class="n">my_pasword</span> <span class="n">port</span> <span class="n">imaps</span>
<span class="n">machine</span> <span class="n">work</span> <span class="n">login</span> <span class="n">work</span>@<span class="n">gmail</span>.<span class="n">com</span> <span class="n">password</span> <span class="n">my_other_password</span> <span class="n">port</span> <span class="n">imaps</span>
</code></pre></div></div>

<p>Now Gnus can read this file to get the credentials and log on the IMAP
servers. Gnus know how to bind a given credential to a specific
account because their share the same name <code class="language-plaintext highlighter-rouge">home</code> and <code class="language-plaintext highlighter-rouge">work</code>.</p>

<p>Ok from now on, we can get emails from Gnus through these two email
accounts.</p>

<p>Now it’s time to configure these accounts to send emails using their
respective SMTP server / credential.</p>

<p>Just for the sake of clarity, I do use the same IMAP / SMTP addresses
for both accounts in my situation but this technique would work the
same way with two accounts on two different email providers.</p>

<p>All the magic happens by taking advantage of <a href="https://www.gnu.org/software/emacs/manual/html_node/gnus/Posting-Styles.html">Gnus posting
styles</a>:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">;; Reply to mails with matching email address</span>
<span class="p">(</span><span class="k">setq</span> <span class="nv">gnus-posting-styles</span>
      <span class="o">'</span><span class="p">((</span><span class="s">".*"</span> <span class="c1">; Matches all groups of messages</span>
         <span class="p">(</span><span class="nv">address</span> <span class="s">"Nicolas Cavigneaux &lt;home@gmail.com&gt;"</span><span class="p">))</span>
        <span class="p">(</span><span class="s">"work"</span> <span class="c1">; Matches Gnus group called "work"</span>
         <span class="p">(</span><span class="nv">address</span> <span class="s">"Nicolas Cavigneaux &lt;work@gmail.com&gt;"</span><span class="p">)</span>
         <span class="p">(</span><span class="nv">organization</span> <span class="s">"Corp"</span><span class="p">)</span>
         <span class="p">(</span><span class="nv">signature-file</span> <span class="s">"~/.signature-work"</span><span class="p">)</span>
         <span class="p">(</span><span class="s">"X-Message-SMTP-Method"</span> <span class="s">"smtp smtp.gmail.com 587 work@gmail.com"</span><span class="p">))))</span>
</code></pre></div></div>

<p>The first line with the <code class="language-plaintext highlighter-rouge">.*</code> is kind of catch-all rule which tells
Gnus that no matter what is the group I’m in, my sender email address
is going to be <code class="language-plaintext highlighter-rouge">home@gmail.com</code>.</p>

<p>For those who are not familiars with Gnus, a group is just an IMAP
folder.</p>

<p>Then the second rule tells Gnus, if the current group matches anything
with <code class="language-plaintext highlighter-rouge">work</code> in it then I want to handle my outgoing emails
differently. My sender address is going to be <code class="language-plaintext highlighter-rouge">work@gmail.com</code>, my
<code class="language-plaintext highlighter-rouge">organization</code> header is going to be <code class="language-plaintext highlighter-rouge">Corp</code>, my automatically inserted
signature at the bottom of the email is going to be read from
<code class="language-plaintext highlighter-rouge">~/.signature-work</code> file <strong>and here happens the magic</strong> we use a
special header that Gnus and <code class="language-plaintext highlighter-rouge">message-mode</code> understand called
<code class="language-plaintext highlighter-rouge">X-Message-SMTP-Method</code> that was designed for this exact purpose,
being able to specify an alternative SMTP server to use. So we specify
that we want to use <code class="language-plaintext highlighter-rouge">smtp</code> protocol using the address <code class="language-plaintext highlighter-rouge">smtp.gmail.com</code>
on port <code class="language-plaintext highlighter-rouge">587</code> and that the user account to use is <code class="language-plaintext highlighter-rouge">work@gmail.com</code>.</p>

<p>There’s one last thing to setup and you’ll be good to go. You need to
provide your SMTP credentials. Once again it takes place in the
<code class="language-plaintext highlighter-rouge">~/.authinfo</code> file:</p>

<div class="language-config highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">machine</span> <span class="n">smtp</span>.<span class="n">gmail</span>.<span class="n">com</span> <span class="n">login</span> <span class="n">home</span>@<span class="n">gmail</span>.<span class="n">com</span> <span class="n">password</span> <span class="n">my_password</span> <span class="n">port</span> <span class="m">587</span>
<span class="n">machine</span> <span class="n">smtp</span>.<span class="n">gmail</span>.<span class="n">com</span> <span class="n">login</span> <span class="n">work</span>@<span class="n">gmail</span>.<span class="n">com</span> <span class="n">password</span> <span class="n">my_other_password</span> <span class="n">port</span> <span class="m">587</span>
</code></pre></div></div>

<p>By searching the server name / username Gnus will be able to know
the right credential to use.</p>

<p>It basically enables multi SMTP accounts in Gnus without bothering with
<a href="https://www.emacswiki.org/emacs/MultipleSMTPAccounts">any</a> of <a href="https://www.emacswiki.org/emacs/MultipleSMTPAccounts">these</a> <a href="https://www.emacswiki.org/emacs/GnusMSMTP">techniques</a>.</p>

<p>Moral of the story, when it comes to Emacs you should always read the
official doc first since most of the time you’ll find the info you
need.</p>]]></content>

      
      
      <author>
          <name>Nicolas Cavigneaux</name>
        <email>nico@bounga.org</email>
        
      </author>

      
        <category term="Tips" />
      
      <category term="Emacs" /><category term="Gnus" /><category term="Editor" />

      
      
        <summary type="html"><![CDATA[When it comes to Emacs and emails reading, one of the most popular options is Gnus. Let's see how to configure it for multiple IMAP / SMTP servers without using any external tool.]]></summary>
      
    </entry>
  
    <entry>
      
      <title type="html">Easily insert Elixir pipe operator in Emacs</title>
      <link href="https://www.bounga.org/emacs/2020/04/22/easily-insert-elixir-pipe-operator-in-emacs/" rel="alternate" type="text/html" title="Easily insert Elixir pipe operator in Emacs" />
      <published>2020-04-22T00:00:00+02:00</published>
      <updated>2020-04-22T00:00:00+02:00</updated>
      <id>https://www.bounga.org/emacs/2020/04/22/easily-insert-elixir-pipe-operator-in-emacs</id>
      <content type="html" xml:base="https://www.bounga.org/emacs/2020/04/22/easily-insert-elixir-pipe-operator-in-emacs/"><![CDATA[<p>I’m using <a href="https://elixir-lang.org/">Elixir</a> more and more lately and
I love it!</p>

<p>Elixir is a functional language and as such it’s very common to feed a
function with the return of another one like so:</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">length</span><span class="p">(</span><span class="no">String</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">line</span><span class="p">))</span>
</code></pre></div></div>

<p>It can quickly become are to read so Elixir provides a syntactic sugar
to pipe a function return to another one:</p>

<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">line</span>
<span class="o">|&gt;</span> <span class="no">String</span><span class="o">.</span><span class="n">split</span><span class="p">()</span>
<span class="o">|&gt;</span> <span class="n">length</span><span class="p">()</span>
</code></pre></div></div>

<p>Easier on the eyes isn’t it?</p>

<p>I like this syntactic sugar a lot but I <strong>hate</strong> to type it. It’s not
an easy one on <a href="https://en.wikipedia.org/wiki/AZERTY">azerty</a> or
<a href="https://en.wikipedia.org/wiki/BÉPO">bépo</a> layouts.</p>

<p>As an <a href="https://www.gnu.org/software/emacs/">Emacs</a> user I can finely
customize everything so I decided to pimp the
<a href="https://github.com/elixir-editors/emacs-elixir">elixir-mode</a> to make
the pipe operator easy to use.</p>

<h1 id="main-function">Main function</h1>

<p>First of all we need a function that will describe what we want to do:</p>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nb">defun</span> <span class="nv">bounga/insert-elixir-pipe-operator</span> <span class="p">()</span>
  <span class="s">"Insert a newline and the |&gt; operator"</span>
  <span class="p">(</span><span class="nv">interactive</span><span class="p">)</span>
  <span class="p">(</span><span class="nv">end-of-line</span><span class="p">)</span>
  <span class="p">(</span><span class="nv">newline-and-indent</span><span class="p">)</span>
  <span class="p">(</span><span class="nv">insert</span> <span class="s">"|&gt; "</span><span class="p">))</span>
</code></pre></div></div>

<p>We defined the function <code class="language-plaintext highlighter-rouge">bounga/insert-elixir-pipe-operator</code> that:</p>

<ul>
  <li>acts interactively</li>
  <li>goes to the end of the current line</li>
  <li>adds a newline and indents the cursor</li>
  <li>inserts the pipe operator followed by a white space</li>
</ul>

<p>If you try it by calling <code class="language-plaintext highlighter-rouge">M-x bounga/insert-elixir-pipe-operator</code> in a
buffer, you’ll see it does what we want.</p>

<h1 id="bind-the-function-to-a-key-chord">Bind the function to a key chord</h1>

<p>To use this new function effectively you should bind it to a key chord
(a keyboard shortcut).</p>

<p>We’ll bind the function to <code class="language-plaintext highlighter-rouge">M-RET</code> (Alt + Enter on most keyboards).</p>

<p>Depending on the way you handle your packages there’s two ways of
setting it up.</p>

<h2 id="vanilla-emacs">Vanilla Emacs</h2>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nv">define-key</span> <span class="nv">elixir-mode-map</span> <span class="p">(</span><span class="nv">kbd</span> <span class="s">"M-RET"</span><span class="p">)</span> <span class="ss">'bounga/insert-elixir-pipe-operator</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="use-package">use-package</h2>

<div class="language-elisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="nb">use-package</span> <span class="nv">elixir-mode</span>
  <span class="ss">:bind</span> <span class="p">(</span><span class="ss">:map</span> <span class="nv">elixir-mode-map</span>
              <span class="p">(</span><span class="s">"M-RET"</span> <span class="o">.</span> <span class="nv">bounga/insert-elixir-pipe-operator</span><span class="p">)))</span>
</code></pre></div></div>

<p>Now you’re good to go!</p>

<p>If you’re in an Elixir buffer then <code class="language-plaintext highlighter-rouge">M-RET</code> will insert a new line and
add the pipe operator whether you’re at the end of the line, at the
beginning or in the middle of it.</p>

<h1 id="demo">Demo</h1>

<p>Let’s see it in action:</p>

<p><img src="/assets/images/emacs_elixir_pipe_operator_demo.gif" alt="emacs_elixir_pipe_operator_demo.gif" /></p>

<p>Have fun with Emacs and Elixir!</p>]]></content>

      
      
      <author>
          <name>Nicolas Cavigneaux</name>
        <email>nico@bounga.org</email>
        
      </author>

      
        <category term="Emacs" />
      
      <category term="Elixir" /><category term="Emacs" /><category term="Editor" />

      
      
        <summary type="html"><![CDATA[When writing [Elixir](https://elixir-lang.org/) code you'll often find yourself typing the pipe operator which is not very convinient. Let's see how to ease this in [Emacs](https://www.gnu.org/software/emacs/).]]></summary>
      
    </entry>
  
    <entry>
      
      <title type="html">Emacs Tramp lag and timeout</title>
      <link href="https://www.bounga.org/tips/2017/11/30/fix-emacs-tramp-lag-and-timeout/" rel="alternate" type="text/html" title="Emacs Tramp lag and timeout" />
      <published>2017-11-30T00:00:00+01:00</published>
      <updated>2017-11-30T00:00:00+01:00</updated>
      <id>https://www.bounga.org/tips/2017/11/30/fix-emacs-tramp-lag-and-timeout</id>
      <content type="html" xml:base="https://www.bounga.org/tips/2017/11/30/fix-emacs-tramp-lag-and-timeout/"><![CDATA[<h2 id="context">Context</h2>

<p>I use <a href="https://www.gnu.org/software/emacs/">Emacs</a> for pretty much everything . When I need to
navigate the file-system, search for a string in a file or edit it, I
do it within <a href="https://www.gnu.org/software/emacs/">Emacs</a>.</p>

<p>Sometimes I need to do this kind of
manipulation on a remote host using SSH. Could I do this using
<a href="https://www.gnu.org/software/emacs/">Emacs</a>? Sure I can!</p>

<p><a href="https://www.gnu.org/software/emacs/">Emacs</a> is able to handle remote files through SSH. It is a
feature shipped with it called <a href="https://www.gnu.org/software/tramp/#Overview">Tramp</a> that allows to access
remote hosts transparently from within <a href="https://www.gnu.org/software/emacs/">Emacs</a> using various
access methods such as SSH, FTP, …</p>

<p>It can do a lot of things, it literally allows to work with remote
hosts as if you were working locally. Pure awesomeness, the power of
my customized Emacs everywhere.</p>

<h2 id="what-happened-to-me">What happened to me</h2>

<p>I decided to give it a try as soon as I got the opportunity. What a
disappointment when I saw that opening a remote file was lagging as hell,
lagging so much that it always ended up with a timeout.</p>

<p>I googled for my issue and found out that it was working flawlessly
for a lot of people.</p>

<p>My immediate thought was “F***ing OS X, that must be your fault!”. I
googled more focusing on “OS X” keyword and found some people having
the same issue. They were using OS X too.</p>

<p>I did try some of the advices given by people that weren’t experiencing
this issue. No luck…</p>

<h2 id="rtfm">RTFM</h2>

<p>After several hours of research I finally landed on
the <a href="https://www.gnu.org/software/emacs/manual/html_node/tramp/Frequently-Asked-Questions.html">Tramp FAQ</a> and I saw this:</p>

<blockquote>
  <p>tramp needs a clean recognizable prompt on the remote host for
accurate parsing</p>
</blockquote>

<p>I knew it was the source of my issue and felt so lame of not having
read the official documentation sooner. My prompts are always fancy
and full of info even on my remote servers.</p>

<p>I did try to simplify my prompt and <a href="https://www.gnu.org/software/tramp/#Overview">Tramp</a> started to work
seamlessly!</p>

<h2 id="so-whats-the-fix">So what’s the fix?</h2>

<p><a href="https://www.gnu.org/software/tramp/#Overview">Tramp</a> expects a really simple prompt on the remote host to
parse it and detect when a command has finished. Basically it wants a
prompt ending with <code class="language-plaintext highlighter-rouge">#</code>, <code class="language-plaintext highlighter-rouge">$</code>, <code class="language-plaintext highlighter-rouge">%</code> or <code class="language-plaintext highlighter-rouge">&gt;</code>. It’s also having hard time to
parse prompts containing escape sequences for coloring.</p>

<p>My remote prompt was using a feature of <a href="http://zsh.sourceforge.net">ZSH</a> which allow to have
a prompt on the right side of the terminal. This was confusing
<a href="https://www.gnu.org/software/tramp/#Overview">Tramp</a> because it couldn’t find the “magical” character at the
end of the line.</p>

<p>There are two solutions to fix this.</p>

<p>The first solution is nice if you can’t change the remote prompt. You
can change a variable in your <a href="https://www.gnu.org/software/emacs/">Emacs</a> config that describes the
regexp used by <a href="https://www.gnu.org/software/tramp/#Overview">Tramp</a> to recognize the prompt. This variable
is named <code class="language-plaintext highlighter-rouge">tramp-shell-prompt-pattern</code>.</p>

<p>The second solution — the one I chose — is even simpler in my
opinion. Just change the prompt on the remote host. You can add a
condition in your <code class="language-plaintext highlighter-rouge">.zshrc</code> (or whatever the init file of your shell
is) and do something like this:</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="o">[</span> <span class="nv">$TERM</span> <span class="o">=</span> <span class="s2">"dumb"</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> unsetopt zle <span class="o">&amp;&amp;</span> <span class="nv">PS1</span><span class="o">=</span><span class="s1">'$ '</span></code></pre></figure>

<p>When <a href="https://www.gnu.org/software/tramp/#Overview">Tramp</a> connects to a remote host it sets the <code class="language-plaintext highlighter-rouge">TERM</code>
environment variable as <code class="language-plaintext highlighter-rouge">dumb</code> so you know you’re in the situation
were you want a really simple prompt. What to do then is to set the
prompt to a simple “$ “ string.</p>

<p>This way, if I connect on my remote using a real terminal I still have
my fancy prompt with all my info, colors, right prompt and everything.</p>

<h2 id="moral-of-this-story">Moral of this story</h2>

<p>Adding a single line in my <code class="language-plaintext highlighter-rouge">.zshrc</code> file on my remote server was
enough to solve the problem I was experiencing for months and that
<a href="https://emacs.stackexchange.com/questions/16489/tramp-is-unbearably-slow-osx-ssh/37177#37177">some other users are experiencing for many
years</a> without a single answer.</p>

<p>I can’t tell you enough to always check the official documentation and
issues in the tracker of a software / lib / whatever when you’re
experiencing an issue. It often gives an answer to your problem and it
will avoid some hours lost in reading half of the web.</p>

<p>Hope it helps if you’re digging for the same issue and you’re left
without answers until now.</p>]]></content>

      
      
      <author>
          <name>Nicolas Cavigneaux</name>
        <email>nico@bounga.org</email>
        
      </author>

      
        <category term="Tips" />
      
      <category term="Emacs" /><category term="SSH" /><category term="Editor" />

      
      
        <summary type="html"><![CDATA[You're experiencing excessive lag and timeouts when using Tramp on remote servers through SSH. Here is the solution to one of the most common cause.]]></summary>
      
    </entry>
  
</feed>
