Andrey Listopadov

Accepting your own solutions to your own problems

There was a weird thought going over and over in my head, regarding my Emacs configuration, and it extends to the other projects I do both at home and at work. You see, my configuration is riddled with custom code, and up until recently I had mixed feelings about that.

For example, I have a custom piece of code to automatically switch themes, based on the dark mode setting in my desktop environment. I do so via the DBUS interface:

(use-package dbus
  :when (featurep 'dbusbind)
  :requires (functions local-config)
  :commands (dbus-register-signal dbus-call-method)
  :preface
  (defun color-scheme-changed (path var value)
    "DBus handler to detect when the color-scheme has changed."
    (when (and (string-equal path "org.freedesktop.appearance")
               (string-equal var "color-scheme"))
      (if (equal (car value) '1)
          (load-theme local-config-dark-theme t)
        (load-theme local-config-light-theme t))))
  (defun dbus-color-theme-dark-p ()
    (equal '1 (caar (condition-case nil
                        (dbus-call-method
                         :session
                         "org.freedesktop.portal.Desktop"
                         "/org/freedesktop/portal/desktop"
                         "org.freedesktop.portal.Settings"
                         "Read"
                         "org.freedesktop.appearance"
                         "color-scheme")
                      (error nil)))))
  :init
  (dbus-register-signal :session
                        "org.freedesktop.portal.Desktop"
                        "/org/freedesktop/portal/desktop"
                        "org.freedesktop.portal.Settings"
                        "SettingChanged"
                        #'color-scheme-changed))

It works perfectly fine for me, yet there’s a certain feeling I experience every time I see it in my configuration.

Or, here’s another example, that kinda motivated me to write this post.

With the introduction of Tree-Sitter, Emacs now can use different parsers to provide syntax highlighting for certain modes. I use it with several modes, but the default experience is a bit rough because while there are functions to install grammars, they’re a bit inconvenient for general use. So to mitigate that, I made this function:

(use-package treesit
  :when (treesit-p)
  :preface
  (defun treesit-p ()
    "Check if Emacs was built with treesiter in a protable way."
    (and (fboundp 'treesit-available-p)
         (treesit-available-p)))
  (cl-defun treesit-install-and-remap (lang url &key revision source-dir modes remap org-src)
    "Convenience function for installing and enabling a ts-* mode.

LANG is the language symbol.  URL is the Git repository URL for
the grammar.  REVISION is the Git tag or branch of the desired
version, defaulting to the latest default branch.  SOURCE-DIR is
the relative subdirectory in the repository in which the
grammar’s parser.c file resides, defaulting to \"src\".  MODES is
a list of modes to remap to a symbol REMAP."
    (when (and (fboundp 'treesit-available-p)
               (treesit-available-p))
      (unless (treesit-language-available-p lang)
        (add-to-list
         'treesit-language-source-alist
         (list lang url revision source-dir))
        (treesit-install-language-grammar lang))
      (when (and remap (treesit-ready-p lang))
        (dolist (mode modes)
          (add-to-list
           'major-mode-remap-alist
           (cons mode remap))))
      (when (and org-src (treesit-ready-p lang))
        (eval-after-load 'org
          (lambda ()
            (add-to-list 'org-src-lang-modes org-src))))))
  :custom
  (treesit-font-lock-level 2))

I use it like this:

(use-package js
  :defer t
  :when (treesit-p)
  :init
  (treesit-install-and-remap
   'javascript
   "https://github.com/tree-sitter/tree-sitter-javascript"
   :revision "master" :source-dir "src"
   :modes '(js-mode javascript-mode js2-mode)
   :remap 'js-ts-mode
   :org-src '("js" . js-ts)))

(use-package json-ts-mode
  :defer t
  :after json
  :when (treesit-p)
  :init
  (treesit-install-and-remap
   'json "https://github.com/tree-sitter/tree-sitter-json"
   :modes '(js-json-mode)
   :remap 'json-ts-mode
   :org-src '("json" . json-ts)))

(use-package elixir-ts-mode
  :defer t
  :when (treesit-p)
  :init
  (treesit-install-and-remap
   'elixir "https://github.com/elixir-lang/tree-sitter-elixir"
   :org-src '("elixir" . elixir-ts)))

As you can see, this function handles everything from installing the grammar to remapping the major modes to use tree-sitter-backed ones, and also handles Org Mode source block languages.

Finally, I have these packages, that I made over the years of using Emacs:

(use-package isayt
  :vc ( :url "https://gitlab.com/andreyorst/isayt.el.git"
        :branch "main"
        :rev :newest)
  :delight isayt-mode
  :hook (common-lisp-modes-mode . isayt-mode))

(use-package region-bindings
  :vc ( :url "https://gitlab.com/andreyorst/region-bindings.el.git"
        :branch "main"
        :rev :newest)
  :commands (region-bindings-mode)
  :preface
  (defun region-bindings-off ()
    (region-bindings-mode -1))
  :hook ((after-init . global-region-bindings-mode)
         ((elfeed-search-mode magit-mode mu4e-headers-mode)
          . region-bindings-off)))

The isayt package automatically keeps the lisp code indented as I type, and the region-bindings package enables mappings while the region is active. Originally both these packages were just a part of my configuration, but the amount of code became a bit too big for my liking so I moved them into separate files, and uploaded them to GitLab.

Now, why am I telling you all this, you might ask, and what it has to do with the title of this post?

Well, you see, each example that I have shown you has a package that already does something like that.

The DBUS theme switching is handled by the auto-dark-emacs package, however, it also handles macOS, Termux, and Windows, while I only support my operating system of choice. Tree-Sitter is handled by the treesit-auto package, which covers much more languages than I am. For automatic indentation, there’s the aggressive-indent-mode which, unlike isayt, handles not only lisps, but other language modes as well. And the region-bindings package is basically a reimplementation of the region-bindings-mode package.

So why did I create all these things, even if there are solutions already available and used by the community?

Well, I created isayt because I wasn’t satisfied with the timer-based approach of aggressive-indent-mode. It felt laggy. Moreover, I don’t really need this in any modes other than lisps. Lisp is structured, and indentation is semantic, so having it automatically adjust is nice. Other languages either can’t do that properly, while having semantic indentation (e.g. Python) or don’t really need it, because there’s no real benefit to having your code always shift around, as it doesn’t really represent a data structure.

I made region-bindings because the original region-bindings-mode didn’t work properly with turned off transient-mark-mode (TMM). I don’t remember why I disabled it, perhaps after reading Mastering Emacs, or some other power-users note on how not having a visible region makes you more productive. In the end, I enabled TMM back, but the package already performed with fewer bugs than the region-bindings-mode so I decided to keep it.

The DBUS stuff is simple, really. At the time I wrote this code, I don’t think any packages did that. Now, it works fine, and I don’t see any reason to replace it with a package from someone else. It works, so don’t touch it. Moreover, I haven’t changed this piece of code ever since I wrote it (except for that time when the API changed in GNOME), and the auto-dark-mode is somehow constantly updated.

Finally, I just don’t need support for all of the tree-sitter-based modes out there, so it’s fine for me to throw a small use-package form with a single call to my custom function.

There are many more examples of custom weird things I do in my configuration, like custom compilation modes with complicated filename handling. Or the mu4e context managing code based on some private data structures I defined to automate the process of adding new inboxes. I have a custom memoization macro and a piece of code that decides if I can scroll my window to the left. I wrote so much code for automating stuff for this very blog that I decided to move it into a separate package. Yet, there are still pieces spread across my init.el that are related to my blog workflow.

I have a lot of custom code that is oriented to solve my problems and enhance my editing experience. After years, I learned to accept this kind of philosophy in programming - if you can solve your problem, and learn something new along the way, then do it. It doesn’t matter if the solution already exists. More often than not I’m frustrated with small annoyances of how many packages kinda do what I want, but also not exactly how I’d like to.

This also affected how I think about code in general. When faced with a problem I don’t want to reach to a library until I see that implementing a solution is either pointless, too complicated, and error-prone, or if I don’t have time to do that. Only then do I go and look for a library. I guess, I’m lucky to code in Clojure at work, as the language has a lot of useful features, making it often unnecessary to search for a library to do something. In other languages, the situation may be different.

It’s kinda the same in Emacs - the core has so much stuff that most of the time you don’t need any extra packages. You just have to dig a bit, and you’ll probably find a function that solves your problem. Or can help you solve it.

Today’s code is often complicated, and this complexity is justified by achieving so-called “reuse”.

This library is so general that you can reuse it in your very specific context! This code is so abstract that it is basically an art form!

I don’t buy that anymore. The most reusable code is the one you can copy from one project and paste into another one, and just use it. Of course, copy-pasting is just a metaphor, we create a library with that code and use it in different projects. But the code in that library could as well just be copied, meaning there were no changes to that code to make it support multiple scenarios. And if you have to support multiple scenarios - just modify this code!

That was a bit of a tangent, but I feel that it applies to what I do in my Emacs configuration. A lot of Emacs packages are way too complicated because they have to support very specific cases that the author of a package might not even have. We became too afraid to take a piece of code and modify it to our needs ourselves. And Emacs actually gives you tools to do that - the hooks, and the advice system. I advice a lot of stuff in my Emacs configuration, and I’m glad that this system is there and that it works in such a robust way that it allows me to do crazy stuff without fear that it’ll break. And if it breaks, I’ll just fix it.

It’s a fine solution.