Calling Emacs from XMonad

Posted on 2022-05-25  ·  last modified: 2023-03-07  ·  7 min read  ·  , ,

In the post about my research workflow, I briefly mentioned having to call Emacs—or other external programs—from within xmonad. I figured that this was perhaps something that could be of use to more people than just me. After a little bit of deliberation and coming up with a generic enough api, I decided to turn it into an xmonad module!

These changes now live as part of the xmonad-contrib repository and are—from version 0.17.1 onwards—available for everyone to try out.
Alternatively, one could use the git versions of xmonad and xmonad-contrib; refer to INSTALL for more information.

I’d like to use this opportunity to both showcase the module—how and why one would use it—and talk a little bit about its only redeeming implementation detail.

Main use cases§

Wouldn’t it be neat to have some kind of edsl for spawning external processes? Something where one can just compose Haskell functions together, not having to worry about the actual underlying string manipulations? Something that’s composable, so that one does not have to write the same "emacsclient -c -a '' …" or "alacritty --working-directory …" prefix over and over again? Well, at least that’s what I thought on some rainy afternoon a few months ago.

Scratchpads§

The first use case that I came up with was scratchpads. The idea of these things is simple: while we normally don’t like floating windows, it’s quite convenient to have some of them around that one can bring to the current workspace, as well as hide, with a single keybinding. This is useful for things like email, a calendar, a daily agenda, a calculator, etc.

For scratchpads to work reliably, windows need to have some unique characteristic they can be recognised by, like a special class or instance name. Endowing an application with additional properties sounds exactly like what our edsl should be good at, so let’s try that!

Using the new features of XMonad.Util.Run, we could spawn an Emacs frame with a special name for our scratchpad hooks to grab onto, and execute notmuch:
mailSession :: X String
mailSession = getInput $
  inEditor >-> setFrameName mailInstName
           >-> eval (function "notmuch")

You can read the >-> operator a little like a pipe, where you start with what you want and thread that information through to the end: “I want an editor with a certain frame name that also starts up notmuch”.

In full, the above function would produce the string (broken into a few lines for better readability)
"emacsclient -c -a ''
             -F '(quote (name . \"notmuch-scratch\"))'
             --eval '(notmuch)'"

which would be quite bothersome to type indeed.

Because the type of mailSession is X String and not just String, the setup for this is a little bit different than usual when using scratchpads. You would use it like this:
myScratchpads :: X [NamedScratchpad]
myScratchpads = do
  -- First, get the finished string.
  mailSession <- getInput $
    inEditor >-> setFrameName mailInst >-> eval (elispFun "notmuch")
  -- Now we can insert it into our scratchpads as normal.
  pure [ NS "Mail" mailSession (appName =? mailInst) quake ]
 where
  mailInst = "notmuch-scratch"
  quake    = customFloating $ RationalRect 0 0 1 (4 / 5)

-- The call to @namedScratchpadManageHook@ in the manageHook also
-- needs to be slightly adjusted.
myManageHook :: ManageHook
myManageHook = mconcat
  [ 
  , namedScratchpadManageHook =<< liftX myScratchpads
  ]

Normally you would also add your myScratchpads list to all calls of namedScratchpadAction; e.g., when you define the keys to call your scratchpads. However, since the former lives in X now, this doesn’t work anymore! Thankfully, nowadays the first argument to namedScratchpadAction is actually unused and only there for backwards compatibility. This means that it’s not necessary to enter your scratchpads there at all if they are added to your manageHook. For example, in the following I just provide the empty list:
  ("M-C-t", namedScratchpadAction [] "Mail")

This works all the same with the above definition of myScratchpads.

A full example of how a scratchpad setup would look using this machinery can be found in my config.

Calling Emacs in scripts§

Spawning frames is nice and all, but how about something more complicated, like Emacs’s batch mode so that we can use it properly in scripts? No problem at all!

For example, I have the following snippet in my config to get the currently selected text and call arxiv-citation with it to produce a citation entry in my bibliography files:
callArXiv :: String -> X ()
callArXiv fun = do
  url <- getSelection  -- from X.U.XSelection
  proc $ inEmacs
     >-> withEmacsLibs [ ElpaLib "dash", ElpaLib "s"
                       , ElpaLib "arxiv-citation"
                       , Special "~/.config/emacs/private-stuff.el" ]
     >-> asBatch
     >-> eval (progn [require "arxiv-citation", fun <> asString url])

When executed, this translates to something like
emacs -L /home/slot/.config/emacs/elpa/dash-20220417.2250
      -L /home/slot/.config/emacs/elpa/s-20210616.619
      -L /home/slot/.config/emacs/elpa/arxiv-citation-20220510.1137/
      --batch
      --eval '(progn
                (require (quote arxiv-citation))
                (arXiv-citation "<url-in-the-primary-selection>"))'

I certainly know which one I’d rather type—especially with elpa directory names changing quite frequently! On that note, arxiv-citation is on melpa now; yay!

Other programs§

As this is my main use case for it, the new features of XMonad.Util.Run are quite specialised for Emacs. However, even for other programs they may well come in handy. Drawing from the point about scratchpads again, here is a hypothetical one that spawns a ghci session:
  ghci <- proc $ inTerm >-> setXClass calcInstName >-> execute "ghci"

Further, something that’s useful when dealing with topic-based workspaces is spawning a terminal or an editor already in the current topic directory:
import XMonad.Actions.TopicSpace  -- for currentTopicDir and more
topicConfig = 

spawnTermInTopic :: X ()
spawnTermInTopic =
  proc $ termInDir >-$ currentTopicDir topicConfig

-- Optionally, modify the path to the editor with a function.
spawnEditorInTopic :: (String -> String) -> X ()
spawnEditorInTopic with =
  proc $ inEditor >-$ with <$> currentTopicDir topicConfig

Quite convenient if you ask me.

If you have or know of a use case you would like to support but which is awkward with the current set of functions and combinators do not hesitate to open a pull request or an issue!

Implementation considerations§

The implementation is actually very straightforward—no really, check out the source if you don’t believe me!

One concept that’s still worth touching upon is the internal use of difference lists. The basic idea of these things is that, instead of concatenating strings one by one, we create functions String -> String and then use function composition to do the work for us:
  -- Ordinary string concatenation
  "string1" <> "string2" <> "string3" <> "string4"

  -- Using difference lists:
  string1, string2, string3, string4 :: String -> String
  string1 s = "string1" <> s
  string2 s = 

  string1 . string2 . string3 . string4 $ ""

Note how we have to apply the entire thing to "" at the end in order to actually get a string back. As a concrete example, assuming we have set "Emacs" as our editor, the inEditor function would essentially be
inEditor :: String -> String
inEditor s = " Emacs " <> s

There are some further considerations to be made, since we are in the X monad and thus the type is actually X (String -> String) instead of just String -> String, but that isn’t too important for us here.

Difference lists have some performance advantages over the traditional concatenation of strings. The concatenation (<>) on strings is left associative by default and so
    "string1" <> "string2" <> "string3" <> "string4"
   (("string1" <> "string2") <> "string3") <> "string4"

However, the complexity characteristics of this operation are working against us here; the definition of (<>) on String
Really, this is the definition of (++) for arbitrary lists [a] and (<>) = (++) for String = [Char], but let’s not get into that here.
is
(<>) :: String -> String -> String
[]       <> ys =           ys
(x : xs) <> ys = x : xs <> ys

We are merely traversing the first string, leaving the second one completely untouched (and unevaluated!). All in all, this means that s₁ <> s₂ is in 𝓞(|s₁|); given an expression of the form
  (("string1" <> "string2") <> "string3") <> "string4"

we will have to walk over "string1" three times! What we actually want is a right-associative ordering—exactly what function compositions gives us. Spelled out,
    string1 . string2 . string3 . string4 $ ""
   string1 (string2 (string3 (string4 "")))
   "string1" <> ("string2" <> ("string3" <> ("string4" <> "")))

which yields the desired behaviour. In fact, this is so canonical that instead of using (.), we could have also—perhaps a bit confusingly—used (<>) directly:
    string1  . string2  . string3  . string4
   string1 <> string2 <> string3 <> string4

This is the fact that the endomorphisms for any type a—the functions a -> a—form a monoid. That is to say that they come equipped with an associative an unital operation: function composition. In Haskell, (<>) is, in some sense, overloaded so that it can be used with any monoidal composition one can think of!
Really, for any semigroup, which is a slightly weaker notion of an operation that is merely associative, but doesn’t necessarily have a unit.

The attentive reader may have concluded that the pipe operator that we called (>->) above is really just (<>) in disguise, and that’s exactly right! I, however, thought that for people not familiar with Haskell, giving it a pipe-like appearance would be more conceptually amenable to the threading idea.

I haven’t benchmarked this, so it’s not entirely clear to me whether performance is actually relevant in this situation
I suspect that the answer is “probably not”—that didn’t stop me, however!
, but using difference lists just feels right here, and so I did.

Conclusion§

I have to say that I’m quite satisfied with this api. In fact, if I compare it with the old code that only resided within my personal config, this new version is even a bit more ergonomic in a few places, despite having essentially no user-specific strings hardcoded anywhere! As I said before, if you try this and find something missing, do let me know and we’ll probably find a solution! If you try this and find it useful, also let me know :)

Of course, technically none of this needs to live only inside your xmonad config at all. In combination with the excellent turtle library, I reckon it would be quite easy to produce Haskell versions of cool tools like magit.sh.
Available here. I also maintain a slightly modified and POSIX shell compatible version here.
Go nuts!