Posts

  • super-save 0.5: Modernized and Better Than Ever

    It’s been a while since the last super-save release. The last time I wrote about it was back in 2018, when I boldly proclaimed:

    It seems that now super-save is beyond perfect, so don’t expect the next release any time soon!

    Famous last words. There was a 0.4 release in 2023 (adding a predicate system, buffer exclusions, silent saving, and trailing whitespace cleanup), but I never got around to writing about it. The package has been rock solid for years and I just didn’t pay it much attention – it quietly did its job, which is kind of the whole point of an auto-save package.

    A Bit of History

    The idea behind super-save goes all the way back to a blog post I wrote in 2012 about auto-saving buffers on buffer and window switches. I had been using IntelliJ IDEA for Java development and loved that it would save your files automatically whenever the editor lost focus. No manual C-x C-s, no thinking about it. I wanted the same behavior in Emacs.

    Back then, the implementation was crude – defadvice on switch-to-buffer, other-window, and the windmove commands. That code lived in Emacs Prelude for a few years before I extracted it into a standalone package in 2015. super-save was born.

    What Prompted This Release

    Yesterday I stumbled upon buffer-guardian.el, a package with very similar goals to super-save. Its README has a comparison with super-save that highlighted some valid points – mainly that super-save was still relying on advising specific commands for buffer-switch detection, while newer Emacs hooks like window-buffer-change-functions and window-selection-change-functions could do the job more reliably.

    The thing is, those hooks didn’t exist when super-save was created, and I didn’t rush to adopt them while Emacs 27 was still new and I wanted to support older Emacsen. But it’s 2026 now – Emacs 27.1 is ancient history. Time to modernize!

    What’s New in 0.5

    This is the biggest super-save release in years! Here are the highlights:

    Modern buffer/window switch detection

    Buffer and window switches are now detected via window-buffer-change-functions and window-selection-change-functions, controlled by the new super-save-when-buffer-switched option (enabled by default). This catches all buffer switches – keyboard commands, mouse clicks, custom functions – unlike the old approach of advising individual (yet central) commands.

    Modern focus handling

    Frame focus loss is now detected via after-focus-change-function instead of the obsolete focus-out-hook, controlled by super-save-when-focus-lost (also enabled by default).

    Soft-deprecated trigger system

    With the new hooks in place, both super-save-triggers and super-save-hook-triggers now default to nil. You can still use them for edge cases, but for the vast majority of users, the built-in hooks cover everything.

    org-src and edit-indirect support

    super-save now knows how to save org-src edit buffers (via org-edit-src-save) and edit-indirect buffers (via edit-indirect--commit). Both are enabled by default and controlled by super-save-handle-org-src and super-save-handle-edit-indirect.

    Safer predicates

    Two new default predicates prevent data loss: verify-visited-file-modtime avoids overwriting files modified outside Emacs, and a directory existence check prevents errors when a file’s parent directory has been removed. Predicate evaluation is also wrapped in condition-case now, so a broken custom predicate logs a warning instead of silently disabling all auto-saving.

    Emacs 27.1 required

    This allowed cleaning up the code and relying on modern APIs.

    Upgrading

    For most users, upgrading is seamless – the new defaults just work. If you had a custom super-save-triggers list for buffer-switching commands, you can probably remove it entirely:

    ;; Before: manually listing every command that switches buffers
    (setq super-save-triggers
          '(switch-to-buffer other-window windmove-up windmove-down
            windmove-left windmove-right next-buffer previous-buffer))
    
    ;; After: the window-system hooks catch all of these automatically
    ;; Just delete the above and use the defaults!
    

    If you need to add triggers for commands that don’t involve a buffer switch (like ace-window), super-save-triggers is still available for that.

    A clean 0.5 setup looks something like this:

    (use-package super-save
      :ensure t
      :config
      ;; Save buffers automatically when Emacs is idle
      (setq super-save-auto-save-when-idle t)
      ;; Don't display "Wrote file..." messages in the echo area
      (setq super-save-silent t)
      ;; Disable the built-in auto-save (backup files) since super-save handles it
      (setq auto-save-default nil)
      (super-save-mode +1))
    

    It’s also worth noting that Emacs 26.1 introduced auto-save-visited-mode, which saves file-visiting buffers to their actual files after an idle delay. This overlaps with super-save-auto-save-when-idle, so if you prefer using the built-in for idle saves, you can combine the two:

    (use-package super-save
      :ensure t
      :config
      ;; Don't display "Wrote file..." messages in the echo area
      (setq super-save-silent t)
      ;; Disable the built-in auto-save (backup files)
      (setq auto-save-default nil)
      (super-save-mode +1))
    
    ;; Let the built-in auto-save-visited-mode handle idle saves
    (auto-save-visited-mode +1)
    

    Burst-Driven Development Strikes Again

    Most of my Emacs packages are a fine example of what I like to call burst-driven development – long periods of stability punctuated by short intense bursts of activity. I hadn’t touched super-save in years, then spent a few hours modernizing the internals, adding a test suite, improving the documentation, and cutting a release. It was fun to revisit the package after all this time and bring it up to 2026 standards.

    If you’ve been using super-save, update to 0.5 and enjoy the improvements. If you haven’t tried it yet – give it a shot. Your poor fingers might thanks for you this, as pressing C-x C-s non-stop is hard work!

    That’s all I have for you today. Keep hacking!

  • Tree-sitter Font-Lock and Indentation in Comint Buffers

    If you maintain a tree-sitter major mode that has a REPL (comint) companion, you’ve probably wondered: can the REPL input get the same syntax highlighting and indentation as source buffers? The answer is yes – and the infrastructure has been in Emacs since 29.1. It’s just not widely known yet.

    Read More
  • surround.el: Vim-Style Pair Editing Comes to Emacs

    In my recent article on removing paired delimiters, I mentioned that I kind of miss Vim’s surround.vim experience in Emacs. Well, it turns out someone has done something about it – surround.el brings the core ideas of surround.vim to native Emacs, without requiring Evil mode.

    surround.vim (and similar plugins like mini.surround in Neovim) are some of my favorite Vim packages. The idea is so simple and so useful that it feels like it should be a built-in. So I’m happy to see someone took the time to port that beautiful idea to Emacs.

    The Core Ideas of surround.vim

    For those who haven’t used surround.vim, the concept is straightforward. You have a small set of operations for working with surrounding characters – the delimiters that wrap some piece of text:

    • Delete surrounding pair: ds( removes the parentheses around point
    • Change surrounding pair: cs(" changes parens to quotes
    • Add surrounding pair: ys + motion + character wraps text with a delimiter

    That’s basically it. Three operations, consistent keybindings, works everywhere regardless of file type. The beauty is in the uniformity – you don’t need different commands for different delimiters, and you don’t need to think about which mode you’re in.

    surround.el in Practice

    surround.el is available on MELPA and the setup is minimal:

    (use-package surround
      :ensure t
      :bind-keymap ("M-'" . surround-keymap))
    

    You bind a single keystroke (M-' in this example) to surround-keymap, and that gives you access to all the operations through a second keystroke. Here are the commands available in the keymap:

    Key Operation
    s Surround region/symbol at point
    d Delete surrounding pair
    c Change pair to another
    k Kill text inside pair
    K Kill text including pair
    i Mark (select) inside pair
    o Mark including pair

    There are also shortcuts for individual characters – pressing an opening delimiter (like () after the prefix does a mark-inner, while pressing the closing delimiter (like )) does a mark-outer.

    A Short Walkthrough

    Let’s see how this works in practice. Starting with the word Hello (with point somewhere on it):

    SurroundM-' s " wraps the symbol at point with quotes:

    Hello  →  "Hello"
    

    ChangeM-' c " ( changes the surrounding quotes to parens:

    "Hello"  →  (Hello)
    

    Mark innerM-' i ( selects just the text inside the parens:

    (|Hello|)    ;; "Hello" is selected
    

    Mark outerM-' o ( (or M-' )) selects the parens too:

    |( Hello)|   ;; "(Hello)" is selected
    

    DeleteM-' d ( removes the surrounding parens:

    (Hello)  →  Hello
    

    KillM-' k ( kills the text inside the pair (leaving the delimiters gone too), while M-' K ( kills everything including the delimiters.

    Inner vs. Outer

    Like surround.vim, surround.el distinguishes between “inner” (just the content between delimiters) and “outer” (content plus the delimiters themselves). The i and k commands operate on inner text, o and K on outer.

    There’s also an “auto” mode – the default for i and k – that behaves as inner when you type an opening character and outer when you type a closing character. So M-' ( marks inner, M-' ) marks outer. Handy shortcut if your fingers remember it (I’m still building the muscle memory).

    One caveat: auto mode can’t distinguish inner from outer for symmetric pairs like quotes (", '), since the opening and closing character are the same. In those cases it defaults to inner.

    How It Differs from Vim

    The biggest difference is in how you surround text. In Vim, surround.vim uses motions – ysiw( means “surround inner word with parens.” In Emacs, surround.el operates on the active region or the symbol at point. So the typical workflow is: select something, then M-' s (.

    This actually pairs beautifully with expreg (which I wrote about recently). Use expreg to incrementally select exactly the text you want, then M-' s to wrap it. It’s a different rhythm than Vim’s motion-based approach, but once you get used to it, it feels natural.

    The other operations (d, c, k, i, o) work similarly to their Vim counterparts – you invoke the command and then specify which delimiter you’re targeting.

    When to Use It

    surround.el fills a specific niche:

    • Using electric-pair-mode? Then surround.el is an excellent complement. electric-pair-mode handles auto-pairing when you type delimiters, but offers nothing for removing, changing, or wrapping existing text with delimiters. surround.el fills exactly that gap.

    • Using smartparens? You probably don’t need surround.elsmartparens already has sp-unwrap-sexp, sp-rewrap-sexp, and friends. The overlap is significant, and adding another package on top would just be confusing.

    • Using paredit? Same story for Lisp code – paredit has you covered with paredit-splice-sexp, paredit-wrap-round, and so on. But if you want the surround experience in non-Lisp buffers, surround.el is a good pick.

    My current setup is paredit for Lisps, electric-pair-mode for everything else, and I’m adding surround.el to complement the latter. Early days, but it feels right.

    Wrapping Up

    If you’ve ever wondered whether some Vim feature you miss exists in Emacs – the answer is always yes. It’s Emacs. Of course someone has written a package for it. Probably several, in fact… Admittedly, I discovered surround.el only when I had decided to port surround.vim to Emacs myself. :D

    That’s all I have for you today. Keep hacking!

  • Buffer-Local Face Remapping with face-remap-add-relative

    Yet another neocaml issue taught me something I didn’t know about Emacs. Someone requested that the docs show how to customize font-lock faces per mode rather than globally. The suggested approach used face-remap-add-relative – a function I’d never heard of, despite 20+ years of daily Emacs use. Time to dig in.

    The Problem

    Let’s say you want type names in OCaml buffers to be displayed in a different color than in, say, Python buffers. The naive approach is to use custom-set-faces or set-face-attribute:

    (set-face-attribute 'font-lock-type-face nil :foreground "DarkGreen")
    

    This works, but it’s a global change – every buffer that uses font-lock-type-face will now show types in dark green.

    When Global is Fine

    To be fair, global face customization is perfectly valid in several scenarios:

    • Your theme doesn’t cover certain faces. Many themes only style a subset of the faces that various packages define. If a face looks wrong or unstyled, a global set-face-attribute or custom-set-faces is the quickest fix.

    • Your theme makes choices you disagree with. I recently ran into this with the Catppuccin theme – the styling of font-lock-variable-name-face didn’t match how I expected it to look, so I filed a PR. It got closed because different modes interpret that face inconsistently (definitions in Elisp, references in C), making a universal fix impractical for the theme. That’s exactly the kind of situation where you’d want to override the face yourself.

    • You want consistent styling everywhere. If you just want all comments to be italic, or all strings to use a specific color regardless of mode, global is the way to go. No need to complicate things with per-buffer remapping.

    When Global Isn’t Enough

    The problem comes when you bounce between several languages (and let’s be honest, most of us do) and you want different visual treatment depending on the mode. Not all modes use the built-in font-lock faces consistently, and for some – especially markup languages – there’s a lot of room for improvisation in how faces get applied. A global change to font-lock-keyword-face might look great in your Python buffers but terrible in your Org files.

    That’s where buffer-local face remapping comes in.

    Enter face-remap-add-relative

    face-remap-add-relative has been around since Emacs 23 (it lives in face-remap.el), and it does exactly what the name suggests – it remaps a face relative to its current definition, and only in the current buffer. The change is buffer-local, so it won’t leak into other buffers.

    Here’s the basic usage:

    (face-remap-add-relative 'font-lock-type-face :foreground "DarkGreen")
    

    To apply this automatically in a specific mode, hook it up:

    (defun my-ocaml-faces ()
      "Customize faces for OCaml buffers."
      (face-remap-add-relative 'font-lock-type-face :foreground "DarkGreen")
      (face-remap-add-relative 'font-lock-function-name-face :weight 'bold))
    
    (add-hook 'neocaml-mode-hook #'my-ocaml-faces)
    

    Now OCaml buffers get their own face tweaks while everything else stays untouched. You can do the same for any mode – just swap the hook and adjust the faces to taste.

    face-remap-add-relative returns a cookie – a token you’ll need if you want to undo the remapping later. If you’re just setting things up in a mode hook and leaving them, you can ignore the cookie. But if you want to toggle the remapping on and off, you’ll need to hold onto it:

    (defvar-local my-type-face-cookie nil
      "Cookie for type face remapping.")
    
    (defun my-toggle-type-face ()
      "Toggle custom type face in current buffer."
      (interactive)
      (if my-type-face-cookie
          (progn
            (face-remap-remove-relative my-type-face-cookie)
            (setq my-type-face-cookie nil)
            (message "Type face remapping removed"))
        (setq my-type-face-cookie
              (face-remap-add-relative 'font-lock-type-face
                                       :foreground "DarkGreen"))
        (message "Type face remapping applied")))
    

    Note the use of defvar-local – since face remapping is buffer-local, your cookie variable should be too.

    A cleaner approach is to wrap this in a minor mode:

    (defvar-local my-type-remap-cookie nil)
    
    (define-minor-mode my-type-remap-mode
      "Minor mode to remap type face in current buffer."
      :lighter " TypeRemap"
      (if my-type-remap-mode
          (setq my-type-remap-cookie
                (face-remap-add-relative 'font-lock-type-face
                                         :foreground "DarkGreen"))
        (when my-type-remap-cookie
          (face-remap-remove-relative my-type-remap-cookie)
          (setq my-type-remap-cookie nil))))
    

    A few more things worth knowing in this area:

    • face-remap-set-base – sets the base remapping for a face in the current buffer. Unlike face-remap-add-relative (which layers on top of the existing face), this replaces the face definition entirely for that buffer. Use this when you want to completely override a face rather than tweak it.

    • buffer-face-mode / buffer-face-set – a built-in minor mode that remaps the default face in the current buffer. This is what powers M-x buffer-face-set and is handy if you want a different base font in specific buffers (say, a proportional font for prose and a monospace font for code).

    • text-scale-adjust (C-x C-= / C-x C--) – the familiar text scaling commands actually use face-remap-add-relative under the hood to remap the default face. So if you’ve ever zoomed text in a single buffer, you’ve been using face remapping without knowing it.

    • face-remapping-alist – the buffer-local variable where all of this state is stored. You generally shouldn’t manipulate it directly (that’s what the functions above are for), but it’s useful for debugging – check its value in a buffer to see what remappings are active.

    Wrapping Up

    I have to admit – I’m a bit embarrassed that face-remap-add-relative has been sitting in Emacs since version 23 and I’d never once used it. Probably because I never felt the need for per-mode face customizations – but I can certainly see why others would, especially when working across languages with very different syntax highlighting conventions.

    Working on neocaml has been a gold mine of learning (and relearning). I’m happy to keep sharing the things I discover along the way. Keep hacking!

  • isearch-lazy-count: Built-in Search Match Counting

    Continuing my Prelude modernization effort (see the previous post for context), another long-standing third-party dependency I was happy to drop was anzu.

    The Problem

    When you’re searching with C-s in Emacs, you can see the current match highlighted, but you have no idea how many total matches exist in the buffer or which one you’re currently on. Are there 3 matches or 300? You just don’t know.

    The Old Way

    For years, I used the anzu package (an Emacs port of anzu.vim) to display match counts in the mode-line. It worked well, but it was yet another third-party dependency to maintain – and one that eventually ended up in the Emacs orphanage, which is never a great sign for long-term maintenance.

    The New Way

    Emacs 27 introduced isearch-lazy-count:

    (setopt isearch-lazy-count t)
    

    With this enabled, your search prompt shows something like (3/47) – meaning you’re on the 3rd match out of 47 total. Simple, built-in, and requires no external packages.

    Unlike anzu, which showed counts in the mode-line, isearch-lazy-count displays them right in the minibuffer alongside the search string, which is arguably a better location since your eyes are already there while searching.

    Customizing the Format

    Two variables control how the count is displayed:

    ;; Prefix format (default: "%s/%s ")
    ;; Shows before the search string in the minibuffer
    (setopt lazy-count-prefix-format "(%s/%s) ")
    
    ;; Suffix format (default: nil)
    ;; Shows after the search string
    (setopt lazy-count-suffix-format nil)
    

    If you prefer the count at the end of the prompt (closer to how anzu felt), you can swap them:

    (setopt lazy-count-prefix-format nil)
    (setopt lazy-count-suffix-format " [%s/%s]")
    

    Good to Know

    The count works with all isearch variants – regular search, regex search (C-M-s), and word search. It also shows counts during query-replace (M-%) and query-replace-regexp (C-M-%), which is very handy for knowing how many replacements you’re about to make.

    The counting is “lazy” in the sense that it piggybacks on the lazy highlighting mechanism (lazy-highlight-mode), so it doesn’t add significant overhead. In very large buffers, you might notice a brief delay before the count appears, controlled by lazy-highlight-initial-delay. One thing to keep in mind – if you’ve disabled lazy highlighting for performance reasons, you’ll need to re-enable it, as the count depends on it.

    If you haven’t read it already, check out my earlier article You Have No Idea How Powerful Isearch Is for a deep dive into what isearch can do. isearch-lazy-count pairs nicely with all the features covered there.

    Between use-short-answers and isearch-lazy-count, that’s two third-party packages I was able to drop from Prelude just by using built-in functionality. Keep hacking!

Subscribe via RSS | View Older Posts