{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "webcodr",
  "home_page_url": "https://webcodr.io/",
  "feed_url": "https://webcodr.io/feed.json",
  "description": "WebCodr is a blog that covers a wide range of programming topics, including the Kotlin and Rust programming languages and ways to improve developer productivity.",
  "language": "en-us",
  "authors": [{ "name": "David Henning" }],
  "items": [
    {
      "id": "https://webcodr.io/2026/07/find-things-even-faster-with-srchr/",
      "url": "https://webcodr.io/2026/07/find-things-even-faster-with-srchr/",
      "title": "Find things even faster with srchr",
      "summary": "Inspired by my last post about rgp and fdp I got the idea to combine both into a more powerful and easier tool called srchr.\nIt\u0026rsquo;s a TUI written in Rust and uses similar or the same libraries as rg, fd, fzf and bat, but in one package. There are no external dependencies and it comes for macOS, Linux and Windows in aarch64 and x86-64.\nCurrently srchr is not available via package managers, but there\u0026rsquo;s an installation script:\n",
      "content_html": "\u003cp\u003eInspired by my last post about \u003ccode\u003ergp\u003c/code\u003e and \u003ccode\u003efdp\u003c/code\u003e I got the idea to combine both into a more powerful and easier tool called \u003ccode\u003esrchr\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eIt\u0026rsquo;s a TUI written in Rust and uses similar or the same libraries as \u003ccode\u003erg\u003c/code\u003e, \u003ccode\u003efd\u003c/code\u003e, \u003ccode\u003efzf\u003c/code\u003e and \u003ccode\u003ebat\u003c/code\u003e, but in one package. There are no external dependencies and it comes for macOS, Linux and Windows in aarch64 and x86-64.\u003c/p\u003e\n\u003cp\u003eCurrently \u003ccode\u003esrchr\u003c/code\u003e is not available via package managers, but there\u0026rsquo;s an installation script:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecurl -fsSL https://raw.githubusercontent.com/webcodr/srchr/main/install.sh \u003cspan class=\"p\"\u003e|\u003c/span\u003e sh\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eI will also publish \u003ccode\u003esrchr\u003c/code\u003e on Homebrew, the AUR and perhaps some other package managers.\u003c/p\u003e\n\u003cp\u003eBeware, it\u0026rsquo;s still under development and the UI is functional, but not finished yet. At the moment there is just a hardcoded Tokyp Night inspired color theme, but this will also change in the near future.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/webcodr/srchr\"\u003esrchr on GitHub\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"update\"\u003e\u003ca href=\"#update\" class=\"heading-anchor\"\u003eUpdate\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eIt\u0026rsquo;s now available on Homebrew for macOS and Linux!\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ebrew install webcodr/tap/srchr\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e",
      "date_published": "2026-07-05T20:28:50Z",
      "date_modified": "2026-07-14T20:52:39+02:00",
      "tags": ["developer-tools","rust"]
    },
    {
      "id": "https://webcodr.io/2026/07/using-fd-rg-fzf-and-bat-to-find-things-fast/",
      "url": "https://webcodr.io/2026/07/using-fd-rg-fzf-and-bat-to-find-things-fast/",
      "title": "Using fd, rg, fzf and bat to find things fast",
      "summary": "Modern terminal programs like fd or rg make it really easy to find stuff, but there\u0026rsquo;s still room for improvement. In this post I will show you how to use fish to write to small functions with fd, rg, fzf and bat to search for file names and file content with an interactive list and even a preview in the terminal.\nI don\u0026rsquo;t know that stuff?# First things first. If you already know fd or the other tools from above, feel free to skip this section.\n",
      "content_html": "\u003cp\u003eModern terminal programs like \u003ccode\u003efd\u003c/code\u003e or \u003ccode\u003erg\u003c/code\u003e make it really easy to find stuff, but there\u0026rsquo;s still room for improvement. In this post I will show you how to use fish to write to small functions with \u003ccode\u003efd\u003c/code\u003e, \u003ccode\u003erg\u003c/code\u003e, \u003ccode\u003efzf\u003c/code\u003e and \u003ccode\u003ebat\u003c/code\u003e to search for file names and file content with an interactive list and even a preview in the terminal.\u003c/p\u003e\n\u003ch2 id=\"i-dont-know-that-stuff\"\u003e\u003ca href=\"#i-dont-know-that-stuff\" class=\"heading-anchor\"\u003eI don\u0026rsquo;t know that stuff?\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eFirst things first. If you already know \u003ccode\u003efd\u003c/code\u003e or the other tools from above, feel free to skip this section.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003efd\u003c/code\u003e is a more user-friendly replacement for \u003ccode\u003efind\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003erg\u003c/code\u003e or ripgrep is \u003ccode\u003egrep\u003c/code\u003e an steroids, respecting git ignore files and skips binaries or hidden files\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003efzf\u003c/code\u003e is a command-line fuzzy finder. You basically can pipe everything into \u003ccode\u003efzf\u003c/code\u003e and just type to filter the output\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ebat\u003c/code\u003e is a modern version of \u003ccode\u003ecat\u003c/code\u003e with syntax highlighting and git integration\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"say-hi-to-rgp-and-fdp\"\u003e\u003ca href=\"#say-hi-to-rgp-and-fdp\" class=\"heading-anchor\"\u003eSay hi to rgp and fdp\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fish\" data-lang=\"fish\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"nf\"\u003ergp\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eset\u003c/span\u003e \u003cspan class=\"na\"\u003e-l\u003c/span\u003e \u003cspan class=\"nv\"\u003esearch_term\u003c/span\u003e \u003cspan class=\"nv\"\u003e$argv\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"k\"\u003etest\u003c/span\u003e \u003cspan class=\"na\"\u003e-z\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$search_term\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Usage: rgp \u0026lt;search_term\u0026gt;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"m\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003erg\u003c/span\u003e \u003cspan class=\"na\"\u003e-l\u003c/span\u003e \u003cspan class=\"nv\"\u003e$search_term\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"nf\"\u003efzf\u003c/span\u003e \u003cspan class=\"na\"\u003e--preview\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;bat --color always {}\u0026#39;\u003c/span\u003e \u003cspan class=\"na\"\u003e--bind\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;enter:become(\u0026#34;$EDITOR\u0026#34; {+})\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efunction\u003c/span\u003e \u003cspan class=\"nf\"\u003efdp\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eset\u003c/span\u003e \u003cspan class=\"na\"\u003e-l\u003c/span\u003e \u003cspan class=\"nv\"\u003esearch_term\u003c/span\u003e \u003cspan class=\"nv\"\u003e$argv\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"k\"\u003etest\u003c/span\u003e \u003cspan class=\"na\"\u003e-z\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$search_term\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Usage: fdp \u0026lt;search_term\u0026gt;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"m\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nf\"\u003efd\u003c/span\u003e \u003cspan class=\"na\"\u003e-tf\u003c/span\u003e \u003cspan class=\"nv\"\u003e$search_term\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"nf\"\u003efzf\u003c/span\u003e \u003cspan class=\"na\"\u003e--preview\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;bat --color always {}\u0026#39;\u003c/span\u003e \u003cspan class=\"na\"\u003e--bind\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;enter:become(\u0026#34;$EDITOR\u0026#34; {+})\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThat\u0026rsquo;s all you need. Put them into your fish config file or the fish functions folder.\u003c/p\u003e\n\u003ch2 id=\"usage\"\u003e\u003ca href=\"#usage\" class=\"heading-anchor\"\u003eUsage\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003ergp something\u003c/code\u003e \u0026ndash; this will use \u003ccode\u003erg\u003c/code\u003e to search recursively search the current directory for files with \u003ccode\u003esomething\u003c/code\u003e inside. The result is piped into \u003ccode\u003efzf\u003c/code\u003e. You know can filter the result further with \u003ccode\u003efzf\u003c/code\u003e or select a file with the arrow keys and \u003ccode\u003ebat\u003c/code\u003e will give you a live preview of the file with syntax highlighting. Pressing \u003ccode\u003eenter\u003c/code\u003e will open the selected file with your default editor (environment variable \u003ccode\u003eEDITOR\u003c/code\u003e).\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003efdp\u003c/code\u003e works similar, but looks for file names instead of the file content and also pipes the result to \u003ccode\u003efzf\u003c/code\u003e for further filtering, preview or opening the selected file.\u003c/p\u003e\n\u003cp\u003eDon\u0026rsquo;t like fish? Here\u0026rsquo;s a version that works with bsh or zsh.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eNote\u003c/strong\u003e: the \u003ccode\u003elocal\u003c/code\u003e keyword is not POSIX-compliant. If you need that, just remove it, but this makes \u003ccode\u003esearch_term\u003c/code\u003e global, so use with care.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ergp\u003cspan class=\"o\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003elocal\u003c/span\u003e \u003cspan class=\"nv\"\u003esearch_term\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"nv\"\u003e$1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e -z \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$search_term\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Usage: rgp \u0026lt;search_term\u0026gt;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"m\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efi\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    rg -l \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$search_term\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e fzf --preview \u003cspan class=\"s1\"\u003e\u0026#39;bat --color always {}\u0026#39;\u003c/span\u003e --bind \u003cspan class=\"s1\"\u003e\u0026#39;enter:become(\u0026#34;$EDITOR\u0026#34; {+})\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003efdp\u003cspan class=\"o\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003elocal\u003c/span\u003e \u003cspan class=\"nv\"\u003esearch_term\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"nv\"\u003e$1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e -z \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$search_term\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Usage: fdp \u0026lt;search_term\u0026gt;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"m\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efi\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    fd -tf \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$search_term\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e fzf --preview \u003cspan class=\"s1\"\u003e\u0026#39;bat --color always {}\u0026#39;\u003c/span\u003e --bind \u003cspan class=\"s1\"\u003e\u0026#39;enter:become(\u0026#34;$EDITOR\u0026#34; {+})\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e",
      "date_published": "2026-07-05T17:05:03Z",
      "date_modified": "2026-07-14T20:52:39+02:00",
      "tags": ["developer-tools"]
    },
    {
      "id": "https://webcodr.io/2025/10/fix-omarchy-gaming-vulkan/",
      "url": "https://webcodr.io/2025/10/fix-omarchy-gaming-vulkan/",
      "title": "Fix Omarchy Gaming (Vulkan)",
      "summary": "After setting up my new notebook, I wanted to try some games on Steam. Works like a charm on my mini PC with Omarchy, but not this time. After starting a game, it takes a few seconds and nothing, the game silently crashes.\nAfter fiddling and searching around, Proton logging etc., still no cause in sight. Then I tried Doom 2016 and it worked! But why? Doom starts with an OpenGL renderer, after switching to Vulkan it behaves like the other games. So there\u0026rsquo;s something wrong with Vulkan?\n",
      "content_html": "\u003cp\u003eAfter setting up my new notebook, I wanted to try some games on Steam. Works like a charm on my mini PC with Omarchy, but not this time. After starting a game, it takes a few seconds and nothing, the game silently crashes.\u003c/p\u003e\n\u003cp\u003eAfter fiddling and searching around, Proton logging etc., still no cause in sight. Then I tried Doom 2016 and it worked! But why? Doom starts with an OpenGL renderer, after switching to Vulkan it behaves like the other games. So there\u0026rsquo;s something wrong with Vulkan?\u003c/p\u003e\n\u003cp\u003eVulkan Tools (Arch package \u003ccode\u003evulkan-tools\u003c/code\u003e) to the rescue! After running \u003ccode\u003evulkaninfo\u003c/code\u003e it became pretty clear that something important was missing: the Radeon Vulkan driver. No driver, no Vulkan. No Vulkan, no Proton \u0026hellip;\u003c/p\u003e\n\u003cp\u003eThankfully it\u0026rsquo;s easy to fix, just install the missing packages:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo pacman -S vulkan-radeon mesa mesa-vdpau lib32-vulkan-radeon lib32-mesa\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIt should work immediately after the installation. There\u0026rsquo;s already a GitHub issue with a pull request to solve this, but it\u0026rsquo;s open for a weeks now and it\u0026rsquo;s not clear, when the fix will be merged and released.\u003c/p\u003e\n\u003cp\u003eI don\u0026rsquo;t know why it\u0026rsquo;s no problem with my other PC, but I have installed Omarchy the old way on this machine (Arch Install and the Omarchy setup script). My best guess is that the bug was introduced with Omarchy\u0026rsquo;s ISO setup.\u003c/p\u003e\n\u003ch2 id=\"how-does-it-run\"\u003e\u003ca href=\"#how-does-it-run\" class=\"heading-anchor\"\u003eHow does it run?\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eWell, that Ryzen AI MAX+ 390 is an absolute beast. Doom 2016 runs with over 100 fps with max settings on the internal display (2880x1800) and still far beyond 60 fps in 4K on my main monitor. Just don\u0026rsquo;t use fullscreen mode, it flickers like hell (known problem of Doom 2016 with the Vulkan renderer).\u003c/p\u003e\n\u003cp\u003eThe HP ZBook Ultra no gaming notebook, but it has a really powerful APU and is still a quite compact and light device on the level of a 14\u0026quot; MacBook Pro. I don\u0026rsquo;t want a 250 W 4 kg behemoth of a notebook.\u003c/p\u003e\n",
      "date_published": "2025-10-31T21:48:59Z",
      "date_modified": "2026-07-12T02:03:38+02:00",
      "tags": ["linux","omarchy"]
    },
    {
      "id": "https://webcodr.io/2025/10/hyprland-trackpad-tips-tricks/",
      "url": "https://webcodr.io/2025/10/hyprland-trackpad-tips-tricks/",
      "title": "Hyprland Trackpad Tips \u0026 Tricks",
      "summary": "For first time in my life I bought a PC notebook. I didn\u0026rsquo;t even consider to boot in the pre-installed Windows 11 and installed Omarchy right away.\nThe HP ZBook has a quite good trackpad, even compared to MacBooks, but some things seemed off. No right-click with two fingers, instead it would only work in the lower right corner. And I absolutely hate tapping, I want real clicks.\nTurns out, it\u0026rsquo;s not the hardware. It\u0026rsquo;s all configurable in Hyprland. In Omarchy you can find the settings in .config/hypr/input, sub-category input:touchpad.\n",
      "content_html": "\u003cp\u003eFor first time in my life I bought a PC notebook. I didn\u0026rsquo;t even consider to boot in the pre-installed Windows 11 and installed Omarchy right away.\u003c/p\u003e\n\u003cp\u003eThe HP ZBook has a quite good trackpad, even compared to MacBooks, but some things seemed off. No right-click with two fingers, instead it would only work in the lower right corner. And I absolutely hate tapping, I want real clicks.\u003c/p\u003e\n\u003cp\u003eTurns out, it\u0026rsquo;s not the hardware. It\u0026rsquo;s all configurable in Hyprland. In Omarchy you can find the settings in \u003ccode\u003e.config/hypr/input\u003c/code\u003e, sub-category \u003ccode\u003einput:touchpad\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eHere are some handy options to tweak the settings:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003einput \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  touchpad \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# Enable two-finger clicks for right-clicking\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nv\"\u003eclickfinger_behaviour\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# Disable tapping\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    tap-to-click \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# Enable natural scrolling\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nv\"\u003enatural_scroll\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# Disable the trackpad while typing (accidental tapping etc.)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nv\"\u003edisable_while_typing\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThere is even more like tapping maps, middle button emulation etc. \u0026ndash; you can find all options \u003ca href=\"https://wiki.hypr.land/Configuring/Basics/Variables/#touchpad\"\u003ehere\u003c/a\u003e.\u003c/p\u003e\n",
      "date_published": "2025-10-29T22:57:34Z",
      "date_modified": "2026-07-14T21:14:07+02:00",
      "tags": ["linux","omarchy"]
    },
    {
      "id": "https://webcodr.io/2025/08/more-awesome-cli-tools/",
      "url": "https://webcodr.io/2025/08/more-awesome-cli-tools/",
      "title": "More awesome CLI tools",
      "summary": "This a follow up article to Awesome CLI tools with even more useful CLI tools.\nbtop# It\u0026rsquo;s top on steroids with a beautiful terminal UI. Way more information at a glance and just a joy to use.\ndelta# Do you often look at git diffs? Are you missing syntax highlighting? delta is here to help. Just install it, configure delta as pager for git and voila, diffs with syntax highlighting.\n",
      "content_html": "\u003cp\u003eThis a follow up article to \u003ca href=\"/2024/03/awsome-cli-tools/\"\u003eAwesome CLI tools\u003c/a\u003e with even more useful CLI tools.\u003c/p\u003e\n\u003ch2 id=\"btop\"\u003e\u003ca href=\"#btop\" class=\"heading-anchor\"\u003ebtop\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eIt\u0026rsquo;s \u003ccode\u003etop\u003c/code\u003e on steroids with a beautiful terminal UI. Way more information at a glance and just a joy to use.\u003c/p\u003e\n\u003ch2 id=\"delta\"\u003e\u003ca href=\"#delta\" class=\"heading-anchor\"\u003edelta\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eDo you often look at git diffs? Are you missing syntax highlighting? \u003ccode\u003edelta\u003c/code\u003e is here to help. Just install it, configure delta as pager for git and voila, diffs with syntax highlighting.\u003c/p\u003e\n\u003ch2 id=\"dog\"\u003e\u003ca href=\"#dog\" class=\"heading-anchor\"\u003edog\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eDog is a modern DNS lookup tool with colored output and a simple interface.\u003c/p\u003e\n\u003ch2 id=\"tokei\"\u003e\u003ca href=\"#tokei\" class=\"heading-anchor\"\u003etokei\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eAre you a fan of statistics? Tokei can summarize file types, lines of code, comments or blank spaces of your project.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m currently converting a large Java project to Kotlin and tokei makes it easy to see the progress.\u003c/p\u003e\n\u003ch2 id=\"procs\"\u003e\u003ca href=\"#procs\" class=\"heading-anchor\"\u003eprocs\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eA modern replacement for \u003ccode\u003eps\u003c/code\u003e with colored output, keyword search, tree view and some useful helpers for networking (ports) and Docker.\u003c/p\u003e\n\u003ch2 id=\"lazygit\"\u003e\u003ca href=\"#lazygit\" class=\"heading-anchor\"\u003elazygit\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eOne of my favorites. Lazygit is a dead simple, yet powerful and elegant to use terminal UI for git. I\u0026rsquo;m not a fan of the git CLI and always preferred a GUI tool like Tower, but nowadays lazygit is my git client of choice. I don\u0026rsquo;t think there is better git client out there. Even many NeoVim distributions support it out-of-the-box (Lazyvim with \u003ccode\u003eLEADER\u003c/code\u003e + \u003ccode\u003egg\u003c/code\u003e).\u003c/p\u003e\n\u003ch2 id=\"sd\"\u003e\u003ca href=\"#sd\" class=\"heading-anchor\"\u003esd\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eDo you use \u003ccode\u003esed\u003c/code\u003e? Do you like it? I don\u0026rsquo;t. \u003ccode\u003esd\u003c/code\u003e is better, much better.\u003c/p\u003e\n",
      "date_published": "2025-08-29T21:34:15Z",
      "date_modified": "2026-07-14T20:52:39+02:00",
      "tags": ["developer-tools"]
    },
    {
      "id": "https://webcodr.io/2025/08/im-using-arch-btw/",
      "url": "https://webcodr.io/2025/08/im-using-arch-btw/",
      "title": "I'm using Arch btw",
      "summary": "Well, that escalated quickly. One week ago I would have said never to touch Arch (except SteamOS). Nothing personal, it\u0026rsquo;s just that I am primarily a Mac guy and never stayed with Linux that long. I really enjoyed Pop!_OS. System76 created a really nice flavor of Ubuntu, but it\u0026rsquo;s still Ubuntu, so packages are often out of date and you always have to rely on custom repos or other means to get current versions.\n",
      "content_html": "\u003cp\u003eWell, that escalated quickly. One week ago I would have said never to touch Arch (except SteamOS). Nothing personal, it\u0026rsquo;s just that I am primarily a Mac guy and never stayed with Linux that long. I really enjoyed Pop!_OS. System76 created a really nice flavor of Ubuntu, but it\u0026rsquo;s still Ubuntu, so packages are often out of date and you always have to rely on custom repos or other means to get current versions.\u003c/p\u003e\n\u003cp\u003eWith Arch and its rolling release model the latest versions are almost immediately available. Sounds good? Of course there\u0026rsquo;s a catch: breaking changes are also immediately available. Updating all dependencies can break many things at once and that can get very annoying pretty fast. From what I\u0026rsquo;ve heard the situation improved over the years, so if you\u0026rsquo;re doing nothing too crazy, it should be okay.\u003c/p\u003e\n\u003cp\u003eA few days ago I discovered \u003ca href=\"https://omarchy.org/\"\u003eOmarchy\u003c/a\u003e from DHH (creator of Ruby on Rails). He created an opinionated Arch setup for developers. \u003cdel\u003eJust install Arch via \u003ccode\u003earchinstall\u003c/code\u003e and run Omarchy\u0026rsquo;s installation script and you\u0026rsquo;re mostly done.\u003c/del\u003e A few days ago Omarchy version 2.0 was released and brings a complete ISO file with its own installer. Just boot the ISO from a USB drive, enter some things like your user name, password, hostname etc. and wait a few minutes. That\u0026rsquo;s it!\u003c/p\u003e\n\u003cp\u003eOmarchy ships with Hyprland, a really efficient and fast tiling Wayland compositor that doesn\u0026rsquo;t look like it\u0026rsquo;s from the 1980s. There\u0026rsquo;s all sorts of eyecandy, everything is customizable and there are also many cool plugins.\u003c/p\u003e\n\u003cp\u003eSetting up Hyprland and plugins on your own can be daunting task and a very time-consuming one as well. Omarchy to the rescue! It ships with a really good default configuration and is perfectly useable from the get-go, but that\u0026rsquo;s only the beginning of the journey. DHH considers Omarchy a starting point for you own configuration and therefore made it very easy to customize the configuration for your needs. There is even a special Omarchy configuration tool for switching the global color theme (for Hyprland, Alacritty, Neovim etc.) or setting up fingerprint sensors for biometric authentication (should be working very well with Framework devices).\u003c/p\u003e\n\u003cp\u003eOmarchy also installs many useful terminal and GUI programs, like Alacritty (terminal emulator), Neovim, Spotify, Lazygit, \u003ccode\u003efd\u003c/code\u003e etc. All GUI programs are mapped to intuitive hotkeys, f.e. \u003ccode\u003eSUPER\u003c/code\u003e + \u003ccode\u003eM\u003c/code\u003e for Spotify or \u003ccode\u003eSUPER\u003c/code\u003e + \u003ccode\u003eB\u003c/code\u003e for the browser (Chromium by default). \u003ccode\u003eSUPER\u003c/code\u003e + \u003ccode\u003eSpace\u003c/code\u003e displays the application launcher (like a simple version of Spotlight or Raycast). There\u0026rsquo;s much more for controlling the current window size to move the focus, switch virtual desktops, fullscreen etc.\u003c/p\u003e\n\u003cp\u003eAll hotkeys are easily customizable in \u003ccode\u003e.config/hypr/bindings.conf\u003c/code\u003e. You don\u0026rsquo;t like Chromium? Just install your favorite browser with the Pacman (Arch\u0026rsquo;s package manager) and update the variable \u003ccode\u003e$browser\u003c/code\u003e in the Hyprland config. That\u0026rsquo;s it. The browser is also used for webapps mapped on hotkeys, ChatGPT f.e. is mapped on \u003ccode\u003eSUPER\u003c/code\u003e + \u003ccode\u003eA\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eA little example to change the music hotkey to Apple Music as webapp (there is no native Linux version).\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003ebindd\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e SUPER, M, Apple Music, exec, \u003cspan class=\"nv\"\u003e$webapp\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;https://music.apple.com/de/library/recently-added?l=en\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003ePressing \u003ccode\u003eSUPER\u003c/code\u003e + \u003ccode\u003eM\u003c/code\u003e now opens a browser window in app mode (no address bar etc.) with the Apple Music website. Nice. I also changed \u003ccode\u003eSUPER\u003c/code\u003e + \u003ccode\u003eA\u003c/code\u003e from ChatGPT to Theo Browne\u0026rsquo;s awesome \u003ca href=\"https://t3.chat/\"\u003eT3.chat\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eThat\u0026rsquo;s far from all that Omarchy offers, but would be a bit much to list every feature in this blog post. Omarchy has a pretty good documentation with many useful tips, a troubleshooting section etc. They even have advice on my problems with Bluetooth. The Minis Forum UM760 comes with a MediaTek WiFi card and sadly MediaTek doesn\u0026rsquo;t offer Linux drivers for this particular model. I switched to an Intel AX210 card for a few bucks and now it works like a charm.\u003c/p\u003e\n\u003cp\u003eAccording to DHH they will even ship an ISO file with Arch and a custom installer in the near future. It\u0026rsquo;s also planned to switch the default terminal from Alacritty to Ghostty, but there are still some issues to solve.\u003c/p\u003e\n\u003ch2 id=\"tldr\"\u003e\u003ca href=\"#tldr\" class=\"heading-anchor\"\u003eTL;DR\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eIf you\u0026rsquo;re not afraid of Linux and want a decent base installation of Arch with many developer-focused feature, give Omarchy a chance!\u003c/p\u003e\n\u003ch3 id=\"updates\"\u003e\u003ca href=\"#updates\" class=\"heading-anchor\"\u003eUpdates\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e2025-08-05: Omarchy 1.11 brings a new Hyprland config file structure and \u003ccode\u003ebindd\u003c/code\u003e command. I updated the file path for key bindings and the example for Apple Music accordingly.\u003c/li\u003e\n\u003cli\u003e2025-08-28: a small update about the release of Omarchy 2.0 with its own ISO and installer.\u003c/li\u003e\n\u003c/ol\u003e\n",
      "date_published": "2025-08-01T20:36:11Z",
      "date_modified": "2026-07-12T23:13:38+02:00",
      "tags": ["linux","omarchy"]
    },
    {
      "id": "https://webcodr.io/2025/05/please-dont-do-this-with-switch-statements/",
      "url": "https://webcodr.io/2025/05/please-dont-do-this-with-switch-statements/",
      "title": "Please don't do this with switch statements",
      "summary": "The classic C-like switch statement is fine, but it has its flaws. It\u0026rsquo;s no coincidence that modern languages like Kotlin or Rust offer alternatives like when or match or a more fine-tuned version of switch like Zig.\nI\u0026rsquo;m currently the in early stages of rewriting a large and complex Java code base to Kotlin. Some parts of this codebase are really ugly and uncessarily complicated and convoluted. Yesterday I crossed the path of a nasty use of a switch statement in a operation on a Java Stream. Unfortunately I can\u0026rsquo;t share the real code, but imagine something like this:\n",
      "content_html": "\u003cp\u003eThe classic C-like switch statement is fine, but it has its flaws. It\u0026rsquo;s no coincidence that modern languages like Kotlin or Rust offer alternatives like \u003ccode\u003ewhen\u003c/code\u003e or \u003ccode\u003ematch\u003c/code\u003e or a more fine-tuned version of \u003ccode\u003eswitch\u003c/code\u003e like Zig.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m currently the in early stages of rewriting a large and complex Java code base to Kotlin. Some parts of this codebase are really ugly and uncessarily complicated and convoluted. Yesterday I crossed the path of a nasty use of a switch statement in a operation on a Java Stream. Unfortunately I can\u0026rsquo;t share the real code, but imagine something like this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003evalue1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003evalue2\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(...)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"k\"\u003eswitch\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kd\"\u003eenum\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ecase\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eEnum\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eFOO\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esomething\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esomethingElse\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003evalue1\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esomeConvulutedStreamOperations\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003evalue2\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esomeOtherConvulutedStreamOperations\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// imagine 30 more lines here\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003evalue2\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003ebreak\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ecase\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eEnum\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eBAR\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003edefault\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003evalue1\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003evalue2\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003ebreak\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis was part of a Java class with over 1,000 lines of code. Streams with many operations everywhere and sometimes very deep nesting thanks to old-style Java code. The original case for \u003ccode\u003eEnum.FOO\u003c/code\u003e stretches over almost the whole display space, so it\u0026rsquo;s not easy to spot any potential pitfalls. After I ran IntelliJ\u0026rsquo;s Kotlin migration tool and cleaned up all errors, it was time to run the unit tests and four out of 24 failed.\u003c/p\u003e\n\u003cp\u003eAs you can imagine it\u0026rsquo;s not straight forward to find problems in such a large and complex class but I came across a notice of an unused value assignment. Why it was there was also not clear immediately, so I compared the original Java file with the Kotlin version.\u003c/p\u003e\n\u003cp\u003eSince Kotlin has no \u003ccode\u003eswitch\u003c/code\u003e IntelliJ converted it to \u003ccode\u003ewhen\u003c/code\u003e. Unlike it\u0026rsquo;s Java counterpart \u003ccode\u003ewhen\u003c/code\u003e can handle \u003ccode\u003enull\u003c/code\u003e and is exhaustive with enums. There also no \u003ccode\u003ebreak\u003c/code\u003e keyword and that\u0026rsquo;s exactly the problem here.\u003c/p\u003e\n\u003cp\u003eLook at the Java code and think about what happens on \u003ccode\u003eEnum.FOO\u003c/code\u003e if \u003ccode\u003esomething\u003c/code\u003e is true but \u003ccode\u003esomethingElse\u003c/code\u003e isn\u0026rsquo;t. The \u003ccode\u003ebreak\u003c/code\u003e keyword is not triggered and the \u003ccode\u003eswitch\u003c/code\u003e statement goes on to the default block. As result \u003ccode\u003evalue1\u003c/code\u003e and \u003ccode\u003evalue2\u003c/code\u003e are assigned their values.\u003c/p\u003e\n\u003cp\u003eIntelliJ\u0026rsquo;s migration tool is quite good, but it didn\u0026rsquo;t catch that and the generated \u003ccode\u003ewhen\u003c/code\u003e statement was wrong. That\u0026rsquo;s also the reason for the unused assignment notice. I assume the migration tool was confused by the scope of \u003ccode\u003ebreak\u003c/code\u003e since both \u003ccode\u003eswitch\u003c/code\u003e and \u003ccode\u003efor\u003c/code\u003e can use it. Also there is no real match in Kotlin for such a structure. You have to assign the values of both variables for every branch inside the \u003ccode\u003ewhen\u003c/code\u003e statement. After fixing that all tests ran successfully.\u003c/p\u003e\n\u003cp\u003eIt\u0026rsquo;s a really good example how not to use \u003ccode\u003eswitch\u003c/code\u003e statements, especially with nested control structures in a case. Since the original author of the code is no longer available, I can only guess why it was written this way. Probably to avoid duplication or even unintended. Well, it could seem as an elegant solution if you\u0026rsquo;re familiar with the code, but to other people it\u0026rsquo;s just an unnecessary pitfall that should be avoided. It\u0026rsquo;s untuitive at best, not easy to comprehend and can lead to nasty bugs, especially if it\u0026rsquo;s unintended behaviour. That\u0026rsquo;s why Kotlin, Rust, Zig and other modern languages avoid breaks in their alternatives to switch statements in the first place.\u003c/p\u003e\n",
      "date_published": "2025-05-28T20:22:07+02:00",
      "date_modified": "2026-07-12T02:31:00+02:00",
      "tags": ["programming","kotlin","java"]
    },
    {
      "id": "https://webcodr.io/2024/10/fixing-no-a2dp-with-bluetooth-headsets-on-linux/",
      "url": "https://webcodr.io/2024/10/fixing-no-a2dp-with-bluetooth-headsets-on-linux/",
      "title": "Fixing no A2DP with Bluetooth headsets on Linux",
      "summary": "Please beware that the following instructions are suitable for media consumption only! After this changes your headset can\u0026rsquo;t make any calls without a dedicated microphone until you undo them.\nHaving trouble with the audio quality of your Bluetooth headset on Linux? It sounds awful if you\u0026rsquo;re listing to music and videos? Well, congratulations, I had the same problem and found a solution. At least if you\u0026rsquo;re only into listening and won\u0026rsquo;t make any calls. This works on Ubuntu or Ubuntu-based distributions like Pop!_OS or any other distribution that relies on Blue Z and Pipewire/WirePlumber for Bluetooth audio.\n",
      "content_html": "\u003cp\u003e\u003cstrong\u003ePlease beware that the following instructions are suitable for media consumption only! After this changes your headset can\u0026rsquo;t make any calls without a dedicated microphone until you undo them.\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eHaving trouble with the audio quality of your Bluetooth headset on Linux? It sounds awful if you\u0026rsquo;re listing to music and videos? Well, congratulations, I had the same problem and found a solution. At least if you\u0026rsquo;re only into listening and won\u0026rsquo;t make any calls. This works on Ubuntu or Ubuntu-based distributions like Pop!_OS or any other distribution that relies on Blue Z and Pipewire/WirePlumber for Bluetooth audio.\u003c/p\u003e\n\u003ch2 id=\"whats-wrong\"\u003e\u003ca href=\"#whats-wrong\" class=\"heading-anchor\"\u003eWhat\u0026rsquo;s wrong?\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eBluetooth has different profiles for different things. If you want to make a call, your headset will switch to the Hands-free Profile (HFP). The available bandwidth will be shared for audio input and output and different audio codecs will be used. It\u0026rsquo;s good for calls, but really shitty if you want to listen to music. The headset needs to switch to A2DP (Advanced Audio Distribution Profile) for good sound quality. This should happen automatically and HFP should only be active, if you\u0026rsquo;re making a call. I had never trouble on macOS or Windows with this, but I\u0026rsquo;m trying Pop!_OS now. It worked for a few days, but today the headset would only connect with the HFP and streaming music or watching videos was as pleasant as dental treatment with a power drill.\u003c/p\u003e\n\u003cp\u003eMany searches later, I found out that WirePlumper (the session manager for the Pipewire multimedia framework) has some bugs that will trigger HFP on BT headsets even if there\u0026rsquo;s no call. That\u0026rsquo;s pretty annoying but at least somewhat easy to solve. There\u0026rsquo;s a \u003ca href=\"https://wiki.archlinux.org/title/Bluetooth_headset#Disable_PipeWire_HSP/HFP_profile\"\u003esolution in the Arch Linux Wiki\u003c/a\u003e, but it needs some modifications for Ubuntu-based distributions.\u003c/p\u003e\n\u003ch2 id=\"the-solution\"\u003e\u003ca href=\"#the-solution\" class=\"heading-anchor\"\u003eThe solution\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eFirst you need to create a directory path in your home directory:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emkdir -p ~/.config/wireplumber/bluetooth.lua.d/\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis will create an directory that allows you to override the default WirePlumper Bluetooth config without overwriting the original file.\u003c/p\u003e\n\u003cp\u003eNow copy the original config to the overwrite directory:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecp /usr/share/wireplumber/bluetooth.lua.d/50-bluez-config.lua ~/.config/wireplumber/bluetooth.lua.d/\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eYou can now edit the file in the override directory.\u003c/p\u003e\n\u003cp\u003eBeware that the original instructions from the Arch Linux Wiki contain a conf file, but at least with Ubuntu (and Pop!_OS) the config file is written in lua, so it\u0026rsquo;s a completely different syntax.\u003c/p\u003e\n\u003cp\u003eLook for \u003ccode\u003ebluez5.roles\u003c/code\u003e \u0026ndash; it should be commented out. I would recommend not replace the comment and just put the following line below. It\u0026rsquo;s easier to undo if something goes wrong or you need to enable HFS.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-lua\" data-lang=\"lua\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;bluez5.roles\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;[ a2dp_sink a2dp_source ]\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSave the file and restart the Bluetooth service:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo systemctl restart bluetooth\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eNow reconnect your device and A2DP should be working fine.\u003c/p\u003e\n",
      "date_published": "2024-10-07T20:00:00+02:00",
      "date_modified": "2026-07-12T02:03:38+02:00",
      "tags": ["linux"]
    },
    {
      "id": "https://webcodr.io/2024/03/awsome-cli-tools/",
      "url": "https://webcodr.io/2024/03/awsome-cli-tools/",
      "title": "Awsome CLI Tools",
      "summary": "There are some incredibly useful CLI tools out there. Here\u0026rsquo;s a list with some awesome tools I\u0026rsquo;m using for my daily work.\natuin# Atuin is a history replacement with a fuzzy finding search and sync/backup options (self-hosted if you need). It\u0026rsquo;s written in Rust (blazingly fast!) and stores the history entries in a SQLite db. You can even import your current history from your shell. Atuin supports bash, zsh, fish and NuShell.\n",
      "content_html": "\u003cp\u003eThere are some incredibly useful CLI tools out there. Here\u0026rsquo;s a list with some awesome tools I\u0026rsquo;m using for my daily work.\u003c/p\u003e\n\u003ch2 id=\"atuin\"\u003e\u003ca href=\"#atuin\" class=\"heading-anchor\"\u003eatuin\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://atuin.sh/\"\u003eAtuin\u003c/a\u003e is a history replacement with a fuzzy finding search and sync/backup options (self-hosted if you need). It\u0026rsquo;s written in Rust (blazingly fast!) and stores the history entries in a SQLite db. You can even import your current history from your shell. Atuin supports bash, zsh, fish and NuShell.\u003c/p\u003e\n\u003ch2 id=\"bat\"\u003e\u003ca href=\"#bat\" class=\"heading-anchor\"\u003ebat\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eNeed \u003ccode\u003ecat\u003c/code\u003e a lot? \u003ca href=\"https://github.com/sharkdp/bat\"\u003eBat\u003c/a\u003e is a cat clone on steroids with syntax highlighting, themes, git integration and much more.\u003c/p\u003e\n\u003ch2 id=\"eza\"\u003e\u003ca href=\"#eza\" class=\"heading-anchor\"\u003eeza\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eEveryone needs \u003ccode\u003els\u003c/code\u003e ? Nope, \u003ca href=\"https://eza.rocks/\"\u003eeza\u003c/a\u003e is much better. Colored output, icons via nerd fonts, git status tracking per file, tons of display options \u0026hellip;\u003c/p\u003e\n\u003ch2 id=\"tldr\"\u003e\u003ca href=\"#tldr\" class=\"heading-anchor\"\u003etldr\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eReading an man page can be frustrating, why can\u0026rsquo;t I just have the TL;DR version? Well, \u003ca href=\"https://tldr.sh/\"\u003etldr\u003c/a\u003e does exactly that. Just use it like \u003ccode\u003eman\u003c/code\u003e and enjoy the TL;DR version of a man page.\u003c/p\u003e\n\u003ch2 id=\"zoxide\"\u003e\u003ca href=\"#zoxide\" class=\"heading-anchor\"\u003ezoxide\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eAs his siblings \u003ccode\u003ecd\u003c/code\u003e is a little dated and clunky. With \u003ca href=\"https://github.com/ajeetdsouza/zoxide\"\u003ezoxide\u003c/a\u003e you can easily jump to directories without typing the full path. It stores a history of your visited paths and you can jump via keywords to your directories.\u003c/p\u003e\n\u003ch2 id=\"chezmoi\"\u003e\u003ca href=\"#chezmoi\" class=\"heading-anchor\"\u003echezmoi\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eYou\u0026rsquo;re using multiple computers or just want a simple and reliable way to store your dot files? \u003ca href=\"https://www.chezmoi.io/\"\u003eChezmoi\u003c/a\u003e is your friend and stores your dot files in a git repo with syncing capabilities to other devices. It\u0026rsquo;s even possible to encrypt your files. If you have secrets in your dot files, chezmoi comes with integrations for many password managers to safely store your passwords, tokens etc.\u003c/p\u003e\n\u003ch2 id=\"starship\"\u003e\u003ca href=\"#starship\" class=\"heading-anchor\"\u003estarship\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eYour shell looks boring? Just theme it with \u003ca href=\"https://starship.rs/\"\u003estarship\u003c/a\u003e! It\u0026rsquo;s pretty easy to build your own and if you don\u0026rsquo;t want to, there many themes available. Starship also has integrations for many dev tools to show the current git status or currently active versions of your runtime environments like NodeJS, Rust, Go, Java etc.\u003c/p\u003e\n\u003ch2 id=\"fzf\"\u003e\u003ca href=\"#fzf\" class=\"heading-anchor\"\u003efzf\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eFinding files with the usual suspects works fine, but \u003ca href=\"https://github.com/junegunn/fzf\"\u003efzf\u003c/a\u003e can do this faster and much more intuitive. It\u0026rsquo;s a fuzzy finding search within your current directory, processes, git commits, history (if you don\u0026rsquo;t like Atuin) and much more.\u003c/p\u003e\n\u003ch2 id=\"ripgrep\"\u003e\u003ca href=\"#ripgrep\" class=\"heading-anchor\"\u003eripgrep\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eRipgrep is a really fast regex-based search tool and can do much more than \u003ccode\u003egrep\u003c/code\u003e alone.\u003c/p\u003e\n\u003ch2 id=\"btop\"\u003e\u003ca href=\"#btop\" class=\"heading-anchor\"\u003ebtop\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eAnother \u003ccode\u003etop\u003c/code\u003e variant? Yup, but \u003ca href=\"https://github.com/aristocratos/btop\"\u003ebtop\u003c/a\u003e is way more like it\u0026rsquo;s modern GUI-based colleagues on macOS or Windows with CPU and GPU usage, process trees, I/O and disk activities, battery status \u0026hellip;\u003c/p\u003e\n",
      "date_published": "2024-03-06T20:21:06Z",
      "date_modified": "2026-07-14T20:52:39+02:00",
      "tags": ["developer-tools","rust"]
    },
    {
      "id": "https://webcodr.io/2024/01/micro-dsls-for-builders-with-kotlin/",
      "url": "https://webcodr.io/2024/01/micro-dsls-for-builders-with-kotlin/",
      "title": "Micro DSLs for builders with Kotlin",
      "summary": "The builder pattern is a great tool and it\u0026rsquo;s heavily used in many Java projects and dependencies. But in a Kotlin code base it\u0026rsquo;s looks a little odd and out-of-date. In this short post I will show you how to write a micro DSL on top of builder with just a few lines of code.\nI\u0026rsquo;m using Spring\u0026rsquo;s ResponseCookie class as base for the DSL as it has a builder already on-board.\n",
      "content_html": "\u003cp\u003eThe builder pattern is a great tool and it\u0026rsquo;s heavily used in many Java projects and dependencies. But in a Kotlin code base it\u0026rsquo;s looks a little odd and out-of-date. In this short post I will show you how to write a micro DSL on top of builder with just a few lines of code.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m using Spring\u0026rsquo;s \u003ca href=\"https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/ResponseCookie.html\"\u003eResponseCookie\u003c/a\u003e class as base for the DSL as it has a builder already on-board.\u003c/p\u003e\n\u003cp\u003eA little example:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-kotlin\" data-lang=\"kotlin\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eval\u003c/span\u003e \u003cspan class=\"py\"\u003ecookie\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eResponseCookie\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efrom\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;cookie name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;cookie value\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ehttpOnly\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ebuild\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eHow would this look like with a micro DSL?\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-kotlin\" data-lang=\"kotlin\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eval\u003c/span\u003e \u003cspan class=\"py\"\u003ecookie\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecreateCookie\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;cookie name\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;cookie value\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ehttpOnly\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epath\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eInstead of calling the static method \u003ccode\u003eResponseCookie.from()\u003c/code\u003e which returns a \u003ccode\u003eResponseCookieBuilder\u003c/code\u003e object, you just give the function three parameters: two strings for name and value, and trailing lambda with the builder context. There is also no need to call \u003ccode\u003eResponseCookieBuilder.build()\u003c/code\u003e anymore. It\u0026rsquo;s shorter and better to read. Since this is only a small example the advantages are not that big. Micro DSLs really shine with large and often used builders. They can also help to automate things, see below.\u003c/p\u003e\n\u003ch2 id=\"how\"\u003e\u003ca href=\"#how\" class=\"heading-anchor\"\u003eHow?\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-kotlin\" data-lang=\"kotlin\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efun\u003c/span\u003e \u003cspan class=\"nf\"\u003ecreateCookie\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003elambda\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nc\"\u003eResponseCookieBuilder\u003c/span\u003e\u003cspan class=\"p\"\u003e.()\u003c/span\u003e \u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003eUnit\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"nc\"\u003eResponseCookie\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efrom\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"k\"\u003evalue\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"n\"\u003eapply\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elambda\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"n\"\u003ebuild\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eEt voila, a new micro DSL is born. Deriving the trailing lambda function from \u003ccode\u003eResponseCookieBuilder\u003c/code\u003e does the trick. The lambda function takes an instance of \u003ccode\u003eResponseCookieBuilder\u003c/code\u003e as context of \u003ccode\u003ethis\u003c/code\u003e, so it\u0026rsquo;s possible to access the methods of the given \u003ccode\u003eResponseCookieBuilder\u003c/code\u003e inside the lambda function. All we have to do is to create an instance of \u003ccode\u003eResponseCookieBuilder\u003c/code\u003e with \u003ccode\u003eResponseCookie.from()\u003c/code\u003e and call Kotlin\u0026rsquo;s apply method on the builder object with the lambda function. It will automatically inject the current instance of \u003ccode\u003eResponseCookieBuilder\u003c/code\u003e into the lambda function and apply the instructions inside the lambda function on the instance. To create a \u003ccode\u003eResponseCookie\u003c/code\u003e object from the builder, just call the build method and return the result.\u003c/p\u003e\n\u003cp\u003eYou can use this little trick with all builders. Need more automation? No problem! In my current project we\u0026rsquo;re using such micro DSLs to create product configurations. The factory method contains sanity checks after the lambda function was applied to the builder object. It will also fetch a YAML file via the product ID given to the builder. This data is parsed into an object and will be put into a property of the builder object. After the configuration object is created, the factory method will add to a map and return the instance to store in a variable. It\u0026rsquo;s now possible to directly access the configuration via its variable name or to fetch it from the map. The variable is very useful for tests, but if we have to fetch the configuration dynamically by ID from a string, the map is the way to go.\u003c/p\u003e\n\u003cp\u003eOf course there are other ways of achieving such automation, but the micro DSL approach is simple, improves readability and can also reduce redundant code. You can even easily nest builders to create a more powerful DSL. Spring\u0026rsquo;s Kotlin extensions also rely on micro DSLs and extension functions. Take Spring Security for example. Their fluent interface for the security configuration is awful to read and difficult to understand, but Spring also provides a Kotlin extension with a micro DSL for that. So much more intuitive and better to read. There are many more extensions like for bean creation, the MVC mock in tests etc.\u003c/p\u003e\n",
      "date_published": "2024-01-19T14:33:07+02:00",
      "date_modified": "2026-07-12T02:03:38+02:00",
      "tags": ["programming","kotlin"]
    },
    {
      "id": "https://webcodr.io/2023/05/us-international-keyboard-layout-without-dead-keys/",
      "url": "https://webcodr.io/2023/05/us-international-keyboard-layout-without-dead-keys/",
      "title": "US International Keyboard Layout Without Dead Keys",
      "summary": "Depending on your country\u0026rsquo;s keyboard layout writing code can be quite annoying. The German ISO layout is an exceptional pain in the ass, as almost all relevant symbols require a modifier key, sometimes even two (I\u0026rsquo;m looking at you, Apple). German has some special characters (ä. ö, ü, ß) and it makes sense to have them readily available without modifier keys, but it just sucks for programming.\nI decided to switch to the US ANSI layout and bought two new keyboards: a Keychron K3 Pro for my MacBook Pro (light and portable, perfect if I have to go to my company\u0026rsquo;s office) and Keychron Q1 version 2 for my Windows PC. By the way, the Q1 is heavily modded and will be tweaked further in the coming weeks. I will write an article about the mods after the keyboard is finished.\n",
      "content_html": "\u003cp\u003eDepending on your country\u0026rsquo;s keyboard layout writing code can be quite annoying. The German ISO layout is an exceptional pain in the ass, as almost all relevant symbols require a modifier key, sometimes even two (I\u0026rsquo;m looking at you, Apple). German has some special characters (ä. ö, ü, ß) and it makes sense to have them readily available without modifier keys, but it just sucks for programming.\u003c/p\u003e\n\u003cp\u003eI decided to switch to the US ANSI layout and bought two new keyboards: a Keychron K3 Pro for my MacBook Pro (light and portable, perfect if I have to go to my company\u0026rsquo;s office) and Keychron Q1 version 2 for my Windows PC. By the way, the Q1 is heavily modded and will be tweaked further in the coming weeks. I will write an article about the mods after the keyboard is finished.\u003c/p\u003e\n\u003cp\u003eBoth keyboards work great, but to type German special characters I have to use the US international layout on Windows and there\u0026rsquo;s a catch. Who would have thought, if Microsoft is involved? Certain keys like single quote/double quote or accents/tilde are so called dead keys.\u003c/p\u003e\n\u003cp\u003eIf you press them, nothing will happen at first. Only after pressing the next key, the symbol will appear. So, if you want to type a text wrapped in double quotes, you press the the key and the double quote will appear, as soon as you type the first character of the actual text. It\u0026rsquo;s annoying as fuck and drives me crazy.\u003c/p\u003e\n\u003ch2 id=\"solution\"\u003e\u003ca href=\"#solution\" class=\"heading-anchor\"\u003eSolution\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eUnfortunately Microsoft does not ship an US international layout without dead keys for Windows. Most Linux distributions do exactly that, but I guess that\u0026rsquo;s to easy for a big international corporation like Microsoft.\u003c/p\u003e\n\u003cp\u003eWhat to do? After a little bit of googling, I found a neat little tool, the Microsoft Keyboard Layout Creator. I\u0026rsquo;ve never heard of this program before but it\u0026rsquo;s legit and works fine.\u003c/p\u003e\n\u003cp\u003eIf you\u0026rsquo;re having trouble with the dead keys like me, I recommend the following steps:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eDownload the \u003ca href=\"https://www.microsoft.com/en-us/download/details.aspx?id=102134\"\u003eMicrosoft Keyboard Layout Creator\u003c/a\u003e from Microsoft\u0026rsquo;s website\u003c/li\u003e\n\u003cli\u003eOpen it and load the US international layout via \u003ccode\u003eFile\u003c/code\u003e -\u0026gt; \u003ccode\u003eLoad Existing Keyboard...\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eAll dead keys are shown with a light grey background. You can remove their dead key status via the context menu\u003c/li\u003e\n\u003cli\u003eDon\u0026rsquo;t forget to activate the shift layer via the checkboxes left of the keyboard layout and disable the dead keys as well\u003c/li\u003e\n\u003cli\u003eSave your config\u003c/li\u003e\n\u003cli\u003eBuild the layout via \u003ccode\u003eProject\u003c/code\u003e -\u0026gt; \u003ccode\u003eBuild DLL and Setup Package\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eAfter the build finished, a dialog will ask you to open the build directory. Open it and run \u003ccode\u003esetup.exe\u003c/code\u003e to install the new layout\u003c/li\u003e\n\u003cli\u003eRestart Windows. This should not be necessary, but unfortunately Windows is Windows \u0026hellip;\u003c/li\u003e\n\u003cli\u003eGo to \u003ccode\u003eSettings\u003c/code\u003e -\u0026gt; \u003ccode\u003eTime and Language\u003c/code\u003e -\u0026gt; \u003ccode\u003eLanguage and Region\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eSelect your preferred language and click the three dots and choose \u003ccode\u003eLanguage options\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eUse \u003ccode\u003eAdd a keyboard\u003c/code\u003e and select \u003ccode\u003eUnited States International - No Dead Keys\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eI recommend to remove all other keyboard layouts, so they can\u0026rsquo;t interfere\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eNow you should have a working US international layout without those annoying dead keys. Happy typing!\u003c/p\u003e\n",
      "date_published": "2023-05-27T21:36:06Z",
      "date_modified": "2026-07-12T02:03:38+02:00",
      "tags": ["hardware"]
    },
    {
      "id": "https://webcodr.io/2023/05/introducing-server-runner/",
      "url": "https://webcodr.io/2023/05/introducing-server-runner/",
      "title": "Introducing Server Runner",
      "summary": "In my recent adventures with Rust, I planned to write a REST API with the help of the excellent book \u0026ldquo;Zero To Production In Rest\u0026rdquo; from Luca Palmieri. That\u0026rsquo;s still happing, but as small side project, I wanted to write some kind of CLI tool.\nA few weeks ago I had wrote a bash script to run some web servers and check their status until they\u0026rsquo;re up and running. When all servers are ready, a command would be executed and all servers would be closed after this command is finished. Since I hate bash with passion, I asked an friend to help me: ChatGPT.\n",
      "content_html": "\u003cp\u003eIn my recent adventures with Rust, I planned to write a REST API with the help of the excellent book \u0026ldquo;Zero To Production In Rest\u0026rdquo; from Luca Palmieri. That\u0026rsquo;s still happing, but as small side project, I wanted to write some kind of CLI tool.\u003c/p\u003e\n\u003cp\u003eA few weeks ago I had wrote a bash script to run some web servers and check their status until they\u0026rsquo;re up and running. When all servers are ready, a command would be executed and all servers would be closed after this command is finished. Since I hate bash with passion, I asked an friend to help me: ChatGPT.\u003c/p\u003e\n\u003cp\u003eI would never trust an AI in this day and age to write a code base for me, but for small scripts? Why not. As long as the scope is small and I can understand the code, ChatGPT is a really good tool. That script does exactly what I want and it\u0026rsquo;s easy enough to understand, even for me as a bash hater.\u003c/p\u003e\n\u003cp\u003eBut I wanted to this properly and so I decided to rewrite this script as a small CLI tool program in Rust: Server Runner. Well, not very creative name, but it does what is says.\u003c/p\u003e\n\u003ch2 id=\"configuration\"\u003e\u003ca href=\"#configuration\" class=\"heading-anchor\"\u003eConfiguration\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eServer Runner is quite simple and just needs a small YAML file as configuration. Here\u0026rsquo;s a small example.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003eservers\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e- \u003cspan class=\"nt\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Hello World\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;http://localhost:3000\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003ecommand\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;node index.js\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003ecommand\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;node sleep.js\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eTo start server runner, just run:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eserver-runner -c servers.yaml\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eServer Runner will execute all server commands defined in the config section \u003ccode\u003eservers\u003c/code\u003e and waits until the URLs return HTTP 200. When all servers are up and running, the primary command will be started. After the command finished, all server processes will be killed off.\u003c/p\u003e\n\u003ch2 id=\"how-do-i-get-it\"\u003e\u003ca href=\"#how-do-i-get-it\" class=\"heading-anchor\"\u003eHow do I get it?\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eServer Runner is available as a \u003ca href=\"https://crates.io/crates/server-runner\"\u003eCargo Crate\u003c/a\u003e and will be published soon on NPM with executables for macOS (aarch64, x86_64), Linux (aarch64, x86_86) and Windows (x86_64).\u003c/p\u003e\n\u003ch3 id=\"installation-via-cargo\"\u003e\u003ca href=\"#installation-via-cargo\" class=\"heading-anchor\"\u003eInstallation via Cargo\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecargo add server-runner\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe source code is available on \u003ca href=\"https://github.com/webcodr/server-runner\"\u003eGitHub\u003c/a\u003e.\u003c/p\u003e\n",
      "date_published": "2023-05-26T23:53:33Z",
      "date_modified": "2026-07-12T02:03:38+02:00",
      "tags": ["developer-tools","rust"]
    },
    {
      "id": "https://webcodr.io/2023/05/terminal-evolved/",
      "url": "https://webcodr.io/2023/05/terminal-evolved/",
      "title": "Terminal evolved",
      "summary": "I﻿ always saw myself as a casual user of the terminal. I preferred zsh with the Prezto framework within in iTerm 2 with tabs and that\u0026rsquo;s about it. No more! A colleague of mine introduced me to kitty as terminal emulator, together with tmux and Neovim. That\u0026rsquo;s a lot to swallow. I was never a fan of the vi/vim user experience and more of a mouse guy. Well, what should I say? It\u0026rsquo;s awesome if you\u0026rsquo;re getting used to it. Let me explain \u0026hellip;\n",
      "content_html": "\u003cp\u003eI﻿ always saw myself as a casual user of the terminal. I preferred zsh with the Prezto framework within in iTerm 2 with tabs and that\u0026rsquo;s about it. No more! A colleague of mine introduced me to kitty as terminal emulator, together with tmux and Neovim. That\u0026rsquo;s a lot to swallow. I was never a fan of the vi/vim user experience and more of a mouse guy. Well, what should I say? It\u0026rsquo;s awesome if you\u0026rsquo;re getting used to it. Let me explain \u0026hellip;\u003c/p\u003e\n\u003ch2 id=\"jfyi\"\u003e\u003ca href=\"#jfyi\" class=\"heading-anchor\"\u003eJFYI\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003ekitty, tmux and nvim are available in Homebrew on macOS and should be also available in your favorite package manager on Linux.\u003c/p\u003e\n\u003cp\u003eBe aware, that most examples contain some macOS-specific settings marked with corresponding comments, as I am a Mac user.\u003c/p\u003e\n\u003ch2 id=\"kitty\"\u003e\u003ca href=\"#kitty\" class=\"heading-anchor\"\u003ekitty\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eiTerm 2 is a pretty good terminal emulator with many features and way better than Apple\u0026rsquo;s sorry excuse of a terminal. To be fair, the macOS terminal app has gotten better over the years, but it still lacks essntial features like true color support. As good as iTerm 2 is, there\u0026rsquo;s one catch: iTerm 2 is slow. GPU-accelerated alternatives like kitty render much faster. Don\u0026rsquo;t get me wrong, iTerm 2 is no slouch and works well, but if you\u0026rsquo;re on the way to a terminal power-user, you will notice it. Switching between tmux windows is much faster in kitty or other terminal emulators like Alacritty. The later is really nice app, but unfortunately has some trouble with macOS key bindings within tmux and I found no easy solution to that. Kitty works out of the box.\u003c/p\u003e\n\u003ch3 id=\"taming-the-kitten\"\u003e\u003ca href=\"#taming-the-kitten\" class=\"heading-anchor\"\u003eTaming the kitten\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eKitty\u0026rsquo;s configuration is very well documented, but can be overwhelming. There are hundreds of options to explore. One of the most important is the font. Grab yourself a \u003ca href=\"https://www.nerdfonts.com/\"\u003enerd-font\u003c/a\u003e, add it to your OS and specify the font family. I\u0026rsquo;m using \u0026ldquo;Hack Nerd Font Mono\u0026rdquo; for this example. Just open your kitty config in \u003ccode\u003e~/.config/kitty/kitty.conf\u003c/code\u003e and add the following:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-toml\" data-lang=\"toml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Replace with your preferred font\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003efont_family\u003c/span\u003e\t\t\t\u003cspan class=\"nx\"\u003eHack\u003c/span\u003e \u003cspan class=\"nx\"\u003eNerd\u003c/span\u003e \u003cspan class=\"nx\"\u003eFont\u003c/span\u003e \u003cspan class=\"nx\"\u003eMono\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ebold_font\u003c/span\u003e\t\t\t\u003cspan class=\"nx\"\u003eauto\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eitalic_font\u003c/span\u003e\t\t\t\u003cspan class=\"nx\"\u003eauto\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ebold_italic_font\u003c/span\u003e\t\u003cspan class=\"nx\"\u003eauto\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Replace with your preferred font size in points\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003efont_size\u003c/span\u003e\t\t\t\u003cspan class=\"mf\"\u003e17.0\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eSave and reload the config via menu bar. Enjoy!\u003c/p\u003e\n\u003cp\u003eNow that\u0026rsquo;s out of the way, how about some comfort features?\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-toml\" data-lang=\"toml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Set how many lines the buffer can scroll back\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003escrollback_lines\u003c/span\u003e \u003cspan class=\"mi\"\u003e10000\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Auto-detect URLs\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003edetect_url\u003c/span\u003e \u003cspan class=\"nx\"\u003eyes\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Open URLs with ctrl + click\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003emouse_map\u003c/span\u003e \u003cspan class=\"nx\"\u003ectrl\u003c/span\u003e\u003cspan class=\"err\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eleft\u003c/span\u003e \u003cspan class=\"nx\"\u003epress\u003c/span\u003e \u003cspan class=\"nx\"\u003eungrabbed\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"nx\"\u003egrabbed\u003c/span\u003e \u003cspan class=\"nx\"\u003emouse_click_url\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Copy the mouse selection directly to the clipboard\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ecopy_on_select\u003c/span\u003e \u003cspan class=\"nx\"\u003eyes\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Paste on right click\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003emouse_map\u003c/span\u003e \u003cspan class=\"nx\"\u003eright\u003c/span\u003e \u003cspan class=\"nx\"\u003epress\u003c/span\u003e \u003cspan class=\"nx\"\u003egrabbed\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"nx\"\u003eungrabbed\u003c/span\u003e \u003cspan class=\"nx\"\u003eno-op\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003emouse_map\u003c/span\u003e \u003cspan class=\"nx\"\u003eright\u003c/span\u003e \u003cspan class=\"nx\"\u003eclick\u003c/span\u003e \u003cspan class=\"nx\"\u003egrabbed\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"nx\"\u003eungrabbed\u003c/span\u003e \u003cspan class=\"nx\"\u003epaste_from_clipboard\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Enable macOS copy \u0026amp; paste via CMD + c/v\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003emap\u003c/span\u003e \u003cspan class=\"nx\"\u003ecmd\u003c/span\u003e\u003cspan class=\"err\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003ec\u003c/span\u003e \u003cspan class=\"nx\"\u003ecopy_to_clipboard\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003emap\u003c/span\u003e \u003cspan class=\"nx\"\u003ecmd\u003c/span\u003e\u003cspan class=\"err\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003ev\u003c/span\u003e \u003cspan class=\"nx\"\u003epaste_from_clipboard\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Jump to beginning and end of a word with alt and arrow keys (macOS)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003emap\u003c/span\u003e \u003cspan class=\"nx\"\u003ealt\u003c/span\u003e\u003cspan class=\"err\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eleft\u003c/span\u003e \u003cspan class=\"nx\"\u003esend_text\u003c/span\u003e \u003cspan class=\"nx\"\u003eall\u003c/span\u003e \u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"nx\"\u003ex1b\u003c/span\u003e\u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"nx\"\u003ex62\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003emap\u003c/span\u003e \u003cspan class=\"nx\"\u003ealt\u003c/span\u003e\u003cspan class=\"err\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eright\u003c/span\u003e \u003cspan class=\"nx\"\u003esend_text\u003c/span\u003e \u003cspan class=\"nx\"\u003eall\u003c/span\u003e \u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"nx\"\u003ex1b\u003c/span\u003e\u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"nx\"\u003ex66\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Jump to beginning and end of a line with cmd and arrow keys (macOS)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003emap\u003c/span\u003e \u003cspan class=\"nx\"\u003ecmd\u003c/span\u003e\u003cspan class=\"err\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eleft\u003c/span\u003e \u003cspan class=\"nx\"\u003esend_text\u003c/span\u003e \u003cspan class=\"nx\"\u003eall\u003c/span\u003e \u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"nx\"\u003ex01\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003emap\u003c/span\u003e \u003cspan class=\"nx\"\u003ecmd\u003c/span\u003e\u003cspan class=\"err\"\u003e+\u003c/span\u003e\u003cspan class=\"nx\"\u003eright\u003c/span\u003e \u003cspan class=\"nx\"\u003esend_text\u003c/span\u003e \u003cspan class=\"nx\"\u003eall\u003c/span\u003e \u003cspan class=\"err\"\u003e\\\u003c/span\u003e\u003cspan class=\"nx\"\u003ex05\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Nicer titlebar on macOS\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003emacos_titlebar_color\u003c/span\u003e \u003cspan class=\"nx\"\u003ebackground\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Make vim the default editor for the kitty config\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eeditor\u003c/span\u003e \u003cspan class=\"nx\"\u003evim\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWant some color? No problem, there are hundreds of themes available just a Google search away. I prefer \u003ca href=\"https://github.com/catppuccin/catppuccin\"\u003eCatppuccin Macchiato\u003c/a\u003e, but choose what ever you want. kitty config files support includes, so it\u0026rsquo;s easy to add a theme:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-toml\" data-lang=\"toml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003einclude\u003c/span\u003e \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"err\"\u003e/\u003c/span\u003e\u003cspan class=\"nx\"\u003etheme\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003econf\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAdd put the file \u003ccode\u003etheme.conf\u003c/code\u003e in the same directory as the kitty config and paste your theme of choice into the file.\u003c/p\u003e\n\u003ch2 id=\"tmux\"\u003e\u003ca href=\"#tmux\" class=\"heading-anchor\"\u003etmux\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eSo, what the hell is tmux? If you need a terminal, tmux will be one of your best friends. Did you ever run something complex on the shell and accidently closed the terminal window or something similar happened during a SSH session? It sucks.\u003c/p\u003e\n\u003cp\u003etmux sessions to the rescue! A session will be open until you close it, so even if your internet connection breaks down during a SSH session, nothing will vanish. Just connect to the server again and re-join the tmux session. Everything will be as you left it.\u003c/p\u003e\n\u003cp\u003eJust type \u003ccode\u003etmux new\u003c/code\u003e or if you want to give the session a name \u003ccode\u003etmux new -s my_new_session\u003c/code\u003e . Of course tmux can handle multiple sessions. To list all open sessions use \u003ccode\u003etmux ls\u003c/code\u003e and to join a session type \u003ccode\u003etmux a -t session_name\u003c/code\u003e .\u003c/p\u003e\n\u003cp\u003eAfter opening a new session, tmux will display window 0. Need more windows? No problem. Need a window inside an window? No problem, they are called panes. Windows can be split in horizontal or vertical panes, as many and wild as you like.\u003c/p\u003e\n\u003cp\u003eInside a tmux session you can trigger commands via a so-called prefix key following one or more keys to tell tmux what you want to do. The default prefix key is \u003ccode\u003ectrl + b\u003c/code\u003e . To split your current window into two horizontal panes press \u003ccode\u003ectrl + b\u003c/code\u003e followed by \u003ccode\u003e%\u003c/code\u003e , for a vertical split use \u003ccode\u003ectrl + b\u003c/code\u003e and \u003ccode\u003e\u0026quot;\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eTo close a pane, just exit the shell of the pane with \u003ccode\u003eexit\u003c/code\u003e. You can switch panes with \u003ccode\u003ectrl + b\u003c/code\u003e followed by an arrow key in the corresponding direction.\u003c/p\u003e\n\u003cp\u003eOur new best friend \u003ccode\u003ectrl + b\u003c/code\u003e is not the most intuitive key combination. I recommend using \u003ccode\u003ectrl + a\u003c/code\u003e and map the caps lock key to ctrl (pro-tip: macOS can do this for you without tools or customizable keyboard firmware). It\u0026rsquo;s way faster and easier to press. Of course, you can map what ever key combination you, just beware of conflicts with other combinations like \u003ccode\u003ecmd + space\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eTo change the command key, go to your tmux config in \u003ccode\u003e~/.tmux.conf\u003c/code\u003e and add the following lines:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-toml\" data-lang=\"toml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eunbind\u003c/span\u003e \u003cspan class=\"nx\"\u003eC-b\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eset\u003c/span\u003e \u003cspan class=\"nx\"\u003e-g\u003c/span\u003e \u003cspan class=\"nx\"\u003eprefix\u003c/span\u003e \u003cspan class=\"nx\"\u003eC-a\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ebind-key\u003c/span\u003e \u003cspan class=\"nx\"\u003eC-a\u003c/span\u003e \u003cspan class=\"nx\"\u003esend-prefix\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis unbinds \u003ccode\u003ectrl + b\u003c/code\u003e and sets the prefix key to \u003ccode\u003ectrl + a\u003c/code\u003e. To reload the tmux config inside a session use \u003ccode\u003etmux source-file ~/.tmux.conf\u003c/code\u003e.\u003c/p\u003e\n\u003ch3 id=\"more-more\"\u003e\u003ca href=\"#more-more\" class=\"heading-anchor\"\u003eMore? More!\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eThere is much more you can do. Here are some recommendations.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-toml\" data-lang=\"toml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Enable mouse support\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eset\u003c/span\u003e \u003cspan class=\"nx\"\u003e-g\u003c/span\u003e \u003cspan class=\"nx\"\u003emouse\u003c/span\u003e \u003cspan class=\"nx\"\u003eon\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Set history limit to 100,000 lines\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eset-option\u003c/span\u003e \u003cspan class=\"nx\"\u003e-g\u003c/span\u003e \u003cspan class=\"nx\"\u003ehistory-limit\u003c/span\u003e \u003cspan class=\"mi\"\u003e100000\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Enable true color support\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eset-option\u003c/span\u003e \u003cspan class=\"nx\"\u003e-sa\u003c/span\u003e \u003cspan class=\"nx\"\u003eterminal-overrides\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;,xterm*:Tc\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Start windows and panes at 1, not 0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eset\u003c/span\u003e \u003cspan class=\"nx\"\u003e-g\u003c/span\u003e \u003cspan class=\"nx\"\u003ebase-index\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eset\u003c/span\u003e \u003cspan class=\"nx\"\u003e-g\u003c/span\u003e \u003cspan class=\"nx\"\u003epane-base-index\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eset-window-option\u003c/span\u003e \u003cspan class=\"nx\"\u003e-g\u003c/span\u003e \u003cspan class=\"nx\"\u003epane-base-index\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eset-option\u003c/span\u003e \u003cspan class=\"nx\"\u003e-g\u003c/span\u003e \u003cspan class=\"nx\"\u003erenumber-windows\u003c/span\u003e \u003cspan class=\"nx\"\u003eon\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Open new panes in the same directory as their parent pane\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ebind\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;\u0026#34;\u0026#39;\u003c/span\u003e \u003cspan class=\"nx\"\u003esplit-window\u003c/span\u003e \u003cspan class=\"nx\"\u003e-v\u003c/span\u003e \u003cspan class=\"nx\"\u003e-c\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;#{pane_current_path}\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ebind\u003c/span\u003e \u003cspan class=\"err\"\u003e%\u003c/span\u003e \u003cspan class=\"nx\"\u003esplit-window\u003c/span\u003e \u003cspan class=\"nx\"\u003e-h\u003c/span\u003e \u003cspan class=\"nx\"\u003e-c\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;#{pane_current_path}\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Vim style pane selection\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ebind\u003c/span\u003e \u003cspan class=\"nx\"\u003eh\u003c/span\u003e \u003cspan class=\"nx\"\u003eselect-pane\u003c/span\u003e \u003cspan class=\"nx\"\u003e-L\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ebind\u003c/span\u003e \u003cspan class=\"nx\"\u003ej\u003c/span\u003e \u003cspan class=\"nx\"\u003eselect-pane\u003c/span\u003e \u003cspan class=\"nx\"\u003e-D\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ebind\u003c/span\u003e \u003cspan class=\"nx\"\u003ek\u003c/span\u003e \u003cspan class=\"nx\"\u003eselect-pane\u003c/span\u003e \u003cspan class=\"nx\"\u003e-U\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ebind\u003c/span\u003e \u003cspan class=\"nx\"\u003el\u003c/span\u003e \u003cspan class=\"nx\"\u003eselect-pane\u003c/span\u003e \u003cspan class=\"nx\"\u003e-R\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Shift arrow to switch windows\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ebind\u003c/span\u003e \u003cspan class=\"nx\"\u003e-n\u003c/span\u003e \u003cspan class=\"nx\"\u003eS-Left\u003c/span\u003e  \u003cspan class=\"nx\"\u003eprevious-window\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ebind\u003c/span\u003e \u003cspan class=\"nx\"\u003e-n\u003c/span\u003e \u003cspan class=\"nx\"\u003eS-Right\u003c/span\u003e \u003cspan class=\"nx\"\u003enext-window\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Don\u0026#39;t scroll down on copy via mouse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eunbind\u003c/span\u003e \u003cspan class=\"nx\"\u003e-T\u003c/span\u003e \u003cspan class=\"nx\"\u003ecopy-mode-vi\u003c/span\u003e \u003cspan class=\"nx\"\u003eMouseDragEnd1Pane\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWith mouse support you can resize panes with drag \u0026amp; drop and even get a context menu with a right click. By default tmux assigns numbers to windows for fast switching via \u003ccode\u003ectrl + b\u003c/code\u003e and \u003ccode\u003enumber key\u003c/code\u003e. Unfortunately the developers decided to begin with 0. This is technically correct, but on the keyboard it\u0026rsquo;s quite unintuitive, so we can tell tmux to begin with 1. The rest is pretty much self-explanatory.\u003c/p\u003e\n\u003ch2 id=\"neovim\"\u003e\u003ca href=\"#neovim\" class=\"heading-anchor\"\u003eNeovim\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eWhy neo? Good old vim is extensible via vimscript. It works, but it\u0026rsquo;s like bash: ugly as fuck. Neovim is a fork of vim and replaces vimscript with support for lua-based extensions. So it\u0026rsquo;s still blazingly fast(tm) and much nicer to write extensions.\u003c/p\u003e\n\u003cp\u003eYou could setup Neovim and the necessary extensions yourself, but won\u0026rsquo;t recommend it in the beginning. Pre-build configs like \u003ca href=\"https://astronvim.com/\"\u003eAstroNvim\u003c/a\u003e or \u003ca href=\"https://github.com/NvChad/NvChad\"\u003eNvChad\u003c/a\u003e will massively speed up the process and have great defaults. It can be very overwhelming to get used to vim/nvim, so I would recommend to wait with your own config until you get more familiar with a keyboard-based editor.\u003c/p\u003e\n\u003ch3 id=\"help-i-cant-quit-vim\"\u003e\u003ca href=\"#help-i-cant-quit-vim\" class=\"heading-anchor\"\u003eHELP! I can\u0026rsquo;t quit vim!\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eDon\u0026rsquo;t worry, you are not the first and will certainly not be the last. vim is a so-called modal-based editor. It has different modes like command, insert, visual etc. As you may have noticed, typing will not add text to the buffer. You need to press certain keys like \u003ccode\u003ei\u003c/code\u003e  to enter insert mode to edit text. There are other keys to go in insert mode and everyone of them has slightly different, but pretty useful function, like \u003ccode\u003eo\u003c/code\u003e which creates a new line below the cursor and starts insert mode.\u003c/p\u003e\n\u003cp\u003eTo exit vim you to leave the insert mode by pressing \u003ccode\u003eesc\u003c/code\u003e. Now you are in command mode and can quit by typing \u003ccode\u003e:\u003c/code\u003e to enter the command line mode and hit \u003ccode\u003eq\u003c/code\u003e for quit, followed by enter to execute the command.\u003c/p\u003e\n\u003cp\u003eIf you want to save a file, enter command line mode and use \u003ccode\u003ew\u003c/code\u003e for write. It\u0026rsquo;s possible to chain certain commands. \u003ccode\u003ewq\u003c/code\u003e will save the file and quit vim.\u003c/p\u003e\n\u003cp\u003eCongratulations, you now know how to exit vim!\u003c/p\u003e\n\u003ch3 id=\"navigation\"\u003e\u003ca href=\"#navigation\" class=\"heading-anchor\"\u003eNavigation\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eTo move the cursor just use the arrow keys in most other editors. But there is more efficient key mapping in command mode: \u003ccode\u003eh\u003c/code\u003e (left), \u003ccode\u003ej\u003c/code\u003e (down), \u003ccode\u003ek\u003c/code\u003e (up) and \u003ccode\u003el\u003c/code\u003e (right). No need to move your to the arrow keys anymore. To be honest, I\u0026rsquo;m still not comfortable with this way of navigation, but it\u0026rsquo;s objectively more efficient than moving the right hand to the arrow keys.\u003c/p\u003e\n\u003cp\u003eOf course vim has way more navigation possibilities. For example, the cursor can jump forward by one word with \u003ccode\u003ew\u003c/code\u003e and backwards with \u003ccode\u003eb\u003c/code\u003e.  Press \u003ccode\u003e$\u003c/code\u003e to jump to the end of the current line or \u003ccode\u003e0\u003c/code\u003e to beginning. \u003ccode\u003eG\u003c/code\u003e navigates you to last line and \u003ccode\u003egg\u003c/code\u003e jumps to the first line. And there is so much more to explore. I recommend a decent \u003ca href=\"https://vim.rtorr.com/\"\u003evim cheat sheet\u003c/a\u003e to learn. But do yourself a favor and try not learn all keys at once. You will only become frustrated and give up more easily, it\u0026rsquo;s just too much to learn everything in the beginning.\u003c/p\u003e\n\u003cp\u003eNetflix developer and Twitch Streamer \u003ca href=\"https://twitter.com/ThePrimeagen\"\u003eThePrimagen\u003c/a\u003e designed a Neovim plug-in to learn the navigation commands as game. it\u0026rsquo;s pretty good and fun: \u003ca href=\"https://github.com/ThePrimeagen/vim-be-good\"\u003ehttps://github.com/ThePrimeagen/vim-be-good\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eAstroNvim has many plug-in out of the box. Syntax highlighting, linting, auto-formatting etc. are all there, but you need to install the corresponding servers, parsers etc.\u003c/p\u003e\n\u003cp\u003eTo do this, enter command line mode and use \u003ccode\u003eLspInstall\u003c/code\u003e followed by the language name for installing a language server protocol. A language server for \u003ca href=\"https://github.com/tree-sitter/tree-sitter\"\u003eTree-Sitter\u003c/a\u003e can be installed with \u003ccode\u003eTSInstall\u003c/code\u003e followed by the language name. If there nothing available, both commands will recommend plug-ins according to your input, if available.\u003c/p\u003e\n\u003cp\u003eBe aware, that LSPs and Tree-Sitter will not bring the necessary tools with them. If you install tooling for Rust, rust-analyzer has to be installed on the system. Same goes for ESlint, Prettier, Kotlin, Java etc.\u003c/p\u003e\n\u003ch2 id=\"the-end\"\u003e\u003ca href=\"#the-end\" class=\"heading-anchor\"\u003eThe End\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003ch2 id=\"-for-now\"\u003e\u003ca href=\"#-for-now\" class=\"heading-anchor\"\u003e\u0026hellip; for now\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThat was a lot unpack, but there is so much more to show to you. I will be back with more productivity tools and tips in the near future. Stay tuned!\u003c/p\u003e\n",
      "date_published": "2023-05-15T19:22:14Z",
      "date_modified": "2026-07-14T20:52:39+02:00",
      "tags": ["developer-tools","linux"]
    },
    {
      "id": "https://webcodr.io/2021/01/real-world-performance-of-the-apple-m1-in-software-development/",
      "url": "https://webcodr.io/2021/01/real-world-performance-of-the-apple-m1-in-software-development/",
      "title": "Real-world performance of the Apple M1 in software development",
      "summary": "There are enough videos on YouTube out there to show how awesome the new Macs are, but I want to share my perspective as a software developer.\nAbout six weeks ago, I was too hyped not to buy an ARM-based Mac, so I ordered a basic MacBook Air with 8 GB RAM (16 GB was hard to get at this time). As strange as it sounds, I don’t regret buying only 8 GB of RAM. On an Intel-based Mac this would be an absolute pain in the ass, even my old 15” MacBook Pro Late 2017 with 16 GB struggles sometimes with RAM usage.\n",
      "content_html": "\u003cp\u003eThere are enough videos on YouTube out there to show how awesome the new Macs are, but I want to share my perspective as a software developer.\u003c/p\u003e\n\u003cp\u003eAbout six weeks ago, I was too hyped not to buy an ARM-based Mac, so I ordered a basic MacBook Air with 8 GB RAM (16 GB was hard to get at this time). As strange as it sounds, I don’t regret buying only 8 GB of RAM. On an Intel-based Mac this would be an absolute pain in the ass, even my old 15” MacBook Pro Late 2017 with 16 GB struggles sometimes with RAM usage.\u003c/p\u003e\n\u003cp\u003eIt’s really amazing how good this small, passively cooled MacBook Air is keeping up. In many scenarios it even surpasses my MacBook Pro with ease. I never had an Intel-based MacBook Air, but the last time is used a dual-core CPU for development, was not pretty und that was a pretty decent i5 and not a ultra-low voltage i3.\u003c/p\u003e\n\u003ch2 id=\"speed-speed-speed\"\u003e\u003ca href=\"#speed-speed-speed\" class=\"heading-anchor\"\u003eSpeed, Speed, Speed\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eUnfortunately I couldn’t really develop software on the MacBook Air for a while, since Java and IntelliJ were not available for aarch64-based Macs. Of course I tried Rosetta 2, but at least for these two, it’s quite slow. NodeJS on the other hand is incredibly fast.\u003c/p\u003e\n\u003cp\u003eAll this changed after my christmas vacation. IntelliJ was updated and thankfully Azul released a JDK 8 for ARM-Macs. A native version of Visual Studio Code is also available and quite fast.\u003c/p\u003e\n\u003cp\u003eSo, no more introductions, here are some real-world scenarios and numbers.\u003c/p\u003e\n\u003cp\u003eI currently work on a Java project with a steadily growing codebase of Kotlin. It’s a little special, since another part of the application is written in Ruby. We’re using JRuby, so it’s bundled all together with a Vue-based frontend in a WAR-File with Maven.\u003c/p\u003e\n\u003ch3 id=\"maven-build-times\"\u003e\u003ca href=\"#maven-build-times\" class=\"heading-anchor\"\u003eMaven Build Times\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eAll build times are from fully cached dependencies, so there are no interferences from my internet connection.\u003c/p\u003e\n\u003cp\u003eUsed devices:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e15” MacBook Pro Late 2017: Intel Core-i7 7700HQ, 16 GB RAM\u003c/li\u003e\n\u003cli\u003e13” MacBook Air Late 2020: Apple M1, 8 GB RAM\u003c/li\u003e\n\u003cli\u003ePC: AMD Ryzen 9 3950X, 32 GB RAM\u003c/li\u003e\n\u003c/ul\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003eDevice\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eBuild time with tests\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eBuild time without tests\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eMacBook Pro\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e223 s\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e183 s\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eMacBook Air\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e85 s\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e63 s\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003ePC\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e84 s\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e66 s\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eWell, a small, passively cooled MacBook Air is as fast as a full-blown and custom water-cooled 16-core monster of a PC. The MacBook Pro gets utterly destroyed. To get this straight: the cheapest notebook Apple makes, destroys a MacBook Pro that costs more than twice as much.\u003c/p\u003e\n\u003ch3 id=\"ruby-unit-test-times\"\u003e\u003ca href=\"#ruby-unit-test-times\" class=\"heading-anchor\"\u003eRuby Unit Test Times\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eThe test suite contains 1,087 examples. Please keep in mind, that I had to use Rosetta 2 in order to get everything running on the MacBook Air, since not all used Ruby Gems are compatible with ARM at this time. All tests were run with Ruby 2.7.1.\u003c/p\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003eDevice\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eTest duration\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eMacBook Pro native\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e1.9 s\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eMacBook Air with Rosetta 2\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e1.1 s\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003ePC native\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e1.3 s\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eYeah, it’s quite fast, compared to a Suite of Java-based unit tests, but even here the MacBook Pro has no chance at all.\u003c/p\u003e\n\u003ch3 id=\"frontend-build-times\"\u003e\u003ca href=\"#frontend-build-times\" class=\"heading-anchor\"\u003eFrontend Build Times\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eThe frontend is a Vue-based single-page application. As with Ruby, I had to use NodeJS with Rosetta 2, since not all used modules are compatible with ARM.\u003c/p\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003eDevice\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eTest duration\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eMacBook Pro native\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e27.8 s\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eMacBook Air with Rosetta 2\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e20.7 s\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003ePC native\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e20.6 s\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eWell, it’s more than obvious now, that the MacBook Pro has no chance at all against my MacBook Air. It’s not just the performance. After a few seconds of load, the MacBook Pro sounds like my F/A-18C in DCS immediately before a carrier launch, while the MacBook Air has no fan and therefore makes no noise at all.\u003c/p\u003e\n\u003cp\u003eAnd the battery life. Oh my god. Ten straight hours of development with IntelliJ and Visual Studio Code is entirely possible now, all while staying cool and quiet.\u003c/p\u003e\n\u003cp\u003eEven the dreaded battery murderer Google Meet is no problem anymore. My MacBook Pro on battery would last perhaps 2.5h max. The MacBook Air is capable of 8, perhaps even 9 hours of Meet. It’s as insane as an 8h long Meet itself.\u003c/p\u003e\n\u003cp\u003eAh, yes, there is another thing: Meet does not cripple the performance anymore. The MacBook Air is totally usable with a Meet going on, while my MacBook Pro becomes sluggish as hell and is barely usable (even without Chrome and frakking Google Keystone).\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003e\u003ca href=\"#conclusion\" class=\"heading-anchor\"\u003eConclusion\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI will make it short: if your tools and languages are already supported or at least quite usable with Rosetta, go for it.  I would recommend 16 GB or more (depending on future models), if you want to buy one. I’m surprised that a 8 GB MacBook Air is that capable and to be honest, I don’t feel like there a going to be a problem for a while, but no one regrets more RAM …\u003c/p\u003e\n\u003ch3 id=\"air-vs-pro\"\u003e\u003ca href=\"#air-vs-pro\" class=\"heading-anchor\"\u003eAir vs Pro\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eThe 13” MacBook Pro is little faster over longer periods of load due to active cooling, it has more GPU-cores, the love or hated Touch Bar and a bigger battery. If you need this, go for it, but if you can wait, I’d recommend to wait for the new 14” and 16” Pro models.\u003c/p\u003e\n\u003cp\u003eThey will be real power houses with 8 instead of 4 Firestorm cores, vastly more RAM and even bigger batteries. And hey, perhaps they come with MagSafe and some other ports we MacBook users didn’t see for a while.\u003c/p\u003e\n",
      "date_published": "2021-01-21T20:39:53Z",
      "date_modified": "2026-07-14T20:52:39+02:00",
      "tags": ["hardware","performance"]
    },
    {
      "id": "https://webcodr.io/2020/11/ryzen-vs-apple-silicon-and-why-zen-3-is-not-so-bad-as-you-may-think/",
      "url": "https://webcodr.io/2020/11/ryzen-vs-apple-silicon-and-why-zen-3-is-not-so-bad-as-you-may-think/",
      "title": "Ryzen vs Apple Silicon and why Zen 3 is not so bad as you may think",
      "summary": "Apple‘s M1 is a very impressive piece of hardware. If you look at the benchmarks, like SPECperf or Cinebench, it‘s an absolute beast with a ridiculously low power consumption compared with a Ryzen 9 5950X.\nZen 3 is that inefficient?# 10 W vs 50 W looks really bad for AMD, but the answer to this question is a little more complicated than this raw comparison of power usage.\nHere are basic things to know:\n",
      "content_html": "\u003cp\u003eApple‘s M1 is a very impressive piece of hardware. If you look at the benchmarks, like SPECperf or Cinebench, it‘s an absolute beast with a ridiculously low power consumption compared with a Ryzen 9 5950X.\u003c/p\u003e\n\u003ch2 id=\"zen-3-is-that-inefficient\"\u003e\u003ca href=\"#zen-3-is-that-inefficient\" class=\"heading-anchor\"\u003eZen 3 is that inefficient?\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e10 W vs 50 W looks really bad for AMD, but the answer to this question is a little more complicated than this raw comparison of power usage.\u003c/p\u003e\n\u003cp\u003eHere are basic things to know:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eApple‘s M1 has four high power cores (Firestorm) and four high efficiency cores (Icestorm), this is called BIG.little architecture.\u003c/li\u003e\n\u003cli\u003eAMD’s Ryzen 9 5950X is a traditional x86 CPU with 16 cores and 32 Threads\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eSo what? The benchmarks are about single core performance and Ryzen needs 40 W more. What a waste.\u003c/p\u003e\n\u003cp\u003eRight, but this says nothing about the efficiency of a single core.\u003c/p\u003e\n\u003cp\u003eI don‘t have a 5950X, but it‘s older brother, the 3950X. Same core count and about the same power usage. While monitoring with HWINFO64 on a single core run of Cinebench R20 the used core needs about 10 - 12 W.\u003c/p\u003e\n\u003ch2 id=\"okay-but-what-happened-to-the-other-38-watts\"\u003e\u003ca href=\"#okay-but-what-happened-to-the-other-38-watts\" class=\"heading-anchor\"\u003eOkay, but what happened to the other 38 watts?\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eAs you may know AMD introduced a so called chiplet architecture with Ryzen 3000. A Ryzen 9 5950X has three dies. Two with 8 cores each and a separate chip that handles I/O like PCIe 4, DDR4 memory and the Infinity Fabric links to the CPU dies.\u003c/p\u003e\n\u003cp\u003eThis I/O die consumes 15 to 17 W. It‘s a quite large chip produced in a 12 nm node by GlobalFoundries. That alone is a big reason for higher power usage compared to a modern node like TSMC‘s N7P (7 nm) used for the other two chiplets.\u003c/p\u003e\n\u003cp\u003eWhy 12 nm? It‘s a compromise, since TSMCs 7 nm production capabilities are still somewhat limited and AMD takes a fairly large share of the capacity. All Zen 2 and 3 cores are produced in 7 nm, as are all modern AMD GPUs and of course there are other customers as well.\u003c/p\u003e\n\u003cp\u003eI hope AMD will improve the I/O die with Zen 4 (Ryzen 6000) dramatically. A modern process node and a bunch of better energy saving functions would do the trick.\u003c/p\u003e\n\u003ch2 id=\"there-are-still-21-w-unaccounted-for\"\u003e\u003ca href=\"#there-are-still-21-w-unaccounted-for\" class=\"heading-anchor\"\u003eThere are still 21 W unaccounted for!\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e15 more cores are also unaccounted for. 21 divided by 15: about 1.4 W per core average. Seems a bit high, but possible. It depends on background tasks from the OS, the current Windows power plan etc.\u003c/p\u003e\n\u003cp\u003eTo be fair, AMDs energy saving functions per core could a little bit better. They are not bad but there‘s room for improvements.\u003c/p\u003e\n\u003ch2 id=\"process-nodes\"\u003e\u003ca href=\"#process-nodes\" class=\"heading-anchor\"\u003eProcess nodes\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eFor a fair comparison we also have to include the process nodes. As mentioned above, Ryzen cores are manufactured in TSMC‘s 7 nm node. Apple is one step ahead in this category. The M1 and it‘s little brother A14 are already on 5 nm, again from TSMC.\u003c/p\u003e\n\u003cp\u003eThis alone could account for up to 30% less power usage. There are no exact numbers available, but 20 to 30% would reasonable.\u003c/p\u003e\n\u003ch2 id=\"core-architecture\"\u003e\u003ca href=\"#core-architecture\" class=\"heading-anchor\"\u003eCore architecture\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eCurrent Zen 3 cores and Apple‘s Firestorm cores have fundamental differences in architecture. Modern x86 cores are small but clock quite high. On a single core load like Cinebench R23 you will get about 5 GHz from 5950X while a Firestorm core will only clock to 3.2 GHz.\u003c/p\u003e\n\u003cp\u003eWait, what? Firestorm with 3.2 GHz is about as fast as Zen 3 with 5 GHz? That can‘t be true. Yet it is. According to the benchmarks a Firestorm core can execute about twice the instructions per cycle (IPC) as a Zen 3 core.\u003c/p\u003e\n\u003cp\u003eFirestorm is an extremely wide core design with many processing units. This is complemented by absurdly large L1 caches and re-order buffers. Much larger than on any x86 core ever.\u003c/p\u003e\n\u003cp\u003eThis allows Apple to lower the clock speed significantly without compromising performance. Lower clock speeds also mean lower voltage and power usage.\u003c/p\u003e\n\u003cp\u003eBut there‘s more: since the A13 (iPhone 11) Apple has quite many incremental power saving functions within every part their SoCs. They can lower the power usage to an absolute minimum or even turn parts of the silicon on and off quite fast, to be ready when the user needs more computational power and as fast they turn it off.\u003c/p\u003e\n\u003cp\u003eModern x86 CPUs have similar functions but not quite as advanced or in as many parts as current Apple SoCs. Remember AMD‘s I/O which has basically the same power usage all the time.\u003c/p\u003e\n\u003ch2 id=\"instruction-set\"\u003e\u003ca href=\"#instruction-set\" class=\"heading-anchor\"\u003eInstruction set\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003ex86 is old and can be quite cumbersome to use. Apple\u0026rsquo;s CPUs are based on ARM, a more modern instruction set. The same tasks can be achieved with fewer commands. This means faster computation and less power usage.\u003c/p\u003e\n\u003cp\u003eUnder the hood current x86 CPUs have noting to do with their older cousins, but the instruction set is still the same. To be fair, over the years AMD and Intel added way more modern instructions like AVX for certain operations, but the core is still 40 years old.\u003c/p\u003e\n\u003ch2 id=\"conclusion\"\u003e\u003ca href=\"#conclusion\" class=\"heading-anchor\"\u003eConclusion\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eTaken all this into account, a Zen 3 is not that bad compared to a Firestorm core. Of course, Apple has advantages over x86, but they are more about the process node, different architecture or the instruction set, than about the efficiency of the cores itself.\u003c/p\u003e\n\u003cp\u003eWell, and then there‘s Intel. The quite slowly awakening giant is not without hope, but they are way behind Apple and AMD. As long as their high performance CPUs are stuck on 14 nm, they are \u0026hellip; to put it simply, fucked.\u003c/p\u003e\n\u003cp\u003e10 nm is on it‘s way, but there a still problems. The next release of desktop- and server-class CPUs will still be on 14 nm, but with a more efficient architecture backported from 10 nm CPUs. It will help, but they need a better process node and they need it asap.\u003c/p\u003e\n\u003cp\u003e10 nm could be ready for the big guys in 2021. At least new Xeons in 10 nm were announced. But the question is: how good is the node? Some of the already released 10 nm notebook CPUs are quite good, so Intel could be back in year or two.\u003c/p\u003e\n\u003cp\u003eThey also plan to release Alder Lake in 2021, a BIG.little CPU design. That would be much appreciated on notebooks, but software has to be ready. Without a decent support of the operating systems, such a CPU will not work properly.\u003c/p\u003e\n\u003cp\u003eThere‘s another problem: Intel‘s 7 nm node. According to YouTubers like AdoredTV or Moore‘s law is dead, 7 nm could be the 10 nm disaster all over again. Let‘s hope not.\u003c/p\u003e\n\u003cp\u003eThree competing CPU vendors on par would be amazing. Not just for pricing, but also computational power. Look what AMD did in the past three years. If someone said in 2016 that AMD would kick Intel‘s and nVidia‘s ass in four years, he would have been called a mad man. The same with Apple.\u003c/p\u003e\n\u003cp\u003e2020 is a shitty year, but the hardware? Awesome would be an understatement.\u003c/p\u003e\n",
      "date_published": "2020-11-18T22:34:23Z",
      "date_modified": "2026-07-14T20:52:39+02:00",
      "tags": ["hardware","performance"]
    },
    {
      "id": "https://webcodr.io/2020/10/webcodr-goes-netlify-cms/",
      "url": "https://webcodr.io/2020/10/webcodr-goes-netlify-cms/",
      "title": "webcodr goes Netlify CMS",
      "summary": "Until today posts on webcodr were published via a simple git-based workflow. If I wanted to create a new posts, I had to open the repository in Visual Studio Code and created a markdown file. After pushing the commit with the new file, a GitHub hook notified Netlify to pull the repo and build and publish the site.\nIt was quite simple and effective, but lacks comfort and does not work on iOS/iPadOS devices. After buying a new iPad Air and Magic Keyboard, I wanted a pragmatic way to write posts without my MacBook or PC.\n",
      "content_html": "\u003cp\u003eUntil today posts on webcodr were published via a simple git-based workflow. If I wanted to create a new posts, I had to open the repository in Visual Studio Code and created a markdown file. After pushing the commit with the new file, a GitHub hook notified Netlify to pull the repo and build and publish the site.\u003c/p\u003e\n\u003cp\u003eIt was quite simple and effective, but lacks comfort and does not work on iOS/iPadOS devices. After buying a new iPad Air and Magic Keyboard, I wanted a pragmatic way to write posts without my MacBook or PC.\u003c/p\u003e\n\u003cp\u003eI always wanted to try Netlify CMS, so this was my chance. The transition was really simple. I followed the guide for Hugo-based sites and adjusted the config to work with Hugo. That‘s it. Netlify CMS works just fine with the already existing markdown files. Even custom frontmatter fields within the markdown files are no problem, just set them up in the config file.\u003c/p\u003e\n\u003cp\u003eA little more configuration in the Netlify admin panel is necessary, but the guide explains everything very well. It was just a matter of 15 minutes to get everything going.\u003c/p\u003e\n\u003cp\u003eIf you use a static site generator and want to have a little more comfort, Netlify CMS makes it really simple. They provide guides for every major player like Gatsby, Jekyll or Nuxt and of course Hugo. You don‘t even have to use GitHub. GitLab and BitBucket are supported as well. As are more complex workflows for more than one editor, custom authentication with OAuth or custom media libraries.\u003c/p\u003e\n\u003ch2 id=\"editor\"\u003e\u003ca href=\"#editor\" class=\"heading-anchor\"\u003eEditor\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eNetlify CMS supports markdown and has a basic, but decent editor with rich-text mode. But I wanted a little bit more, so I decided to write my posts in Ulysses — a specialized writing app with support of GitHub-flavored markdown, including syntax highlighting preview . It‘s available for macOS and iOS/iPadOS. All files and settings are synched via iCloud. So, once you have setup everything, you‘re good to go on any of your Apple devices.\u003c/p\u003e\n\u003cp\u003eSince Ulysses requires a subscription, I will use this to „force“ me writing more posts. 😁\u003c/p\u003e\n\u003cp\u003eI wrote this post entirely on my iPad Air and so far, I‘m quite happy with the new workflow. Of course I will not write every post this way. New posts with code examples will be easier to handle on a Mac or PC. (Hey Apple, how about IntelliJ on an iPad?)\u003c/p\u003e\n\u003cp\u003ebtw: even as an enthusiast of mechanical keyboards I have to say, it‘s quite nice to type on a Magic Keyboard. I just have to get used to the smaller size. The Magic Keyboard for the 10.9“ iPad Air or 11“ iPad Pro is a compromise in size and some keys like tab, shift, backspace, enter and the umlaut keys (bracket keys on english keyboard layouts) are way smaller compared to normal-sized keyboards. It‘s bigger brother for the 13“ iPad Pro has a normal layout, but that monster of an iPad seems a bit excessive for my use case.\u003c/p\u003e\n",
      "date_published": "2020-10-31T22:54:24Z",
      "date_modified": "2026-07-12T02:02:36+02:00",
      "tags": ["developer-tools","web-development"]
    },
    {
      "id": "https://webcodr.io/2020/08/kotest-and-junit-with-intellij-or-dont-frak-up-your-toolchain-upgrades/",
      "url": "https://webcodr.io/2020/08/kotest-and-junit-with-intellij-or-dont-frak-up-your-toolchain-upgrades/",
      "title": "Kotest and JUnit with IntelliJ or: don’t frak up your toolchain upgrades",
      "summary": "My team and I recently decided to use Kotlin for new features in our existing project. It was a great choice to implement a new authentication process and we’re now rewriting some older parts of the application from Java to Kotlin.\nActually I wanted to use Kotlin for a while now, but there were only minor tasks within the Java part of the project. That finally changed and we can focus to improve the Java backend drastiscally.\n",
      "content_html": "\u003cp\u003eMy team and I recently decided to use Kotlin for new features in our existing project. It was a great choice to implement a new authentication process and we’re now rewriting some older parts of the application from Java to Kotlin.\u003c/p\u003e\n\u003cp\u003eActually I wanted to use Kotlin for a while now, but there were only minor tasks within the Java part of the project. That finally changed and we can focus to improve the Java backend drastiscally.\u003c/p\u003e\n\u003cp\u003ePart of this process was a library update. We decided to upgrade JUnit from 4 to 5. A big pain in the ass. I don’t think, I would do it again. JUnit 5 was also part of a bigger problem, even if it was actually PEBCAC.\u003c/p\u003e\n\u003ch2 id=\"kotest-and-mockk-features\"\u003e\u003ca href=\"#kotest-and-mockk-features\" class=\"heading-anchor\"\u003eKotest and MockK features\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eIf you already know about Kotest or want to know more about the problem I had, just skip to next headline. The Kotest introduction is a little bit longer.\u003c/p\u003e\n\u003cp\u003eAs I dived more and more into Kotlin, I stumbled over a Kotest. A really neat testing framework for Kotlin. There’s nothing wrong with JUnit, but Kotest gives you way more awesome ways to structure your tests.\u003c/p\u003e\n\u003cp\u003eA little example:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-kt\" data-lang=\"kt\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003epackage\u003c/span\u003e \u003cspan class=\"nn\"\u003eio.webcodr.demo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003eio.webcodr.demo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003eio.kotest.assertions.throwables.shouldThrow\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003eio.kotest.core.spec.style.FunSpec\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003eio.mockk.*\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eUserServiceTest\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eFunSpec\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"k\"\u003eval\u003c/span\u003e \u003cspan class=\"py\"\u003euserRepository\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003emockk\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eUserRepository\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"k\"\u003eval\u003c/span\u003e \u003cspan class=\"py\"\u003eservice\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eUserService\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euserRepository\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"n\"\u003elateint\u003c/span\u003e \u003cspan class=\"k\"\u003evar\u003c/span\u003e \u003cspan class=\"py\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eUser\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003einit\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003ebeforeTest\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003euser\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eUser\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Jane\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Doe\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;jane@doe.com\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eafterTest\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003eclearAllMocks\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003econtext\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;getUser()\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003efun\u003c/span\u003e \u003cspan class=\"nf\"\u003everifyRepoCalls\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003everify\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"n\"\u003euserRepository\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efindById\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003econfirmVerified\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euserRepository\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003etest\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;should succeed\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eevery\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"n\"\u003euserRepository\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efindById\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"n\"\u003ereturns\u003c/span\u003e \u003cspan class=\"n\"\u003euser\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egetUser\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"n\"\u003eshouldBe\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003euser\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003everifyRepoCalls\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"n\"\u003etest\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;should fail\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eevery\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"n\"\u003euserRepository\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efindById\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"n\"\u003ethrows\u003c/span\u003e \u003cspan class=\"n\"\u003eUserNotFoundException\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003eshouldThrow\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eUserNotFoundException\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                    \u003cspan class=\"n\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egetUser\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"n\"\u003everifyRepoCalls\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eKotest offers serveral different styles to write tests. I chose the \u003ccode\u003eFunSpec\u003c/code\u003e style for this example. You could also use a BDD-like or Jasmine-like style, if you want to.\u003c/p\u003e\n\u003cp\u003eIt’s much more intuitive to nest tests with Kotest. To be fair, JUnit 5 allows you to use \u003ccode\u003e@Nested\u003c/code\u003e with an inner class to acomplish nesting as well, but it’s not as intuitive and harder to read than trailing lambdas.\u003c/p\u003e\n\u003cp\u003eAssertions are also a little easier to write. Kotest has over 100 different matchers. You can use them as extensions functions, like in the example above or alternatively as infix functions, for example \u003ccode\u003eservice.getUser(1) shouldBe user\u003c/code\u003e. It’s also quite simple to write custom matchers.\u003c/p\u003e\n\u003cp\u003eThere are many more features like soft assertions, tagging, easy temporay file creation or handling for non-deterministic test cases.\u003c/p\u003e\n\u003cp\u003eFor mocking we decided to use MockK, since it’s way more intuitive to use than Mockito with Kotlin. Don’t get me wrong, Mockito is a great library, but it has one flaw: the \u003ccode\u003ewhen\u003c/code\u003e method. \u003ccode\u003ewhen\u003c/code\u003e is a keyword in Kotlin and to use it with Mockito, you need to write it in backticks. That’s quite ugly and not intuitive at all.\u003c/p\u003e\n\u003cp\u003eSo, in summary, Kotest offers a bunch of pretty neat features and is very intuitive to use. Of course, JUnit can achieve much of this as well, it’s just not that shiny and little harder to read.\u003c/p\u003e\n\u003ch2 id=\"the-actual-problem-or-why-the-frak-is-there-intellij-in-the-title\"\u003e\u003ca href=\"#the-actual-problem-or-why-the-frak-is-there-intellij-in-the-title\" class=\"heading-anchor\"\u003eThe actual problem or: why the frak is there IntelliJ in the title?\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eIf you migrate an old codebase to Kotlin and want to use Kotest, you will have no choice and have to use JUnit and Kotest in coexistence.\u003c/p\u003e\n\u003cp\u003eThat shouldn’t be a problem, since Kotest uses the JUnit 5 Jupiter engine under the hood. But …\u003c/p\u003e\n\u003cp\u003eAs I wrote a new service in Kotlin and some tests with Kotest, I could not start the JUnit tests anymore. As soon as the maven depedency of Kotest was present, IntelliJ didn’t recognize JUnit tests and used the Kotest files only. With \u003ccode\u003emvn test\u003c/code\u003e (Maven Surefire) everything worked fine.\u003c/p\u003e\n\u003cp\u003eI tried several things and was ready to give up. Search engines didn’t find anything about this problem. Nothing on GitHub, nothing on Stack Overflow.\u003c/p\u003e\n\u003cp\u003eI hate to give up, so I decided to create a small demo project to open a GitHub issue. Well, that didn’t work out as intended, since the discovery of JUnit tests in the demo project worked fine. The IntelliJ JUnit runner did what it was supposed to: run JUnit and Kotest.\u003c/p\u003e\n\u003cp\u003eWell, frak. There must be some kind configuration problem with my real project. I already looked at the IntelliJ runner config, Maven files etc. — nothing worked.\u003c/p\u003e\n\u003cp\u003eI compared the Maven files from both codebases and there was one difference: my real project did not include the JUnit Jupiter Engine depedency. Bingo. I added to the Maven file and guess what? It worked like a charm.\u003c/p\u003e\n\u003cp\u003eWhat an embarassment. As we upgraded from JUnit 4 to 5, we forgot to add the new depedency for the engine. I don’t know why the tests worked at all, but it seems the engine is not really necessary for all cases. But it can screw up test discovery quite well, if you forget it.\u003c/p\u003e\n\u003cp\u003eThe dependency configuration in POM file should like this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.junit.jupiter\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003ejunit-jupiter-api\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e${junit.version}\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;scope\u0026gt;\u003c/span\u003etest\u003cspan class=\"nt\"\u003e\u0026lt;/scope\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;groupId\u0026gt;\u003c/span\u003eorg.junit.jupiter\u003cspan class=\"nt\"\u003e\u0026lt;/groupId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;artifactId\u0026gt;\u003c/span\u003ejunit-jupiter-engine\u003cspan class=\"nt\"\u003e\u0026lt;/artifactId\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;version\u0026gt;\u003c/span\u003e${junit.version}\u003cspan class=\"nt\"\u003e\u0026lt;/version\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;scope\u0026gt;\u003c/span\u003etest\u003cspan class=\"nt\"\u003e\u0026lt;/scope\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/dependency\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eWell, that was a long post, but IMO it was necessary to show how this problem came to be and even if you have no trouble at all, perhaps you’ll consider to use Kotest. It’s awesome!\u003c/p\u003e\n\u003ch2 id=\"gradle\"\u003e\u003ca href=\"#gradle\" class=\"heading-anchor\"\u003eGradle\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI’m not sure, but this issue could happen with Gradle as well, when you are migrating to JUnit 5.\u003c/p\u003e\n",
      "date_published": "2020-08-29T22:26:07+02:00",
      "date_modified": "2026-07-12T02:02:36+02:00",
      "tags": ["developer-tools","kotlin","testing"]
    },
    {
      "id": "https://webcodr.io/2019/10/macos-catalina-edid-override-aka-hdmi-color-fix/",
      "url": "https://webcodr.io/2019/10/macos-catalina-edid-override-aka-hdmi-color-fix/",
      "title": "macOS Catalina EDID Override AKA HDMI color fix",
      "summary": "HDMI connections from your Mac to monitor can be a pain in the ass. There is a chance that macOS will detect your monitor as a TV and set the color space to YCbCr. You will get wrong colors and sometimes blurry fonts.\nIf you\u0026rsquo;re having this problem, like me, you know the fix: a patched EDID created with this little Ruby script.\nThe installation of this EDID override could be tedious since the release of El Capitan, as SIP won\u0026rsquo;t let you access the necessary system files. Just disable it in recovery mode, copy the file and enable it again. Sucks, but works just fine.\n",
      "content_html": "\u003cp\u003eHDMI connections from your Mac to monitor can be a pain in the ass. There is a chance that macOS\nwill detect your monitor as a TV and set the color space to YCbCr. You will get wrong colors\nand sometimes blurry fonts.\u003c/p\u003e\n\u003cp\u003eIf you\u0026rsquo;re having this problem, like me, you know the fix: a patched EDID created with this\n\u003ca href=\"https://embdev.net/attachment/168316/patch-edid.rb\"\u003elittle Ruby script\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eThe installation of this EDID override could be tedious since the release of El Capitan, as SIP won\u0026rsquo;t let you\naccess the necessary system files. Just disable it in recovery mode, copy the file and enable\nit again. Sucks, but works just fine.\u003c/p\u003e\n\u003cp\u003eNow, Catalina is out for a few hours and has a new way to annoy people who need EDID overrides.\nAll system-related directories and files are read-only, regardless of the status of SIP.\u003c/p\u003e\n\u003cp\u003eFortunately Apple was not crazy enough to disable the write access completely.\u003c/p\u003e\n\u003ch2 id=\"help-is-on-the-way-eta-0-seconds\"\u003e\u003ca href=\"#help-is-on-the-way-eta-0-seconds\" class=\"heading-anchor\"\u003eHelp is on the way, ETA 0 seconds!\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003ePatch your EDID\u003c/li\u003e\n\u003cli\u003eBoot into recovery mode with CMD+R\u003c/li\u003e\n\u003cli\u003eLogin with your user\u003c/li\u003e\n\u003cli\u003eOpen Disk Utility, select your volume (in most cases \u003ccode\u003eMacintosh HD\u003c/code\u003e) and mount it with your password (yes, again)\u003c/li\u003e\n\u003cli\u003eClose the Disk Utility and open a Terminal window\u003c/li\u003e\n\u003cli\u003eCopy the directory with the patched EDID to\n\u003ccode\u003e/Volumes/$VOLUME_NAME/System/Library/Displays/Contents/Resources/Overrides\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eReboot and enjoy the right colors again\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eHere\u0026rsquo;s an example of the shell commands:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ecd\u003c/span\u003e /Volumes/Macintosh\u003cspan class=\"se\"\u003e\\ \u003c/span\u003eHD/System/Library/Displays/Contents/Resources/Overrides\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecp -rf /Volumes/Macintosh\u003cspan class=\"se\"\u003e\\ \u003c/span\u003eHD/Users/webcodr/DisplayVendorID-5a63 .\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003eDon\u0026rsquo;t forget to use the correct volume and user names!\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"dear-apple\"\u003e\u003ca href=\"#dear-apple\" class=\"heading-anchor\"\u003eDear Apple\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eJust add a simple solution to select the HDMI color\nspace. A simple shell commando with \u003ccode\u003esudo\u003c/code\u003e would suffice or at least let us use an override directory\nwithin the user library as it was possible many years ago. It just sucks to do this after every\nmacOS upgrade and every time you improve system security, it gets harder.\u003c/p\u003e\n\u003cp\u003ePlease, don\u0026rsquo;t forget us powers users \u0026hellip;\u003c/p\u003e\n",
      "date_published": "2019-10-07T22:26:07+02:00",
      "date_modified": "2026-07-12T02:02:36+02:00",
      "tags": ["hardware","macos"]
    },
    {
      "id": "https://webcodr.io/2019/09/hello-dark-mode/",
      "url": "https://webcodr.io/2019/09/hello-dark-mode/",
      "title": "Hello, Dark Mode",
      "summary": "Dark mode for Android and iOS? Hold my beer \u0026hellip;\nIt\u0026rsquo;s quite simple to implement. Every modern browser can evaluate media queries in JavaScript with window.matchMedia() and supports CSS variables.\nI added the following to my application JavaScript file:\nconst preferColorSchemeResult = window.matchMedia(\u0026#39;(prefers-color-scheme: dark)\u0026#39;) if (preferColorSchemeResult \u0026amp;\u0026amp; preferColorSchemeResult.matches === true) { document.documentElement.setAttribute(\u0026#39;data-theme\u0026#39;, \u0026#39;dark\u0026#39;) } else { document.documentElement.setAttribute(\u0026#39;data-theme\u0026#39;, \u0026#39;light\u0026#39;) } The script will set the data attribute theme on the document element (html) with the possible values dark or light depending on the result of the media query.\n",
      "content_html": "\u003cp\u003eDark mode for Android and iOS? Hold my beer \u0026hellip;\u003c/p\u003e\n\u003cp\u003eIt\u0026rsquo;s quite simple to implement. Every modern browser can evaluate media queries in JavaScript with\n\u003ccode\u003ewindow.matchMedia()\u003c/code\u003e and supports CSS variables.\u003c/p\u003e\n\u003cp\u003eI added the following to my application JavaScript file:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-js\" data-lang=\"js\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003epreferColorSchemeResult\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003ewindow\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ematchMedia\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;(prefers-color-scheme: dark)\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003epreferColorSchemeResult\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"nx\"\u003epreferColorSchemeResult\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ematches\u003c/span\u003e \u003cspan class=\"o\"\u003e===\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nb\"\u003edocument\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003edocumentElement\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003esetAttribute\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;data-theme\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;dark\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nb\"\u003edocument\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003edocumentElement\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003esetAttribute\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;data-theme\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;light\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe script will set the data attribute \u003ccode\u003etheme\u003c/code\u003e on the document element (html) with the possible values\n\u003ccode\u003edark\u003c/code\u003e or \u003ccode\u003elight\u003c/code\u003e depending on the result of the media query.\u003c/p\u003e\n\u003cp\u003eThere\u0026rsquo;s no need for a polyfill, even IE 10 supports \u003ccode\u003ewindow.matchMedia()\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eStylesheet changes is even simpler, since I already had introduced SCSS color variables a while ago. I just\nhad to replace them with CSS variables.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-scss\" data-lang=\"scss\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// colors\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003e$c_white\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"mh\"\u003e#fff\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003e$c_dark-grey\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"mh\"\u003e#4A4A4A\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e:root\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"na\"\u003e--container-background-color\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"si\"\u003e#{\u003c/span\u003e\u003cspan class=\"nv\"\u003e$c_white\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nc\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"nt\"\u003edata-theme\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;dark\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"na\"\u003e--container-background-color\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"si\"\u003e#{\u003c/span\u003e\u003cspan class=\"nf\"\u003edarken\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003e$c_dark-grey\u003c/span\u003e\u003cspan class=\"o\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e20\u003c/span\u003e\u003cspan class=\"kt\"\u003e%\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nc\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThat\u0026rsquo;s basically it. If you use SCSS, please take notice to use interpolations to map the SCSS\nvariables to CSS variables. This change in SassScript expressions was necessary to provide full\ncompatibility with plain CSS.\u003c/p\u003e\n\u003cp\u003eSince the theme selection is fully automated, I will provide a toggle possibiliry in a future\nrelease for those of you who prefer the light mode. This can be easily achieved with a flag in local\nstorage and some minor changes in the JavaScript part.\u003c/p\u003e\n",
      "date_published": "2019-09-25T22:20:12+02:00",
      "date_modified": "2026-07-12T02:02:36+02:00",
      "tags": ["javascript","web-development"]
    },
    {
      "id": "https://webcodr.io/2018/06/snapshot-tests-with-jest/",
      "url": "https://webcodr.io/2018/06/snapshot-tests-with-jest/",
      "title": "Snapshot Tests With Jest",
      "summary": "Writing tests can sometimes be a tedious task. Mocks and assertions can be a pain in the ass. The latter is especially nasty when HTML is involved. Give me the second p element from the 30th div within an article in aside etc. \u0026ndash; no thanks.\nThe creators of Jest (Facebook) have found a better way: Snapshot tests!\nHow does it work?# Take a look the following assertion:\nit(\u0026#39;should create a foo bar object\u0026#39;, () =\u0026gt; { const result = foo.bar() expect(result).toMatchSnapshot() }) toMatchSnapshot() takes what ever you give to expect(), serializes it and saves it into a file. The next test run will compare the expected value to the stored snapshot and will fail if they don\u0026rsquo;t match. Jest shows a nicely formatted error message and diff view on failed tests.\n",
      "content_html": "\u003cp\u003eWriting tests can sometimes be a tedious task. Mocks and assertions can be a pain in the ass. The latter is especially nasty when HTML is involved. Give me the second p element from the 30th div within an article in aside etc. \u0026ndash; no thanks.\u003c/p\u003e\n\u003cp\u003eThe creators of Jest (Facebook) have found a better way: Snapshot tests!\u003c/p\u003e\n\u003ch2 id=\"how-does-it-work\"\u003e\u003ca href=\"#how-does-it-work\" class=\"heading-anchor\"\u003eHow does it work?\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eTake a look the following assertion:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-javascript\" data-lang=\"javascript\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eit\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;should create a foo bar object\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eresult\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003efoo\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ebar\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003eexpect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eresult\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nx\"\u003etoMatchSnapshot\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003etoMatchSnapshot()\u003c/code\u003e takes what ever you give to \u003ccode\u003eexpect()\u003c/code\u003e, serializes it and saves it into a file. The next test run will compare the expected value to the stored snapshot and will fail if they don\u0026rsquo;t match. Jest shows a nicely formatted error message and diff view on failed tests.\u003c/p\u003e\n\u003cp\u003eThis is really useful with generated HTML and/or testing UI behaviour. Just call the method and let it compare to the snapshot.\u003c/p\u003e\n\u003ch2 id=\"updating-snapshots\"\u003e\u003ca href=\"#updating-snapshots\" class=\"heading-anchor\"\u003eUpdating snapshots\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eYou added something to your code and the snapshot has to be updated? No problem:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ejest --updateSnapshot\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIf you\u0026rsquo;re using the Jest watcher it\u0026rsquo;s even simpler. Just press \u003ccode\u003eu\u003c/code\u003e to update all snapshots or press \u003ccode\u003ei\u003c/code\u003e to update the snapshots interactively.\u003c/p\u003e\n\u003ch2 id=\"what-about-objects-with-generated-values\"\u003e\u003ca href=\"#what-about-objects-with-generated-values\" class=\"heading-anchor\"\u003eWhat about objects with generated values?\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eHere\u0026rsquo;s an example with an randomized id:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-javascript\" data-lang=\"javascript\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eit\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;should fail every time\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eship\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eid\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eMath\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003efloor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eMath\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003erandom\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mi\"\u003e20\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ename\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;USS Defiant\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003eexpect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eship\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nx\"\u003etoMatchSnapshot\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThe id will change on every test run, so this test will fail every time. Well, shit? Nope. Jest got you covered:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-javascript\" data-lang=\"javascript\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003eit\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;should create a ship\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003econst\u003c/span\u003e \u003cspan class=\"nx\"\u003eship\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eid\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eMath\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003efloor\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eMath\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003erandom\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mi\"\u003e20\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003ename\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;USS Defiant\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nx\"\u003eexpect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003eship\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"nx\"\u003etoMatchSnapshot\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nx\"\u003eid\u003c/span\u003e\u003cspan class=\"o\"\u003e:\u003c/span\u003e \u003cspan class=\"nx\"\u003eexpect\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eany\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eNumber\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eJest will now only compare the type of the id and the test will pass.\u003c/p\u003e\n\u003cp\u003eFor certain objects like a date, there is another possibility:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-javascript\" data-lang=\"javascript\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eDate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003enow\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nx\"\u003ejest\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003efn\u003c/span\u003e\u003cspan class=\"p\"\u003e(()\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e1528902424828\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eA call of \u003ccode\u003eDate.now()\u003c/code\u003e will call the mock method and always return the same value.\u003c/p\u003e\n\u003ch2 id=\"some-advice\"\u003e\u003ca href=\"#some-advice\" class=\"heading-anchor\"\u003eSome advice\u003cspan class=\"heading-anchor-hash\" aria-hidden=\"true\"\u003e#\u003c/span\u003e\u003c/a\u003e\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eAlways commit your snapshots! If they are missing,CI systems will always create new snapshots and the tests will become useless.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eSnapshot tests are an awesome tool, but don\u0026rsquo;t be too lazy. They are no replacement for other assertion types, especially if you\u0026rsquo;re working test-driven. Rather use them alongside with your other tests.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eWrite meaningful test names. Well, you heard that one before, didn\u0026rsquo;t you? Really, it helps a a lot when tests fail or you have to look inside a snapshot file. Jest takes a test name as an id inside a snapshot file. That\u0026rsquo;s why you have to update a snapshot after changing the name.\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n",
      "date_published": "2018-06-13T16:31:12+02:00",
      "date_modified": "2026-07-12T02:02:36+02:00",
      "tags": ["javascript","testing"]
    }
  ]
}
