<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:dc="http://purl.org/dc/elements/1.1/">
    <channel>
        <title>All posts</title>
        <link>https://jeancharles.quillet.org</link>
        <description><![CDATA[Jean-Charles personal blog]]></description>
        <atom:link href="https://jeancharles.quillet.org/rss.xml" rel="self"
                   type="application/rss+xml" />
        <lastBuildDate>Tue, 10 Mar 2026 00:00:00 UT</lastBuildDate>
        <item>
    <title>The Badger 2350 is a cool e-ink badge</title>
    <link>https://jeancharles.quillet.org/posts/2026-03-10-The-Badger-2350-is-a-cool-e-ink-badge.html</link>
    <description><![CDATA[<p>I have been looking for a simple yet versatile hackable e-ink screen for a
while. I had no idea what I would do with one but I knew I really wanted one. I
like how these screens are so low power and how easy e-ink is on the eyes. I
have a Kobo reader and this is definitely my favorite electronic device. I have
a few projects in my backlog for that reader by the way. But that’s another
story.</p>
<p>So when Pimoroni released its <a href="https://shop.pimoroni.com/products/badger-2350">Badger 2350</a>, I was very interested and I
pre-ordered one straight away. It includes everything you need out of the box:
wifi, bluetooth, buttons and a battery. You can get the full technical details
on the <a href="https://shop.pimoroni.com/products/badger-2350">Pimoroni website</a>. You
can program it using micropython and Pimoroni developed a few easy to use
libraries to draw on the screen, read the buttons, connect to wifi and so on. I
was not really excited by using python for this project to be honest. That’s
ok, I’ll start with that, but I might rewrite that firmware in a lower
level language later on, like Rust, Zig or even C. We will see.</p>
<p>Anyway I got mine in the mailbox a few weeks ago and then I thought “Now what?
What am I going to do with it?”.</p>
<p>I first developed a simple badge for myself when I go to conferences, simple
enough.</p>
<p><img src="/images/badger-badge.jpg" class="center" /></p>
<p>Then I got an idea. I have a <a href="https://www.home-assistant.io/">Home Assistant</a> installation at home connected to a
few zigbee devices, a cat feeder, a couple of smart bulbs, plugs and a few
temperature sensors. I have a temperature sensor in my office. I thought it
would be fun to use that badge to display its data.</p>
<p><img src="/images/badger-sensor.jpg" class="center" /></p>
<p>Now how can that work? zigbee2mqtt manages the zigbee devices and exposes them
to <a href="https://www.home-assistant.io/">Home Assistant</a> via the message broker <a href="https://mosquitto.org/">mosquitto</a>. The data I’m interested
in is available via <a href="https://mosquitto.org/">mosquitto</a>. If I had an MQTT client and listened to the
sensor topic, I’d get the data I need. The badger would need to connect to the
WIFI and implement an MQTT client. Looks like a plan.</p>
<p>Unfortunately after a few tests I saw that it couldn’t work that way. My router
is too far away and the WIFI signal is too weak for the badge to connect to it.</p>
<p>Second option is for the badger to get the data via bluetooth. It is actually a
better option for a low powered device like that. Bluetooth and especially BLE
is more energy efficient than WIFI. And it happens that my MQTT broker runs on
a Raspberry Pi 5 which has bluetooth capabilities. Here is a diagram of how that
would work:</p>
<pre class="mermaid">
  flowchart LR
      A[Sensor] <-->|Zigbee| B[zigbee2mqtt]
      B <-->|MQTT| C[Bridge app]
      C <-->|BLE| D[Badger]
      B <-->|MQTT| E[Home Assistant]
  </pre>
<p>All I need is some kind of bridge between the MQTT broker and the badge. Ok,
fair enough, this won’t work out of the box.</p>
<p>But this is great actually! When starting this project, this is exactly the
kind of problems I like to run into. I now need to write this program that
will run on the Raspberry Pi, and plug the service into my NixOS configuration.
I decided to use Rust for this project. I’m starting to have <a href="/posts/2023-06-01-A-perfect-pet-project-to-learn-Rust.html">a
bit</a> of
<a href="/posts/2024-01-30-Advent-of-code-2023.html">experience</a> with that programming
language and I know that the Rust ecosystem has everything I need to write this
program.</p>
<p>I called that program <a href="https://github.com/jecaro/mqttooth">mqttooth</a>. Check it out. The source is available on
<a href="https://github.com/jecaro/mqttooth">GitHub</a> along with <a href="https://github.com/jecaro/badger2350">my fork</a> of the Badger 2350 firmware.</p>]]></description>
    <pubDate>Tue, 10 Mar 2026 00:00:00 UT</pubDate>
    <guid>https://jeancharles.quillet.org/posts/2026-03-10-The-Badger-2350-is-a-cool-e-ink-badge.html</guid>
    <dc:creator>Jean-Charles Quillet</dc:creator>
</item>
<item>
    <title>Htagcli - a new command line audio tagger</title>
    <link>https://jeancharles.quillet.org/posts/2025-11-18-Htagcli-a-new-command-line-audio-tagger.html</link>
    <description><![CDATA[<p>There we go. I just released a new command line audio tagger called htagcli.
You can check it out on my <a href="https://github.com/jecaro/htagcli">GitHub</a>.</p>
<h1 id="the-problem">The Problem</h1>
<p>As a longtime digital music enthusiast, I’ve spent decades curating my music
library. Over the years, I’ve tried countless tools to keep it organized,
ranging from Windows GUIs (forgive my younger self) to Linux command line
utilities, and even web-based server apps.</p>
<p>My current workflow looks like this:</p>
<ul>
<li>Normalize audio files using <a href="https://mp3gain.sourceforge.net/">mp3gain</a>, <a href="https://github.com/dgilman/aacgain">aacgain</a>, <a href="https://sjeng.org/vorbisgain.html">vorbisgain</a>, or
<a href="https://xiph.org/flac/documentation_tools_metaflac.html">metaflac</a>, depending on the format.</li>
<li>Use <a href="https://beets.io/">beets</a> to scrape metadata, tag files, and organize them.</li>
<li>Check collection consistency with <a href="https://www.blisshq.com/">bliss</a>.</li>
<li>Edit tags as needed with various CLI tools, including <a href="https://eyed3.readthedocs.io/en/latest/">eyeD3</a>, <a href="https://squell.github.io/id3/">id3</a>, and
<a href="https://github.com/kaworu/tagutil">tagutil</a>.</li>
</ul>
<p>Yes, it’s complicated. I need to simplify this workflow.</p>
<h1 id="htagcli"><a href="https://github.com/jecaro/htagcli">htagcli</a></h1>
<p>Here is a short demo of <a href="https://github.com/jecaro/htagcli">htagcli</a> in action:</p>
<p><img src="/images/htagcli.png" /></p>
<p>While <a href="https://github.com/jecaro/htagcli">htagcli</a> doesn’t yet streamline the entire process, it’s a solid step
toward taking ownership of my music management workflow.</p>
<p>Currently, <a href="https://github.com/jecaro/htagcli">htagcli</a> can replace both a command line tagger and <a href="https://www.blisshq.com/">bliss</a> to
check the library consistency. I plan to add auto-tagging features similar to
<a href="https://beets.io/">beets</a>, and I’m still considering how to handle normalization. We’ll see how
it evolves.</p>
<p>I like these well-scoped personal projects, small but non-trivial, and which
solve a real problem for me. Looking at the git history, I realized I started
this three years ago but didn’t have the time to push it forward. I’m thrilled
to finally have it in a very usable state.</p>
<h1 id="roadmap">Roadmap</h1>
<p>Here are the features I plan to implement soon:</p>
<ul>
<li><del>Handle disc ID tags</del></li>
<li><del>Check that cover art size is within a configurable range</del></li>
<li><del>Ensure no missing tracks in an album</del></li>
<li><del>Verify genre consistency at the artist level</del><a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a></li>
</ul>
<p>Another major goal is to scrape metadata from third-party sources like
<a href="https://musicbrainz.org/">MusicBrainz</a>, <a href="https://www.discogs.com/">Discogs</a>, and <a href="https://bandcamp.com/">Bandcamp</a> to automatically tag files correctly.</p>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>All done in version <a href="https://github.com/jecaro/htagcli/releases/tag/v0.1.1.0">v0.1.1.0</a><a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>]]></description>
    <pubDate>Tue, 18 Nov 2025 00:00:00 UT</pubDate>
    <guid>https://jeancharles.quillet.org/posts/2025-11-18-Htagcli-a-new-command-line-audio-tagger.html</guid>
    <dc:creator>Jean-Charles Quillet</dc:creator>
</item>
<item>
    <title>NixOS in a gameboy shell</title>
    <link>https://jeancharles.quillet.org/posts/2025-08-13-NixOS-in-a-gameboy-shell.html</link>
    <description><![CDATA[<h1 id="introduction">Introduction</h1>
<p>I built a retro gaming handheld based on a raspberry compute module 3 <a href="/posts/2021-12-21-The-coolest-DIY-project-around.html">sometime
ago</a>. It was a super fun
project. I often think my work as a developper misses a bit of manual work.
Something that I love to do. This project was great for that reason as it
involved a bit of soldering, 3D printing, hot gluing and precise dremmelling.</p>
<p>Anyway, after I built it, I installed <a href="https://github.com/kiteretro/Circuit-Sword">a custom Linux
distribution</a> based on
<a href="https://retropie.org.uk/"><code>retropie</code></a> made by the author of the board. It’s a
bit rough in the edge. But it’s been working fine so far, can’t complain.</p>
<p>Unfortunately, after a couple of years, you know, software gets out of date and
that distribution needed to be maintained. Seeing that the latest image for
<code>retropie</code> itself is from <a href="https://retropie.org.uk/download/">March 2022</a>, what
can you expect from a super niche distribution built on top of it? Not much,
only a couple of forks <a href="https://github.com/weese/Circuit-Sword">here</a> and
<a href="https://github.com/Antho91/Circuit-Sword">there</a>, but nothing actively
maintained.</p>
<p>I wanted to tackle this issue and bring the latest and greatest software to
this handheld I built myself which has been sitting on my desk for too long.
Additionally, this is one of the last piece of hardware I have not nixified
yet.</p>
<p>So here am I, installing NixOS on a gameboy handheld. How cool is that?</p>
<p><img src="/images/circuix-nixos.jpg" class="center" /></p>
<p>I named the project <a href="https://github.com/jecaro/circuix-sword"><code>circuix-sword</code></a>
based on the name of the board: <code>Circuit-Sword</code>. If by chance you are one of
the lucky few to have one of those, you can now run NixOS on it. It is very
easy. Get started by downloading the latest image from the <a href="https://github.com/jecaro/circuix-sword/releases/">releases
page</a>. Then burn it on an SD
card, and you are ready to go. All the instructions are in the
<a href="https://github.com/jecaro/circuix-sword"><code>README.md</code></a> of the repository.</p>
<p>While working on this project, I ran into countless issues, which is part of
the reasons I wanted to do it in the first place. I will describe only a few of
them here in chronological order.</p>
<h1 id="boot-the-system">Boot the system</h1>
<p>This one wasn’t too hard, I just downloaded <a href="https://hydra.nixos.org/job/nixos/release-24.11/nixos.sd_image.aarch64-linux">the latest NixOS image for
aarch64</a>,
and it was able to boot on the CM3 out of the box. I just plugged a screen to
the HDMI out, a keyboard to the USB port, and I was ready to go.</p>
<h1 id="wifi">WiFi</h1>
<p>There is not much you can do without internet. I first reached out for a USB
ethernet adapter I had around. It is good enough for hacking but not great for
day to day use.</p>
<p>Hopefully, the <code>Circuit-Sword</code> has a WiFi chip on board. It is a <code>Realtek  8723bs</code> but requires two things for it to work properly:</p>
<ul>
<li>The right firmware. This is solved using the option:
<code>hardware.enableRedistributableFirmware = true;</code>
The closure induced by this option is quite big however (not less than 589
MiB). But we will address this issue later on.</li>
<li>The <a href="https://github.com/jecaro/circuix-sword/blob/99078fc328865c9a61cb82d9c3b3571151556375/system/hardware.nix#L56">sdio
overlay</a>.
This requires messing up with the device tree. NixOS doesn’t provide a nice
solution for that yet. But I found <a href="https://github.com/NixOS/nixpkgs/issues/320557#issuecomment-2176067772">this
one</a>
which works good enough for me.</li>
</ul>
<h1 id="screen-first-attempt">Screen, first attempt</h1>
<p>Next, I needed to get the built-in screen working. The <code>Circuit-Sword</code> has a
<code>320x240</code> DPI screen. Making it work was just a matter of copying the right
lines from the original distribution in the <a href="https://github.com/jecaro/circuix-sword/blob/f86544da642e517c09cadd091b6ff283de1e55dd/flake.nix#L63-L80"><code>config.txt</code>
file</a>.</p>
<p>While this worked fine for the console output, it does not for graphics. The
drivers used in the original distribution have been deprecated to be replaced
by the KMS DRM drivers.</p>
<h1 id="screen-second-attempt">Screen, second attempt</h1>
<p>Ok, now let’s make those KMS DRM drivers work.</p>
<p>First, I needed to mess again with the device tree and enabled the
<code>vc4-kms-v3d</code> overlay, which is the KMS driver. I also needed the
<code>vc4-kms-dpi-generic</code> overlay to drive the DPI screen. That second overlay is
especially tricky to get right as it needs <a href="https://github.com/jecaro/circuix-sword/blob/99078fc328865c9a61cb82d9c3b3571151556375/system/hardware.nix#L61-L63">a lot of
parameters</a>
to work properly. One can map the original parameters from the <code>config.txt</code>
file to the new format. But the documentation is not very clear, and it took me
a fair deal of tinkering to get it right.</p>
<p>But wait, this is not enough. Now the <code>SDL</code> library needs to support the KMS
DRM driver too. I needed to <a href="https://github.com/jecaro/circuix-sword/blob/99078fc328865c9a61cb82d9c3b3571151556375/overlays/SDL2.nix">tweak
slightly</a>
the compilation of <code>SDL</code> to enable it.</p>
<p>At this point, I was able to run <code>retroarch</code> and start games, that was already
a big achievement!</p>
<h1 id="sound">Sound</h1>
<p>The sound card is a <code>MicroII</code> chip. For some reason, it is stuck at a very high
volume. The Linux module needs <a href="https://github.com/jecaro/circuix-sword/blob/99078fc328865c9a61cb82d9c3b3571151556375/system/snd-usb-audio-modules/default.nix">to be
patched</a>
for it to work properly.</p>
<h1 id="the-cs-hud-service">The <code>cs-hud</code> service</h1>
<p>The <code>cs-hud</code> service is a custom service written in C especially for the board.</p>
<ul>
<li>It handles the safe-shutdown circuit</li>
<li>When pressing the special mode button, it shows a help screen on top of the
framebuffer. This screen shows the keyboard shortcuts along the current
state: the levels of the volume, brightness and battery.</li>
</ul>
<p>I integrated the source code in <a href="https://github.com/jecaro/circuix-sword/tree/99078fc328865c9a61cb82d9c3b3571151556375/overlays/cs-hud/src">the git
repo</a>,
created <a href="https://github.com/jecaro/circuix-sword/blob/99078fc328865c9a61cb82d9c3b3571151556375/overlays/cs-hud/default.nix">the
derivation</a>
to build it and wrote <a href="https://github.com/jecaro/circuix-sword/blob/99078fc328865c9a61cb82d9c3b3571151556375/system/configuration.nix#L74-L89">the systemd
service</a>
to run it. Standard NixOS stuff so far.</p>
<p>Unfortunately, with the new KMS setup, it is not possible to write an image on
top of the framebuffer anymore. I then hacked the original code and removed
pretty much everything related to that OSD feature. I kept the safe-shutdown
handling, the brightness and volume control, and the battery level management.</p>
<p>On the way, I lost the ability to see all the info from that help screen. I
can cope without it most of the time. But at some point I will definitely need
to be able to see the battery level.</p>
<p>In the next section, I will explain how I solved that issue.</p>
<h1 id="retroarch"><code>retroarch</code></h1>
<p>The original distribution runs <code>emulationstation</code>. At first, I tried to make it
work in NixOS. But soon I realized that <code>emulationstation</code> is actually some
kind of frontend on top of <code>retroarch</code>. To make things simpler, I decided to
just use plain <code>retroarch</code> directly, at least for a start. That project is
involved enough already.</p>
<p>I have made two quality of life improvement changes to <code>retroarch</code>:</p>
<ul>
<li>I fixed the <code>NetworkManager</code> WiFi driver. It is now possible to set up the
WiFi using the gamepad itself which is great as it wasn’t possible in the
original distribution (<a href="https://github.com/libretro/RetroArch/pull/17857">merged
upstream</a>).</li>
<li>I exposed the battery state to a unix socket using <code>cs-hud</code> and made it
available in <code>retroarch</code>. This way, I can see the battery level in the
<code>retroarch</code> interface. I keep that change <a href="https://github.com/jecaro/RetroArch/tree/circuix-sword">in my
fork</a> as it has no
chance to be merged upstream.</li>
</ul>
<h1 id="closure-size-optimization">Closure size optimization</h1>
<p>At this point, I had a working system, but it was way bigger than it should be:
1.81 GiB for the image of the version
<a href="https://github.com/jecaro/circuix-sword/releases/tag/v0.0.2">v0.0.2</a>. I
started to look at the closure to see what I could remove. This is achieved by
tweaking the compilation options of various packages using nix overlays.</p>
<p>The most obvious thing was everything related to the desktop, namely: <code>xorg</code>,
<code>wayland</code>, <code>pulseaudio</code>, <code>pipewire</code> and so on. <code>gtk</code> was especially difficult
to get rid of. <code>NetworkManager</code> pulls it in the <code>openconnect</code> plugin but
removing all its plugins was not enough for some reason. The solution was to
get rid of
<a href="https://github.com/jecaro/circuix-sword/blob/99078fc328865c9a61cb82d9c3b3571151556375/flake.nix#L91"><code>openconnect</code></a>
itself.</p>
<p>The <code>mesa</code> library was pretty big as well (197 MiB). I trimmed it down to 20
MiB.</p>
<p>The redistribuable firmware derivation is about 589 MiB for only one single
firmware needed! I did <a href="https://github.com/jecaro/circuix-sword/blob/99078fc328865c9a61cb82d9c3b3571151556375/overlays/rtl8723-firmware.nix">my own
derivation</a>
for the firmware I need. The resulting derivation is now 41 KiB. That’s much
better!</p>
<p>The <code>retroarch</code> assets derivation is still huge (494 MiB) but I’m ok with that.
I don’t want to track down every single asset and check if it is needed or not.</p>
<p>I wanted to get rid of <code>perl</code> as well. But it <a href="https://github.com/jecaro/circuix-sword/issues/7">didn’t work out
nicely</a>.</p>
<p>The final image is now around 800 MiB, under 1 GiB, which is not great but good
enough.</p>
<h1 id="firmware-flash">Firmware flash</h1>
<p>The last piece of the puzzle is the gamepad firmware that runs on the arduino.
I wanted <a href="https://github.com/jecaro/circuix-sword/tree/99078fc328865c9a61cb82d9c3b3571151556375/overlays/cs-firmware/CS_FIRMWARE">the
source</a>
to be part of the repo as well. I also made it sure it compiles and <a href="https://github.com/jecaro/circuix-sword/blob/99078fc328865c9a61cb82d9c3b3571151556375/flake.nix#L65-L66">make it
possible to
flash</a>
it out of the box.</p>
<h1 id="conclusion">Conclusion</h1>
<p>This has been a difficult but fun journey. I love this project. Building the
handheld was a great experience and porting the software to NixOS was also
super fun.</p>
<p>There are so many different parts to it, going from very low to high level that
makes it super interesting. From the Linux drivers that must be patched, to
fine-tuning the booting process, the software that needs to be patched as well,
some custom components, the gamepad firmware, not to mention the closure size
optimization.</p>
<p>Really, there is something for everyone.</p>
<p>I have found out that NixOS is incredibly good at gluing all these pieces
together. The fact that it is possible to patch every single package in one
single repository is a huge win. That whole project took me some time. But the
final amount of code is fairly reasonable. And I am very confident that I will
be able to maintain it for a very long time.</p>]]></description>
    <pubDate>Wed, 13 Aug 2025 00:00:00 UT</pubDate>
    <guid>https://jeancharles.quillet.org/posts/2025-08-13-NixOS-in-a-gameboy-shell.html</guid>
    <dc:creator>Jean-Charles Quillet</dc:creator>
</item>
<item>
    <title>Multiple components in ghci is finally here</title>
    <link>https://jeancharles.quillet.org/posts/2025-03-24-Multiple-components-in-ghci-is-finally-here.html</link>
    <description><![CDATA[<p>Loading multiple components in <code>ghci</code> is finally possible with <code>ghc 9.4</code> and
<code>cabal 3.12</code>. They have been available for a while both in <code>nixos-2411</code> and
<code>ghcup</code>. There is no reason not to use it.</p>
<p>This is a big deal because it directly affects the length of the feedback loop.
I have been writing about it
<a href="/posts/2024-09-04-Haskell-dev-workflow-with-ghcid-and-neovim.html">in</a>
<a href="/posts/2024-11-28-ghcid-error-file.nvim.html">the</a>
<a href="/posts/2025-02-21-Fast-compilation-loop-with-tmux-and-neovim.html">past</a>. And
I do think that a short feedback loop is the key to a good productivity.</p>
<p>Back to the story, until recently, <code>ghci</code> were unable to load multiple
components, say an executable, like a test suite for example, and the library
it depends on at the same time. And <code>ghci</code> is the centerpiece to get the
fastest feedback loop when working with Haskell. Until now, working on a
feature usually consists in doing some kind of back and forth between working
on the library that contains the actual code and working on the test suite to
make sure it works, restarting <code>ghci</code> between each iteration. This is far from
being ideal and makes the feedback loop way longer than it needs to be.</p>
<p>That’s only a concern for <code>cabal</code> users by the way. <code>stack</code> have had a
workaround for this for a while (see <a href="https://docs.haskellstack.org/en/stable/commands/ghci_command/">the
documentation</a>
for <code>stack ghci</code>). What it does is merging all <code>ghc</code> options of the components
before loading them in <code>ghci</code>. Basically, it is like creating a fake component
which is the union of all wanted components. Pretty basic, but it has been an
effective workaround for this issue.</p>
<p>But now it is possible to do it as well in <code>cabal</code>. At last, hurrah 🎉</p>
<p>Now, how does that work in practice?</p>
<p>The first thing is of course to have at least the versions of <code>ghc 9.4</code>and
<code>cabal 3.12</code>.</p>
<p>Then <code>cabal</code> must be configured to enable the feature. When trying to load two
components with <code>cabal</code>. It outputs a pretty informative message:</p>
<pre><code>$ cabal repl test:my-app-tests lib:my-app
Error: [Cabal-7076]
Cannot open a repl for multiple components at once. The targets &#39;my-app&#39; and &#39;tests&#39; refer to different components..

Your compiler supports a multiple component repl but support is not enabled.
The experimental multi repl can be enabled by
  * Globally: Setting multi-repl: True in your .cabal/config
  * Project Wide: Setting multi-repl: True in your cabal.project file
  * Per Invocation: By passing --enable-multi-repl when starting the repl</code></pre>
<p>As for me, I just turn it on globally. It is just too useful.</p>
<p>Once <code>cabal</code> is configured, that’s it really. If you try again that last
command, that should work. Then after you edit a file in any of the loaded
components, all required files will be recompiled by a simple <code>:r</code> in <code>ghci</code>.</p>
<p>The feedback loop I explained
<a href="/posts/2024-09-04-Haskell-dev-workflow-with-ghcid-and-neovim.html">here</a> works
out of the box. Interestingly, it seems that it sometime makes <code>ghc</code> output the
absolute path of the files that contains errors. <a href="/posts/2024-11-28-ghcid-error-file.nvim.html">This
workaround</a> is thus not always
needed which is also awesome.</p>
<p>One caveat though, <code>ghcid</code> is able to run a command after the compilation
succeeds. It is super useful to run the tests on any change for example. Let’s
try this:</p>
<pre><code>$ ghcid -c &quot;cabal repl test:my-app-tests lib:my-app&quot; -T :main
Command is not supported (yet) in multi-mode</code></pre>
<p>Oh no, that doesn’t work.</p>
<p>But there is a way to work around this. I learned it in <a href="https://www.youtube.com/watch?v=B1WFMave-r4">this
video</a>
(recommended watch to see the feature in action). Instead or running a command,
we’ll just execute the <code>main</code>. For example, if the <code>main</code> function is in the
<code>Main</code> module, we can do this:</p>
<pre><code>$ ghcid -c &quot;cabal repl test:my-app-tests lib:my-app&quot; -T Main.main</code></pre>
<p>This works fine. Watch out that the executable must come first in the list of
the components for some reason. If one needs to pass arguments to the main
function, it is possible to use the <code>:set</code> command in <code>ghci</code>.</p>
<p>For example:</p>
<pre><code>$ ghcid -c &quot;cabal repl test:my-app-tests lib:my-app&quot; -s &quot;:set args -p /testpattern/&quot; -T Main.main</code></pre>
<p>That’s it. Once one tried it, it is hard to believe we have been waiting for so
long for this.</p>
<p>Note: <a href="https://cabal.readthedocs.io/en/stable/cabal-commands.html#cmdoption-enable-multi-repl">cabal
documentation</a></p>]]></description>
    <pubDate>Mon, 24 Mar 2025 00:00:00 UT</pubDate>
    <guid>https://jeancharles.quillet.org/posts/2025-03-24-Multiple-components-in-ghci-is-finally-here.html</guid>
    <dc:creator>Jean-Charles Quillet</dc:creator>
</item>
<item>
    <title>Fast compilation loop with tmux and neovim</title>
    <link>https://jeancharles.quillet.org/posts/2025-02-21-Fast-compilation-loop-with-tmux-and-neovim.html</link>
    <description><![CDATA[<p>Following up my two previous posts about getting a fast compilation loop when
working with Haskell in neovim (see
<a href="/posts/2024-09-04-Haskell-dev-workflow-with-ghcid-and-neovim.html">here</a> and
<a href="/posts/2024-11-28-ghcid-error-file.nvim.html">here</a>), today I want to share a
more general approach that works with pretty much any compiled language. I have
successfully used it with: C, C++, Java, Rust, Haskell and Zig with no
modification whatsoever.</p>
<p>But first, a short demo of me working on <a href="https://github.com/jecaro/pomodozig">one of my projects</a>:</p>
<p><img src="/images/tmux-neovim-workflow.gif" /></p>
<p>Basically, I have a tmux session with two panes. On the left, I have my code
editor neovim. On the right, I have the compiler running each time I save a
file.</p>
<p>When the compiler finds an error in my code, with a shortcut I find the error
in the compilation panel and yank it. Back to neovim, with another shortcut I
jump straight to the error in my code, fix it, save the file, rinse and repeat.</p>
<h1 id="moving-between-panes">Moving between panes</h1>
<p>By default, moving the focus between panes in tmux is done with <code>CTRL-b</code>
followed by an arrow key (as for me, I use <code>CTRL-Space</code> as my tmux prefix). In
neovim, to move the focus between windows, one uses <code>CTRL-w</code> followed by a vim
direction key: <code>h</code>, <code>j</code>, <code>k</code> and <code>l</code>.</p>
<p>I find it cumbersome. To make it easier, I use the neovim plugin <a href="https://github.com/aserowy/tmux.nvim">tmux.nvim</a>.
With it, one can move the focus with <code>CTRL-h</code>, <code>CTRL-j</code>, <code>CTRL-k</code> and <code>CTRL-l</code>
seemingly between tmux panes and neovim windows.</p>
<p>Check out the <a href="https://github.com/aserowy/tmux.nvim">README.md</a> of the plugin for details on the
configuration.</p>
<h1 id="compilation-pane">Compilation pane</h1>
<p>For the compilation pane, I run the compiler through <a href="https://github.com/NorfairKing/feedback">feedback</a>. By default,
this tool watches for changes in files tracked by git and runs a command on any
change. 99% of the time, this is what I want. For the remaining 1%, <a href="https://github.com/NorfairKing/feedback">feedback</a>
has a few options to define the files to watch. Have a look at <a href="https://github.com/NorfairKing/feedback">its
repository</a> for more information.</p>
<p>Now, to find the error in the compilation pane, tmux is able to search for
regexes. I use this feature in my <code>~/.config/tmux/tmux.conf</code> and map such
searches to keybindings.</p>
<pre class="tmux"><code># Find errors and warnings in the current pane
bind C-e copy-mode \; send -X search-backward &quot;[^\s]+:[^\s]+: (error|Error):&quot;
bind C-w copy-mode \; send -X search-backward &quot;[^\s]+:[^\s]+: (error|Error|warning|Warning):&quot;</code></pre>
<p>With this configuration, if I hit <code>CTRL-Space</code> (my tmux prefix) followed by
<code>CTRL-e</code>, tmux will search in the current pane for an error and highlight it.
With <code>CTRL-Space</code> followed by <code>CTRL-w</code>, tmux will search for an error or a
warning.</p>
<p>Once the error has been found, I can yank it with <code>Enter</code>. The error string is
now available in the system clipboard and, in neovim, in the <code>+</code> register.</p>
<h1 id="neovim-pane">Neovim pane</h1>
<p>Back to neovim, to jump to the error I can enter command mode, type <code>:edit</code>
and paste the error string with <code>CTRL-SHIFT-v</code> or <code>CTRL-r-+</code>.</p>
<pre><code>:edit some/file.zig:12:10: error:</code></pre>
<p>But don’t hit <code>Enter</code> yet, that’s not a proper vim command. I still need to
edit the line such as:</p>
<pre><code>:edit +12 some/file.zig</code></pre>
<p>or even simpler:</p>
<pre><code>:edit some/file.zig | 12</code></pre>
<p>Now hit <code>Enter</code> and neovim will jump to the file and line number.</p>
<p>This is already better than trying to find the error by hand, open the file and
scroll to the right line number. But we can do better.</p>
<h1 id="lua-to-the-rescue">Lua to the rescue</h1>
<p>Here is a Lua module that can go in your <code>~/.config/nvim/lua</code> directory. Let’s
call it <code>parse_error_string.lua</code> for example.</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode lua"><code class="sourceCode lua"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">local</span> <span class="cn">M</span> <span class="op">=</span> <span class="op">{}</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="kw">local</span> <span class="va">parse_error_string</span> <span class="op">=</span> <span class="kw">function</span><span class="op">(</span><span class="va">str</span><span class="op">)</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>  <span class="co">-- src/Parser.hs:(30,3)-(32,11): warning:</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>  <span class="kw">local</span> <span class="va">file</span><span class="op">,</span> <span class="va">line</span> <span class="op">=</span> <span class="va">str</span><span class="op">:</span><span class="fu">match</span><span class="op">(</span><span class="st">&#39;([^:]+):%((%d+),%d+%)%-%(%d+,%d+%): .+&#39;</span><span class="op">)</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>  <span class="co">-- src/Parser.hs:10:3-8: error:</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>  <span class="cf">if</span> <span class="kw">not</span> <span class="va">file</span> <span class="cf">then</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>    <span class="va">file</span><span class="op">,</span> <span class="va">line</span> <span class="op">=</span> <span class="va">str</span><span class="op">:</span><span class="fu">match</span><span class="op">(</span><span class="st">&#39;([^:]+):(%d+):%d+%-%d+: .+:&#39;</span><span class="op">)</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a>  <span class="cf">end</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a>  <span class="co">-- src/Model.hs:4:12: error:</span></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a>  <span class="cf">if</span> <span class="kw">not</span> <span class="va">file</span> <span class="cf">then</span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a>    <span class="va">file</span><span class="op">,</span> <span class="va">line</span> <span class="op">=</span> <span class="va">str</span><span class="op">:</span><span class="fu">match</span><span class="op">(</span><span class="st">&#39;([^:]+):(%d+):%d+: .+:&#39;</span><span class="op">)</span></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a>  <span class="cf">end</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a>  <span class="co">-- /absolute/path/to/some/java/source.java:229: error:</span></span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a>  <span class="cf">if</span> <span class="kw">not</span> <span class="va">file</span> <span class="cf">then</span></span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a>    <span class="va">file</span><span class="op">,</span> <span class="va">line</span> <span class="op">=</span> <span class="va">str</span><span class="op">:</span><span class="fu">match</span><span class="op">(</span><span class="st">&#39;([^:]+):(%d+): .+:&#39;</span><span class="op">)</span></span>
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a>  <span class="cf">end</span></span>
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="va">file</span><span class="op">,</span> <span class="va">line</span></span>
<span id="cb5-23"><a href="#cb5-23" aria-hidden="true" tabindex="-1"></a><span class="kw">end</span></span>
<span id="cb5-24"><a href="#cb5-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-25"><a href="#cb5-25" aria-hidden="true" tabindex="-1"></a><span class="kw">local</span> <span class="kw">function</span> file_exists<span class="op">(</span><span class="va">filename</span><span class="op">)</span></span>
<span id="cb5-26"><a href="#cb5-26" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="va">vim</span><span class="op">.</span><span class="va">fn</span><span class="op">.</span>filereadable<span class="op">(</span><span class="va">filename</span><span class="op">)</span> <span class="op">==</span> <span class="dv">1</span></span>
<span id="cb5-27"><a href="#cb5-27" aria-hidden="true" tabindex="-1"></a><span class="kw">end</span></span>
<span id="cb5-28"><a href="#cb5-28" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-29"><a href="#cb5-29" aria-hidden="true" tabindex="-1"></a><span class="kw">local</span> <span class="kw">function</span> open_file_line<span class="op">(</span><span class="va">filename</span><span class="op">,</span> <span class="va">line</span><span class="op">)</span></span>
<span id="cb5-30"><a href="#cb5-30" aria-hidden="true" tabindex="-1"></a>  <span class="kw">local</span> <span class="va">line_arg</span> <span class="op">=</span> <span class="st">&#39;&#39;</span></span>
<span id="cb5-31"><a href="#cb5-31" aria-hidden="true" tabindex="-1"></a>  <span class="cf">if</span> <span class="va">line</span> <span class="cf">then</span></span>
<span id="cb5-32"><a href="#cb5-32" aria-hidden="true" tabindex="-1"></a>    <span class="va">line_arg</span> <span class="op">=</span> <span class="st">&#39;+&#39;</span> <span class="op">..</span> <span class="va">line</span></span>
<span id="cb5-33"><a href="#cb5-33" aria-hidden="true" tabindex="-1"></a>  <span class="cf">end</span></span>
<span id="cb5-34"><a href="#cb5-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-35"><a href="#cb5-35" aria-hidden="true" tabindex="-1"></a>  <span class="va">vim</span><span class="op">.</span>cmd<span class="op">(</span><span class="fu">table.concat</span><span class="op">({</span> <span class="st">&#39;edit&#39;</span><span class="op">,</span> <span class="va">line_arg</span><span class="op">,</span> <span class="va">filename</span> <span class="op">},</span> <span class="st">&#39; &#39;</span><span class="op">))</span></span>
<span id="cb5-36"><a href="#cb5-36" aria-hidden="true" tabindex="-1"></a><span class="kw">end</span></span>
<span id="cb5-37"><a href="#cb5-37" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-38"><a href="#cb5-38" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> <span class="cn">M</span><span class="op">.</span>open_error<span class="op">(</span><span class="va">error_str</span><span class="op">)</span></span>
<span id="cb5-39"><a href="#cb5-39" aria-hidden="true" tabindex="-1"></a>  <span class="kw">local</span> <span class="va">filename</span><span class="op">,</span> <span class="va">line</span> <span class="op">=</span> parse_error_string<span class="op">(</span><span class="va">error_str</span><span class="op">)</span></span>
<span id="cb5-40"><a href="#cb5-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-41"><a href="#cb5-41" aria-hidden="true" tabindex="-1"></a>  <span class="cf">if</span> file_exists<span class="op">(</span><span class="va">filename</span><span class="op">)</span> <span class="cf">then</span></span>
<span id="cb5-42"><a href="#cb5-42" aria-hidden="true" tabindex="-1"></a>    open_file_line<span class="op">(</span><span class="va">filename</span><span class="op">,</span> <span class="va">line</span><span class="op">)</span></span>
<span id="cb5-43"><a href="#cb5-43" aria-hidden="true" tabindex="-1"></a>  <span class="cf">else</span></span>
<span id="cb5-44"><a href="#cb5-44" aria-hidden="true" tabindex="-1"></a>    <span class="va">vim</span><span class="op">.</span>notify<span class="op">(</span><span class="st">&#39;Could not open file: &#39;</span> <span class="op">..</span> <span class="va">filename</span><span class="op">,</span> <span class="va">vim</span><span class="op">.</span><span class="va">log</span><span class="op">.</span><span class="va">levels</span><span class="op">.</span><span class="cn">ERROR</span><span class="op">)</span></span>
<span id="cb5-45"><a href="#cb5-45" aria-hidden="true" tabindex="-1"></a>  <span class="cf">end</span></span>
<span id="cb5-46"><a href="#cb5-46" aria-hidden="true" tabindex="-1"></a><span class="kw">end</span></span>
<span id="cb5-47"><a href="#cb5-47" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-48"><a href="#cb5-48" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> <span class="cn">M</span><span class="op">.</span>open_yanked_error<span class="op">()</span></span>
<span id="cb5-49"><a href="#cb5-49" aria-hidden="true" tabindex="-1"></a>  <span class="kw">local</span> <span class="va">error_str</span> <span class="op">=</span> <span class="va">vim</span><span class="op">.</span><span class="va">fn</span><span class="op">.</span>getreg<span class="op">(</span><span class="st">&#39;+&#39;</span><span class="op">)</span></span>
<span id="cb5-50"><a href="#cb5-50" aria-hidden="true" tabindex="-1"></a>  <span class="cn">M</span><span class="op">.</span>open_error<span class="op">(</span><span class="va">error_str</span><span class="op">)</span></span>
<span id="cb5-51"><a href="#cb5-51" aria-hidden="true" tabindex="-1"></a><span class="kw">end</span></span>
<span id="cb5-52"><a href="#cb5-52" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-53"><a href="#cb5-53" aria-hidden="true" tabindex="-1"></a><span class="cf">return</span> <span class="cn">M</span></span></code></pre></div>
<p>The code is pretty straight forward. Most of the work is done in the parsing
function <code>parse_error_string</code> which extract the filename and the line from the
error string. Then the exposed function <code>open_yanked_error</code> reads the <code>+</code>
register, parses the string it contains and opens the file at the right line.</p>
<p>As for calling the function itself, this is the mapping I use:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode lua"><code class="sourceCode lua"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="kw">local</span> <span class="va">parse_error_string</span> <span class="op">=</span> <span class="fu">require</span><span class="op">(</span><span class="st">&#39;parse_error_string&#39;</span><span class="op">)</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="va">vim</span><span class="op">.</span><span class="va">keymap</span><span class="op">.</span>set<span class="op">(</span><span class="st">&#39;n&#39;</span><span class="op">,</span> <span class="st">&#39;&lt;Leader&gt;fy&#39;</span><span class="op">,</span> <span class="va">parse_error_string</span><span class="op">.</span><span class="va">open_yanked_error</span><span class="op">,</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a>  <span class="op">{</span> <span class="va">desc</span> <span class="op">=</span> <span class="st">&#39;Open yanked error&#39;</span> <span class="op">})</span></span></code></pre></div>
<p>Now hitting <code>&lt;Leader&gt;fy</code> in normal mode will make neovim jump on the error
found in the compilation pane.</p>
<h1 id="conclusion">Conclusion</h1>
<p>I hope this post has demonstrated that it is not too difficult to set up a good
development workflow in the terminal. With a few basic tools, some regexes, a
bit of Lua and we can have a fast and efficient compilation loop that is
language agnostic.</p>
<p>Feel free to adapt it to your needs and let me know if you like it.</p>]]></description>
    <pubDate>Fri, 21 Feb 2025 00:00:00 UT</pubDate>
    <guid>https://jeancharles.quillet.org/posts/2025-02-21-Fast-compilation-loop-with-tmux-and-neovim.html</guid>
    <dc:creator>Jean-Charles Quillet</dc:creator>
</item>
<item>
    <title>ghcid-error-file.nvim: A ghcid plugin for neovim</title>
    <link>https://jeancharles.quillet.org/posts/2024-11-28-ghcid-error-file.nvim.html</link>
    <description><![CDATA[<p>In <a href="/posts/2024-09-04-Haskell-dev-workflow-with-ghcid-and-neovim.html">my previous
post</a>, I
presented a simple workflow for a fast compilation loop using <a href="https://github.com/ndmitchell/ghcid">ghcid</a> and
<code>neovim</code>.</p>
<p>Unfortunately, this doesn’t work with multi-package projects. This is because
GHC outputs filenames relative to a package and not relative to where it runs
(see <a href="https://gitlab.haskell.org/ghc/ghc/-/issues/15680">this GHC issue</a> and
also <a href="https://github.com/haskell/cabal/issues/6670">this cabal issue</a>). That
means that <code>neovim</code> could be able to load the error file in the same way as
previously, but it would not be able to find the files in the quickfix list,
which would not be very useful.</p>
<p>To solve this, I came up with a relatively simple solution. I wrote some lua
code to manually parse the error file and fill up the quickfix list prepending
the relative path of the package currently loaded by <a href="https://github.com/ndmitchell/ghcid">ghcid</a>. I mapped the lua
function to a <code>neovim</code> user command, and it results in a very similar workflow
as before. One just need to give to <code>neovim</code> the path to the package worked on.</p>
<p>See below for an example:</p>
<p><img src="/images/ghcid-error-file.gif" /></p>
<p>This code was present in my personal <code>neovim</code> config for a while. But to share
it, I thought it would be easier to make a plugin out of it. And so I did. The
plugin includes both approaches and is then working out of the box for both
cases.</p>
<p>You can find it on <a href="https://github.com/jecaro/ghcid-error-file.nvim">GitHub</a>.</p>]]></description>
    <pubDate>Thu, 28 Nov 2024 00:00:00 UT</pubDate>
    <guid>https://jeancharles.quillet.org/posts/2024-11-28-ghcid-error-file.nvim.html</guid>
    <dc:creator>Jean-Charles Quillet</dc:creator>
</item>
<item>
    <title>Haskell dev workflow with ghcid and neovim</title>
    <link>https://jeancharles.quillet.org/posts/2024-09-04-Haskell-dev-workflow-with-ghcid-and-neovim.html</link>
    <description><![CDATA[<h1 id="introduction">Introduction</h1>
<p>The tooling in the Haskell ecosystem has greatly improved these last years.
Especially, the introduction of <a href="https://github.com/haskell/haskell-language-server">Haskell Language Server</a> dramatically
lowered the barrier to entry for newcomers. But despite its undeniable quality,
<a href="https://github.com/haskell/haskell-language-server">HLS</a> still chokes on big codebases such as the ones we find in
professional environments.</p>
<p>Also, for a fast feedback loop, it is not always the most suited tool to reach
for. And having the fastest feedback loop is extremely important for a good
developer experience.</p>
<h1 id="ghcid"><a href="https://github.com/ndmitchell/ghcid">ghcid</a></h1>
<p>The tool that always works, no matter what, even on big codebases, is <code>ghcid</code>. I
find myself constantly getting back to it for my feedback loops.</p>
<p><code>ghcid</code> basically starts a <code>ghci</code> session and watches for changes in the loaded
files. Just save a file with your editor, it instantaneously reloads it and
prints the eventual errors or warnings, depending on your project
configuration. It is extremely versatile and can adapt to many workflows.</p>
<p>This is how I use it.</p>
<p><img src="/images/ghcid-manual.png" /></p>
<p>I open a <code>tmux</code> session with two vertical panes. On the left, I have <code>neovim</code>
with my Haskell project. On the right, I have <code>ghcid</code> running. When I save a
file, <code>ghcid</code> reloads the file and prints the errors. I can then look at the
errors and fix them in <code>neovim</code>, save again, repeat.</p>
<p>That’s pretty cool. And this kind of setup is super general by the way. It
basically works with any language. Have an editor on the left, and runs a
compilation loop on the right. There are a lot of tools that watch for file
changes and run a command when a file is modified. I personally like
<a href="https://github.com/eradman/entr/"><code>entr</code></a> and <a href="https://github.com/NorFairKing/feedback"><code>feedback</code></a> for this kind of task.</p>
<p>But anyway, we can do better. <code>neovim</code> has <a href="https://neovim.io/doc/user/quickfix.html#errorformat">a built-in feature</a>
for this kind of compilation loop. If your compilation task can produce a file
(and <code>ghcid</code> can do that), <code>neovim</code> can parse it and populate the quickfix list
with the errors and warnings. Then one can browse the quickfix list and fix the
errors one after another.</p>
<p>This makes the feedback loop even faster: I save a Haskell file in my editor,
<code>ghcid</code> automatically updates the error file, then, with one single command
(<code>:cfile</code> or <code>:cf</code>), I reload the file in <code>neovim</code> and jump to the first error.</p>
<p>Simple, easy, fast.</p>
<p>What I like with this workflow is also that it is not intrusive at all. It
doesn’t force me to fix any issue right now. I can jump to the error whenever I
want while having the compilation state of my project right under my eyes all
the time.</p>
<p><img src="/images/ghcid-errorformat.gif" /></p>
<h1 id="configuration">Configuration</h1>
<p>Now let’s talk about the required configuration for this to work. First we must
configure <code>ghcid</code>. This can be done at the root of the project in a file called
<code>.ghcid</code>.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> echo <span class="st">&quot;-a -o errors.err&quot;</span> <span class="op">&gt;</span> .ghcid</span></code></pre></div>
<p>This tells <code>ghcid</code> to output the errors in the file <code>errors.err</code>, the default
error file for <code>neovim</code> (see <a href="https://neovim.io/doc/user/options.html#&#39;errorfile&#39;"><code>:h errorfile</code></a>). <code>-a</code> is not strictly
necessary. It allows executing REPL commands in the <code>ghci</code> session. I find it
sometimes useful. More information about this can be found on
<a href="https://github.com/ndmitchell/ghcid#evaluation">here</a>.</p>
<p>Now we only need to tell <code>neovim</code> how to parse the error file. This is done
with the option <code>errorformat</code> (see <a href="https://neovim.io/doc/user/quickfix.html#errorformat"><code>:h errorformat</code></a>). A good
place to set it is in the <code>ftplugin</code> file for Haskell. That file will be
sourced whenever a Haskell file is opened.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> cat ~/.config/nvim/after/ftplugin/haskell.lua</span></code></pre></div>
<div class="sourceCode" id="cb3"><pre class="sourceCode lua"><code class="sourceCode lua"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="va">vim</span><span class="op">.</span><span class="va">opt_local</span><span class="op">.</span><span class="va">errorformat</span> <span class="op">=</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>    <span class="co">-- %W multi-line warning</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>    <span class="co">-- For some reason, %m doesn&#39;t work with %\\?, we need to add two lines for</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>    <span class="co">-- each case</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;%W%f:(%l</span><span class="sc">\\</span><span class="st">,%c)-(%e</span><span class="sc">\\</span><span class="st">,%k): warning: %m,&quot;</span> <span class="op">..</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;%W%f:(%l</span><span class="sc">\\</span><span class="st">,%c)-(%e</span><span class="sc">\\</span><span class="st">,%k): warning:,&quot;</span> <span class="op">..</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;%W%f:%l:%c-%k: warning: %m,&quot;</span> <span class="op">..</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;%W%f:%l:%c-%k: warning:,&quot;</span> <span class="op">..</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;%W%f:%l:%c: warning: %m,&quot;</span> <span class="op">..</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;%W%f:%l:%c: warning:,&quot;</span> <span class="op">..</span></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a>    <span class="co">-- %E multi-line error</span></span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;%E%f:(%l</span><span class="sc">\\</span><span class="st">,%c)-(%e</span><span class="sc">\\</span><span class="st">,%k): error: %m,&quot;</span> <span class="op">..</span></span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;%E%f:(%l</span><span class="sc">\\</span><span class="st">,%c)-(%e</span><span class="sc">\\</span><span class="st">,%k): error:,&quot;</span> <span class="op">..</span></span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;%E%f:%l:%c-%k: error: %m,&quot;</span> <span class="op">..</span></span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;%E%f:%l:%c-%k: error:,&quot;</span> <span class="op">..</span></span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;%E%f:%l:%c: error: %m,&quot;</span> <span class="op">..</span></span>
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;%E%f:%l:%c: error:,&quot;</span> <span class="op">..</span></span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-20"><a href="#cb3-20" aria-hidden="true" tabindex="-1"></a>    <span class="co">-- %Z Ends a multi-line message. We end it on the first line of the carret</span></span>
<span id="cb3-21"><a href="#cb3-21" aria-hidden="true" tabindex="-1"></a>    <span class="co">-- message.</span></span>
<span id="cb3-22"><a href="#cb3-22" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;%Z %</span><span class="sc">\\</span><span class="st">+|%.%#,&quot;</span> <span class="op">..</span></span>
<span id="cb3-23"><a href="#cb3-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-24"><a href="#cb3-24" aria-hidden="true" tabindex="-1"></a>    <span class="co">-- Continue a multi-line message</span></span>
<span id="cb3-25"><a href="#cb3-25" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;%C    %m,&quot;</span> <span class="op">..</span></span>
<span id="cb3-26"><a href="#cb3-26" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-27"><a href="#cb3-27" aria-hidden="true" tabindex="-1"></a>    <span class="co">-- Swallow everything else</span></span>
<span id="cb3-28"><a href="#cb3-28" aria-hidden="true" tabindex="-1"></a>    <span class="st">&quot;%-G%.%#&quot;</span></span></code></pre></div>
<p>And that’s it. Now when calling the command <code>:cf</code> in <code>neovim</code>, it will read the
error file, parse it according to the <code>errorformat</code> option and jump to the first
error.</p>
<p>Then one can browse the errors with the usual <code>quickfix</code> commands: <code>:cnext</code>,
<code>:cprev</code>, <code>:cfirst</code>, <code>:clast</code>, etc…</p>
<h1 id="conclusion">Conclusion</h1>
<p>This simple trick allows having a fast feedback loop when working with Haskell.</p>
<p>One shortcoming of this setup is that it doesn’t work when working in a
multi-package project. This is because the errors returned by GHC are relative
to the project. This can be easily solved by <code>cd</code> into the directory before
starting <code>ghcid</code>.</p>
<p>I have a better solution for this as well, but it is slightly more involved and
requires a bit of lua code. I will write about it in a future post.</p>
<p>That’s it for today. Let me know if you find this useful.</p>
<p>28-11-2024: Read the follow-up in <a href="/posts/2024-11-28-ghcid-error-file.nvim.html">this next
post</a>.</p>
<h1 id="related-tools">Related tools</h1>
<ul>
<li><a href="https://github.com/ndmitchell/ghcid/tree/master/plugins/nvim">ghcid.nvim</a> : A <code>neovim</code> plugin for <code>ghcid</code>. I was using it at some point.
But I like my solution better because it is simpler and only use <code>neovim</code>
built-in features.</li>
<li><a href="https://github.com/MercuryTechnologies/ghcidwatch">ghcidwatch</a> : A rewrite of <code>ghcid</code> in rust developed by Mercury. I have
never tried it, but it seems that it is able to write an error file in the
same format as <code>ghcid</code>. It should then work with this setup.</li>
</ul>]]></description>
    <pubDate>Wed, 04 Sep 2024 00:00:00 UT</pubDate>
    <guid>https://jeancharles.quillet.org/posts/2024-09-04-Haskell-dev-workflow-with-ghcid-and-neovim.html</guid>
    <dc:creator>Jean-Charles Quillet</dc:creator>
</item>
<item>
    <title>Columns - a new XMonad layout</title>
    <link>https://jeancharles.quillet.org/posts/2024-05-02-Columns-a-new-XMonad-layout.html</link>
    <description><![CDATA[<p>Today I would like to share the <a href="https://xmonad.org/">XMonad</a> layout I have written sometime
ago. It has been very stable for a while and I think it is now a good time to
share it.</p>
<p>It is a layout that organizes windows in columns while offering great
flexibility when it comes to arrange the windows. I use it along a <a href="https://hackage.haskell.org/package/xmonad-contrib-0.18.0/docs/XMonad-Layout-Tabbed.html">tabbed
layout</a> and with those two layouts, I do not need anything else
really.</p>
<p>Let’s first start with a quick demo of the layout in action:</p>
<p><video src="/images/columns.mp4" controls=""><a href="/images/columns.mp4">Video</a></video></p>
<p>As one can see in the video, the layout organizes the windows in columns. You
can have as many columns as you like. The windows can be freely moved from one
column to another, left and right, and, in a single column, up and down. They
can also be resized both horizontally and vertically. Adding new column is done
by moving a window to the spot you want the column to appear. Simple, easy.</p>
<p>I also use <a href="https://hackage.haskell.org/package/xmonad-contrib-0.18.0/docs/XMonad-Layout-SubLayouts.html#v:subLayout">this trick</a> to dock windows together in tabs as
showcased in the video.</p>
<p>I can change the focus of the windows using <a href="https://hackage.haskell.org/package/xmonad-contrib-0.18.0/docs/XMonad-Layout-WindowNavigation.html">WindowNavigation</a>. Every other
command is handled by sending custom messages to the layout. See the <a href="https://github.com/jecaro/xmonad-contrib/blob/a1ef65ff957cc97ca0d237c2f07802a5f90a5117/XMonad/Layout/Columns.hs">source
code</a> for details.</p>
<p>With this layout, I can arrange the windows really in any way I want while
keeping them always tiled.</p>
<p>I could not find anything like this in the <a href="https://github.com/xmonad/xmonad-contrib">xmonad-contrib</a> repository. The
closest I found was <a href="https://hackage.haskell.org/package/xmonad-contrib-0.18.0/docs/XMonad-Layout-ResizableTile.html">ResizableTall</a> that I used for a while before developping
my own. However, it is limited to two columns. Another interesting one is
<a href="https://hackage.haskell.org/package/xmonad-contrib-0.18.0/docs/XMonad-Layout-Groups-Examples.html#g:2">rowOfColumns</a>. It is pretty similar to mine, but it wasn’t working very well
with the tabbed sub-layout if I remember correctly.</p>
<p>That’s it. The layout has now been
<a href="https://github.com/xmonad/xmonad-contrib/pull/887">upstreamed</a> and <a href="https://github.com/jecaro/xmonad-contrib/blob/a1ef65ff957cc97ca0d237c2f07802a5f90a5117/XMonad/Layout/Columns.hs">the source
code</a> is fully available in the <a href="https://github.com/xmonad/xmonad-contrib">xmonad-contrib</a> repository.</p>]]></description>
    <pubDate>Thu, 02 May 2024 00:00:00 UT</pubDate>
    <guid>https://jeancharles.quillet.org/posts/2024-05-02-Columns-a-new-XMonad-layout.html</guid>
    <dc:creator>Jean-Charles Quillet</dc:creator>
</item>
<item>
    <title>Advent of code 2023</title>
    <link>https://jeancharles.quillet.org/posts/2024-01-30-Advent-of-code-2023.html</link>
    <description><![CDATA[<p>That’s it! I’m done with <a href="https://github.com/jecaro/advent-of-code-2023">advent of code</a> for this year.
I had the chance to have a bit of free time this year in December, so I thought
it would be cool to tackle that advent of code challenge everybody in the
developer community is talking about. It was the first time I was able to
participate. I hope I’ll be able to do it again in the future because it is
incredibly fun.</p>
<p><img src="/images/advent_of_code_2023.png" class="center" /></p>
<p>With my years of developing experience, I was confident I could handle the
rhythm of one puzzle a day and at the same time do it in a language I was not
very familiar with, Rust. I have written <a href="https://github.com/jecaro/mprisqueeze">a small project</a> in Rust
sometime ago, and I wanted to bring my knowledge one step further and learn
this language for good. One month of intense problem-solving in Rust would make
it at last.</p>
<p>Considering the language, Rust is actually a pretty decent choice when it comes
to solving AOC puzzles. Indeed, strong typing offers nice guarantees that the
code is going to do what it is meant to. And the good performance of the
language allows brute force a lot of the problems without too much thinking.</p>
<p>The first thing that struck me was that the challenges were way harder than I
expected. Here is how the whole thing is organized. You have one puzzle to
solve a day. Each puzzle comes in two-parts. The first one is usually not too
hard and can be solved with a direct or, call it naive, approach. The second
part is somehow a variation of the first one. But there is a catch. Very often
the solution developed for the first part blows up in complexity for the second
part: time or memory.</p>
<p>And this is where the fun actually begins. How come those two closely related
problems need to be solved in different algorithmic approaches? I can only bow
to <a href="http://was.tl/">Eric Wastl</a>, the person behind all this, for his amazing work.
This two-parts organization is incredibly crafted. The same remark goes for the
input data, which is different for every participant by the way. The tests are
always very helpful and fit smoothly into unit tests. And not to mention the
crystal clear explanations of each puzzle! All of this is very well done. And
if <a href="https://adventofcode.com/2023">AOC</a> took me so much time to solve, I can not
imagine how much time <a href="http://was.tl/">Eric Wastl</a> takes to prepare it every year.</p>
<p>One thing that I did not foresee thus was that 25 days is a long time. After
solving all the problems I was a bit exhausted. Depending on how clean you want
your solution to be, I do, it can easily eat all your free time. Also, the
puzzles are getting slightly harder each day. Interestingly, having a tight
time constraint makes the challenge very close to real work in this regard. In
my opinion, it is a good thing when it comes to learning a new language. It
puts you in the shoes of a working developer. Debugging can be quite hard
sometimes as some of the algorithms involve very big numbers. This also feels
like real-life work. At day job, it is very often that we have some weird
behavior that is hard to reproduce and inspect. Being creative on how to debug
programs is a very important skill for a developer. Tackling that kind of
problem helps to practice this skill.</p>
<p>I had heaps of fun with <a href="https://adventofcode.com/2023">Advent of Code</a>. All my
solutions are on <a href="https://github.com/jecaro/advent-of-code-2023">GitHub</a>. I find it refreshing to hack
on those puzzles. In everyday job, it is unfortunately very rare to run into
such interesting algorithmic problems. And it feels good practicing pure
algorithmic problem-solving.</p>
<p>To finish this post, I want to mention the <a href="https://adventofcode.com/2023/leaderboard">leaderboard page</a> from
the website. It shows how much time the fastest people take to solve the
problems. Sometimes a couple of minutes when it takes me hours. This is crazy.
But let’s recognize that we are not in the same positions here. My primary goal
is not to be the fastest but to learn a new language while having fun! That
goal is achieved along the challenge. I’m happy with that.</p>]]></description>
    <pubDate>Tue, 30 Jan 2024 00:00:00 UT</pubDate>
    <guid>https://jeancharles.quillet.org/posts/2024-01-30-Advent-of-code-2023.html</guid>
    <dc:creator>Jean-Charles Quillet</dc:creator>
</item>
<item>
    <title>Writing an Android app in Haskell</title>
    <link>https://jeancharles.quillet.org/posts/2023-11-07-Writing-an-Android-app-in-Haskell.html</link>
    <description><![CDATA[<h1 id="introduction">Introduction</h1>
<p>I have been willing to learn <a href="https://en.wikipedia.org/wiki/Functional_reactive_programming">FRP</a> for a long time. Since I had the chance to
have a little more time than usual these days, I thought it was the right time
for me to dive into this paradigm and learn this stuff for good.</p>
<p>Among the different implementations of <a href="https://en.wikipedia.org/wiki/Functional_reactive_programming">FRP</a> in Haskell, I was especially
interested in <a href="https://reflex-frp.org/">Reflex</a>. What I find interesting in this implementation is that
the very same code can be deployed on the web, as an Android app, or as an
iPhone app which is spectacular when you think about it.</p>
<p>The idea of being capable of running Haskell code on a phone is so appealing
that I could not resist. Once I heard about it, I knew that I needed to write
an app at some point in my Haskell journey. And that promise of being able to
run the very same code on different platforms. How true can that be? I needed
to find out.</p>
<p>Unfortunately, examples of such apps in the wild are not that common, if not
non-existent<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>. I spent a fair amount of time looking for examples. Only
tutorials are sometimes deployed on the web but nothing on Google Play (I did
not dig in the app store as I don’t own an iPhone). And as a professional
developer, I know there is often a big gap between tutorials and real-life
applications.</p>
<p>I needed to find out by myself. And that is what I did with this project.</p>
<h1 id="the-project">The project</h1>
<p>As I like to do it, I needed an idea of something simple yet nontrivial to get
my hands dirty. I found something that might be a good fit.</p>
<p>I am a big note taker. I take notes all the time about just anything, technical
or not. I have tried many different tools. But what works best for me is plain
markdown files in a private git repository.</p>
<p>What I am missing in my workflow is the ability to browse and search my notes
from my phone. I could use the official GitHub app for that purpose. However,
that would spoil the fun. I also know that there is a lot of value in owning
the code of a tool you use. Once the code is written and works, it becomes easy
to fix bugs and add features as needed.</p>
<p>The project would then be an app able to browse and search in a GitHub
repository. It should also be able to render markdown files. For simplicity’s
sake, I decided to use the GitHub API to talk to the Git repository.</p>
<p>Here is the summary of the things the app needs to do:</p>
<ul>
<li>have some settings</li>
<li>validate and store the settings</li>
<li>sends requests to the GitHub API</li>
<li>react to responses, parse them, and show the results</li>
<li>render markdown</li>
<li>handle errors</li>
<li>wrap everything in an easy-to-use UI</li>
</ul>
<h1 id="experience">Experience</h1>
<p>I had great fun learning <a href="https://en.wikipedia.org/wiki/Functional_reactive_programming">FRP</a>. As a developer, this is really the kind of
experience I am looking at. A new concept that, once grokked, gives a deeper
understanding of the domain, here, reactive UI development.</p>
<p>People usually say that <a href="https://en.wikipedia.org/wiki/Functional_reactive_programming">FRP</a> is hard to learn. I would not say that. But it is
true that one has to change mindset to be able to be productive with it. And
that is exactly the interesting point. My opinion is that it helps to
understand the interactions of the widgets and user actions as a whole. It
makes it clear that if something is difficult to implement in <a href="https://en.wikipedia.org/wiki/Functional_reactive_programming">FRP</a> that
usually means the interactions are themselves complicated.</p>
<p>I put the app <a href="https://diverk.quillet.org">online</a> and on the <a href="https://play.google.com/store/apps/details?id=org.jecaro.diverk">Play
Store</a>
for you to try out (and me to use). As advertised by <a href="https://obsidian.systems/">Obsidian
Systems</a> folks, it does work the same on the web and on Android. The
whole code is available on <a href="https://github.com/jecaro/diverk">GitHub</a>.</p>
<h1 id="missing-pieces-and-dark-corners">Missing pieces and dark corners</h1>
<p>I want to take advantage of this post to shed light on some dark corners I
encountered during the development of this project.</p>
<h2 id="developer-tools">Developer tools</h2>
<p>For me, Obelisk is a developer tool, such as ghc, cabal,
haskell-language-server, or even gcc. The documentation states that it must be
installed.</p>
<p>But I don’t usually install the developer tools I use. I pull all the ones I
need from a pinned version of <a href="https://github.com/NixOS/nixpkgs">nixpkgs</a> and put them in scope with a
<code>nix-shell</code>.</p>
<p>Unfortunately, the <code>ob</code> command is not in <a href="https://github.com/NixOS/nixpkgs">nixpkgs</a>. So I came up with that
<code>shell.nix</code>:</p>
<div class="sourceCode" id="cb1" data-url="https://raw.githubusercontent.com/jecaro/diverk/e777f2d44ed4ecdb08ed8f2b3c832d327bea7611/shell.nix" data-language="nix"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>  <span class="va">obeliskSrc</span> <span class="op">=</span> fetchGit <span class="op">{</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>    <span class="va">url</span> <span class="op">=</span> <span class="st">&quot;https://github.com/obsidiansystems/obelisk.git&quot;</span><span class="op">;</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>    <span class="va">ref</span> <span class="op">=</span> <span class="st">&quot;refs/tags/v1.2.0.0&quot;</span><span class="op">;</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>  <span class="op">};</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="kw">in</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="op">(</span><span class="bu">import</span> obeliskSrc <span class="op">{</span> <span class="op">}).</span>shell</span></code></pre></div>
<p>One can see, that for this shell, we pin obelisk version v1.2.0.0. Yet the <code>ob</code>
command, whatever version, doesn’t care about this and uses <a href="https://github.com/jecaro/diverk/blob/main/.obelisk/impl/github.json">the rev pinned
there</a>
instead. It fetches it, compiles it, and passes the commands to it. This is
undocumented and can be super confusing if you don’t know about it.</p>
<p>Now I also like to have <a href="https://github.com/haskell/haskell-language-server">haskell-language-server</a> available in my editor.
This is not very hard to add it in the development shell, yet quite difficult
if you don’t know how to do it. These are the relevant lines to be added to
<a href="https://github.com/jecaro/diverk/blob/78ff0683f0b77d40d907ff19f4a9771c5406957a/default.nix#L20">default.nix</a>.</p>
<div class="sourceCode" id="cb2" data-url="https://raw.githubusercontent.com/jecaro/diverk/78ff0683f0b77d40d907ff19f4a9771c5406957a/default.nix" data-from="20" data-to="23" data-language="nix"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>  shellToolOverrides = <span class="va">self</span><span class="op">:</span> <span class="va">super</span><span class="op">:</span> <span class="op">{</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>    <span class="va">haskell-language-server</span> <span class="op">=</span> pkgs<span class="op">.</span>haskell<span class="op">.</span>packages<span class="op">.</span>ghc8107<span class="op">.</span>haskell<span class="op">-</span>language<span class="op">-</span>server<span class="op">;</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>    <span class="va">implicit-hie</span> <span class="op">=</span> pkgs<span class="op">.</span>haskell<span class="op">.</span>packages<span class="op">.</span>ghc8107<span class="op">.</span>implicit<span class="op">-</span>hie<span class="op">;</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span>;</span></code></pre></div>
<p>At the end, working around those weird uses of nix, my workflow to work on this
project is:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-shell</span></code></pre></div>
<p>brings <code>ob</code> into scope. I can now use <code>ob run</code> or <code>ob watch</code> and develop. Then:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> ob shell</span></code></pre></div>
<p>brings <a href="https://github.com/haskell/haskell-language-server">haskell-language-server</a> into scope. From this shell, I start my
editor to have type hovers, auto-formatting and all the niceties that
<a href="https://github.com/haskell/haskell-language-server">haskell-language-server</a> provides.</p>
<h2 id="build-and-upload-the-android-app">Build and upload the Android app</h2>
<p>The Play Store expects a signed AAB file. That file can be built with:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-build <span class="at">-A</span> android.frontend <span class="at">--arg</span> androidIsRelease true</span></code></pre></div>
<p>The AAB file is then available in <code>./result/android-app-release.aab</code>.</p>
<p>It must be signed with some java tools. Let’s start a shell with them:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-shell <span class="at">-p</span> jdk17_headless</span></code></pre></div>
<p>Now create a key store for storing the keys that will be used to sign the file.</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> keytool <span class="at">-genkey</span> <span class="at">-v</span> <span class="at">-keystore</span> diverk.keystore <span class="at">-alias</span> diverk <span class="at">-keyalg</span> RSA <span class="at">-keysize</span> 2048 <span class="at">-validity</span> 10000</span></code></pre></div>
<p>It will ask a few questions, along with a password. That one must be kept in a
safe place. It will be needed for the next commands.</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> cp ./result/android-app-release.aab .</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> jarsigner <span class="at">-verbose</span> <span class="at">-sigalg</span> SHA256withRSA <span class="at">-digestalg</span> SHA-256 <span class="at">-storepass</span> the-passord-given-to-keytool <span class="at">-keystore</span> diverk.keystore ./android-app-release.aab diverk</span></code></pre></div>
<p>And that’s it! The AAB file is now signed and ready to be uploaded on the Play
Store.</p>
<h2 id="deploy-the-app-on-the-web">Deploy the app on the web</h2>
<p>To deploy the app on the web, one must first build the derivation:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix-build <span class="at">-A</span> linuxExe</span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> ls <span class="at">-L</span> result</span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="ex">backend</span>  frontend.jsexe.assets  static.assets  version</span></code></pre></div>
<p>It contains the following files:</p>
<ul>
<li><code>backend</code> is the executable of the HTTP server itself</li>
<li><code>frontend.jsexe.assets</code> is the web app compiled by ghcjs</li>
<li><code>static.assets</code> are the static assets used by the frontend</li>
</ul>
<p>Now you can copy that derivation on any system having nix, reverse proxy it
with nginx if you want, starts the backend, and it will serve your app on the
web.</p>
<p>The backend supports many command line options. Find out which ones with
<code>backend --help</code>.</p>
<h1 id="learning-resources">Learning resources</h1>
<p>Here you can find the best resources I have found on the subject:</p>
<ul>
<li>Presentation of the library by its creator Ryan Trinkle: <a href="https://www.youtube.com/watch?v=mYvkcskJbc4">part
1</a>, <a href="https://www.youtube.com/watch?v=3qfc9XFVo2c">part
2</a></li>
<li><a href="https://reflex-frp.org/tutorial">The official tutorial</a> and <a href="https://github.com/jecaro/reflex-tutorial">my
playground</a></li>
<li><a href="https://qfpl.io/projects/reflex/">Great tutorials</a> made by the Queensland
Functional Programming Lab</li>
<li>Real World Reflex: a video addressing more advanced concepts
<ul>
<li><a href="https://www.youtube.com/watch?v=dNBUDAU9sv4">video</a></li>
<li><a href="https://github.com/mightybyte/real-world-reflex/blob/master/index.md">slides</a></li>
</ul></li>
</ul>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>Except for <a href="https://github.com/dfordivam/tenjinreader/">tenjinreader</a>
<del>on the Play Store</del> Not there anymore.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>]]></description>
    <pubDate>Tue, 07 Nov 2023 00:00:00 UT</pubDate>
    <guid>https://jeancharles.quillet.org/posts/2023-11-07-Writing-an-Android-app-in-Haskell.html</guid>
    <dc:creator>Jean-Charles Quillet</dc:creator>
</item>

    </channel>
</rss>
