Jeremy Friesen: Adding an Emacs Command to Summarize Headlines with Chosen Tags

I’ve been playing a solo game of Ironsworn: Starforged 📖 . Follow session reports if you are interested.

I’m using Emacs 📖 for tracking that campaign, and exporting those sessions to blog posts. While playing I often find myself needing to review information.

I set about writing a function to help me review those notes by rendering a summary of the headlines with matching tags.

I also set about writing this function to be generally useful.

Organization of My Notes

The following represents most of the hierarchy of my notes; with the words in parentheses indicating the tags.

  • Character (#characters)
    • Attributes (#attributes)
    • Assets (#assets)
    • Vows (#vows)
    • Legacy Tracks (#tracks #legacy)
  • Expedition Tracks (#tracks #expeditions)
  • Clocks (#clocks)
  • Connections (#connections)
  • Factions (#factions)
  • Spaceships (#spaceships)
  • Sectors (#sectors)
    • Sector
      • Systems (#systems)
        • Settlements (#settlements)
  • Sessions (#sessions)

Below each of those headlines are additional detailed headlines. Important in this is that all headlines inherit the tags of their ancestor headlines.

Within each headline, I might write properties; the character’s momentum attribute for example. Or the progress track for a vow.

There’s quite a lot of information that I might need to cross-index while playing.

The Functions of Summarization

I ended up writing two functions:

jf/org-mode/buffer-headline-tags
Create a list of all non-file level tags.
jf/org-mode/summarize-tags
Prompt for the tag or tags to summarize then render a summary of headlines that match those tags.

The summary is itself an Org-Mode 📖 document that shows each headline and the relevant properties; suitable for quick scanning.

jf/org-mode/buffer-headline-tags

The jf/org-mode/buffer-headline-tags function:

(defun jf/org-mode/buffer-headline-tags ()
    "Return a list of `org-mode' tags excluding filetags.

  In the present implementation, I'm relying on `denote'
  conventions.  However, by creating a function I'm hiding the
  implementation details on how I get that."

    ;; This is here to indicate the dependency
    (require 'denote)
    (let* ((all-tags
             (org-get-buffer-tags))
            (file-level-tags
              (denote-extract-keywords-from-path (buffer-file-name))))
      ;; Given that I want inherited tags and the filetags are
      ;; considered to be on all headlines, I want to remove those tags.
      (cl-reduce (lambda (mem el)
                   (if (member
                         (substring-no-properties (car el))
                         file-level-tags)
                     mem
                     (add-to-list 'mem el)))
        all-tags :initial-value '())))

jf/org-mode/summarize-tags

And the jf/org-mode/summarize-tags

(defun jf/org-mode/summarize-tags (&optional tags)
    "Create `org-mode' buffer that summarizes the headlines for TAGS.

This reducing function \"promotes\" the property drawer elements
to list elements while providing the same functionality of an
`org-mode' buffer.

Some of this could be accomplished with column/table declarations
but the headlines and content might not fit so well in the
buffer."
    (interactive (list
                   (completing-read-multiple
                     "Tags: "
                     (jf/org-mode/buffer-headline-tags) nil t)))

    (require 's)
    ;; With the given tags map the headlines and their properties.
    (let* ((prop-names-to-skip
             ;; This is a list of headline properties that I really
             ;; don't want to report.  I suspect some may be buffer
             ;; specific.  But for now, this should be adequate.
             ;;
             ;; Perhaps in later iterations we'll prompt for additional
             ;; ones to ignore.
             '("ID" "ALLTAGS" "FILE" "PRIORITY" "ITEM" "TIMESTAMP"
                "TIMESTAMP_IA" "CATEGORY" "TAGS"
                "BLOCKED" "TODO" "CLOSED"))
            (text-chunks
              ;; In using `org-map-entries' I can access inherited tags,
              ;; which I find structurally useful
              (org-map-entries
                ;; Yes this could be its own function but for now, we'll
                ;; leave it at that.
                (lambda ()
                  (let* ((h (org-element-at-point))
                          ;; Rebuild a terse header: depth, todo, title
                          ;; only
                          (header-text
                            (format
                              "%s%s %s\n"
                              (s-repeat
                                (org-element-property :level h) "*")
                              (if-let ((todo
                                         (org-element-property
                                           :todo-keyword h)))
                                (format " %s" todo)
                                "")
                              (org-element-property :title h)))

                          ;; Only select relevant properties, converting
                          ;; those properties into a list of strings.
                          (properties-text
                            (cl-reduce
                              (lambda (mem prop-value)
                                (if (member (car prop-value)
                                      prop-names-to-skip)
                                  mem
                                  (add-to-list 'mem
                                    (format "- %s :: %s"
                                      (car prop-value)
                                      (cdr prop-value))
                                    t)))
                              (org-entry-properties h)
                              :initial-value nil)))

                    ;; If we have properties we want to render, we'll
                    ;; have one format.
                    (if properties-text
                      (format "%s\n%s\n" header-text
                        (s-join "\n" properties-text))
                      header-text)))
                (s-join "|" tags)
                'file 'comment))
            ;; Let's have only one of these
            (buffer-name "*Org Mode Tag Summary*")
            (display-buffer-mark-dedicated t))

      ;; We've run this command again, so let's destroy what we had
      ;; and start anew.
      (when (get-buffer buffer-name) (kill-buffer buffer-name))
      (get-buffer-create buffer-name)
      (with-current-buffer buffer-name
        ;; Minimize the chatter of the mode-line
        (let ((mode-line
                (concat
                  (propertize (format "%s Tags: #%s"
                                ;; Show a lock icon
                                (char-to-string #xE0A2)
                                (s-join " #" tags))
                    'face 'mode-line-buffer-id)
                  "  "
                  (propertize
                    "C-c C-k to exit"
                    'face 'jf/mode-line-format/face-shadow))))
          ;; This came from `org-mode' so let's continue to keep it that
          ;; way.
          (org-mode)
          (insert (s-join "\n" text-chunks))
          ;; Let's not have the illusion that we're allowing ourselves
          ;; to edit this text
          (read-only-mode)
          (pop-to-buffer buffer-name
            `((display-buffer-in-side-window)
               (side . right)
               (window-width 72)
               (window-parameters
                 (tab-line-format . none)
                 (mode-line-format . ,mode-line)
                 (no-delete-other-windows . t))))))))

Conclusion

While I envisioned these functions to help facilitate Role Playing Game (RPG 📖) sessions, I know I’ll get use out of jf/org-mode/summarize-tags for some of my non-gaming notes.

Now, maybe instead of writing tools to help facilitate playing a game, I should sit down and play a game.

-1:-- Adding an Emacs Command to Summarize Headlines with Chosen Tags (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--May 19, 2024 07:50 PM

Irreal: Make Box

A very short quickie today.

The ever prolific Nicolas Rougier has another nice snippet for us. It’s a way of enclosing some text in a box. It’s a tiny bit of code but it can produce some very nice output. Take a look at the example at the end of his gist.

It’s another nice example of how flexible Emacs is and how it lets you adapt it to whatever you need it do be.

-1:-- Make Box (Post jcs)--L0--C0--May 19, 2024 04:24 PM

Magnus: Nix, cabal, and tests

At work I decided to attempt to change the setup of one of our projects from using

to the triplet I tend to prefer

During this I ran into two small issues relating to tests.

hspec-discover both is, and isn't, available in the shell

I found mentions of this mentioned in an open cabal ticket and someone even made a git repo to explore it. I posted a question on the Nix discorse.

Basically, when running cabal test in a dev shell, started with nix develop, the tool hspec-discover wasn't found. At the same time the packages was installed

(ins)$ ghc-pkg list | rg hspec
    hspec-2.9.7
    hspec-core-2.9.7
    (hspec-discover-2.9.7)
    hspec-expectations-0.8.2

and it was on the $PATH

(ins)$ whereis hspec-discover
hspec-discover: /nix/store/vaq3gvak92whk5l169r06xrbkx6c0lqp-ghc-9.2.8-with-packages/bin/hspec-discover /nix/store/986bnyyhmi042kg4v6d918hli32lh9dw-hspec-discover-2.9.7/bin/hspec-discover

The solution, as the user julm pointed out, is to simply do what cabal tells you and run cabal update first.

Dealing with tests that won't run during build

The project's tests were set up in such a way that standalone tests and integration tests are mixed into the same test executable. As the integration tests need the just built service to be running they can't be run during nix build. However, the only way of preventing that, without making code changes, is to pass an argument to the test executable, --skip=<prefix>, and I believe that's not possible when using developPackage. It's not a big deal though, it's perfectly fine to run the tests separately using nix develop . command .... However, it turns out developPackage and the underlying machinery is smart enough to skip installing package required for testing when it's turned off (using dontCheck). This is the case also when returnShellEnv is true.

Luckily it's not too difficult to deal with it. I already had a variable isDevShell so I could simply reuse it and add the following expression to modifier

(if isDevShell then hl.doCheck else hl.dontCheck)
-1:-- Nix, cabal, and tests (Post)--L0--C0--May 19, 2024 03:21 PM

Protesilaos Stavrou: Emacs: Denote file name components can be reordered

As part of the development work towards Denote version 3 (technically 3.0.0), we now provide a user option to change the order of the file name components. This feature empowers users to create file listings that make more sense to them. Want to have the identifier (date+time) at the end? No problem; no stress! Wish to place the signature after the title? Done!

The author of this new feature is long-time contributor Jean-Philippe Gagné Guay, to whom I am most thankful. Others contributed to the discussion that helped flesh out some of the technical details. As usual, I will record everything in the change log for the new version. Until then, here is a preview of what to expect.

The default order

By default, the Denote file-naming scheme stipulates this order, assuming all the components are present (the signature is an opt-in component):

IDENTIFIER==SIGNATURE--TITLE__KEYWORDS.EXTENSION

This arrangement is efficient and well-tested. If it works for you, there is no need to change anything.

Some alternative schemes

The new user option that sets the order of file name components is denote-file-name-components-order. Below are some permutations of it. First the code and right below it the corresponding file name:

(setq denote-file-name-components-order '(identifier signature title keywords))
;; => 20240519T07345==hello--this-is-the-title__denote_testing.org

(setq denote-file-name-components-order '(signature identifier title keywords))
;; => ==hello@@20240519T07345--this-is-the-title__denote_testing.org

(setq denote-file-name-components-order '(title signature identifier keywords))
;; => --this-is-the-title==hello@@20240519T07345__denote_testing.org

(setq denote-file-name-components-order '(keywords title signature identifier))
;; => __denote_testing--this-is-the-title==hello@@20240519T07345.org

Note that the identifier gets its own delimiter (@@). This only happens when the identifier is not the first item on the list.

All other field delimiters are preserved to keep things unambiguous.

Emergent possibilities

Remember that Denote can already be configured to not “sluggify” the file name components or to process them with user-defined functions. The details are covered at length in the manual. As such, it is possible to have file names like this:

20240519T07345==hElLo--This is the Title__denote_testing.org

A word of caution

Just because we provide the flexibility for those who need it, this does not mean that you have to change anything. If you do want to experiment, do it with files that are not critical long-term. Once you find what you like, stick with it and never modify it. The reason is that inconsistencies in file names make it harder for your future self to quickly retrieve the data they need.

Still in development; coming soon

From the outset Denote is designed to be an Emacs-y package par excellence, meaning that it is highly customisable and easily extensible. This new option opens up more possibilities for users to set up their own workflows with the tools we provide.

There is a lot more that will be available as part of version 3.0.0. I hope to have it ready by the end of June, to coincide with the 2nd anniversary of Denote.

Plus, I am also preparing the new consult-denote to provide an optional power-up to all relevant minibuffer interactions.

Stay tuned!

About Denote

Denote is a simple note-taking tool for Emacs. It is based on the idea that notes should follow a predictable and descriptive file-naming scheme. The file name must offer a clear indication of what the note is about, without reference to any other metadata. Denote basically streamlines the creation of such files while providing facilities to link between them.

Denote’s file-naming scheme is not limited to “notes”. It can be used for all types of file, including those that are not editable in Emacs, such as videos. Naming files in a consistent way makes their filtering and retrieval considerably easier. Denote provides relevant facilities to rename files, regardless of file type.

-1:-- Emacs: Denote file name components can be reordered (Post)--L0--C0--May 19, 2024 12:00 AM

Irreal: Texinfo

CleanPrinciple5, over at the Emacs subreddit, asks an interesting question. He wonders why texinfo isn’t more popular. If you are and always have been an Emacs user, you might find that a reasonable question. On the other hand, if you ever tried to use Info without Emacs the question is apt to elicit a, “Well duh!”

Here’s my answer to CleanPrinciple5’s question: The Info utility is basically unusable. In the old days, most of the (useful) GNU documentation was available only in Info format so I was forced to use a TCL application to read it. It was still horrible but at least it was usable. All that got better when I started using Emacs; its Info interface at least makes sense.

The Unix tradition is to use man pages for a summary of how to use a program and, when necessary, a longer document—in PDF these days—to describe details not appropriate for a man page. For example, the yacc man page described the command line options and some other details but the description of how to use it to generate parsers was left to a separate paper, typically in the documents directory or the printed manual. GNU combined those two approaches into texinfo documents.

It seemed like a win and would have been except for the brain dead Info reader. Even after moving to Emacs, Info documents still leave a bad taste in my mouth and I avoid them when I can. I’m sure many other users feel the same.

Still, they’re better than simple PDF documents because they’re indexed and hyperlinked and most of them have a section with the same information traditionally contained in the Man pages. All the information you need in a single document that should be easy to read but isn’t because the readers are terrible.

Update [2024-05-19 Sun 12:37]:
Techinfo → Texinfo. Also better distinguished between Info documents and the Texinfo language.

-1:-- Texinfo (Post jcs)--L0--C0--May 18, 2024 04:13 PM

Jeremy Friesen: A Quiet Morning of Practice to Address an Observed Personal Computering Workflow Snag

, afternoon I was pairing with a colleague. I was driving as she was helping navigate. Both of us were unfamiliar with the code-base, but she was more familiar with the technical domain. Sidenote She had done a bit of Angular, but quite a bit of React. Meanwhile, I was at hour 6 of my “break fixing” Angular journey.

Taking Private Time to Think

There were times where she would tell me “I need to orient to this code-base; I’m going to disconnect and re-join in 10 minutes.” In the moment, I thought that was a unique response, but I thought about it more: she needed time to use her tools to “load” a better working understanding of the project.

Awesome, I thought. I know the all too real sensation of “lack of control” when pairing. I learn code by moving through it. Sidenote See ’s post Modifying Emacs link-hint Package Open URL Behavior for Local Repositories for some glimpses of my process.

My Own Exploration

During one of those moments when she went to do her own research, I started down an exploratory thread that required me to have open four different files:

  • Component A
  • Component A’s button sub-component
  • Component B
  • Component B’s button sub-component

Why? Because Component A and Component B had similar functional expectations, but when I clicked the Component A button it would reset data on the web-page. I did not see that behavior when I clicked Component B’s button.

I started reading and refactoring, as I saw how these two components were constructed differently. I had my editor configured such that:

  • Component A in top left
  • Component A’s button in bottom left
  • Component B in top right
  • Component B’s button in bottom right

Most of the times when I’m coding, I have one or maybe two files open at the same time. This four-way split is very infrequent. But here I was using comparisons to help guide a refactor towards similarity.

Return to Pairing

And then my colleague hopped back on and she was ready to try something. In that moment, I realized “I haven’t learned how to save window state for later re-use.”

Not wanting to delay our pairing time, as we were coming up on 5pm on a Friday afternoon, I hid that context and opened a new one. Yet a little gremlin in the back of my head kept nagging me that I might lose that window context.

We continued pairing and got to a point where I had what I think to be enough to proceed on my own. Sidenote A problem I left for Jeremy to deal with.

Listening to that Little Gremlin

But I listened to that little gremlin and made a mental note to practice and learn two things:

  • How to swap the location of windows in Emacs 📖 .
  • How to save the configuration of windows in Emacs .

Swap Location of Windows

I’ve long used the Ace Window 📖 package to quickly jump from one window to another. I have also used Ace Window to flip the location of two windows that are the only windows in a frame.

So I knew there was a way to swap the location of one window with another window when there were more than two windows in the frame.

I explored the documentation a bit more, and found ace-swap-window, which can be invoked within the M-x ace-window command.

This would help me better reconstruct the aforementioned four window arrangement; as I could re-arrange later.

Save Configuration of Windows

The other issue, namely preserving windows state over the weekend required a different approach. I know that I can use M-x window-configuration-to-register, and did in the moment. However, in my experiments this was not as reliable as I’d hoped. Sidenote I got some buffer gone errors as I moved about my business.

As a regular subscriber of Sacha Chua’s blog, I had seen project-butler roll across my feed earlier this week. I did some reading, at it wasn’t quite what I was looking for. But it did mention (and remind me of) the Activities 📖 package.

I did some quick reading, and it looked like what I was after. So I installed it and started exploring.

And it is exactly what I want for that situation and perhaps others. It allows me to take a brief moment before switching contexts to consider bookmarking my current activity.

Conclusion

I’ve done a lot of pairing over the years and have often used pairing as a chance to learn other people’s approaches. I found it inspiring that in the moment of our pairing, my coworker asked and took a few breaks to further investigate. She came back with greater context and we moved quickly to isolating the specific problem.

I also took that time to observe that I had created a situation for myself that I wanted to learn how to better navigate, namely having a context that I wanted to “stash” and re-join our shared context.

I spent time reflecting on both of these moments and took time to practice and reflect on my observations.

-1:-- A Quiet Morning of Practice to Address an Observed Personal Computering Workflow Snag (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--May 18, 2024 03:40 PM

Jeremy Friesen: Modifying Emacs link-hint Package Open URL Behavior for Local Repositories

I use Org-Mode 📖 as my markdown format of choice. One feature that I use as I work through code is capturing to a current clock. Sidenote See Clocking Work Time (The Org Manual). My Custom Org-Mode Capture Function for Annotating Bad Code walks through some of that functionality; however I’ll go over a refined workflow.

Background

As part of my new role, I’m working on a code base that is new to me. It’s also in a language that I’m familiar with but by no means an expert: Javascript. And the framework chosen is Angular. All told a lot of new concepts.

I have a task at work that requires fixing a bug; and it’s confounding as it’s related to mutating state.

In order to orient, I use the capture to clock concept. In my projects file, I created a headline (e.g. “Working through UI Reset Bug”). Then I start a clock for that headline. And I begin my code exploration.

When I find a bit of code to consider, or that I want to “find again”, I invoke org-capture Sidenote See Capture (The Org Manual). and pick my “Capture to Clock”. Sidenote My “Capture to Clock” template is defined here. This grabs the current selected region and creates a link to the remote repository. I then add some more information about why it’s important and then complete the capture.

Prior to , I would also include a link to the local file. But I removed that. In part because I would often times export that headline as a PDF or Markdown and share with teammates; and they likely didn’t have those files on their machine.

I still wanted to open those code blocks locally in Emacs 📖 over opening the remote document. Which lead me down a bit of exploration.

I have for quite some time used the Link Hint package. I love it. I invoke M-x link-hint-open-link and am presented with several overlays of characters. Each overlay is position above a “link” that Emacs knows how to handle. Sidenote Which is a lot of things.

I type the character combination to select the link I want, and it opens the link in the “correct” application. In the case of https, it defaults to using my browser…until .

I added some advice that will intercept handling any Org-Mode link and test if the link’s URI is a Github URL.

If it is, I test if I have that repository on my local machine. And when I do, I jump to the local file.

Below is that code:

(defun jf/link-hint--apply (advised-function func &rest args)
  "Hijack opening `org-mode' URLs by attempting to open local file.

The ADVISED-FUNCTION is the original `link-hint--apply'.

The FUNC is the dispatched function for handling the link
type (e.g. `link-hint--open-org-link').

The ARGS are the rest of the ARGS passed to the ADVISED-FUNCTION."
  (if (and
       (eq 'link-hint--open-org-link func)
       (eq :open (caddr args)))
      (progn
        (if-let* ((url
                   (car args))
                  (_match
                   (string-match
                    (concat "^https://github.com/"
                            "\\([^/]+\\)/\\([^/]+\\)" ;; org/repo
                            "/[^/]+/[^/]+/" ;; blob/sha
                            "\\([^#]+\\)" ;; path to fil
                            "\\(#L\\([0-9]+\\)\\)?") ;; line number
                    url)))
            ;; Due to my present file structure I have some repositories
            ;; in ~/git/ and others in ~/git/sub-dir
            ;;
            ;; In most every case, the Github org and repo match the
            ;; remote URL.
            (let ((filename-without-org
                   (format "~/git/%s/%s"
                           (match-string 2 url)
                           (match-string 3 url)))
                  (filename-with-org
                   (format "~/git/%s/%s/%s"
                           (match-string 1 url)
                           (match-string 2 url)
                           (match-string 3 url)))
                  (line-number
                   (match-string 5 url)))
              (cond
               ((f-exists? filename-without-org)
                (progn
                  (find-file filename-without-org)
                  (when line-number
                    (goto-char (point-min))
                    (forward-line
                     (1- (string-to-number line-number))))))
               ((f-exists? filename-with-org)
                (progn
                  (find-file filename-with-org)
                  (when line-number
                    (goto-char (point-min))
                    (forward-line
                     (1- (string-to-number line-number))))))
               (t (funcall func args))))

          (funcall func args)))
    (apply advised-function func args)))

(advice-add 'link-hint--apply
            :around #'jf/link-hint--apply)

As always, there’s room for refactor. But this gets the job done.

Conclusion

When exploring a code-base, I’m finding it helpful to annotate my explorations and be able to refer back to those findings.

With this change, I can continue to do that without sharing documents that have extra chatter regarding “local links.”

I used the above process (with local links) to add my working notes to a Github issue. I intended for those notes to be a way-finding tools for other folks who might come along; or for someone who might help guide me in a more fruitful direction.

Post Script

I’ve also used the process of capturing to a clock to create blog posts as well as run-book pages. In fact, for this post I started a clock on the Link Hint Package heading and then used my capture process to grab the code.

-1:-- Modifying Emacs link-hint Package Open URL Behavior for Local Repositories (Post Jeremy Friesen (jeremy@takeonrules.com))--L0--C0--May 17, 2024 09:52 PM

Irreal: Renaming Git Files With Emacs Dired

Álvaro Ramírez has a really handy tip for renaming files under version control with wdired. If you don’t already know about wdired, stop whatever you’re doing and find out about it right now. It’s really magical. The TL;DR is that you can set a dired buffer to be editable and any changes you make—typically to the file names—will be reflected in the actual files. Mickey, over at Mastering Emacs, has a nice introduction.

Ramírez really likes being able to change file names with wdired but was unhappy that those changes weren’t reflected in git in a convenient way. Naturally he set out to remedy the situation. As he discovered, he needn’t have bothered. Wdired already has that handled: you just need to set dired-vc-rename-file non nil. Once you do that, git will show the files as renamed and ask you to commit the changes.

On the one hand, this is a small thing but Emacs, of course, makes it easy to do the right thing so even if your files are under version control—not just git—you can rename them and be sure that dired will inform your version control system of the changes. It makes wdired seem even more magical.

-1:-- Renaming Git Files With Emacs Dired (Post jcs)--L0--C0--May 17, 2024 03:44 PM

Alvaro Ramirez: Emacs: git rename, courtesy of dired

16 May 2024 Emacs: git rename, courtesy of dired

Emacs wdired is a beautiful thing. You turn a directory representation into an editable buffer and you can do some magic. By magic, I mean you can apply your favourite text-editing commands to a directory and do some file management.

Take, for example, batch-renaming. Turn wdired on via dired-toggle-read-only, use something like Magnar's multiple-cursors (or built-in keyboard macros) and commit via wdired-finish-edit (using the often-familar C-c C-c binding). You've now renamed multiple files as it if were any other text buffer. Pretty magical.

wdired.gif

One downside (or so I thought) is that wdired didn't automagically also take care of git renames for me, you know DWIM-style.

Every time I renamed anything via wdired and subsequently pulled up my trusty magit, I was a little sad it wasn't all just handled… The renamed files were seen as deleted, along with all the untracked counterparts.

rename-no-git.png

So, I set out to change this unacceptable state of affairs 😀. I started off by setting a breakpoint on wdired-finish-edit via edebug (see why this util is awesome).

I wanted to see what wdired-finish-edit did under the hood, which led me to dired-rename-file. As I stepped through the code, I spotted the dired-vc-rename-file variable, which does exactly what you think it does 🤦.

One setq later…

(setq dired-vc-rename-file t)

…and boom! From now on, renaming from dired does exactly what you would expect. Here's magit to prove it:

rename-with-git.png

lol. I was so fixated on "adding git rename support", that I forgot to first search the documentation.

While you can search for variables via the built-in describe-variable, I'm a fan of Wilfred's helpful equivalent: helpful-variable. Coupled with with your favourite completion framework (Abo Abo's ivy for me), it's as easy a fuzzy searching for anything you're after:

dired-vc-rename-file.png

This post is also at lmno.lol.

-1:-- Emacs: git rename, courtesy of dired (Post)--L0--C0--May 16, 2024 08:34 PM

James Dyer: Writing Elisp to Find Available Keybindings in a Sway Config

I thought I would provide an example of how I typically use elisp to make my life easier (well eventually - I still need to write the function after all!)

In the example below, I wanted to find a simple elispy method to determine which Sway keybindings are available, specifically those that utilize the $mod key (typically Mod4/Super). Running out of available $mod keybindings in Sway, and then realizing upon reload that I have inadvertently added a duplicate keybinding, can be quite frustrating.

When this happens, I usually then have to resort to scrolling through the config file or using `isearch` to determine which keybindings are available. This can be tedious and is made more difficult in that my keybindings are not listed alphabetically but are grouped by function.

I am having a similar problem with Emacs but that is where the package free-keys comes in handy, listing the free and available keybindings.


I initially wanted to create something similar to free-keys for Sway, but by parsing the Sway configuration file and generating a report.

A Sway config file is typically defined in the following format:

# Set Defaults
set $mod Mod4
set $left h
set $down j
set $up k
set $right l

# Key bindings
bindsym $mod+b exec toggle_waybar.sh
bindsym $mod+c exec screen-record.sh
bindsym $mod+e exec thunar ~/DCIM/Camera
bindsym $mod+m exec emacs
bindsym $mod+n exec firefox
bindsym $mod+p exec wl-color-picker
bindsym $mod+q kill
bindsym $mod+return exec $term

# Change window focus
bindsym $mod+Left focus left
bindsym $mod+Down focus down
bindsym $mod+Up focus up
bindsym $mod+Right focus right
bindsym $mod+$left focus left
bindsym $mod+$down focus down
bindsym $mod+$up focus up
bindsym $mod+$right focus right

For now, to keep things simple, I will focus solely on writing elisp to list the current Sway keybindings that use Mod4 or any alias for Mod4 in alphabetical order. Then, by visually scanning and leveraging the human brain’s familiarity with the alphabet, I can determine which keybindings are unused!

For example, my new elisp function could yield the following typical output for the sway snippet defined above:

Mod4+Down
Mod4+Left
Mod4+Right
Mod4+Up
Mod4+b
Mod4+c
Mod4+e
Mod4+h
Mod4+j
Mod4+k
Mod4+l
Mod4+m
Mod4+n
Mod4+p
Mod4+q
Mod4+return

Now I should be able to more easily gauge which bindings are not used.

Of course my full Sway config has many more Mod4 keybindings and when running the elisp function defined below I realised I now only have 3 alphabetical mappings left for $mod - eeek!, but at least I now know what they are and don’t have to go trawling through the config anymore.

…wait, um, just as a side note, while I’m editing this blog post, it has just occurred to me (pardon the pun), can I use occur, push to a buffer then re-order, mmmm….., well lets ignore that thought for now, I want to dig into some elisp, its a learning experience after all!!

Here is the elisp:

(defun swaywm-list-mod-bindsyms (path)
  "List all bindsyms that start with $mod or its resolved value in the SwayWM config file at PATH."
  (interactive "fSway config file path: ")
  (with-temp-buffer
    (insert-file-contents path)
    (goto-char (point-min))
    (let ((vars nil)
          (bindsyms nil))
      ;; Collect variable definitions
      (while (re-search-forward "^set \\$\\([a-zA-Z0-9_]+\\) \\(.*\\)$" nil t)
        (let ((var (match-string-no-properties 1))
              (value (match-string-no-properties 2)))
          (setq vars (cons (cons var value) vars))))
      ;; Prepare to translate $mod and other keys
      (let* ((mod (cdr (assoc "mod" vars)))
             (left (cdr (assoc "left" vars)))
             (down (cdr (assoc "down" vars)))
             (up (cdr (assoc "up" vars)))
             (right (cdr (assoc "right" vars)))
             (mod-re (format "\\(%s\\|$mod\\)" (regexp-quote mod))))
        (goto-char (point-min))
        ;; Collect all bindings that start with $mod or its resolved value
        (while (re-search-forward (format "^bindsym %s\\+\\([^ ]+\\) \\(.*\\)$" mod-re) nil t)
         (let* ((mod-key (match-string-no-properties 1))
                 (action (match-string-no-properties 2))
                 (full-key (concat mod "+" action)))
            ;; Replace variable references in keys
            (setq full-key (replace-regexp-in-string "\\$left" left full-key))
            (setq full-key (replace-regexp-in-string "\\$down" down full-key))
            (setq full-key (replace-regexp-in-string "\\$up" up full-key))
            (setq full-key (replace-regexp-in-string "\\$right" right full-key))
            ;; Replace $mod with the actual mod key or keep as $mod for clarity
            (setq full-key (replace-regexp-in-string "\\$mod" mod full-key))
            ;; Collect the key-action pair
            (setq bindsyms (cons (format "%s" full-key) bindsyms)))))
      ;; Return reversed to maintain order
      (mapconcat 'identity (sort (nreverse bindsyms) 'string-lessp) "\n"))))

and to call just by:

(swaywm-list-mod-bindsyms "~/.config/sway/config.d/default")

or interactively.

I might evolve this a little more over the coming weeks (and actually maybe this is where occur and in-house Emacsing may at last have its limitations!). There is potentially a lot to do, for example some such improvements could be :

  • iterate over multiple files to accommodate a typical distributed Sway configuration set
  • show bindings that are free - just like free-keys
  • make more generic for multiple different types of config files containing bindings such as :
    • hyprland
    • other tiling window managers that typically define bindings in config files
    • emacs ?!
  • translate all aliases not just the common $left, $right, $up, $down

Here is a mini manual for this function :



Manual for swaywm-list-mod-bindsyms

List and sort bindsym commands from a SwayWM configuration file that are assigned to the mod key.

Synopsis

(swaywm-list-mod-bindsyms PATH)

Description

The `swaywm-list-mod-bindsyms` function parses a specified SwayWM configuration file and extracts all `bindsym` commands that begin with the modifier key (usually designated as `$mod` in SwayWM configurations). The resolution of the `$mod` key, along with other potential sway variables like `$left`, `$down`, `$up`, and `$right`, is dynamically handled according to their definitions within the file. The commands are then returned as a single string, with each bindsym command on a new line, sorted alphabetically.

This function is particularly useful for users of the Sway Window Manager who wish to quickly audit their keybindings associated with the mod key — a common requirement for optimizing workflow efficiency or for documentation purposes.

Parameters

  • `PATH`: Path to the SwayWM configuration file to be parsed.

Usage

To use `swaywm-list-mod-bindsyms`, call the function with the path to your SwayWM configuration file as its argument. For interactive use:

M-x swaywm-list-mod-bindsyms

When prompted, enter the full path to your SwayWM configuration file. The output will be shown, depending on the Emacs settings, either directly in the minibuffer (for shorter lists) or in a separate buffer for longer outputs.

Example

Assuming there’s a SwayWM configuration file located at `~/sway/config`, you would use the function as follows:

M-x swaywm-list-mod-bindsyms RET ~/sway/config RET

The returned value will be a sorted list of all `bindsym` commands associated with the `$mod` key or its resolved value (e.g., `Mod4`) from the file, neatly formatted for easy inspection.

Notes

  • This function assumes a standard formatting of the SwayWM configuration file, as deviations might affect the parsing logic.

  • The sorting is done in a case-sensitive alphabetical order, following Emacs’ `string-lessp` function conventions.

  • All modifications to variables like `$mod`, `$left`, `$down`, `$up`, and `$right` are dynamically accounted for based on their set values in the configuration file.


-1:-- Writing Elisp to Find Available Keybindings in a Sway Config (Post James Dyer)--L0--C0--May 16, 2024 05:25 PM

Irreal: Two World Views

The comments to my recent post Emacs And Modal Editing veered off to a discussion of why it’s not really possible to compare Emacs and Vim. Ag ibragimov expressed a point of view that I’ve long held: Emacs and Vim are really two different type of applications so it makes no sense to compare them.

I have always said that whether you embrace Vim or Emacs is a matter of your world view regarding editors. If you are looking for a fast, relatively light weight editor that excels in editing text and doesn’t bother with anything else, Vim is the correct choice. If you are looking for a Lisp Machine-like operating environment that serves as the center of your computing workflow, then you should choose Emacs. They’re both really good editors and which is right for you depends on that worldview.

All this was in the context of the editor wars, one of our oldest and most cherished holy wars. As I’ve said many times before, the editor wars can be funny and are often a source of entertainment but you shouldn’t take them seriously. These days, I doubt anyone does.

That brings us to this post by Odd-Distribution2887 from the Emacs subreddit. His worldview, I submit, is clearly that of the Vim user and he would probably be happier there. He appears to think that he should want to use Emacs but why should he? He should use whatever editor is in most accord with his workflow and disposition.

That’s not to say it’s a lifetime commitment. You can, and lots of folks do, change their mind and switch from one to the other. I spent at least 25 years with Vi(m) before I changed to Emacs. If you’re interested, you can read my two part series on why I changed here and here.

The takeaway from this post is that neither Vim nor Emacs is “better”. It’s simply a matter of what you’re looking for in an editor.

-1:-- Two World Views (Post jcs)--L0--C0--May 16, 2024 03:08 PM

Irreal: A New Emacs Maintainer

Richard Stallman writes to announce that he’s appointed a new Emacs maintainer. Not to worry, Eli Zaretskii is still a maintainer but Andrea Corallo will now be a co-maintainer along with Zaretskii and Stallman.

Corallo, of course, is the engineer who brought us native compilation, inarguably a tour de force.
Whatever you think of RMS, he does have a good record of appointing excellent Emacs maintainers. As I’ve written recently, heading up Emacs development takes a lot of knowledge and work and I’m sure Eli can use a bit of help.

I extend my best wishes to Corallo and am confident that he will be another in a long line of excellent maintainers. You can consider this post as my thanks to him in advance. As I’ve said repeatedly, we all owe those who step up to take on this task a huge amount of thanks.

-1:-- A New Emacs Maintainer (Post jcs)--L0--C0--May 15, 2024 05:14 PM

Anand Tamariya: Alternatives in GNU Emacs - Wheel of time

 



 Imagine you have a collection of images. Sometimes you want to view them in chronological order. However, chances are these pictures are not taken uniformly over a period of time. In other words, certain periods have more pictures than others. What kind of view can give you -

  • A complete picture of the whole timeline for which pictures are available
  • Time periods when you have more pictures than others
  • An easy way to drill down into any period of interest

This is wheel of time view in graph-brain.

Since this is just a view and not a rigid directory structure, it allows additional conveniences. e.g. if you only have a single picture in a particular year, clicking on the year will take you directly to the picture. You don't have to navigate through month and day.

Lessons Learnt

  • On Linux, you can change the modification time of a file to an older date via touch. Timestamp format is %Y%m%d%H%M.
 touch -t 202308281900 file
 
  • On Linux, you can obtain file modification time as a timestamp via ls command.
ls -l --time-style="+%Y%m%d%H%M"

 

 

 

 


-1:-- Alternatives in GNU Emacs - Wheel of time (Post Anand Tamariya (noreply@blogger.com))--L0--C0--May 15, 2024 03:44 PM

Isa Mert Gurbuz: Announcement: upver.el -- Interactively update your project dependencies

-1:-- Announcement: upver.el -- Interactively update your project dependencies (Post)--L0--C0--May 14, 2024 09:00 PM

Irreal: Dash

Over at the Emacs subreddit, meedstrom raises an interesting point: why is everyone hating on the Dash library? The short answer appears to be “dependencies”. Using the Dash library increases the number of dependencies and everyone knows that’s bad.

The slightly longer answer is that most of the Dash functions have—at least rough—analogs in the builtin libraries and thus using Dash increases the number of dependencies and everyone knows that’s bad.

I’m a huge fan of Magnar Sveen from his Emacs Rocks videos and other work on Emacs. I often find myself using his Dash library because its functions do just what I need doing in a simple way. That said, I’ve never once actually required the Dash library. That’s because it’s used by so many packages that it gets pulled in to just about every Emacs installation. Bad form, I know, but I don’t generally distribute my code so I never think to include Dash explicitly.

Increased dependencies or not, Dash is everywhere and no one that I know of has anything bad to say about its quality. So the argument against Dash boils down to: “Dependencies bad”. Meedstrom raises the heretical thought that maybe—at least in the case of Emacs—dependencies aren’t really a bad thing. He also notes that in order to get the same functionality you need not only the builtin libraries but another third party library (llama) anyway so the hating on Dash is really pointless.

You can read meedstrom’s post and decide for yourself but I find his argument convincing. In any event, Dash isn’t going anywhere so maybe all that negative energy should be redirected to a more deserving target such as … Well, you know.

-1:-- Dash (Post jcs)--L0--C0--May 14, 2024 04:15 PM

Bryan Murdock: How To Retroactively Annex Files Already in a Git Repo


UPDATE: With current versions of git, I no longer recommend git annex or git LFS unless you really need to store your large files on a separate server from your git repository. Just add your large files to git like any other file and when you clone, you can avoid downloading the full repository history with git clone --filter=blob:none and use git as normal.

Table of Contents

How To Retroactively Annex Files Already in a Git Repo

In my last post I talked about how surprisingly easy it is to use git annex to manage your large binary files (or even small ones). In this post, I'm going to show how hard it is to go back and fix the mistake you made when you decided not to learn and use git annex at the start of your project. Learn from my mistake!

When I started developing the website for my business, I figured that editing history in git is easy, and I could just check in binary files (like the images) for now and fix it later. Well, it was starting to get a little sluggish, and I had some bigger binary files that I wanted to start keeping with the website code, so I figured the time had come. Once I decided on git annex, it was time to go edit that history.

First Tries: filter-branch, filter-repo

There is a very old page of instructions for doing this using git filter-branch. The first thing I noticed when I tried that was this message from git:

WARNING: git-filter-branch has a glut of gotchas generating mangled history
         rewrites.  Hit Ctrl-C before proceeding to abort, then use an
         alternative filtering tool such as 'git filter-repo'
         (https://github.com/newren/git-filter-repo/) instead.  See the
         filter-branch manual page for more details; to squelch this warning,
         set FILTER_BRANCH_SQUELCH_WARNING=1.

Yikes! A warning like that from a tool (git) that is already known for its gotchas is one I decided to take seriously. Besides, I'm always down to try the new hotness, so I started reading about git-filter-repo. The more I read and experimented, even dug into the source code, the more I came to understand that it could not do what I needed, sadly. Maybe someone will read this and correct me.

Success with git rebase –interactive

Not seeing a nice pre-built tool or command that could do this for me, I set out to manually edit the repository history using good ol' git rebase --interactive. First, I had to find the all the binary files that are in the repo (not just the ones in the current revision). Here's how I did it:

# The --stat=1000 is so it doesn't truncate anything
git log --stat=1000 | grep Bin | sort | uniq > binary-files

Note the comment. Isn't it cute that git log truncates long lines even when stdout is not connected to your terminal? There are lots of little annoying gotchas like that throughout this process. Makes me miss mercurial, but don't worry, I will try not to mention mercurial again.

Now, you'll still have duplicates in binary-files because the other stuff that git log --stat spits out on each line. I personally used some emacs commands to remove everything but the filename from each line of the binary-files file, and then did a sort and uniq again.

Next, I had to find each commit that modified any of these binary files. Here's how I did that:

for file in $(cat binary-files); do
    git log --pretty=oneline --follow -- $file >> commits;
 done

Then I did another sort and uniq on that. Luckily there were only about 15 commits. Phew.

Next I tried to find the earliest commit in the list I had, but that was a pain (don't…mention…mercurial…), so I just ran git rebase --interactive and gave it one of the first commits I made in the repository. I actually used emacs magit to start the rebase, but the surgery required throughout the process made me drop to the command-line for most of it. magit did make it really easy to mark the 15 commits from my commits file with an e though.

OK, once the rebase got rolling I ran into a few different scenarios. Commits that added a new binary file, commits that deleted binary files, commits that modified binary files, and a commit that moved binary files.

Added binary files

When a binary file was added, git would act like I have always seen rebase interactive work, it would show the normal thing:

Stopped at 53fc550...  some commit message here
You can amend the commit now, with

  git commit --amend 

Once you are satisfied with your changes, run

  git rebase --continue

In that case I did this:

git show --stat=1000 # to see binary (Bin) files
git rm --cached <the-binary-files>
git add <the-binary-files> # git annex will annex them
git commit --amend
git rebase --continue

Easy peasy, as long as you have set up annex like my previous post explains so that annexing happens automatically.

Deleted binary files

When a binary file was deleted, git would throw up a message like this up:

$ git rebase --continue
[detached HEAD 130bcc4] banner on each page now
 21 files changed, 190 insertions(+), 42 deletions(-)
 create mode 100644 msd/webshop/static/webshop/img/common/adi-goldstein-EUsVwEOsblE-unsplash.jpg
 create mode 100644 msd/webshop/static/webshop/img/common/alexandre-debieve-FO7JIlwjOtU-unsplash.jpg
 delete mode 100644 msd/webshop/static/webshop/img/common/file-icons.png
 create mode 100644 msd/webshop/static/webshop/img/common/kevin-ku-w7ZyuGYNpRQ-unsplash.jpg
 create mode 100644 msd/webshop/static/webshop/img/common/levi-saunders-1nz-KjRdg-s-unsplash.jpg
 create mode 100644 msd/webshop/static/webshop/img/common/max-duzij-qAjJk-un3BI-unsplash.jpg
 create mode 100644 msd/webshop/static/webshop/img/common/nick-fewings-ZJAnGFg-rM4-unsplash.jpg
 create mode 100644 msd/webshop/static/webshop/img/common/umberto-jXd2FSvcRr8-unsplash.jpg
 create mode 100644 msd/webshop/static/webshop/img/common/yogesh-phuyal-mjwGKmwkDDA-unsplash.jpg
CONFLICT (modify/delete): msd/webshop/static/webshop/img/common/nick-fewings-ZJAnGFg-rM4-unsplash.jpg deleted in 90d71fb... refactored banners in pricing.css to reduce code duplication and modified in HEAD. Version HEAD of msd/webshop/static/webshop/img/common/nick-fewings-ZJAnGFg-rM4-unsplash.jpg left in tree.
error: could not apply 90d71fb... refactored banners in pricing.css to reduce code duplication
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 90d71fb... refactored banners in pricing.css to reduce code duplication

I guess in this case it was that I had added some new files too, so the message was extra verbose. The key message in all that was: "msd/webshop/static/webshop/img/common/nick-fewings-ZJAnGFg-rM4-unsplash.jpg deleted…" Here's what you do in this case:

git rm msd/webshop/static/webshop/img/common/nick-fewings-ZJAnGFg-rM4-unsplash.jpg
git diff --stat=1000 --staged # to find full paths for any Bin files
git restore --staged <binary-files>
git add <binary-files>
git diff --stat --staged # just to double check there are no Bin files now
git rebase --continue

Looks so simple (heh), but it took me a decent amount of web searching and experimentation to figure it out. All for you, dear reader, all for you.

Modified binary files

Here's one where I resized several images, git helpfully uttered:

$ git rebase --continue
[detached HEAD 7dfb28c] refactored banners in pricing.css to reduce code duplication
 4 files changed, 28 insertions(+), 75 deletions(-)
 create mode 100644 msd/webshop/static/webshop/img/common/connor-betts-QK6Iwzd5MhE-unsplash.jpg
 delete mode 100644 msd/webshop/static/webshop/img/common/nick-fewings-ZJAnGFg-rM4-unsplash.jpg
warning: Cannot merge binary files: msd/webshop/static/webshop/img/common/yogesh-phuyal-mjwGKmwkDDA-unsplash.jpg (HEAD vs. a90710f... scaled images down to max width of 1920 pixels)
warning: Cannot merge binary files: msd/webshop/static/webshop/img/common/umberto-jXd2FSvcRr8-unsplash.jpg (HEAD vs. a90710f... scaled images down to max width of 1920 pixels)
warning: Cannot merge binary files: msd/webshop/static/webshop/img/common/max-duzij-qAjJk-un3BI-unsplash.jpg (HEAD vs. a90710f... scaled images down to max width of 1920 pixels)
warning: Cannot merge binary files: msd/webshop/static/webshop/img/common/levi-saunders-1nz-KjRdg-s-unsplash.jpg (HEAD vs. a90710f... scaled images down to max width of 1920 pixels)
warning: Cannot merge binary files: msd/webshop/static/webshop/img/common/kevin-ku-w7ZyuGYNpRQ-unsplash.jpg (HEAD vs. a90710f... scaled images down to max width of 1920 pixels)
warning: Cannot merge binary files: msd/webshop/static/webshop/img/common/connor-betts-QK6Iwzd5MhE-unsplash.jpg (HEAD vs. a90710f... scaled images down to max width of 1920 pixels)
warning: Cannot merge binary files: msd/webshop/static/webshop/img/common/alexandre-debieve-FO7JIlwjOtU-unsplash.jpg (HEAD vs. a90710f... scaled images down to max width of 1920 pixels)
warning: Cannot merge binary files: msd/webshop/static/webshop/img/common/adi-goldstein-EUsVwEOsblE-unsplash.jpg (HEAD vs. a90710f... scaled images down to max width of 1920 pixels)
Auto-merging msd/webshop/static/webshop/img/common/yogesh-phuyal-mjwGKmwkDDA-unsplash.jpg
CONFLICT (content): Merge conflict in msd/webshop/static/webshop/img/common/yogesh-phuyal-mjwGKmwkDDA-unsplash.jpg
Auto-merging msd/webshop/static/webshop/img/common/umberto-jXd2FSvcRr8-unsplash.jpg
CONFLICT (content): Merge conflict in msd/webshop/static/webshop/img/common/umberto-jXd2FSvcRr8-unsplash.jpg
Auto-merging msd/webshop/static/webshop/img/common/max-duzij-qAjJk-un3BI-unsplash.jpg
CONFLICT (content): Merge conflict in msd/webshop/static/webshop/img/common/max-duzij-qAjJk-un3BI-unsplash.jpg
Auto-merging msd/webshop/static/webshop/img/common/levi-saunders-1nz-KjRdg-s-unsplash.jpg
CONFLICT (content): Merge conflict in msd/webshop/static/webshop/img/common/levi-saunders-1nz-KjRdg-s-unsplash.jpg
Auto-merging msd/webshop/static/webshop/img/common/kevin-ku-w7ZyuGYNpRQ-unsplash.jpg
CONFLICT (content): Merge conflict in msd/webshop/static/webshop/img/common/kevin-ku-w7ZyuGYNpRQ-unsplash.jpg
Auto-merging msd/webshop/static/webshop/img/common/connor-betts-QK6Iwzd5MhE-unsplash.jpg
CONFLICT (content): Merge conflict in msd/webshop/static/webshop/img/common/connor-betts-QK6Iwzd5MhE-unsplash.jpg
Auto-merging msd/webshop/static/webshop/img/common/alexandre-debieve-FO7JIlwjOtU-unsplash.jpg
CONFLICT (content): Merge conflict in msd/webshop/static/webshop/img/common/alexandre-debieve-FO7JIlwjOtU-unsplash.jpg
Auto-merging msd/webshop/static/webshop/img/common/adi-goldstein-EUsVwEOsblE-unsplash.jpg
CONFLICT (content): Merge conflict in msd/webshop/static/webshop/img/common/adi-goldstein-EUsVwEOsblE-unsplash.jpg
error: could not apply a90710f... scaled images down to max width of 1920 pixels
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply a90710f... scaled images down to max width of 1920 pixels

The trick to fixing this is to notice which commit it's trying to let you edit, which is in the last line of that message, and then checkout that version of each of the unmerged binary files it mentions, like so:

git status # to get the names of the unmerged binary files
git checkout a90710f <filenames>

Now you can do the same thing you did for the deleted file:

git restore --staged <filenames>
git add <filenames>
git diff --stat --staged # just to double check there are no Bin files now
git rebase --continue

Moved binary files

When I ran git log --follow to find all the commits that modified binary files, it flagged one where I had moved them. I'm not sure I actually had to edit that commit and I wonder if I would not have had this weird situation if I had not edited it. But for completeness, here's what I saw. Git rebase stopped to let me edit the commit and git annex printed out this message for every file that was moved:

git-annex: git status will show <filename> to be modified, since content availability has changed and git-annex was unable to update the index. This is only a cosmetic problem affecting git status; git add, git commit, etc won't be affected. To fix the git status display, you can run: git update-index -q --refresh <filename>

Sounds…quite weird. But git rebase would not continue until I did run the suggested command:

git update-index -q --refresh <filenames>
git rebase --continue

Dealing with Tags

Once the rebase was done I noticed that the tags I had all still pointed to the original commits. Oops. A quick internet search led me to this post about rebasing and moving tags to the new commits (written by a former co-worker, it just so happens). Too bad I didn't look for that before I rebased. I thought about redoing the whole rebase, but in the end I just wrote my own quick python script (using snippets from Nacho's) to take care of my specific situation. Here it is:

#! /usr/bin/env python
from subprocess import run, PIPE

tags = run(['git', 'show-ref', '--tags'],
           stdout=PIPE).stdout.decode('utf-8').splitlines()

tags_with_comments = {}
for tag in tags:
    tag_hash, tag_name = tag.split(' ')
    tag_name = tag_name.split('/')[-1]
    comment = run(['git', '--no-pager', 'show', '-s',
                   '--format=%s', tag_hash],
                  stdout=PIPE).stdout.decode('utf-8').splitlines()[-1]
    print(f'{tag_name}: {comment}')
    tags_with_comments[tag_name] = comment

commits = run(['git', 'log', '--oneline'],
              stdout=PIPE).stdout.decode('utf-8').splitlines()

for tag_name in tags_with_comments:
    for c in commits:
        commit_hash = c.split(' ')[0]
        comment = c.split(' ')[1:]
        comment = ' '.join(comment)
        if comment == tags_with_comments[tag_name]:
            run(['git', 'tag', '--force', tag_name, commit_hash])

Clean Up and Results

Well, with all that done, it was time to see how it all turned out. My original git repo was sitting at about 1.4 GB. This new repo was…3 GB!? Something wasn't right. Here are some steps I took to clean it up after making sure there weren't any old branches or remotes laying around:

git clean -fdx
git annex fsck
git fsck
git reflog expire --verbose --expire=0 --all
git gc --prune=0

The git clean command showed that I had a weird leftover .git directory in another directory somehow, so I deleted that. I don't think the fsck commands really did anything, but the gc definitely did. Size was now down to 985 MB. Much better. Wait a minute, what if I did a git gc on the original repo? It's size went down to 984 MB. Oh shoot. I guess it makes sense though, if both git and git annex are storing full versions of each binary file they would end up the same size. The real win is the faster git operations, especially clones.

A local git clone now happens in the blink of an eye, and its size is only 153 MB. Now, that's a little unfair because it doesn't have any of the binary files. After a git annex get to get the binary files for the current checkout it goes up to 943 MB. Not a huge savings, but it only gets better as time goes on and more edits happen. Right? This was all worth it, wasn't it?!

Let me know in the comments if this is helpful, hurtful, or if I did this totally wrong.

-1:-- How To Retroactively Annex Files Already in a Git Repo (Post Bryan (noreply@blogger.com))--L0--C0--May 14, 2024 03:45 PM

Unwound Stack: Type Theory & Reasoning About Code

Using the Feynman Technique to learn about Type Theory

-1:-- Type Theory &amp; Reasoning About Code (Post Michael (sp1ff@pobox.com))--L0--C0--May 14, 2024 07:00 AM

Unwound Stack: The Lambda Calculus

We'll begin at the beginning: Church's Lambda Calculus

-1:-- The Lambda Calculus (Post Michael (sp1ff@pobox.com))--L0--C0--May 14, 2024 07:00 AM

Sacha Chua: 2024-05-13 Emacs news

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Hacker News, lobste.rs, kbin, programming.dev, lemmy, communick.news, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

-1:-- 2024-05-13 Emacs news (Post Sacha Chua)--L0--C0--May 13, 2024 04:16 PM

Irreal: Vi(m) Navigation Without Modality (Sort Of)

Like many of us, James Dyer sometimes has to use Vim even though he’s a dedicated Emacs user. He does this often enough that he’d like to maintain a bit of muscle memory for the Vim navigation commands. He doesn’t want a full blown Vim emulation like Evil or even something like Meow so he rolled his own. His solution is (really) simple: he merely made a few remappings:

  • Ctrl+bMeta+h
  • Ctrl+fMeta+l
  • Ctrl+nMeta+j
  • Ctrl+pMeta+k

It would, of course, be easy to expand this. You do need to find other bindings for the ones yours stealing but at least in this case Dyer says he didn’t use them much anyway.

I know I’m always going on about muscle memory and the problems I have with trying to use two editors but even I have to use Vim sometimes and when I do I may struggle to remember the bindings but it’s mostly pretty easy. What I don’t want to do is rearrange all my Emacs bindings just to ease my occasional forays into Vim land. Of course, my travels over the wall are infrequent so perhaps I’d feel differently if I had to make the trip more often.

In any event, Dyer’s solution may point the way to a solution for those of you have similar problems. Take a look at his post if you, too, occasionally live in two worlds.

-1:-- Vi(m) Navigation Without Modality (Sort Of) (Post jcs)--L0--C0--May 12, 2024 03:42 PM

James Dyer: Emacs Vim Navigation Without Evil



Introduction

Every now and then I find it necessary to use Vim! (mainly for work) - was that clickbait on an Emacs blog?! 😀

I therefore like to have the muscle memory for basic Vim navigation keybindings already built up as that for me is half the battle when using Vim and I also appreciate the efficiency and natural feel of the navigation keybindings `hjkl`.

In light of this I have decided to incorporate this Vim navigation paradigm into Emacs. My muscle memory is already fully baked and attuned to the Emacs default navigation keys so why not learn something new? with the benefits of an already oven warm familiar feel if I do have to use Vim and maybe anything else that might support vim keybindings (which is not uncommon), for example pacseek and seemingly most browser key navigation add-ons.

Of course before delving a little further I shall have to talk about the herd of elephants in the room… and that is the current plethora of Emacs modal editing packages, such as `evil-mode`, `Meow` et al. They have been developed to bridge the modal gap, for example, `evil-mode` is a comprehensive emulation layer that replicates Vim’s keybindings and modes, offering Vim users a more familiar experience within Emacs. On the other hand, `Meow` presents a more streamlined approach, designed with simplicity in mind, aiming to provide the efficiency of modal editing without mirroring Vim’s functionality in its entirety.

My Non-Modal Solution

Despite the advantages of these packages, I wanted to find a method that involves neither the full adoptation of `evil-mode` nor the simplified modal editing of `Meow` or anything similar. My goal here is to utilize Vim’s `hjkl` navigation keys within Emacs in a non-modal context, thereby retaining Emacs’s modeless editing advantages while enjoying the familiarity and comfort of Vim’s navigation system.

To achieve this, I tapped into the power of Emacs’s keybinding customization capabilities.

Simply, I reassigned the original Emacs functions defined by `M-hjkl`. This allows a Vim-style navigation in Emacs’s default mode-less environment using the following keybindings:

(bind-key* (kbd "M-h") #'backward-char)
(bind-key* (kbd "M-j") #'next-line)
(bind-key* (kbd "M-k") #'previous-line)
(bind-key* (kbd "M-l") #'forward-char)

The original functions bound to these keys were seldom used in my workflow so I didn’t mind replacing them:

  • `M-h` (`mark-paragraph`) found a new home at `M-s h`.
  • `M-j` (`default-indent-new-line`), which I rarely used, was easily replaced by simply using `tab` or `C-i`.
  • `M-k` (`kill-sentence`), another feature I never utilized, was made redundant by the use of `kill-line`.
  • `M-l` (`downcase-word`), also rarely used, didn’t find a new binding as it wasn’t needed in my daily tasks.

The only disadvantage is that it requires holding down the Meta (or Alt) key with my left hand, but ergonomically that doesn’t seem too bad as that will be the only requirement for my left hand as my right will be busy with the navigation.

Conclusion

While `evil-mode` and `Meow` offer powerful modal editing solutions that closely mimic or simplify Vim’s interface within Emacs, my approach demonstrates an alternative path. By creatively reassigning keybindings, I have integrated Vim’s efficient navigation into Emacs without adopting a modal editing framework, blending the best of both worlds to enhance my text editing efficiency. This approach underscores the adaptability of Emacs, proving it to be an incredibly versatile tool that can accommodate a wide range of user preferences and workflows.

-1:-- Emacs Vim Navigation Without Evil (Post James Dyer)--L0--C0--May 11, 2024 08:40 PM

Irreal: Sunrise, Sunset

No, not that Sunrise, Sunset. This one. Today’s post is just a quickie on some already plowed ground. Eight years ago, I wrote about finding the times of sunrise and sunset from within Emacs. I thought it was sort of interesting but never did anything about it.

Now Charles Choi has resurrected the subject. He likes to walk in the evening and finds it useful to know when the sun will set. It turns out that this is easy to do from within Emacs. One more reason to never leave the comfort of our favorite editor.

The only wrinkle is that you have to know the latitude/longitude of your current location. On macOS, that’s easy to find. Just open the Map application and your current location will be marked on the map. Click on that and it brings up an information block that, among other things, tells you the lat/lon coordinates. I’m sure the process is similar on Google maps or whatever you use on your system.

I don’t really have any need for this because:

  1. I seldom care about the comings and goings of the sun
  2. The sunrise/sunset times are available from the Apple Weather app, which is just a hotkey (via Alfred) away.

Still, it’s good to be able to do as much as possible in Emacs so I added the lat/lon to my init.el and I can now query the sun’s appearance and disappearance right from Emacs. It’s especially nice to do it from the Org agenda. See Choi’s post for the details.

-1:-- Sunrise, Sunset (Post jcs)--L0--C0--May 11, 2024 03:22 PM

Emacs APAC: Announcing Emacs Asia-Pacific (APAC) virtual meetup, Saturday, May 25, 2024

This month’s Emacs Asia-Pacific (APAC) virtual meetup is scheduled for Saturday, May 25, 2024 with BigBlueButton and #emacs on Libera Chat IRC. The timing will be 1400 to 1500 IST. The meetup might get extended by 30 minutes if there is any talk, this page will be updated accordingly. If you would like to give a demo or talk (maximum 20 minutes) on GNU Emacs or any variant, please contact bhavin192 on Libera Chat with your talk details:
-1:-- Announcing Emacs Asia-Pacific (APAC) virtual meetup, Saturday, May 25, 2024 (Post)--L0--C0--May 11, 2024 12:01 AM

Tony Zorman: Leveraging LaTeX in Anki

Posted on 2024-05-11  ·  5 min read  ·  , ,

Recently, I’ve been quite invested in learning maths with Anki. Sadly, there are relatively few good resources out there on how to twist Anki’s LaTeX pipeline into something generally acceptable, without having to rely on MathJax. As such, this post collects some bits and pieces from all over, hoping to weave them together into something coherent.

1Note that I will only concerns myself with “how to make LaTeX work to an acceptable degree”, instead of “how to write mathematics notes”. The latter is extremely dependent on the desired outcome, types of maths that one studies, experience of the person writing the notes, and so on. Too many facets for me—or anyone, really—to give blanket recommendations.

A lot of things here are rehashes of Unhewn Thought’s post about the same thing, as well as the official Anki documentation on the matter. As always, this post is mostly about reminding myself about some details in two months’ time.

MathJax is not enough

Anki fully supports MathJax as a backend for its LaTeX previewing system. This is great for most users: MathJax is trivial to set up, and fits snugly into the generated HTML—there’s a reason it’s used so widely all over the internet!

However, especially for personal notes like these, MathJax might be amongst the worst solutions for me: its macro support is awkward at best, and since it only implements maths macros, many of the packages that I’m using on a daily basis are not available at all. I for one am certainly not going to rewrite my bespoke style file, just so I get something that half-works with MathJax, when I could be using the real thing instead!

Actually writing LaTeX

To get this out of the way: I have absolutely no opinions on how to write LaTeX with Anki. I haven’t ever actually composed a note inside of the program, and I’m doubtful that I ever will.

Instead, I write all of my notes in Org and then export them via anki-editor. This is very convenient, as it means that I have all of the key bindings and snippets available that I normally write LaTeX with.2 Plus, rough edges that one would otherwise have to care about, like }} inside of maths closing the current cloze deletion, are solved completely by anki-editor without me even having to think about them.3 As an added bonus, Org mode’s new LaTeX preview functionality is just fantastic, and makes crafting notes quite fun!

Aligning fragments vertically

One thing that MathJax does very well is its pixel-perfect alignment of LaTeX fragments. In contrast, the default alignment is really rather bad. It can, however, be somewhat improved by using CSS to vertically centre the image:

img[src*="latex"] {
  vertical-align: middle;
}

It looks like this:4

A note with approrimately centred LaTeX

Very far from perfect, but readable. Since this seems to be the only drawback with not using MathJax, I’ll take it. Content over form—at least in this case.

Preview generation with preview.sty

The preview package is specifically designed for these kinds of previews. Originally written for AUCTeX, it has made its way into all TeX distributions that I know of. In addition to being used all over Emacs, preview is being employed by quite a few other programs as well.

Amongst intangible benefits such as familiarity, I chose preview mostly because it “correctly” tightens display maths environments not just to the maths itself, but to the whole line. Operationally this means that display maths is automatically centred on my Anki card, even if the rest of the text is left-aligned.5

To use preview.sty in this way, one simply has to include it Anki’s LaTeX preamble—C-S-n and then “Options”—and wrap the whole document (i.e., the formula being rendered) with the preview environment:

%%% Header
\documentclass{article}
\usepackage[active,tightpage]{preview}
\begin{document}
% Remove extra space above display maths.
\setlength{\abovedisplayskip}{0pt}
\begin{preview}
%%% Footer
\end{preview}
\end{document}

For this to work smoothly, I also highly recommend to enable SVG support, and to tweak Anki’s LaTeX compilation pipeline. The handy Edit LaTeX build process addon can be used for that purpose. My svgCommands settings look like this:6

 "svgCommands": [
      [
          "latex",
          "-interaction=nonstopmode",
          "tmp.tex"
      ],
      [
          "dvisvgm",
          "--page=1-",
          "--optimize",
          "--clipjoin",
          "--relative",
          "--bbox=preview",
          "--no-fonts",
          "tmp.dvi",
          "-o",
          "tmp.svg"
      ]
  ]

The generated LaTeX might be too small, though fixing this just involves a small CSS modification to the latex class:

.latex {
  zoom: 160%;
}

If you want to dig deeper into how Anki generates maths, I recommend looking at latex.py, latex.rs, and the .tex Generation section in Unhewn Thought’s blog post.7

Complete preamble

My complete preamble is not much larger than what I have shown above; it merely also includes my personal style file.

%%% Header
\documentclass{article}
\usepackage[type=org,math=fancy]{$HOME/.tex/styles/style}
\usepackage[active,tightpage]{preview}
\usepackage{xcolor}
\begin{document}
\setlength{\parindent}{0in}
\setlength{\abovedisplayskip}{0pt}
\begin{preview}
%%% Footer
\end{preview}
\end{document}

Conclusion

With these relatively straightforward tweaks, Anki’s LaTeX integration is really plug and play—I can just use my whole style file as-is, no extra care needed. Nice.


  1. {-} I will not waste the reader’s time with an introduction of what Anki is or why spaced repetition is useful; other people have done a much better job at that than I ever could.↩︎

  2. See, for example, here, here, here, or here.↩︎

  3. For the specific example of maths and cloze deletions, one needs to set anki-editor-break-consecutive-braces-in-LaTeX. This should probably be the default—and perhaps it will be in the future!↩︎

  4. {-} As you can see, I haven’t bothered with fancy CSS to make the cards pretty. Content over form.↩︎

  5. One could solve this by making anki-editor output an extra div that it only wraps display maths with—and I have done that in the past—but using preview.sty feels much cleaner to me.↩︎

  6. {-} For compatibility reasons with all of my papers (and other notes) I use latex here. In case you are not bound by these restrictions—or more adventurous than me—I would recommend you use lualatex instead.↩︎

  7. {-} It should be noted that Anki—thankfully—caches previews by hashing their contents, and reuses the same SVG every time it encounters a LaTeX fragment with the same hash. Thus, if you want to completely regenerate everything, just delete the respective directory. For me, this is ~/.local/share/Anki2/User 1/collection.media/.↩︎

-1:-- Leveraging LaTeX in Anki (Post)--L0--C0--May 11, 2024 12:00 AM

Protesilaos Stavrou: Emacs: notmuch-indicator version 1.2.0

This is a simple package that renders an indicator with an email count of the notmuch index on the Emacs mode line. The underlying mechanism is that of notmuch-count(1), which is used to find the number of items that match the given search terms. In practice, the user can define one or more searches and display their counters. These form a listing which realistically is like: @50 😱1000 💕0 for unread messages, bills, and love letters, respectively.

Below are the release notes.


This version brings quality-of-life refinements to an already stable package.

More styles for the indicator

The user option notmuch-indicator-args always accepted an optional face that was applied to the label that accompanies the given counter. The same can now be done for the counter itself. To be concrete:

LABEL       COUNTER

[unread]    10

Or what you will most likely prefer:

[U] 10

The technicalities of how to set those up are covered in the documentation string of notmuch-indicator-args. Here is a variant of what I use (I actually have custom faces):

(setq notmuch-indicator-args
      '(( :terms "tag:unread and tag:inbox"
          :label "[A] "
          :label-face font-lock-string-face
          :counter-face font-lock-string-face)
        ( :terms "tag:unread and tag:inbox and not tag:package and not tag:coach"
          :label "[U] "
          :label-face font-lock-type-face
          :counter-face font-lock-type-face)
        ( :terms "tag:unread and tag:package"
          :label "[P] "
          :label-face font-lock-function-name-face
          :counter-face font-lock-function-name-face)
        ( :terms "tag:unread and tag:coach"
          :label "[C] "
          :label-face font-lock-preprocessor-face
          :counter-face font-lock-preprocessor-face)))

For backward-compatibility, :face has the same meaning as :label-face.

Control exactly where the indicator is placed

This is for advanced users, though I am happy to help you set it up if you are interested.

By default, the indicator (the block with all the email counters) is appended to the mode line. It thus shows up on the mode line of the current window but also on that of all inactive windows.

To control exactly where the indicator is placed set the user option notmuch-indicator-add-to-mode-line-misc-info to nil. This will no longer display the indicator on the mode line. Then do any of the following:

  1. If you are using the built-in tab-bar-mode, add the notmuch-indicator-tab-bar-format to the list of tab-bar-format. Like this:

    (setq tab-bar-format
          '( tab-bar-format-history
             tab-bar-format-tabs
             tab-bar-separator
             tab-bar-format-add-tab
             tab-bar-format-align-right
             notmuch-indicator-tab-bar-format ; here it is
             tab-bar-separator
             tab-bar-format-global))
    
  2. Add the notmuch-indicator-mode-line-construct to the mode-line-format. This allows you to put it wherever you want, such as before the buffer name.

  3. Create your own mode line construct with the extra logic you need and then add it anywhere you want in the mode-line-format list. This is what I do to display the indicator only on the active mode line:

    ;; Here is my variant of the indicator.
    (defvar-local prot-modeline-notmuch-indicator
        '(notmuch-indicator-mode
          (" "
           (:eval (when (mode-line-window-selected-p)
                    notmuch-indicator--counters))))
      "The equivalent of `notmuch-indicator-mode-line-construct'.
    Display the indicator only on the focused window's mode line.")
    
    ;; And here I format my modeline to place everything exactly where I want.
    (setq-default mode-line-format
                  '("%e"
                    ;; ... things before
                    prot-modeline-notmuch-indicator
                    ;; ... things after
                    prot-modeline-misc-info))
    

Escape queries to avoid shell errors

We now escape all special shell characters before running the notmuch shell command to get the number of emails. This way, more complex queries are read without any issue. Thanks to Sébastien Delafond for the contribution, which was done in pull request 4: https://github.com/protesilaos/notmuch-indicator/pull/4.

The change is small, meaning that Sébastien does not need to assign copyright to the Free Software Foundation.

Bind call to the notmuch binary to the user’s home directory

The reason for this is explained by @shimeike (MikeS): https://github.com/protesilaos/notmuch-indicator/issues/2#issuecomment-1819853525. In short, we want to make sure we use the correct binary while visiting files via TRAMP.

Also thanks to Mohamed Suliman for corroborating the findings discussed therein.

-1:-- Emacs: notmuch-indicator version 1.2.0 (Post)--L0--C0--May 11, 2024 12:00 AM

Arne Bahlo: Emacs Config From Scratch, Part 3: LSP & Tree-sitter

This is Part 3 of my series, Emacs Config From Scratch1, where I create my perfect editor using Emacs. In this post, I’ll do some housekeeping, set up LSP2, language modes for Rust, Go, TypeScript and Zig, and add search.

Table Of Contents

Housekeeping

The first thing I want to do is make the UI titlebar match the Emacs theme and hide the file icon:

(use-package emacs
  :init
  (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t))
  (add-to-list 'default-frame-alist '(ns-appearance . light))
  (setq ns-use-proxy-icon  nil)
  (setq frame-title-format nil))

The next thing is automatically loading $PATH from my shell using exec-path-from-shell—this is important so Emacs can find our installed binaries, e.g. for language servers:

(use-package exec-path-from-shell
  :init
  (exec-path-from-shell-initialize))

Another problem I ran into was writing square brackets when on the MacBook, as it would interpret the keys as Meta-5/Meta-6. I fixed that by updating the keybindings from Part 1:

(use-package emacs
  :init
  (when (eq system-type 'darwin)
    (setq mac-command-modifier 'super)
    (setq mac-option-modifier nil)
    (setq mac-control-modifier nil)))

I like to keep most of my code at 80 characters, so let’s add a ruler:

(use-package emacs
  :init
  (setq-default fill-column 80)
  (set-face-attribute 'fill-column-indicator nil
                      :foreground "#717C7C" ; katana-gray
                      :background "transparent")
  (global-display-fill-column-indicator-mode 1))

Finally, we want to store backup files in ~/.saves instead of next to the file we’re saving:

(use-package emacs
  :config
  (setq backup-directory-alist `(("." . "~/.saves"))))

LSP

Let’s install company-mode first, for auto-completion:

(use-package company-mode
  :init
  (global-company-mode))

Now we configure the built-in LSP package eglot:

(use-package emacs
  :hook (zig-mode . eglot-ensure)
  :hook (rust-mode . eglot-ensure)
  :hook (go-mode . eglot-ensure)
  :hook (typescript-mode . eglot-ensure)
  :general
  (leader-keys
    "l" '(:ignore t :which-key "lsp")
    "l <escape>" '(keyboard-escape-quit :which-key t)
    "l r" '(eglot-rename :which-key "rename")
    "l a" '(eglot-code-actions :which-key "code actions")))

This runs eglot-ensure in languages we have language servers installed for. It also sets up SPC l r to rename a symbol and SPC l a to prompt for code actions.

Tree-sitter

We’ll use treesit-auto to automatically install and use tree-sitter major modes:

(use-package treesit-auto
  :custom
  (treesit-auto-install 'prompt)
  :config
  (treesit-auto-add-to-auto-mode-alist 'all)
  (global-treesit-auto-mode))

This is handy because it doesn’t require us to think about using e.g. zig-ts-mode instead of zig-mode, it handles everything for us.

Language support

Next, we install all language modes we need:

(use-package markdown-mode
  :config
  (setq markdown-fontify-code-blocks-natively t))
(use-package zig-mode
  :general
  (leader-keys
    "m" '(:ignore t :which-key "mode")
    "m <escape>" '(keyboard-escape-quit :which-key t)
    "m b" '(zig-compile :which-key "build")
    "m r" '(zig-run :which-key "run")
    "m t" '(zig-test :which-key "test")))
(use-package rust-mode
  :general
  (leader-keys
    "m" '(:ignore t :which-key "mode")
    "m <escape>" '(keyboard-escape-quit :which-key t)
    "m b" '(rust-compile :which-key "build")
    "m r" '(rust-run :which-key "run")
    "m t" '(rust-test :which-key "test")
    "m k" '(rust-check :which-key "check")
    "m c" '(rust-run-clippy :which-key "clippy")))
(use-package go-mode)
(use-package gotest
  :general
  (leader-keys
    "m" '(:ignore t :which-key "mode")
    "m <escape>" '(keyboard-escape-quit :which-key t)
    "m t" '(go-test-current-project :which-key "test")
    "m r" '(go-run :which-key "run")))
(use-package typescript-mode)

I’m using SPC m to change based on major-mode, e.g. means SPC m t means test in most programming modes, but won’t exist in markdown-mode.

Search

Sometimes we don’t know what file we’re looking for, so let’s add rg.el to help us find it:

(use-package rg
  :general
  (leader-keys
    "f" '(rg-menu :which-key "find")))

This opens a Magit-like menu and allows you to search in various modes (dwim, regex, literal, etc.).

Wrapping up

Opening a Zig project now looks like this3; see also the final init.el:

A screenshot of Emacs with a dark theme showing Zig code and a context menu for code actions

I’m going to switch to Emacs as my primary editor and tune it further in the coming weeks. In the next part, I want to add support for Org-mode, show a dashboard on startup, enable font ligatures and fix all the small things that I’ll find.

Subscribe to the RSS feed so you don’t miss Part 4, and let me know what you think!

  1. Check out parts one and two if you haven’t already!

  2. Language Server Protocol

  3. By the way, I switched my theme to Kanagawa.

-1:-- Emacs Config From Scratch, Part 3: LSP &amp; Tree-sitter (Post Arne Bahlo)--L0--C0--May 11, 2024 12:00 AM

Karthik Chikmagalur: The Emacs Window Management Almanac

Window management in Emacs gets a bad rap.

Some of this is deserved, but mostly this is a consequence of combining a very flexible and granular layout system with rather coarse controls. This leaves the door open to creating and using tools for handling windows that employ and provide better metaphors and affordances.

As someone who’s spent an unnecessary amount of time trying different approaches to window management in Emacs over the decades, I decided to summarize them here. Almanac might be overstating it a bit – this is a primer to and a collection of window management resources and tips.

Window management in Emacs bleeds into buffer, state, workspace and frame management, so it’s difficult to contain the scope of any article that aims to be comprehensive. To that end,

  • this write-up assumes that you’ve finished at least the Emacs tutorial and are familiar with basic Emacs terminology (what’s a buffer, window and a frame) and with window actions: splitting windows, deleting them or deleting other windows, and switching focus.
  • There are only a few brief mentions of tabs, as they are primarily a tool for workspace management, as opposed to window management.
  • I’m focusing on window/buffer management within an Emacs frame. Many of the below tools work across frames just as well, but you’ll have to find the right switches to flip to enable cross-frame support.
  • Finally, this is more my almanac than a wiki: It covers only tools or ideas I’ve personally explored over the years, with brief mentions of potentially useful packages that I haven’t tried. Any omissions are not value judgments, please let me know if I miss something useful.

At some point this transitions from listing well known tools to tips, then hacks, and finally unvarnished opinions. It’s front-loaded: the first chunk of the write-up gives you a 70% solution. If you are new to Emacs, feel free to stop at 30%. If you are an old hand, feel free to skip the first 30%. It also lists substitutes: several ways to do the same things, so you can pick just one method and ignore the rest. Things get progressively more opinionated and idiosyncratic in the second half.

If you are reading this in the future, this write-up is probably out of date. The Emacs core is very stable, but the package ecosystem tends to drift around as packages are developed and abandoned. The built-in solutions will still be around, but there are no guarantees on the third-party packages! That said, the longer a package has been around the more likely it’s going to stick around in a functional state – even if only as a frozen entry in the Emacs Orphanage.

As new ideas emerge, there will be new approaches to window management that aren’t covered here. These innovations don’t need to happen in the Emacs sphere – Emacs likes to steal reinvent ideas that originate elsewhere, much as other applications rediscover ideas that Emacs introduced in the 1990s. So this topic might be worth revisiting afresh in a few years.

What we mean by “window management”

Emacs separates the idea of a window (a “viewport” or “pane” in the frame) from the buffer, a contiguous chunk of text that may or may not be the contents of a file. These concepts are usually fused in IDEs and text editors – this reduces the cognitive load of using the application, but closes the door on more flexible behavior and free-form arrangments. For example, many editors don’t let you have two views of the same file, which is trivial in Emacs. They’re often uncomfortable even with the idea of a dissociated buffer – a buffer that does not represent the (possibly edited) contents of a file. Reified concepts like Emacs’ indirect buffers are completely foreign to them Unfortunately for Emacs, its current design rules out some clever ideas that other editors have implemented. One example of this is the 4coder’s yeetsheet or Dion systems’ views: You can have buffers whose contents are “live” substrings of multiple other buffers, i.e. you can mix and match pieces of buffers. In Emacs the most you can have is indirect buffers, i.e. full “live” copies of a buffer. .

Emacs allows you to do a lot more, but users have to contend with this cognitive cost. New users pay thrice: they have to deal with getting windows into the right places in the frame, getting buffers into the right windows, and they miss out on the upside because they don’t yet realize what this decoupling makes possible. Hopefully this write-up can address two of these costs.

For reference in the rest of this article, here’s a non-exploded schematic of an Emacs frame, with the left window selected:

  • Each colored block is a window, the numbers represent buffers being shown in them.
  • The active window is the one with a black border.

This article is not about…

Since actions with or on windows in Emacs are primitive, common and unavoidable operations at any level of Emacs usage, this topic is suprisingly subtle, broad and deep, and there’s only so much I can explore in 15,000 words. So we begin with some disambiguation and a narrowing of focus. This article is not about the following things.

Rules for displaying buffers

Emacs keeps popping up windows in the wrong places and destroying my window arrangement!

The situation is… less than ideal. Displaying buffers in the right windows automagically is generally possible but this configuration is involved and requires knowledge of minutiae of the Emacs API, like window-parameters, slots and dedicated windows. the display-buffer API is so involved that describing it takes up a big chunk of the Elisp manual, and even that concludes by saying “just go with it”. And this is the one aimed at developers using elisp. It’s not even the Emacs user manual! I mention automatic window management briefly towards the end, but this article is not about reining in the behavior of display-buffer. I recommend Mickey Peterson’s article on demystifying the window manager for this, this video by Protesilaos Stavrou, or the manual if you’ve got the stomach for it.

Window configuration persistence, workspaces or buffer isolation

I want Emacs to group together windows for a given task and persist them across sessions!

Two common factors affecting Emacs use:

  • Emacs sessions tend to be long lasting, and
  • its gravity pulls users into using it for an increasing number of tasks.

The result is that you end up with hundreds of buffers and start looking for ways to group them, isolate the groups and then preserve them. This is tied to window management, but only in the sense that your arrangement of windows is part of the state you want to preserve. This is a finicky and complex subject, and well beyond the scope of this write-up. Take your pick: between tab-bar, tabspaces, eyebrowse, tab-bookmark, desktop.el, persp-mode.el, perspective, project-tab-groups, beframe and activities.el there is no paucity of projects to help you do this.

Paradigmatic changes to window behavior in Emacs

Why is window placement in Emacs so capricious? Tiling window managers solved this problem ages ago!

Some packages provide all-encompassing, radical solutions to window arrangment and management – essentially, they are window managers for Emacs. For example, Edwina modifies Emacs’ manual window-tree based behavior to enforce a master-and-stack DWM-style auto-tiling layout, with a complete suite of accompanying window management commands. HyControl provides a control panel for window layout actions and can display windows in a uniform grid on the frame, among other features. Apologies for the terse and possibly inaccurate descriptions, I have only brief experience with these. .

In my experience “complete” solutions like these are great when you start using them, but eventually cause more friction than elation. This is the case the more you customize Emacs, as the abstractions they build on top of the Emacs API end up limiting, as opposed to liberating you in the long run.


So what’s left? In this article, we mean window management in the manual and mundane sense: switching window focus, moving buffers around windows, splitting or closing them and so on. Even if you’ve got display-buffer all sorted out and your windows grouped into workspaces, these are the kinds of things you have to do with windows – repeatedly and often – in the course of minute-to-minute, regular editing.

Let’s address a couple of common concerns and dismissals before we get started in earnest.

The two-window perspective

This palaver is pointless, I need two windows at most.

Correction: You need at most two windows at a time. And that’s partly because corralling windows is a mess in Emacs. Except during bursts of writing or coding, chances are you need easy access to more than one buffer – for reference material or look-ups, search and compilation results, file access, shells and REPLs, table of contents and so on. Whether these are on screen in windows all the time or easily displayed on demand is a matter of screen size and preference, but both involve interacting with windows and buffers manually. Both approaches are thus under the purview of “window management”, and addressed in this article.

The pointer prescription

Just use the mouse? This isn’t even an issue in most software.

The mouse is indeed the most natural way to navigate windows. Without stepping into contentious discussions on economy of motion, RSI trouble or personal preferences, the main problem with the mouse approach is that the lack of a learning curve (relative to the keyboard) is balanced by the lack of expressivity (relative to the keyboard).

Even so, you can squeeze a lot more expressivity out of the mouse in Emacs than you can in most other applications The ACME editor might be the most notable exception. . I use the mouse for managing windows in Emacs often – but only in certain contexts, see Mousing around.

Warming up

Our appetizer: a short run-through of the most popular and commonly recommended window management options. These cover changing the focused window, moving windows around and undoing oopsies, with a side of buffer management and bespoke window actions This is the part you can skip if you’ve been around the block a few times. Jump ahead to digging in. .

other-window and the “next window” (built-in)

other-window offers: selecting windows

The other-window (C-x o) command is the baseline window switching experience. It’s what the Emacs tutorial teaches you, and it works well enough when you have a small number of windows:

The window selection cycles (roughly) clockwise across the frame. The advantage of this approach is simplicity – it’s a single command and keybinding. As you might expect or have experienced, it takes progressively more invocations to get somewhere as you accumulate more windows, and works best if you rarely have more than two windows showing at once.

Basic other-window tips and tweaks
  1. It’s possibly one of the most used Emacs commands – bind it to a more convenient key like M-o.
  2. You can use digit arguments to skip windows or to cycle windows backwards. M-3 M-o will select three windows ahead, and M-- M-2 M-o two windows back. Unfortunately this requires a visual understanding of the order in which the cycling happens. It’s not obvious which window is three windows away in more complex window layouts.
  3. Turn on repeat-mode (M-x repeat-mode) to continue switching windows with just o and (backwards) O. C-x o o o o... or M-o o o o... is faster than C-x o C-x o C-x o....
other-window hacks

You can make other-window skip over a window by setting its no-other-window window parameter. A window parameter is a property of Emacs’ window data structure, and there are Elisp functions to set them. This is usually something you’d specify in advance for certain classes of buffers in display-buffer-alist, not a manual toggle. If you’ve ever wondered why other-window does not select the windows of fancy file-manager listings (like dired-sidebar or dirvish-side), this is it.

If you only ever have two windows showing in Emacs, or if you don’t mind punching o a few extra times, you can stop here. The rest is just varying degrees of optimization applied to a problem that you probably (and perhaps realistically) don’t believe needs solving!

Understanding the “next window”

The “next window” is the window that other-window selects, usually clockwise from the current one. You can access it in elisp by calling the next-window function. With daily usage, you automatically develop intuition for the clockwise ordering of windows in an Emacs frame – in the sense that you know instead of think. This is handy, because the notion of the next window is useful for more than just window selection. There are better ways to select windows, or there wouldn’t be much to this write-up! The next window is the default window for commands that operate in another window, like scroll-other-window. See Do you need to switch windows?

windmove (built-in)

windmove offers: selecting windows, swapping buffers in windows, deleting them

Windmove is a built-in Emacs library for moving the focus across windows – and for moving buffers across windows – by direction. Vim users, this is what you expected. evil-mode users, you already use Windmove, you just don’t know it.

If other-window is the alt-tab of Emacs, Windmove is the tiling window manager equivalent. It makes the spatial arrangement of windows in the frame relevant to the selection, which I imagine is the most natural way to do it short of using the mouse.

Using Windmove is simple: bind windmove-left (resp -right, -up and -down) to a modifier or leader key plus whatever keys you associate with directions: WASD, HJKL or the arrow keys perhaps.

The fork: movement to the right in this schematic depends on what window is exactly to the right of the cursor. Calling windmove-right from near the top of buffer 1 moves the focus to buffer 2, starting near the bottom moves the focus to 3.

You can also swap the buffers of windows directionally with Windmove, a handy way of rearranging windows on the frame Again, Windmove is how evil-mode does this. . The relevant commands are windmove-swap-states-left, -right, -up and -down.

Note that the focus moves along with the buffer when you do this.

There’s more yet to Windmove, you can delete the window next along any direction with windmove-delete-*, for example. But we cover better ways to do this below.

Tiling manager integration

If you use Emacs in a tiling environment, you’ve got a nested tiling window manager situation – it might be desirable to integrate the two so you can (wind)move seamlessly across Emacs windows and OS windows with the same keys. (Vim+tmux users should be familiar with this.) It takes a bit of work but is quite doable: Pavel Korytov has an i3 integration description for Emacs+i3wm (and possibly Sway), and I wrote one for the qtile window manager. I discuss this project in more detail below.

frames-only-mode

frames-only-mode offers: to leave Emacs window handling to the OS.

While we’re on the subject of tiling, another resolution to the nested window manager situation – Emacs inside a tiling WM – is to simply not bother with Emacs’ window management. Opening every buffer in a new frame instead of window makes corralling them the window manager’s job. This puts Emacs buffers on par with OS windows, and you can manage both with the same keys.

Most other commands described in this write-up (such as Avy, winum, ace-window or scroll-other-window) can work across frames just as easily as windows, meaning that you can have the best of both approaches. There are bound to be edge cases with other Emacs commands though – many of them make assumptions about being able to split the frame at will This is especially true of org-mode commands! Thankfully the Org situation is slowly improving. .

For Linux users: I haven’t tried frames-only-mode with Wayland compositors yet.

winum-mode

winum offers: Selecting and deleting windows

Winum is next in the natural progression of the effort to switch between n windows: From O(n) (other-window) to O(√n) (windmove) to O(1). It adds window numbers to the mode-line so you can select windows by number:

There are two convenient bonus features:

  • Invoking the command to switch to a window with a negative prefix argument deletes the window, and
  • when the minibuffer is active, it is always assigned the number 0.

It’s simple and short, and works across Emacs frames. winum-mode is the method I use the most for switching windows.

Speeding up window access with winum

The default keybinding (C-x w <n> to select window n) is too verbose for my liking, as is any other two step keybinding. If you don’t mind losing access to digit arguments with M-0 through M-9, you can use them to select windows instead:

(defvar-keymap winum-keymap
  :doc "Keymap for winum-mode actions."
  "M-0" 'winum-select-window-0-or-10
  "M-1" 'winum-select-window-1
  "M-2" 'winum-select-window-2
  "M-3" 'winum-select-window-3
  "M-4" 'winum-select-window-4
  "M-5" 'winum-select-window-5
  "M-6" 'winum-select-window-6
  "M-7" 'winum-select-window-7
  "M-8" 'winum-select-window-8
  "M-9" 'winum-select-window-9)
(require 'winum)
(winum-mode)

While it is possible to extend winum-mode to include other actions on windows (or on buffers displayed in them) besides switching to or deleting them, there’s little reason to, thanks to the existence of…

ace-window

Offers: Any window or buffer management action

ace-window is the endgame for keyboard-driven Emacs window control.

The ace-window command places “hints” at the top of each window, and typing in the key switches focus to the corresponding one:

So far it’s a slightly slower, two-stage version of winum. You can turn on ace-window-display-mode to have the hints always showing in the mode-line like winum’s window numbers, which speeds up the process a bit:

ace-window is to windows what Avy is to characters on screen The similar design is not a coincidence. They’re both authored by Oleh Krehel. . But jumping to a character on screen is the least useful of the many things you can do with Avy. Similarly, if all ace-window could do was switch windows, there wouldn’t be much to recommend it. Instead, it offers a generic method to “pick” a window, across all visible Emacs frames if necessary. What you do with this window is up to you. Similar to Avy, ace-window can dispatch actions on any window on the screen. So you can delete windows, move or swap them around, split them, show buffers in them and more – without moving away from your selected window. These are just the built-in actions, provided as part of ace-window:

Pressing ? when using ace-window brings up the dispatch menu See Fifteen ways to use Embark for further explorations of this idea. .

Play by play
  1. With two or more windows open, call ace-window. (For two windows or fewer, you will need to ensure that the variable aw-dispatch-always is set to t.)
  2. Press ? to bring up the dispatch menu.
  3. Press the dispatch key to split a window horizontally (v in my video)
  4. Press the ace-window key corresponding to the buffer you want to split (e in my video)
  5. Repeat steps 1 and 2
  6. Press the dispatch key to split a window vertically (s in my video)
  7. Press the ace-window key corresponding to the buffer you want to split (w in my video)

Mousing around (built-in)

The mouse offers: Any window or buffer management action

So, the pointer. Finally.

The advantanges of using the mouse for window management are immediate and obvious. Window selection is a natural extension of basic mouse usage. Resizing windows is a snap. Context (right-click) menus and drag and drop support, which improve with each new Emacs release, are very intuitive See context-menu-mode. Also, while not limited to window management, discoverability via Emacs’ menu-bar is surprisingly good. . Unfortunately, I have to address the rodent in the room before we can talk about mitigating the disadvantages, since Emacs users tend to be very opinionated about mouse usage.

I never use the mouse in Emacs… until I’m already using the mouse for something else. Then driving Emacs with the mouse is actually the path of least resistance. If your hand’s already off the keyboard, it’s pretty easy to drive Emacs with the mouse:

Play by play

This demo showcases the use of mouse gestures to do the following:

  • Split the frame vertically and horizontally
  • Delete windows
  • Cycle through buffers in windows
  • Swap windows to the right and left
  • Toggle between the last two buffers shown in a window

You may want to turn on focus-follows-mouse behavior:

;; Consider setting this to a negative number
(setq mouse-autoselect-window t)

transpose-frame (rotation, flip and flop)

transpose-frame offers: easy window layout transformations.

What it says on the tin: transpose-frame offers commands to rotate or mirror the window layout on the frame. I found myself using these often enough to bind rotate-frame, flip-frame and flop-frame to suitable keys. Ironically, the transpose-frame command itself is rarely useful – it transposes along the main diagonal of the frame.

rotate-frame

flip-frame

flop-frame

The window-prefix-map (built-in)

window-prefix-map offers: Bespoke window management commands

The window-prefix-map, bound to C-x w by default in Emacs, collects a few useful window-management commands:

split-root-window-right and split-root-window-below

Split the root window of the frame. Better illustrated than explained:

These are bound to C-x w 3 and C-x w 2 respectively.

The window tree

This is a good time to mention that windows in Emacs are arranged in a tree, with all “real” windows as leaves. Each splitting action turns a leaf node into a parent of two windows: the window that was split and the new one. This is very similar to the window arrangement in manual tiling window managers like i3 or bspwm, leading to a redundancy we seek to patch over.

These are the only built-in Emacs commands, to my knowledge, that allow you to modify the tree structure at a non-leaf level that doesn’t just clear the whole tree (as delete-other-windows does). Practically speaking, these are often useful to create a space for a logically separate task in the frame – the default splitting commands only further dice up existing windows.

Getting to grips with the tree arrangment should make a lot more fine-grained control available, but the tooling isn’t there yet – see below for a proposal.

tab-window-detach and tear-off-window

Handy commands to move a window into a new tab or a new frame.

Like splitting the root window, these are quite handy for logical window management: grab a window and move it into a new tab or frame to start a new task.

These are bound to C-x w ^ t and C-x w ^ f, which sheesh. You can do these as ace-window dispatch actions instead, since you can do anything with ace-window. Alternatively you can rebind these to the slightly saner C-x w t and C-x w f, which are currently unbound. I prefer to just use the mouse when I need to tear off a window:

;; mouse-9 is the "forward" button on my mousee
(keymap-global-set "M-<mouse-9>" 'tear-off-window)

The other-window-prefix (built-in)

other-window-prefix offers a method to decouple window selection from buffer display, and solves three window-related annoyances.

Annoyance I

Many Emacs commands tightly couple a primary action, a buffer and a window. For example, running find-file involves selecting a file, creating a buffer and displaying it in the current window. If you want to decouple the choice of window from the command, you have to pick one of several alternate commands: find-file-other-window, find-file-other-tab or find-file-other-frame, each with its own keybinding. If you want to open the file in read-only mode, you’ve got find-file-read-only, find-file-read-only-other-window, find-file-read-only-other-tab and find-file-read-only-other-frame. More keybindings.

Want the same choices when selecting a buffer? You’ve got switch-to-buffer-⋆, another constellation of commands. Opening a bookmark with bookmark-jump? Pick one of several bookmark-jump-* commands. This is the road to insanity.

The problem is the coupling: picking a window to display a buffer should be a separable action from the command’s primary function: opening a file, in this example. The solution is to call other-window-prefix, bound to (C-x 4 4). This makes it so that the next command – any command that involves displaying a buffer in a window – is shown in the next window, creating one if necessary. Now you only need find-file, find-file-read-only and switch-to-buffer, and can use the prefix to redirect the resulting buffer to another window when required:

  1. Call other-window-prefix (C-x 4 4)
  2. Call find-file, find-file-read-only, switch-to-buffer, bookmark-jump, or any command that shows a buffer.
  3. Result: the buffer is shown in the next window.

In a past write-up I’ve mentioned Embark as the way. Indeed, Embark solves this problem more elegantly than the built-in other-window-prefix. But avoiding command proliferation is only the first of three problems other-window-prefix solves.

Annoyance II

In the above examples, we at least have the choice of calling *command*-other-window instead of *command*. There are just too many options. More often there are none, and we’re at the mercy of fixed, undesirable behavior. This is typically the case when activating a link-like object. In this example (from the Forge package), pressing RET on an issue title opens the issue in the current buffer:

Play by play

This is a list of issues from a code repository, as displayed by the Forge package.

  1. Press RET on an issue.
  2. It opens in the current window, denying us the Listing & Item pattern: a simultaneous view of the full listing and the selected issue.

Forge provides no way, as of this writing, to “open a link” in another window. other-window-prefix to the rescue:

Play by play
  1. Call other-window-prefix, via C-x 4 4
  2. Press RET on the issue. It opens in the “next window” – there isn’t one so a new window is created.

Annoyance III

The third problem it solves is the combination of the first two. Consider: Magit, the sibling package to Forge, does provide a way to do this. It generally opens “links” in the next window if you use a universal arg (C-u) before RET. Org mode, Notmuch, Elfeed and EWW all provide either no way or mutually distinct ways of opening links in a different window. If Forge did provide a way, it would actually make things worse in a sense. With other-window-prefix, you’re blessedly free from having to customize or conform to each package author’s idea of how this should work. Run other-window-prefix, then activate the “link” object – click on it with the mouse if you’d like. It’s going to uniformly open in the next window.

See also: same-window-prefix (C-x 4 1), which forces the next command’s buffer (if there is one) to use the current window, and other-frame-prefix (C-x 5 5) and other-tab-prefix (C-x t t), which open the next command’s buffer in a new frame and tab respectively.

What’s with these keybindings?

There is a method to the seeming madness of keybindings like C-x 4 4, C-x 4 1 and C-x 5 5.

Keybindings involving specific window actions are grouped into prefixes, like a menu. C-x 4, the ctl-x-4-map broadly contains commands that use the other-window. For instance, C-x 4 . jumps to the definition of the thing at point (like the default M-.), but in the other-window. Most commands in the ctl-x-5-map create a new frame. Tab-bar actions are grouped under C-x t.

The final “base” key in each map follows a consistent pattern: f opens files, r opens things in read-only mode, b switches to buffers and so on. The final 4, 5 and t in C-x 4 4, C-x 5 5 and C-x t t reinforce the idea that the next buffer action is going to be redirected to another window, a new frame and tab respectively.

Further below we take this approach to its logical extreme with (what else) ace-window, redirecting the next command’s buffer to any window, including ones we create just-in-time.

Saving and restoring window configurations

window-configuration-to-register is a bit of a blunt instrument, but perfect as a big red reset button, especially if you’re new to Emacs. At any point, you can save the current window configuration to a register A register is a named bucket that can hold many kinds of data. Each register is assigned to a character (like a through z), and operations on register are available under the C-x r prefix. with this command, bound to C-x r w by default. After Emacs predictably messes up the frame, you can restore your saved configuration with jump-to-register (C-x r j). That’s it.

Persisting window configurations across restarts

The elisp version of window-configuration-to-register is the function current-window-configuration, whose return value you can bind to a variable, and apply to the frame with set-window-configuration. Coupled with a way to persist this lisp object data to disk, such as with prin1 or via a library like persist or multisession, we have the seed of a state restoration feature that works across Emacs sessions. Needless to say, this approach is rudimentary and you’re better off using one of the many packages listed above in window configuration persistence.

One issue with this method is that it restores the window arrangement down to each window’s cursor position, which is rarely what you want.

Another problem is that it requires an unreasonable level of foresight to remember to save window configurations at appropriate times. If only Emacs could do this automatically for us every time the window configuration changed…

The “oops” options

…which of course it can. You can ask Emacs to maintain a stack of your past window arrangements, and cycle through them as you would through changes in a buffer with undo/redo. You’ve got three minor-modes depending on how you use Emacs, and you can turn them on independently.

winner-mode
If you don’t use tabs. Call winner-undo and winner-redo to undo/redo window configuration changes. It maintains a separate window configuration history for each frame.
tab-bar-history-mode
If you use tabs. Each tab gets its own history stack. The relevant commands are tab-bar-history-back and tab-bar-history-forward.
undelete-frame-mode and tab-undo
If you use create and delete frames or tabs all the time. If you close a frame by accident, you can call undelete-frame, bound to C-x 5 u. Ditto tab-undo, bound to C-x t u.

These options are handy for going on excursions, such as when you want to maximize the selected window temporarily before reverting to the previous arrangement.

But winner-mode & co are also frequently recommended as a band-aid for when Emacs messes up your careful manual window arrangement, for instance by popping up windows in the wrong places, or resizing your window splits. I think of this as an antipattern. If you find yourself using winner-undo (or equivalent) all the time to fix Emacs’ behavior, the problem is Emacs displaying buffers in the wrong windows in the first place, a result of frustrating defaults. See the whack-a-mole problem.

Digging in

With our appetite whetted, we can move onto our main course: Tweaks, customization and variations of the above tools that I’ve found to work better.

Emacs can be frustrating on two levels. It’s frustrating at first because you don’t know your way around the place, the keybindings and terminology are obtuse, and nothing works the way it does in other software. Your attempts at mitigating its perceived shortcomings by installing packages leads to mysterious, cryptic errors. The single-threadedness makes it too easy to accidentally slow things down to a crawl. The garbage collector fires at the worst times. Things that should just work, don’t. The perceived shortcomings of Emacs are frustrating: Window management shouldn’t be this complicated!

Over time (years, decades?) you can develop a better mental model of what’s happening under the hood: how Emacs’ event loop works, the anatomy of buffers, windows, keymaps, text properties and overlays – the data structures Emacs is built on. Perhaps you even steal some sneaking glances at the lumbering behemoth that is redisplay. You’re familiar with common Elisp idioms and macros, as well as the common traps. Now the actual shortcomings of Emacs’ API are frustrating: Window management shouldn’t be this complicated!

Oops.

So here we are. The rest of this write-up is aimed at someone in between these two kinds of frustration. It’s mostly me throwing out suggestions, many of them mutually exclusive, that might give you ideas of your own to work with windows. Implementing these ideas will require a little tweaking, copying code verbatim might not give you the results you expect. For this reason, I suggest coming back here with a little more Emacs mileage if you’re new to Emacs.

The back-and-forth method

Offers: Quick window selection

An observation: no matter how many simultaneous windows you have or require on screen, most of the time you only need to switch between two of them. Examples include the Code & REPL setup, the Code & Grep (search results) setup, and the Prose & Notes setup. The Listing & Item pattern is an example outside of programming or prose: this includes a calendar or agenda window with an expanded entry window, or an email inbox window with an opened email.

The other windows on screen usually show useful information – documentation, debugging info, messages, logs or command output, table of contents, a file explorer, document previews – things you glance at often but switch to rarely.

Usually major-modes provide semi-consistent keybindings to switch back and forth between two associated windows – a common example is C-c C-z, used by several programming modes in Emacs to switch between a code window and an associated REPL This works for Org-babel blocks too via org-babel-switch-to-session, bound via org-babel-map to the slightly different C-c C-v C-z. .

But we can generalize the idea and provide a command to switch between any pair of windows:

(defun other-window-mru ()
  "Select the most recently used window on this frame."
  (interactive)
  (when-let ((mru-window
              (get-mru-window
               nil nil 'not-this-one-dummy)))
    (select-window mru-window)))

(keymap-global-set "M-o" 'other-window-mru)

It doesn’t matter how you select the second window for the back-and-forth – you could use the mouse, ace-window, winum or any other method. other-window-mru’s got you covered from then on.

Improving other-window

We can retain the basic idea of other-window – move between windows in the frame in some cyclic ordering – but improve the ordering to be more of a DWIM affair Do-What-I-Mean .

other-window is a simple idea – the simplest you’ll find in this write-up – but you can play around with the order in which windows are selected to better fit how you work.

Double duty

First, you could make other-window split the frame when there’s only one window, giving the command a use when it has none.

(advice-add 'other-window :before
            (defun other-window-split-if-single (&rest _)
              "Split the frame if there is a single window."
              (when (one-window-p) (split-window-sensibly))))

switchy-window

Another modification that you might find intuitive is to cycle through windows in order of last use instead of in clockwise spatial order, similar to alt-tab or how some web browsers cycle through tabs. This is possible with some elbow grease, but this work has been done for us by the switchy-window package, which provides a switchy-window substitute command for other-window.

When cycling through windows, switchy-window waits for a window to stay selected for a couple of seconds before marking it as used and updating the recency list. In practice this works quite seamlessly – calling switchy-window moves you to to where you need to be most of the time.

That said, I usually prefer the simpler variant described in the back-and-forth method.

other-window-alternating

And speaking of back-and-forth, here’s another other-window variant – it might sound confusing at first, but turns out to be a pleasingly DWIM affair. Except when chaining other-window, reverse the window-switching direction after each call. With just two windows, this makes no difference. With more, this makes alternating between two windows natural, even when the windows are not adjacent in the cyclic ordering.

(defalias 'other-window-alternating
    (let ((direction 1))
      (lambda (&optional arg)
        "Call `other-window', switching directions each time."
        (interactive)
        (if (equal last-command 'other-window-alternating)
            (other-window (* direction (or arg 1)))
          (setq direction (- direction))
          (other-window (* direction (or arg 1)))))))

(keymap-global-set "M-o" 'other-window-alternating)

;; repeat-mode integration
(put 'other-window-alternating 'repeat-map 'other-window-repeat-map)
(keymap-set other-window-repeat-map "o" 'other-window-alternating)

Window magic with ace-window dispatch

ace-window is to windows what completing-read is to lists of strings, or Avy to characters on screen. This makes it ideal as the first two of a three-step process to invoke any action on any window: the filter and selection steps:

aw-select, the completing-read for Emacs windows

The way ace-window is designed to be extended is by defining an “ace-window action” and adding a binding for it in aw-dispatch-alist It ships with several predefined actions, captured in this schematic above. . This function accepts a window and does something useful with it. The ace-window command acts as the entry point:

This control flow is generally similar to how Avy works. But as a completing-read alternative, this is somewhat lacking – we’d like to flip the pattern around and use ace-window’s selection method in our commands. Conveniently, aw-select does exactly that.

The basic pattern is very simple: the call (aw-select nil) The argument to aw-select is for adding a message to the mode-line during the selection process, we don’t bother with that. returns the window we select, which we can use for our task. One example of such a task is to set the window that scroll-other-window should scroll. Here are a couple more, but don’t try them just yet! We’re going to generalize the idea a little further below.

tear-off-window or tab-window-detach

Every interactive window command in Emacs acts on the current window. Here we make a couple of commands in the window-prefix-map (C-x w) something you can apply interactively to any window.

(defun ace-tear-off-window ()
  "Select a window with ace-window and tear it off the frame.

This displays the window in a new frame, see `tear-off-window'."
  (interactive)
  (when-let ((win (aw-select " ACE"))
             (buf (window-buffer win))
             (frame (make-frame)))
    (select-frame frame)
    (pop-to-buffer-same-window buf)
    (delete-window win)))

(defun ace-tab-window-detach ()
  "Select a window with ace-window and move it to a new tab."
  (interactive)
  (when-let ((win (aw-select " ACE")))
    (with-selected-window win
      (tab-window-detach))))

Of course, defining one ace-window-based command for each action isn’t a scalable or useful way to go about this. It would be preferable to decouple the window selection step from the action step and generalize the latter. We explore two distinct approaches to do this, starting with…

ace-window-one-command: Any command with ace-window

Generalizing the above examples gives us a pretty good idea of what the flipped ace-window pattern should look like. The most general and composable version would be the following:

  1. Call aw-select to pick a window (the completing-read step)
  2. Run any action in this window
  3. Switch back to the original window.

We can do this by simulating Emacs’ event loop, but in the chosen window: Switch windows, then read any key sequence and execute it before switching back.

(defun ace-window-one-command ()
  (interactive)
  (let ((win (aw-select " ACE")))
    (when (windowp win)
      (with-selected-window win
        (let* ((command (key-binding
                         (read-key-sequence
                          (format "Run in %s..." (buffer-name)))))
               (this-command command))
          (call-interactively command))))))

(keymap-global-set "C-x O" 'ace-window-one-command)

In a demo, this looks the same as ace-window, except that you select the window before executing the action. The win here is the action: it works with any simple command, there is no need to pre-configure actions in aw-dispatch-alist. There’s nothing to set up or memorize. In this demo I use ace-window-run-command to shrink an unselected window with C-x - (the descriptively named shrink-window-if-larger-than-buffer)

Play by play
  1. Pulse the line to show which window is active.
  2. Call ace-window-one-action and select the Occur buffer to the top left. Emacs waits for you to execute any single command.
  3. Run shrink-window-if-larger-than-buffer, using C-x -. This shrinks the Occur buffer, our cursor position and window is unchanged.

ace-window-one-command is a convenient way to quickly run any command in a different window, an idea we explore in more detail below.

Embark much?

This reversal of Emacs’ (and ace-window’s) usual paradigm of actionselection is at the heart of Embark, as covered in my write-up on ways to use Embark. Of course, this “object-first” approach is only one way to look at it – Embark has many hearts.

A window-prefix command for ace-window

Handy as it is, the other-window-prefix system has the same problem as the other-window command: it enforces a rigid cyclic ordering on the window it will pick, and about the most we can consistently expect is that the active window will not be taken over by the next command. We can do better.

aw-select gives us a bespoke solution with more control: we select the window that should be used if the next command involves displaying a buffer in a window. In this example, we explicitly pick a window to show a man page in, since the “next window” is not where we want it:

Play by play
  1. Pulse the line to indicate the active window (lower left)
  2. Run ace-window-dispatch (C-x 4 o), then M-x man and choose curl(1). Emacs waits for us to pick a window.
  3. Pick the window on the right with “e”. The Man page is displayed in that window.

Note that the Man elisp library actually offers a suite of options to customize where it should be displayed, in the fiddly way typical of all things Emacs. We can sidestep that whole undertaking here.

Here’s the example from above of viewing a Forge link in a busy frame with many windows. We compare the result of using other-window-prefix, where a random window is chosen, to using ace-window-prefix, where we can pick a specific window:

Play by play

In this frame, the window “next” to the Forge topics window (the bottome one) is the one window at the top left.

  1. Move down to the last listed topic and pulse the line (so you can find the active window)
  2. Call other-window-prefix (C-x 4 4) and press RET on the “link”. It opens in the top left window, not where we’d like to see it.
  3. Call tab-bar-history-back to restore the previous window configuration.
  4. Call ace-window-prefix (C-x 4 o) instead, and press RET. Emacs waits for us to pick a window to show the resulting buffer in.
  5. Pick the window on the right with “r”. Forge shows the link contents in that window.

ace-window works across visible frames, so we can pick any Emacs window on our screen. Even better, we can use ace-window actions to create new windows on the fly and use them instead. Here I use an ace-window action to create a new window to be used by the next command:

Play by play

Normally, activating an Org mode link opens it in the current window or the next one, depending on your Org settings. We want something different.

  1. Press RET on the link to open the image in the next window.
  2. Press q to quit and return to the Org buffer.
  3. Call ace-window-prefix and press RET on the link. Emacs waits for us to pick a window to show the linked file in.
  4. Use an ace-window action to split a window and select the split. The action now finishes and the linked image is shown in that window.

The implementation of ace-window-prefix is actually simpler than other-window-prefix:

(defun ace-window-prefix ()
  "Use `ace-window' to display the buffer of the next command.
The next buffer is the buffer displayed by the next command invoked
immediately after this command (ignoring reading from the minibuffer).
Creates a new window before displaying the buffer.
When `switch-to-buffer-obey-display-actions' is non-nil,
`switch-to-buffer' commands are also supported."
  (interactive)
  (display-buffer-override-next-command
   (lambda (buffer _)
     (let (window type)
       (setq
        window (aw-select (propertize " ACE" 'face 'mode-line-highlight))
        type 'reuse)
       (cons window type)))
   nil "[ace-window]")
  (message "Use `ace-window' to display next command buffer..."))

In keeping with the keybinding pattern for the ⋆-window-prefix commands, we bind it to C-x 4 o

(keymap-global-set "C-x 4 o" 'ace-window-prefix)

Do you need to switch windows?

Let’s pause for a moment to ask a basic question: why do you need to switch windows in the first place? A little reductive thinking distills the answer down to two – and only two – possibilities:

  1. Switch and stay: To work persistently in the destination window, for some measure of “work”: this covers text editing in all its forms. In this event the window we switch to becomes our primary work area.
  2. Switch and return: To interact with the window or its contents briefly. Perhaps we want to scroll through, or copy some text before moving back, or to delete the window. In this event the window is a temporary destination, for auxiliary purposes.

In either case, switching windows is a cost, not our objective. Ideally this should happen automatically as part of our editing process. So why not just “fold” this little chore into our primary editing action?

Switch and stay: Avy as a window switcher

Eventually any kind of navigation in Emacs comes down to Avy. If you are switching windows to edit (or select) text, you intend to move to a specific point on the screen. Getting the cursor there is a two step process: switch windows, move the cursor to the right location. Avy short-circuits this process into a single action. It treats the frame as a single pool of jump locations: in helping you jump to any character(s) on the screen, it moves you across windows seamlessly:

Play by play
  1. Call avy-goto-char-timer
  2. Type in “se”. This shows hints for all matches with “se”, including “sentence”.
  3. Type in the hint char corresponding to “sentence”, which is g here.

With a slight mental shift you can stop thinking of windows as distinct objects entirely, at least for the purposes of navigation. Any character(s) – across all visible Emacs windows and frames – is a couple of keypresses away. And it’s not the only way to jump across windows: you can jump back to your starting point (switching windows in the process) with pop-global-mark, for instance:

Play by play
  1. Call avy-goto-char-timer
  2. Type in “demo”. There is only one candidate for this string, so Avy jumps to the other window.
  3. Type in “jump”. This shows hints for all matches with “jump”.
  4. Pick one of the matches. Avy jumps again, this time to the third window.
  5. Call pop-global-mark (C-x C-SPC) to jump back to the previous location. (Details below)
  6. Call pop-global-mark (C-x C-SPC) again to jump back to the previous location.
Making Avy window-agnostic

If Avy does not move you across windows and frames, you probably need to customize avy-all-windows.

While we’re here, consider customizing avy-style, there’s more than one way to jump with Avy!

Of course, this only scratches the surface of what you can do with Avy, but that’s well tread ground at this point.

Switch and return: Actions in other windows

And here’s the other case. Often the reason you switch windows is to run a single logical action – perhaps a compound action like isearching to focus the view somewhere, before switching back to your main buffer. This is the switch → act → switch-back dance.

We’re going to automate this dance away in steps, working through solutions obvious and specific, through to repeatable and general, ending at the abstract and generic.

The obvious first: if you find yourself performing this dance repeatedly, you can automate it with a keyboard macro (left as an exercise for the reader). If the action is something you do all the time, you can go a step further and write a general-purpose command. ace-window-one-command above would be one way to do it. Emacs paves the way for us with…

scroll-other-window (built-in)

scroll-other-window and scroll-other-window-down have been part of Emacs for ages, perhaps because it fits neatly into the two-window paradigm that Emacs’ default settings are suited for: editing in one window while using the contents of the other one as a reference. You can scroll up and down in the other window without leaving this one. Note that this works with any number of windows: the window that is scrolled is the “next window”, clockwise from the current one. In this schematic, the selected window is the one with the border, the one that scroll-other-window scrolls is the one with the arrows:

With more than two windows this requires careful placement of windows to work as expected. For instance, you cannot have three side-by-side buffers (1-3 above) and use 1 as a reference when working in both 2 and 3, since scroll-other-window in 2 will scroll 3. Thankfully, we can specify the rule by which to select the window for scrolling. One option is

(setq other-window-scroll-default #'get-lru-window)

which will always scroll the least-recently-used window, since you won’t be wading into buffer 1 – the reference – often. Alternatively, you might want scroll-other-window in buffers 2 and 3 to scroll each other as you switch between them and ignore buffer 1. You’d then use the most-recently-used window:

(setq other-window-scroll-default
      (lambda ()
        (or (get-mru-window nil nil 'not-this-one-dummy)
            (next-window)               ;fall back to next window
            (next-window nil nil 'visible))))

This works great with The back-and-forth method.

Setting the window to scroll

There is another way to change the window that is scrolled instead: by setting a variable (other-window-scroll-buffer), you can specify the buffer whose window should be scrolled instead of the next window. But this is mostly an option for package authors. To do it on the fly, we’d need to write another elisp command, something like

(defun ace-set-other-window ()
  "Select a window with ace-window and set it as the \"other
window\" for the current one."
  (when-let* ((win (aw-select " ACE"))
              (buf (window-buffer buf)))
    (setq-local other-window-scroll-buffer buf)))

This is only useful if we want this association to be persistent. Otherwise the LRU/MRU method does what we need most of the time. See also master-mode below.

Scrolling other windows: minutiae
  1. The viability of the default bindings for scroll-other-window (C-M-v and C-M-S-v) depends on your tolerance for modifiers. A good candidate for remapping, especially if you use a modal input method. C-M-v can be invoked as ESC C-v already, I bind the other one to ESC M-v.

  2. scroll-other-window works from the minibuffer too. The window scrolled is usually the one that the minibuffer-using command was invoked from, and can be set explicitly as the value of minibuffer-scroll-window.

  3. From Emacs 29 onwards, scroll-other-window is better at handling non-text buffers like PDFs, where scrolling is handled by special functions. It now calls whatever the standard scrolling commands (scroll-up-command and scroll-down-command) are bound to. To scroll PDF buffers managed by the pdf-tools package in the “next window” position, for instance:

       (with-eval-after-load 'pdf-tools
         (keymap-set pdf-view-mode-map "<remap> <scroll-up-command>"
                     #'pdf-view-scroll-up-or-next-page)
         (keymap-set pdf-view-mode-map "<remap> <scroll-down-command>"
                     #'pdf-view-scroll-down-or-previous-page))
    

    Another example: after rebinding the regular paging commands via pixel-scroll-precision-mode, scroll-other-window will smooth-scroll the other window:

isearch-other-window

Continuing with the idea of using a buffer in another window as a reference, a straightforward extension of scroll-other-window is to search the “next window” instead isearch is a fantastic navigational tool. . We make sure to search in the same window that we’ve configured to scroll with scroll-other-window above.

(defun isearch-other-window (regexp-p)
    "Function to isearch-forward in the next window.

With prefix arg REGEXP-P, perform a regular expression search."
    (interactive "P")
    (unless (one-window-p)
      (with-selected-window (other-window-for-scrolling)
        (isearch-forward regexp-p))))

(keymap-global-set "C-M-s" #'isearch-other-window)

The function other-window-for-scrolling returns a suitable window, respecting our choice of other-window-scroll-default above.

Here’s an example of using isearch-other-window to work in a shell and a documentation (Man) buffer together:

Play by play
  1. Type in a partial Curl command
  2. Invoke isearch-other-window (C-M-s here), which starts searching the Man buffer
  3. Search for --ssl revoke, which finds the option we’re looking for. (This special matching behavior is from setting isearch-whitespace-regexp.)
  4. Pressing RET ends isearch and we’re back in the shell.
  5. Scroll the other window with scroll-other-window, then use hippie-expand to type in the argument we want.

The keybinding C-M-s is already bound to isearch-forward-regexp, but there are many other ways to call that command: via a prefix arg to isearch-forward (C-u C-s), or by toggling regexp search with M-r when isearching, for instance.

Performing actions in other windows

There are two simple ways to temporarily switch to another window in elisp: (save-window-excursion (select-window somewin) ...) and (with-selected-window somewin ...).

For our purposes, the difference between them is that the former restores the window configuration at the time it was executed, which includes the buffer positions relative to the windows and the values of (point) in the buffer. The latter persists changes across the frame, and is typically what we want. If the changes were not persistent, there would be no point to this exercise!

Switch buffers in the next window.

You can have hundreds of buffers in Emacs but only a handful of windows. This is, in fact, the source of the window management problem. So any comprehensive solution has to involve changing buffers shown in existing windows. The ace-window dispatch system is one solution. But the built-in next-buffer and previous-buffer commands offer another easy 80% solution to changing buffers shown in other windows: we just automate away the window switching dance. We don’t need a dedicated next-buffer-other-window command for this – we can just replace next-buffer with the new function.

(defun my/next-buffer (&optional arg)
  "Switch to the next ARGth buffer.

With a universal prefix arg, run in the next window."
  (interactive "P")
  (if-let (((equal arg '(4)))
           (win (other-window-for-scrolling)))
      (with-selected-window win
        (next-buffer)
        (setq prefix-arg current-prefix-arg))
    (next-buffer arg)))

(defun my/previous-buffer (&optional arg)
  "Switch to the previous ARGth buffer.

With a universal prefix arg, run in the next window."
  (interactive "P")
  (if-let (((equal arg '(4)))
           (win (other-window-for-scrolling)))
      (with-selected-window win
        (previous-buffer)
        (setq prefix-arg current-prefix-arg))
    (previous-buffer arg)))

And we can take over next-buffer and previous-buffer:

(keymap-global-set "<remap> <next-buffer>"     'my/next-buffer)
(keymap-global-set "<remap> <previous-buffer>" 'my/previous-buffer)

Finally, we define a fallback version of switch-to-buffer and shove all of these into a repeat-map so we can call them consecutively with n, p and b:

;; switch-to-buffer, but possibly in the next window
(defun my/switch-buffer (&optional arg)
  (interactive "P")
  (run-at-time
   0 nil
   (lambda (&optional arg)
     (if-let (((equal arg '(4)))
              (win (other-window-for-scrolling)))
         (with-selected-window win
           (switch-to-buffer
            (read-buffer-to-switch
             (format "Switch to buffer (%S)" win))))
       (call-interactively #'switch-to-buffer)))
   arg))

(defvar-keymap buffer-cycle-map
  :doc "Keymap for cycling through buffers, intended for `repeat-mode'."
  :repeat t
  "n" 'my/next-buffer
  "p" 'my/previous-buffer
  "b" 'my/switch-buffer)

The result of this keymap gymnastics, with key descriptions in the top right:

Play by play
  • Call my/next-buffer or my/previous-buffer (I’ve bound them to C-x C-n and C-x C-p instead of remapping the default next-buffer binding C-x <right>).
  • This activates the repeat-map buffer-cycle-map, so I can continue cycling through buffers with n and p.
  • Exit the repeat-map by pressing any other key.
  • Call my/next-buffer with a prefix argument (C-u C-x C-n). This activates the buffer-cycle-map, but in the other window, so you can cycle buffers in the other window with n and p.
  • Pressing b when the repeat map is active calls switch-to-buffer in the window that is selected. This is a fallback when the buffer you need is not one or two away in the window’s buffer history.

Using b to display a buffer in another window is consistent with how ace-window’s dispatch version works.

master-mode and scroll-all-mode

A passing note: Emacs provides master-mode, a bespoke solution for performing actions in other windows without leaving this one. You can designate a buffer as the “slave” buffer of the current buffer (the “master”). This opens up a keymap for scrolling the slave buffer without leaving the current one. By itself, this is a worse alternative to the more transparent and immediate solutions involving other-window-scroll-default above. But you can add to this keymap with the plumbing command master-says, which helps you set up keys to do predefined actions in the slave buffer. This built-in action, for example, recenters the slave buffer:

(defun master-says-recenter (&optional arg)
  "Recenter the slave buffer.
See `recenter'."
  (interactive)
  (master-says 'recenter arg))

But this can be any action: you could set a shell or compilation buffer as the slave buffer of every project buffer, and use master-mode to page through them, copy the latest output, send commands and so on.

And while we’re focused on scrolling, scroll-all-mode is a simple way of tying together scroll actions in all windows on the frame. On occasions where you want to keep two more more window views in sync, this is a handier method than scrolling the active window and then the other window.

with-other-window: An elisp helper

What’s better than writing a general-purpose command to automate one switch → act → switch-back dance? A general-purpose macro to automate writing the general-purpose command! We can decouple the action from the switching with a macro:

(defmacro with-other-window (&rest body)
  "Execute forms in BODY in the other-window."
  `(unless (one-window-p)
    (with-selected-window (other-window-for-scrolling)
      ,@body)))

The above examples become straightforward applications of this macro

(defun isearch-other-window (regexp-p)
  (interactive "P")
  (with-other-window (isearch-forward regexp-p)))

(defun isearch-other-window-backwards (regexp-p)
  (interactive "P")
  (with-other-window (isearch-backward regexp-p)))

This is the elisp counterpart to the interactive ace-window-one-command.

Do you need many windows?

The world seems to have converged on a single UI for editors: one main window, a tab bar at the top (with one window per tab), a directory or contents sidebar on the left, an optional doodad on the right, and a terminal emulator below.

Modern text and code editor window layouts.  Clockwise from top left -- Zed, Neovim, Obsidian, VSCode.

Every editor got the memo… except Emacs, it appears. You could recreate this window layout and workflow in Emacs. Or any other, for that matter. But all this furious window management behooves us to ask an even more basic question: Why even have more than one window?

There’s some merit to this: the screen could be devoted to one buffer at a time, with buffer-switching taking the place of window switching. There’s no need to worry about resizing windows, and anything that pops up in the course of introspection or regular editing (like documentation windows) can be dismissed with a keypress, typically q. Special buffers like the file browser are accessible via dedicated commands like dired-jump.

Relaxing this requirement to two-windows-at-a-time helps retain most of the hassle-free behavior while adding the benefits of using the second window as a live reference. By default, Emacs is set up to do this well, as evidenced by scroll-other-window and other commands. No rigid layout imposed via decree from on high, but no chaotic structureless and windows popping up like weeds either.

Effectively, we’ve circled our way back to The Zen of Buffer Display. While it would be ironic if the window management freedom Emacs provides causes us to reject it, we can route around the problem and not deal with windows at all, irrespective of how many we’d like to have on screen simultaneously.

So here are two more strategies for window management, both of which involve minimizing dealing with windows. The first one is “window management” in the loosest sense of the term:

Windows are made up, let’s ignore them

Window-agnostic jumping with Avy is a special case of a general idea: when using Emacs, we are primarily concerned with text. As a container for text, a window can be an unnecessary abstraction. This framing is natural when a destination is outside the screen contents, such as when jumping to definitions with xref-find-definitions.

But there are several other ways to apply this window-agnosticism. The mark-ring and global-mark-ring keep track of locations we jump from, letting us jump back with pop-to-mark-command (C-u C-SPC) and pop-global-mark (C-x C-SPC), the latter of which can jump across windows if necessary. A package like dogears can provide more granular control and a nicer UI to retrace your steps.

Making pop-to-buffer jump across windows.

By default, pop-global-mark always switches buffers (if required) in the current window. We’d like it to double as a window-switcher, which requires a little advice:

(define-advice pop-global-mark (:around (pgm) use-display-buffer)
  "Make `pop-to-buffer' jump buffers via `display-buffer'."
  (cl-letf (((symbol-function 'switch-to-buffer)
                         #'pop-to-buffer))
                (funcall pgm)))

To manually pin a position to jump back to later, there is point-to-register (C-x r SPC) and jump-to-register (C-x r j). Again, this switches windows as a side-effect.

For more permanent records, you can create and jump to bookmarks with bookmark-set (C-x r m) and bookmark-jump (C-x r b).

Between these, you have plenty of options for navigating across windows to locations that are meaningful, as ascertained by either Emacs or you. These work just as well with a single window in an Emacs frame as they do with the canonical twenty-first century IDE window layout.

Deal with windows so we don’t have to deal with windows

i.e. Fixing the Whack-A-Mole window problem.

As much as this write-up is about manual actions involving Emacs windows, it was unavoidable: at some point I was going to have to mention display-buffer-alist and automatic window behavior. The idea is simple. Every time elisp code wants to show you a buffer, it tries to match the buffer it is displaying against a list of rules in this variable. The matching entry specifies how it should be displayed.

If we set up rules – specifying window sizes, positions, roles, focus – for every kind of buffer we see in our daily Emacs use, that’s most window management sorted… right?

Right, actually. The reality hews close to the aspiration. The problem with display-buffer-alist is not that it doesn’t work, but that it’s a lot of work. Creating rules for displaying buffers involves understanding many more aspects of Emacs’ API than is reasonable for most users: buffer and mode predicates, window types and slots, display-buffer action functions, window parameters, and a whole lot more gibberish. And at the end of this expedition into the elisp manual, there is no easy way to express a simple intention, like “do not disturb my window arrangement” Specifying overreaching and overriding display-buffer preferences can do this, but they lead to dozens of edge cases and unintended behavior. . As such, it’s a tool primarily used by package authors to surface a more approachable interface to specifying automatic window behaviors for their package.

But in the spirit of the almanac, let’s not leave this topic empty handed.

  • The Shackle package papers over the display-buffer-alist oddness and presents a simplified elisp interface for specifying window rules. If you want to corral a couple of pesky buffer types that always ruin your window arrangement and have you reaching for winner-undo, this is your best bet.

  • Emacs distributions usually provide a simple interface for specifying these preferences. Doom Emacs provides a convenient set-popup-rule! command for this. If you’re using one, you’re probably covered.

  • And if you’ve got a hankering for tinkering, Mickey Peterson’s article on demystifying the window manager, this video by Protesilaos Stavrou and the elisp manual are all fine resources, as alluded to in the preface.

Popper, Popwin, shell-pop and vterm-toggle

While we’re aiming at the ideal of the minimal workspace uncluttered by windows, a popup manager is another helpful tool.

Popwin and Popper are Emacs packages based on the observation that not all buffers are created equal. There are (primary) buffers we spend most of our time in, and (popup) buffers we’d like to access temporarily – to use as a reference, page through documentation, run shell commands, check a task or compilation status, access search results, read messages, and so on. Using display-buffer-alist or an equivalent method, you can make these buffers use smaller, auxiliary windows and not grab the cursor when they appear. But that doesn’t solve the access problem: what we’d like is one key access to summon these popup buffers, and easy ways to dismiss their windows, cycle through or kill them.

Popper provides this for all kinds of buffers you choose to (pre-)designate as popups, helping you stick to the one (or two) window paradigm, raising and dismissing these auxiliary windows as needed. This image shows popups available in the current context as a line of tabs that can be accessed or cycled through with one key:

Popwin is an older and more comprehensive implementation of this idea, but it bundles together quick key access and its own bespoke display-buffer configuration, which may not be what you want. If you only want one key-access to summon and dismiss shell buffers, shell-pop or vterm-toggle might be all you need instead.

The Missing Pieces

We conclude with window management options that should exist… but don’t.

window-tree

There is a fundamental disconnect between how Emacs represents windows and how we manipulate them using the approaches discussed above.

Windows in a frame in Emacs are arranged in a tree: the leaf nodes are “live” (real) windows, and the rest are “internal” (virtual) windows The minibuffer is technically not part of this tree, although it can be reached by traversing it. (See the window-tree function.) .

Most user-facing operations on windows, such as moving between them with other-window or Windmove, ignore the tree structure and work by examining their spatial positions instead. This often causes unexpected and unintuitive behavior when splitting or deleting windows, or imposes confusing constraints on what splits you can create. For example, there is no way to perform these transformations:

The window that needs to be split here is neither the frame root nor a leaf window – it’s some internal node in the tree.

Adding commands for window-tree operations opens the door to many new possibilities. Frame transformations like splitting, transposing, mirroring and so on are elementary operations on window-tree branches. Multiple window selection is possible via “selecting” internal windows, and partial window configurations can then be operated on, handed off to other tabs or frames, duplicated or persisted. Tree branches can be protected from being mangled by display-buffer and friends This kind of all-or-nothing window behavior is currently enabled via Elisp’s atomic windows API, which is a significantly more restrictive approach. , and you can have sections of a frame devoted to one task, with the sibling branch tolerating flexible, looser behavior.

How do we write this hypothetical wintree package?

  1. Elisp already provides functions to query the window tree: window-tree returns the tree itself. frame-root-window returns the tree root, and window-parent, window-child, window-*-sibling do what you’d expect.
  2. There is some support for tree traversal via walk-window-tree and walk-windows.
  3. There are no elementary functions for mutating the tree, except via splitting and deleting live windows the usual way.
  4. There is no concept of “selecting” an internal window, so this will have to be simulated via the UI, perhaps by adding a border inside each window in the sub-tree.

So some of the required elements are present. The missing ingredient is a motivated Emacs user (perhaps you) stepping into window.el and getting their hands dirty!

The tiling-wm integrator

Emacs’ window-tree model is almost exactly that of manual tiling window managers like i3 or bspwm, sans some affordances like i3’s tabbed windows. This leads us to a natural question: why use a tiling window manager inside of another one? Yo dawg, I heard you like tiling… If you use i3, bspwm or Emacs inside tmux, it’s natural to want to be able to navigate both seamlessly, with the same keybindings. There are a couple of Emacs packages for this: Pavel Korytov’s i3-integration, and something I hacked up for qtile. But providing a cleaner and more unified interface for this from Emacs can make integrating with all window managers much easier. Again, most elements we need are already present:

  1. The window manager should provide some way to identify the active window class, and to move across and manipulate windows programmatically. This can be via a shell command, socket or server-based IPC, or (on Linux) via D-Bus methods. This covers most window managers and terminal multiplexers.
  2. On the Emacs side, we need a communication method-agnostic interface for window operations that mimics how most window managers do them, supporting a common subset of operations or (more ambitiously) their union.
  3. When switching windows in the window manager, we check if the active window is Emacs, and yield control to it if required. Emacs then performs the window operation within or without the Emacs frame as required.

As before, the missing ingredient is you!

The view from here

Believe it or not, this was the short version. To keep the scope of this piece under check, there are several window management strategies I had to exclude, such as anything involving tab-line-mode, or window types and properties like atomic, dedicated or side windows. And we are all safer for skirting around the issue of display-buffer.

Where does that leave us? With about a dozen ways to switch, move, jump around, create, delete and otherwise manipulate windows and window configurations in Emacs, many ways to control window display on the fly when invoking commands, and half a dozen ways to work across windows and avoid thinking about them at all. Once again, this collection is colored and limited by my experience with window wrangling in Emacs, and thus it’s not an exhaustive list. If I’ve missed something simple and useful please let me know!

For better or worse, window management in Emacs is not so much complicated as it is open-ended. Emacs provides the ingredients and some instructions, and the ingredients can work as basic meals by themselves.

But with a little cooking we can make something delicious. Bon appétit.


Updates and Corrections

Thanks to the following folks for corrections and suggestions:

  • JD Smith for pointing out that winner-mode maintains separate window configuration history per Emacs frame, so it remains viable if you use multiple frames.
  • Grant Rosson for reminding me that pop-global-mark does not work across Emacs windows by default, and needs a little advice.
  • u/simplex5d for corrections to the winum-keymap definition.
  • kotatsuyaki mentions that aw-dispatch-always needs to be set for ace-window actions to be available when there are two or fewer windows on the screen.
-1:-- The Emacs Window Management Almanac (Post)--L0--C0--May 10, 2024 11:13 PM

Irreal: Red Meat Friday: Things No Sensible Person Would Want

People often say that as you get older you grow wiser and come to understand more and more things. Those people are lying. The older I get, the more things I don’t understand. Case in point.

Why would anyone want to run Emacs in a browser? Let’s face it, one of the frequent gripes about Emacs is that it’s slower than, say, Vim. Why would you want to make it slower by running it in a browser?

Then there’s the security issue. The browser is the leading point of exploit for the normal user so, again, why would you want to put everything you do in Emacs under that threat? It’s worse if, like me, you do the bulk of your work in Emacs.

But wait. It gets worse. Every time your browser updates there’s a good chance it will break your embedded Emacs. It’s bad enough when the OS updates. I can only imagine the problems that would occur if Emacs were running in the browser.

As far as I can see, the constituency for this is the same as that for Chromebooks: those who want to live in the browser. A case could be made for this, I guess, for your Aunt Millie but why would anyone doing serious, technical work on their computer embrace such a thing? In the end, all you’ve done is make Emacs slower, less secure, and given all the wrong people—like Google and Microsoft—more power to control how you use your computer.

I always say, “Use whatever works for you” but the idea of running Emacs in the browser is enough to make me reconsider—or at least amend—that. Really, I just don’t understand why anyone would want to do such a thing but I’m sure my much wiser readers will enlighten me.

-1:-- Red Meat Friday: Things No Sensible Person Would Want (Post jcs)--L0--C0--May 10, 2024 03:33 PM

Charles Choi: Sunrise and Sunset in Emacs

We’ve been having great weather this past week in San Francisco, where the days have been balmy. I enjoy being outside in the early evening, and often wonder when sunset is. As it turns out, Emacs has a command to help answer that: sunrise-sunset. Running it will display the sunrise and sunset times for the current day in the mini-buffer.

Before you use sunrise-sunset, you’ll need to customize two variables to establish your location:

  • calendar-latitude
  • calendar-longitude

Use your maps app/website to help find those values and set them appropriately with either the command customize-variable or your configuration method of choice.

Once done, run M-x sunrise-sunset to get the sunrise and sunset times for the current day.

If you want the sunrise and sunset times for a different date, use C-u M-x sunrise-sunset. You’ll be prompted for the year, month, and day.

If you are in an Org Agenda or Calendar (calendar) window, you can get the sunrise and sunset times for the day the point is on by pressing S.

In the rare event of Emacs not being able to get the correct time zone from your operating system, you can set some variables manually as described in the Emacs manual. Regardless, use your favorite weather app/website to confirm that the sunrise and sunset times being returned by Emacs are correct.

Hope that this knowledge brings you as much joy as its done for me. I’ve used this to plan for many a great day.

References

-1:-- Sunrise and Sunset in Emacs (Post Charles Choi)--L0--C0--May 10, 2024 05:30 AM

Irreal: Casual Dired

Charles Choi is on a roll. He discovered the trick of improving the interfaces to various Emacs functionalities by capturing them in a transient menu. I’ve already written about his considerable contributions to the usability of Calc: my last post is here.

Now he’s back with a nice interface to Dired. I do virtually ail my file operations with Dired but for whatever reason I don’t do a lot of them so whenever I want to do anything even slightly non-routine, I have to bring up the Dired cheat sheet, which I have bookmarked for easy reference.

Choi has solved that problem. Now a simple keystroke brings up the menu shown on his post and you can invoke any of the Dired functions right from the menu. Notice how it doesn’t get in your way. If you already know how to do something, you can just do it. If you can’t remember some command you can bring up the menu to invoke it and immediately return to your normal workflow.

I really like this package. It mostly stays out of your way but when you need it, it’s just a keystroke away. Doubtless Choi will make adjustments as he gains experience with the package but even now it seems like a win.

-1:-- Casual Dired (Post jcs)--L0--C0--May 09, 2024 04:25 PM

Sven Seebeck: My EMMS Dired-Player Transient

My EMMS Dired-Player Transient

Transients seemingly are all the rage recently and I have built a few myself once I realized that Transient is built-in already, thus doesn't need any additional packages. The recently released CASUAL DIRED package inspired me to share my EMMS-DIRED-PLAYER transient that I have built a while ago.

I am not a "playlist-guy" and my usual use-case for listening to music is selecting either a file or folder in DIRED and simply play that. To make the process a little more convenient I have built an EMMS-DIRED-PLAYER transient.

20240509T095700--emms-transient.webp

Figure 1: My EMMS Dired Transient. And yes, I started to use TABS

I am sure this transient can be still improved, and likely I will do so at some point, but I am using this for a while and for the time being it does seem to do the trick.

(transient-define-prefix svbck/emms-dired-transient ()
  :transient-suffix     'transient--do-stay
  ["EMMS Dired Player\n"
   ["Player Controls"
    ("P" "Play Dired" emms-play-dired )
    ("SPC" "Pause" emms-pause)
    ("s" "Stop" emms-stop)
    ("k" "Seek" emms-seek)
    (">" "Forward" emms-seek-forward)
    ("<" "Backward\n" emms-seek-backward)]

   ["Navigate Library"
    ("n" "Next Line" next-line)
    ("p" "Previous Line" previous-line)
    ("RET" "Open" dired-find-file)
    ("q" "Close" bury-buffer)
    ]
   ["Playlist"
    ("g" "Exit & Goto Playlist" emms :transient nil)
    ("N" "New Playlist" emms-playlist-new)
    ]
   [""
    ("ESC" "Exit Player" transient-quit-one)]
   ])

(define-key dired-mode-map "ä" #'svbck/emms-dired-transient)

Since I am using a Scandinavian keyboard-layout I have bound the transient to "ä" since it is unlikely to be bound to anything else.

I am still torn whether I like the whole transient thing, but it is quite convenient and setting one up is rather straight forward. One thing that I specifically enjoy is that I can name the commands in a way that it makes to me, as opposed to the "usual" approach which shows the full command-name in say "which-key".

In any case have set a few up to make my most-common tasks a little more convenient.

-1:-- My EMMS Dired-Player Transient (Post)--L0--C0--May 09, 2024 07:05 AM

Arne Bahlo: Writing an SDK in Zig

The first project I used Zig for was a rewrite of a custom static site generator for the Fire Chicken Webring, you can read that post here: Thoughts on Zig.

Writing a small application is a lot easier than writing a library, especially if you’re hacking it together like I was. Let’s do something harder.

And because I work at Axiom, we’re going to write an SDK for the public API. In this first part I’ll set up the library and add a simpel getDatasets fn to fetch all datasets the token has access to.

We’re using Zig 0.12. It might not work with a different version.

Bootstrap the SDK

First, we create a directory called axiom-zig and run zig init:

info: created build.zig
info: created build.zig.zon
info: created src/main.zig
info: created src/root.zig
info: see `zig build --help` for a menu of options

We also want to create a .gitignore to ignore the following folders:

/zig-cache
/zig-out

Perfect. Next step: Create a client struct in root.zig. We’ll need an Axiom API token to authenticate requests, a std.http.Client to make requests and an std.mem.Allocator to allocate and free resources:

const std = @import("std");
const Allocator = std.mem.Allocator;
const http = std.http;
// We'll need these later:
const fmt = std.fmt;
const json = std.json;

/// SDK provides methods to interact with the Axiom API.
pub const SDK = struct {
    allocator: Allocator,
    api_token: []const u8,
    http_client: http.Client,

    /// Create a new SDK.
    fn new(allocator: Allocator, api_token: []const u8) SDK {
        return SDK{ .allocator = allocator, .api_token = api_token, .http_client = http.Client{ .allocator = allocator } };
    }

    /// Deinitialize the SDK.
    fn deinit(self: *SDK) void {
        self.http_client.deinit();
    }
}

test "SDK.init/deinit" {
    var sdk = SDK.new(std.testing.allocator, "token");
    defer sdk.deinit();
    try std.testing.expectEqual(sdk.api_token, "token");
}

Initially I had deinit(self: SDK) (without the pointer). Zig didn’t like this at all and led me down a rabbit hole of storing the http.Client as a pointer too—once I found my way out and remembered I need a pointer everything worked.

I like that Zig encourages writing tests not only next to the source code (like Go), not only in the same file (like Rust), but next to the code it’s testing.

Add getDatasets

Our first method will be getDatasets, which returns a list of Axiom datasets (see the api documentation).

Create a model

First we need a model:

pub const Dataset = struct {
    id: []const u8,
    name: []const u8,
    description: []const u8,
    who: []const u8,
    created: []const u8,
};

Don’t worry about created being a datetime, we’ll deal with that later.

Add the getDatasets fn

Let’s add a function to get the datasets to our SDK struct:

/// Get all datasets the token has access to.
/// Caller owns the memory.
fn getDatasets(self: *SDK) ![]Dataset {
    // TODO: Store base URL in global const or struct
    const url = comptime std.Uri.parse("https://api.axiom.co/v2/datasets") catch unreachable;

    // TODO: Draw the rest of the owl
}

We’re taking a pointer to SDK called self again, this means that this is a method you call on a created SDK. The ! means it can return an error. In a later post I want to go deeper into error handling, for now it can return any error.

Because there is no dynamic part of the URL, we can parse it at compile time using comptime. I like this explicit keyword, in Rust you need to rely on macros to achieve something similar, or use lazy_static.

Make the HTTP request

Let’s open a connection to the server:

var server_header_buffer: [4096]u8 = undefined; // Is 4kb enough?
var request = try self.http_client.open(.GET, url, .{
    .server_header_buffer = &server_header_buffer,
});
defer request.deinit();

I wonder if 4kb is always enough for server headers. Especially in a library I don’t want it to fail because the server is suddenly sending more headers.

Axiom uses Bearer auth, so let’s set the Authorization header:

var authorization_header_buf: [64]u8 = undefined;
// TODO: Store this on the SDK for better re-use.
const authorization_header = try fmt.bufPrint(&authorization_header_buf, "Bearer {s}", .{self.api_token});
request.headers.authorization = .{ .override = authorization_header };

An Axiom API is 41 characters, plus Bearer ’s 7 characters equals 48 characters. We’re allocating 64 to be safe if it ever changes (it really shouldn’t).

Also note that I’m calling try fmt.BufPrint; this will return the error to the caller of our function (that’s what the ! indicating).

Finally, we can send the headers to the server and wait for a response:

try request.send();
try request.wait();

Parse the JSON-response

First, we need to read the body into a buffer:

var body: [1024 * 1024]u8 = undefined; // 1mb should be enough?
const content_length = try request.reader().readAll(&body);

Same issue as with the server headers: What is a good size for a fixed buffer here?

I’ve tried doing this dynamically with request.reader().readAllAlloc(...), but parsing the JSON with this allocated memory relied on the allocated []const u8 for string values. This means as soon as I deallocated the body, all string values in the returned JSON were invalid (use-after-free). Yikes.

Next, we call json.parseFromSlice with our body:

const parsed_datasets = try json.parseFromSlice([]Dataset, self.allocator, body[0..content_length], .{});
defer parsed_datasets.deinit();

Now we need to copy the memory out of the parsed_datasets.value to prevent it from being freed on the parsed_datasets.deinit() above and return it:

const datasets = try self.allocator.dupe(Dataset, parsed_datasets.value);
return datasets;

Edit: Matthew let me know on Mastodon that this implementation is still illegal, and it’s only working because the stack memory is not getting clobbered. You can set .{ .allocate = .alloc_always } in json.parseFromSlice, which will dupe the strings, but not actually solve the problem (where do the strings live?). What I ended up doing is creating a Value(T) struct, which embeds both the value and an arena allocator which I pass to json.parseFromSliceLeaky. This means the value you get back from getDatasets will have a deinit() method and you need to do .value to get the actual value. You can read the updated source code on GitHub.

Write a test

And finally we’ll write a test where we initialize the SDK, get the datasets and ensure _traces is the first one returned. Once I set up CI, I’ll create an Axiom org just for testing so we can be sure which datasets are returned.

test "getDatasets" {
    const allocator = std.testing.allocator;

    const api_token = try std.process.getEnvVarOwned(allocator, "AXIOM_TOKEN");
    defer allocator.free(api_token);

    var sdk = SDK.new(allocator, api_token);
    defer sdk.deinit();

    const datasets = try sdk.getDatasets();
    defer allocator.free(datasets);

    try std.testing.expect(datasets.len > 0);
    try std.testing.expectEqualStrings("_traces", datasets[0].name);
}

If you want to see everything together, check it out on GitHub.

Next steps

In the next part I’ll add createDataset, updateDataset and deleteDataset, initial error handling and show how you can import the library in a Zig project.

Any thoughts? Let me know!

-1:-- Writing an SDK in Zig (Post Arne Bahlo)--L0--C0--May 09, 2024 12:00 AM

Irreal: A Note Of Thanks

One of my everyday chores is to read through the Emacs and Org-mode mailing lists. I don’t read most of the posts unless they catch my interest but just checking the headlines gives me an idea of what’s going on. The other day, I saw this post asking why such and such a function was written in C rather than Elisp.

It’s a common question. Someone is reading through the source code, finds a function written in C, and thinks, “this code could/should be written in Elisp.” Everyone’s always complaining about the C core so why not reduce that C core as much as we can?

It turns out that it’s not just inertia as Eli Zaretskii tells us in his answering post. He explains in detail the problems that arise from making an apparently simple change like rewriting a function in Elisp. It’s not, as you might think, a worry about introducing a surface error due to the rewrite. It’s that the C core is available during the whole boot process but an Elisp function may not be so before rewriting a function in Elisp you have to make sure it gets loaded before any other function calls it. Sometimes that function, as the one under discussion, may be called from C, making it even harder to make sure it gets loaded before being called. See Eli’s post for a complete explanation.

Most of us, of course, wouldn’t think of all that. We’d merely say, “Yeah, of course, rewrite that sucka.” Those two messages gave me a renewed appreciation for Eli and the other devs who have to deal with keeping Emacs the world class editor that it is. It’s easy to think they’re just being overly conservative but it turns out they have good reason for their caution.

So I’d like to take a moment to thank all the devs for bringing their deep knowledge and caution to the task of keeping Emacs running. It’s harder than it seems.

-1:-- A Note Of Thanks (Post jcs)--L0--C0--May 08, 2024 03:17 PM

Arialdo Martini: Emacs as a F# IDE - Setup

So you want to code in F# with Emacs?
I can relate, I also love both. Oh dear, if I love them!

So, presto! Let’s make Emacs your next F# IDE!

TD;DR

Summary

You need 3 components:

Package Purpose
Eglot The Language Server Protocol client
eglot-fsharp Integrates Eglot with fsharp-mode.
It provides completion, syntax checking and the like.
fsharp-mode The major mode responsible for syntax highlighting and indentation

Optionally, you might also want to have:

Package Purpose
corfu.el It provides a pop-up for IntelliSense

Steps

1. Install Eglot

(use-package eglot
  :ensure t)

M-x Eglot, Emacs Polyglot — or just Eglot — is the official Language Server Protocol client for Emacs. It is built-in since version 29.
It is not the only LSP client available. You might prefer using lsp-mode. This post, though, covers Eglot only. If you are interested in an lsp-mode version, drop me a message, I will find the time to extend the post.

Notes

An LSP client is that piece of software that communicates with the underlying Language Server to provide features like auto-completion (“IntelliSense” in the Microsoft lingo), go-to-definition, find-references, and the like.

As a client, Eglot is server agnostic, but you will need a specific package for glueing it with fsharp-mode. The integration is provided by eglot-fsharp, a separate package. We will see this in the next step.

As you have imagined, LSP is based on the client-server architecture. Therefore, an LSP client needs a corresponding running server. As a matter of fact, a server covers one single language, so you will need an LSP server for F#, one for Haskell and so on.
The LSP server for F# is called FsAutoComplete, which is part of the Ionide tool family.

You can install it via dotnet tool or let eglot-fsharp do this for you. I will cover both approaches.

2. Install fsharp-mode

(use-package fsharp-mode
  :defer t
  :ensure t)

:defer t enhances the startup speed by delaying the loading of the package until it is actually needed.

:ensure t conveniently downloads the package from the internet.

Once fsharp-mode is installed, you should see F# files properly syntax-highlighted. Indentation will also work.

3. Install eglot-fsharp:

Let’s connect Eglot with fsharp-mode:

(use-package eglot-fsharp
  :ensure t
  :after fsharp-mode
  :config
  (add-hook 'fsharp-mode-hook #'eglot-ensure))

The hook makes sure that when fsharp-mode is activated, Eglot is also loaded.

4. Let eglot-fsharp install fsautocomplete

Now, you just need to install the F# Language Server.
There are 2 ways to do this:

  1. Use dotnet.
  2. Let eglot-fsharp perform the installation.

For the latter:

  • Execute M-x eglot.
  • When asked, select fsharp-mode.
  • Wait for eglot-fsharp to download fsautocomplete.

fsautocomplete will be downloaded and saved in ~/.config/emacs/FsAutoComplete/netcore.

Notes

Although this is the standard procedure, I’m not super happy with it and I prefer a different approach. Read about it in fsautocomplete installed via dotnet.

5. Enable the IntelliSense pop-up with corfu.el

(use-package corfu
  :ensure t
  :init
  (global-corfu-mode)
  :config
  (setq corfu-min-width 250
        corfu-min-height 750
        corfu-count 20
        corfu-auto t
        corfu-cycle t
        corfu-separator ?\s
        corfu-preview-current "insert"
        corfu-scroll-margin 25
        ;; enable corfu on TAB
        tab-always-indent 'complete
        ;; shows documentation after `corfu-popupinfo-delay'
        corfu-popupinfo-delay '(1.25 . 0.5))
  (corfu-popupinfo-mode 1)

  ;; Sort by input history (no need to modify `corfu-sort-function').
  (with-eval-after-load 'savehist
    (corfu-history-mode 1)
    (add-to-list 'savehist-additional-variables 'corfu-history)) )

6. Optionally, make corfu.el beautiful with nerd-icons

;; Icons
(use-package nerd-icons
  :ensure t)

(use-package nerd-icons-completion
  :ensure t
  :after marginalia
  :config
  (nerd-icons-completion-marginalia-setup)
  (nerd-icons-completion-mode 1))

(use-package nerd-icons-corfu
  :ensure t
  :after corfu
  :config
  (add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter))

Alternative: install fsautocomplete via dotnet

What I don’t like of eglot-fsharp installing fsautocomplete is the following:

  • It requires to manually run eglot the first time.
  • fsautocomplete will not be available from the terminal.
  • Installing fsautocomplete with dotnet would download a second copy.

Therefore, I would rather install fsautocomplete the way the official NuGet page recommends to:

dotnet tool install --global fsautocomplete

This stores fsautocomplete.exe in .dotnet/tools, which is nothing specific to Emacs.

Now, it’s just a matter of instructing eglot-fsharp where to find fsautocomplete. Ideally, this is obtained by setting the variable eglot-fsharp-server-install-dir to "~/.dotnet/tools/". Unfortunately, this does not work as expected, because of https://github.com/fsharp/emacs-fsharp-mode/issues/341.

A workaround Protesilaos helped me find is to configure eglot-fsharp to overwrite the function eglot-fsharp--path-to-server where the path for fsautocomplete is defined:

(use-package eglot-fsharp
  :ensure t
  :after fsharp-mode
  :config
  (setq eglot-fsharp-server-install-dir "~/.dotnet/tools/")
  (add-hook 'fsharp-mode-hook #'eglot-ensure)

  ;; This fixes https://github.com/fsharp/emacs-fsharp-mode/issues/341

  ;; The original function used to prefix "dotnet" to the fsautocomplete path
  (defun eglot-fsharp--path-to-server ()
    "Return FsAutoComplete path."
    (file-truename (concat eglot-fsharp-server-install-dir "fsautocomplete" (if (eq system-type 'windows-nt) ".exe" "")))))

That should make the trick.

Now what?

Now profit!
I will cover which functionalities are enabled with these packages in one of the next posts. I also have to learn!
Stay tuned! Happy coding.

References

-1:-- Emacs as a F# IDE - Setup (Post Arialdo Martini)--L0--C0--May 08, 2024 12:00 AM

Irreal: Emacs And Modal Editing

Over at the Emacs subreddit, TheTwelveYearOld asks an interesting question: Would Emacs be more popular than Vim if it used modal editing by default? Experienced users will recognize that there’s an unstated premise buried in the question. The premise is that modality is what separates Emacs from Vim and that that modality is responsible for Vim’s greater popularity. That is, I think, a flawed premise. Vim’s real strength, in my opinion, is not modality but the regularity of its command set.

TheTwelveYearOld’s post has some other misunderstandings but the interesting thing about it is the comments. Most of those comments take issue with the notion that Vim’s popularity is due to its modality. Almost all of them ascribed Vim’s popularity to its ubiquitousness. If you log on to almost any Unix system, Vi(m) will be there so it made a lot of sense for students and beginners to learn it and make it their go to editor.

Most of the comments dispute that either editor enables faster editing than the other. Several commenters noted that both editors use about the same number of keys to do any given task. There were a lot of good, interesting, and informed comments and they’re well worth reading.

For me, the most shocking, or perhaps sad, comment was from ilemming who said,

Until the Emacs development model shifts away from its exclusive focus
on super-smart nerds and starts accommodating regular coders, it is
likely to remain less popular and may struggle to introduce new
game-changing features on a regular basis.

The idea that Emacs is only for the super smart seems to me to be self-revelatory. Emacs is not, of course, a tool restricted to the “super smart” and thinking so says more about the person saying it than it does about Emacs. If you’re a developer and find Emacs, or any other editor for that matter, too hard to master, you should find another line of work.

In any event, the post and the comments are an interesting part of the never ending editor wars.

-1:-- Emacs And Modal Editing (Post jcs)--L0--C0--May 07, 2024 03:25 PM

Sven Seebeck: The Books I Read in 2023

The Books I Read in 2023

For some reason the year 2023 hasn't been the best year for reading and it took me quite some time to get started and get into the flow. Eventually I managed, but it took some took some time. That being said I managed to finish some books at the least.

As usual the collection is as eclectic as it can be, but there is certainly some pattern there, but you can see that for yourself.

The Books I read in 2023 in reading order

  • V.E.Schwab: The Near Witch
  • David McRaney: You Are Not So Smart
  • David McRaney: You Are Now Less Dumb
  • Christopher Paolini: To Sleep in a Sea of Stars
  • Anne Rice: Interview with the Vampire
  • Adrian Tchaikovsky: Walking to Aldebaran
  • Haruki Murakami: Novelist as a Vocation
  • Cal Newport: Digital Minimalism (re-read)
  • Riley Sager: Home After Dark
  • T. Kingfisher: What Moves the Dead
  • TJ Klune: The House in the Cerulean Sea
  • Ryder Carroll: The Bullet Journal Method
  • Yuval Noah Harari: Homo Deus
  • Adam Grant: Think Again
  • Hector Garcia, F. Miralles: The Ikigai Journey
  • Grady Hendrix: How to Sell a Haunted House
  • T. Kingfisher: A House with Good Bones
  • Dan Schreiber: The Theory Of Everything Else
  • C.L Polk: Even Though I Knew The End
  • S.L. Coney: Wild Spaces
  • F.Miralles/H.Garcia: The Ikigai Journey
  • Shunmyo Masuno: Don't Worry
  • Oliver Burkeman: Four Thousand Weeks
  • Björn Natthiko Lindeblad: I May Be Wrong
  • Sequoia Nagamatsu: How High We Go In the Dark
  • Benjamin Labatut: When we cease to understand the World
  • Jay Anson: Amityville Horror
  • Andy Weir: Project Hail Mary
  • Dalai Lama: The Art Of Happiness
  • Dan Harris: Ten Percent Happier
  • Austin Kleon: Steal Like An Artist
  • Nat Segaloff: The Exorcist Legacy
  • T. Kingfisher: Thornhedge
  • Camilla Sten: The Lost Village
  • P.G. Wodehouse: The Inimitable Mr. Jeeves
  • P.G. Wodehouse: Carry On Jeeves
  • P.G. Wodehouse: Right Ho, Jeeves
  • Haruki Murakami: First Person Singular
  • Martha Wells: System Collapse

I read those books in plenty of different platforms: some on Libby, some on Audible - where I took advantage of a 3 months for 1€ each (or such) sale as I have no money to give away - and eventually I realized that my preferred and favorite service LIBRO.FM became available for subscription in Europe, so I more or less immediately switched there. Non-fiction books I figured work much better for me on paper or PDF as I can easily make notes while I'm reading.

As I am close to finishing another I will likely be able to update/start the list for 2024, but let's see how this next year will progress.

-1:-- The Books I Read in 2023 (Post)--L0--C0--May 07, 2024 06:38 AM

Sven Seebeck: I hate that I can't decide on anything…

I hate that I can't decide on anything…

Seriously, the moment there are two (or more) options available I am unable to decide which one to use.

In the past there had been text-editors, note-taking-apps, mono-spaced fonts, blogging-platforms, blog-themes, phones, operating-systems, notebooks, notebook-covers, notebook-grid-sizes and papers and not even talking about notebook-book-sizes, fountain-pens, fountain-pen-nib-sizes, pen-cases, bags of all kinds and… I save you the time even though I could continue.

While I believe to have found an answer to any of the above, the last one to add to this list: black or blue-black ink.

It's mindboggling.

-1:-- I hate that I can't decide on anything… (Post)--L0--C0--May 07, 2024 06:38 AM

Sven Seebeck: The Books I Read in 2022

The Books I Read in 2022

It hasn't been a necessarily good year for reading or, come to think about it, blogging, but quantity is of course not the important factor. Now that there are only a couple of hours left in this year it's unlikely that I will finish any books today anymore, it seemed like a good time to post the list of books I read this year.

In fact I started the year the way I ended the previous one: with continuing The Wheel Of Time series and as each book clocks-in about 40 hours of audio-book reading (which equals about one month of reading) it is reasonable that there aren't too many books on this list.

I enjoy the series but at some point (can't remember exactly when, but likely sometime in April) I needed to take a break from the series and read something else. My reading list is all over place but overall there is a lot less Horror in these books like in the previous years.

From the other books on this list I really enjoyed the Dirk Gently books which reading had been excellent and very entertaining, also Sea of Tranquility really stood out. Great book!

So here's the list in reading-order.

My 2022 reading list

  • Robert Jordan: The Dragon Reborn (Wheel of Time Book 3)
  • Robert Jordan: The Shadow Rising (The Wheel of Time Book 4)
  • Robert Jordan: The Fires of Heaven (The Wheel of Time Book 5)
  • Neil Gaiman: Don't Panic
  • Douglas Adams: Dirk Gently's Holistic Detective Agency
  • Grady Hendrix: The Final Girl Support Group
  • Douglas Adams: The Long Dark Tea Time of the Soul
  • V.E.Schwab: Galant
  • Neil Gaiman/Terry Pratchett: Good Omens (BBC Dramatisation)
  • Cassandra Khaw: Nothing But Blackened Teeth
  • William Peter Blatty: Legion
  • Neil Gaiman/Terry Pratchett: Good Omens (full cast audiobook)
  • Peter Straub: Ghost Story
  • Edward Snowdon: Permanent Record
  • Todd Keisling: Devil's Creek
  • Oscar Wilde: The Canterville Ghost
  • Haruki Murakami: The Wind-Up Bird Chronicles
  • David Almond/David McKean: Joe Quinn's Poltergeist
  • Neil Gaiman: The Sandman Act III (audio-drama)
  • H.G.Wells: The Time Machine
  • Neil Gaiman: The Graveyard Book (ull-cast audiobook edition)
  • Edgar Allen Poe: The Murders in the Rue Morgue/The Fall of the House of Usher
  • Robert Louis Stevenson: The Body Snatcher (short story)
  • Emily St. John Mandel: Sea of Tranquility
  • Stephen Fry: Troy
  • Haruki Murakami: Hear the Wind Sing

I have no real clue which book I will read next, but you can follow along on my book-log, where I have listed the books I read since 2015.

-1:-- The Books I Read in 2022 (Post)--L0--C0--May 07, 2024 06:38 AM

Sven Seebeck: Repeating Navigation in Emacs

Repeating Navigation in Emacs

A few weeks ago I was getting somewhat annoyed while navigating with a wild set of combinations of C-n, C-n, M-f, M-f and whatnot through an org-document that I had been working on, and wondered why there isn't a simpler way to do that. Something that could for example make the C or M stick of sorts.

This being Emacs, I was thinking there had to be a solution. I did some search but couldn't really find something that did just what I had been looking for (then again, I didn't know what to look for).

I ended up trying EVIL-Mode and God-Mode and even though the latter did work well for me (the former confused me too much) neither did what I really wanted to. Somehow switching to another mode only to navigate a little more efficient seemed overkill.

That being said, I did use God-Mode for a while, until I learned about the Repeat-Mode introduced in Emacs via Karthinks and with this found the solution!

I ended up with this code which does exactly what I wanted to: I invoke the motion I want to use, say M-b to move back a word and then happily continue moving backwards pressing only b.

Even though there is likely a much more elegant solution to write this code, it works and best of all, I did it myself (more or less that is :-) ).

Here's the snippet I came up with:

    (repeat-mode t)


(defvar svbck-nav-repeat-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd "f") #'forward-char)
    (define-key map (kbd "b") #'backward-char)
    (define-key map (kbd "n") #'next-line)
    (define-key map (kbd "p") #'previous-line)
    (define-key map (kbd "a") #'backward-sentence)
    (define-key map (kbd "e") #'forward-sentence)
    map))

(dolist (cmd '(forward-char backward-char next-line previous-line backward-sentence forward-sentence))
  (put cmd 'repeat-map 'svbck-nav-repeat-map))


(defvar svbck-word-repeat-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd "f") #'forward-word)
    (define-key map (kbd "b") #'backward-word)
    map))

(dolist (cmd '(forward-word backward-word))
  (put cmd 'repeat-map 'svbck-word-repeat-map))


(defvar svbck-org-sentence-repeat-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd "a") #'org-backward-sentence)
    (define-key map (kbd "e") #'org-forward-sentence)
    map))

(dolist (cmd '(org-backward-sentence org-forward-sentence))
  (put cmd 'repeat-map 'svbck-org-sentence-repeat-map))


I tried to combine this into one repeat-map but that didn't work out that well since some of the keys are the same, hence I did one for each, then again it works this way.

Upadate 2022-12-10 11:15: In the meanwhile I figured out how to make the code a little bit more compact, but I'm sure it could be improved more.

-1:-- Repeating Navigation in Emacs (Post)--L0--C0--May 07, 2024 06:38 AM

Sven Seebeck: Weeknotes - Yet another Vacation Edition

Weeknotes - Yet another Vacation Edition

I wrote my last entry in the weeknotes series at the end of my last vacation, and now I am already back in my next vacation. No, I don't have vacation all the time, but it seems that is the only time I am in the mood to write - for the blog at the least.

I do write/journal though every day, nothing of that is though going onto the blog to begin with.

But what I have I been up to. I daresay nothing special.

Some concerts with the Big Band every now and then, lots of teaching, started to get more familiar with the bash, general tinkering with my Emacs config, and more recently: with my Sway WM config.

If the above means anything to you, you might as well stop reading now and come back and some point.

And yes, for reasons I already wrote about, I have changed the domain of this blog, and will in the process change various social media handles and while I'm at it, also delete some of those.

Social

I do not feel very social to be honest and for the greater part continue to neglect (read: ignore or generally don't GAS) about the majority of social accounts that I have left. Recently (or whatever counts as recently in my timeline) I have deleted my Facebook account after years of being reminded of its existence only on my birthday and naturally haven't noticed it. Should I even bother to log-in then Instagram will be next and then Twitter. Not using either anyway.

The only place you might find me is on Mastodon for the time being, but I will change my user handle at some point to be in-line with this domain.

Sway WM

Over time I have realized that I would like to have my setup as simple as possible, definitely prefer a text-based interface and really don't like window decorations - like, at all.

This is my first exposure to a tiled window-manage and I really like this and can't see myself going back to a "normal" window manager. I can, and need in fact, configure all things about about my setup and make it inherently just the way I like it. In the process I learn a lot of new skills about the commandline and how to configure my system.

20221028T145022--full__screenshot.jpg

Figure 1: A screenshot of my current setup

It's like my Emacs config, perfect.

And while I had been at it I have configured all my applications and the window-manager with the Dracula-theme, and hence have a consistent interface over all the applications that I'm using on a daily basis.

Emacs

Through the use of said window-manager I have developed the habit of opening things in emacs not in a new window, but in a new frame, which somehow makes quite a few things a lot nicer. I can have my Agenda open in one frame and use another as my active working area and don't have to worry about accidentally re-using that window. Somehow this workflow suits me better.

Upcoming Concerts

Even though I'm now on vacation from teaching there are some concerts coming in the next few weeks.

  • 29.10 OASBB feat. Kasmir in Tampere
  • 19.11 TMQ feat. Mikko Leppilampi @Rio/Oulu
  • 3/4.12 OASBB feat. Kasmir in Espoo + Jyväskylä

So, I think this might be it for the time being. The next post will be up at some point.

-1:-- Weeknotes - Yet another Vacation Edition (Post)--L0--C0--May 07, 2024 06:38 AM

Please note that planet.emacslife.com aggregates blogs, and blog authors might mention or link to nonfree things. To add a feed to this page, please e-mail the RSS or ATOM feed URL to sacha@sachachua.com . Thank you!