Posts

  • Little known macOS keybindings

    Today’s article is going to be a bit more weird than usual… mostly because I’ve set to write about one topic, and ended up about writing something completely different in the end… Here we go!

    I’m guessing 99% of Emacs users know that the most common ways to start isearch are with isearch-forward (C-s) and isearch-backward (C-r). That’s not the full story, though! While working on my recent isearch article I noticed that out-of-the-box there are two other keybindings for those commands:

    • s-f (isearch-forward)
    • s-F (isearch-backward)

    Note: s in this context means Super, which is usually Win in Windows and Command in macOS.

    When I saw those I was like “hmm, seems someone wanted to make Emacs a bit more approachable to macOS users coming other editors”. But here things got interesting…

    I tried to find out where those extra keybindings were defined, and after a bit of digging I found them in the ns-win.el library1, which defines a ton of macOS-specific keybindings:

    ;; Here are some Nextstep-like bindings for command key sequences.
    (define-key global-map [?\s-,] 'customize)
    (define-key global-map [?\s-'] 'next-window-any-frame)
    (define-key global-map [?\s-`] 'other-frame)
    (define-key global-map [?\s-~] 'ns-prev-frame)
    (define-key global-map [?\s--] 'center-line)
    (define-key global-map [?\s-:] 'ispell)
    (define-key global-map [?\s-?] 'info)
    (define-key global-map [?\s-^] 'kill-some-buffers)
    (define-key global-map [?\s-&] 'kill-current-buffer)
    (define-key global-map [?\s-C] 'ns-popup-color-panel)
    (define-key global-map [?\s-D] 'dired)
    (define-key global-map [?\s-E] 'edit-abbrevs)
    (define-key global-map [?\s-L] 'shell-command)
    (define-key global-map [?\s-M] 'manual-entry)
    (define-key global-map [?\s-S] 'ns-write-file-using-panel)
    (define-key global-map [?\s-a] 'mark-whole-buffer)
    (define-key global-map [?\s-c] 'ns-copy-including-secondary)
    (define-key global-map [?\s-d] 'isearch-repeat-backward)
    (define-key global-map [?\s-e] 'isearch-yank-kill)
    (define-key global-map [?\s-f] 'isearch-forward)
    (define-key esc-map [?\s-f] 'isearch-forward-regexp)
    (define-key minibuffer-local-isearch-map [?\s-f]
      'isearch-forward-exit-minibuffer)
    (define-key isearch-mode-map [?\s-f] 'isearch-repeat-forward)
    (define-key global-map [?\s-F] 'isearch-backward)
    (define-key esc-map [?\s-F] 'isearch-backward-regexp)
    (define-key minibuffer-local-isearch-map [?\s-F]
      'isearch-reverse-exit-minibuffer)
    (define-key isearch-mode-map [?\s-F] 'isearch-repeat-backward)
    (define-key global-map [?\s-g] 'isearch-repeat-forward)
    (define-key global-map [?\s-h] 'ns-do-hide-emacs)
    (define-key global-map [?\s-H] 'ns-do-hide-others)
    (define-key global-map [?\M-\s-h] 'ns-do-hide-others)
    (define-key global-map [?\s-j] 'exchange-point-and-mark)
    (define-key global-map [?\s-k] 'kill-current-buffer)
    (define-key global-map [?\s-l] 'goto-line)
    (define-key global-map [?\s-m] 'iconify-frame)
    (define-key global-map [?\s-n] 'make-frame)
    (define-key global-map [?\s-o] 'ns-open-file-using-panel)
    (define-key global-map [?\s-p] 'ns-print-buffer)
    (define-key global-map [?\s-q] 'save-buffers-kill-emacs)
    (define-key global-map [?\s-s] 'save-buffer)
    (define-key global-map [?\s-t] 'menu-set-font)
    (define-key global-map [?\s-u] 'revert-buffer)
    (define-key global-map [?\s-v] 'yank)
    (define-key global-map [?\s-w] 'delete-frame)
    (define-key global-map [?\s-x] 'kill-region)
    (define-key global-map [?\s-y] 'ns-paste-secondary)
    (define-key global-map [?\s-z] 'undo)
    (define-key global-map [?\s-+] 'text-scale-adjust)
    (define-key global-map [?\s-=] 'text-scale-adjust)
    (define-key global-map [?\s--] 'text-scale-adjust)
    (define-key global-map [?\s-0] 'text-scale-adjust)
    (define-key global-map [?\s-|] 'shell-command-on-region)
    (define-key global-map [s-kp-bar] 'shell-command-on-region)
    (define-key global-map [?\C-\s- ] 'ns-do-show-character-palette)
    (define-key global-map [s-right] 'move-end-of-line)
    (define-key global-map [s-left] 'move-beginning-of-line)
    
    (define-key global-map [home] 'beginning-of-buffer)
    (define-key global-map [end] 'end-of-buffer)
    (define-key global-map [kp-home] 'beginning-of-buffer)
    (define-key global-map [kp-end] 'end-of-buffer)
    (define-key global-map [kp-prior] 'scroll-down-command)
    (define-key global-map [kp-next] 'scroll-up-command)
    
    ;; Allow shift-clicks to work similarly to under Nextstep.
    (define-key global-map [S-mouse-1] 'mouse-save-then-kill)
    (global-unset-key [S-down-mouse-1])
    
    ;; Special Nextstep-generated events are converted to function keys.  Here
    ;; are the bindings for them.  Note, these keys are actually declared in
    ;; x-setup-function-keys in common-win.
    (define-key global-map [ns-power-off] 'save-buffers-kill-emacs)
    (define-key global-map [ns-open-file] 'ns-find-file)
    (define-key global-map [ns-open-temp-file] [ns-open-file])
    (define-key global-map [ns-open-file-line] 'ns-open-file-select-line)
    (define-key global-map [ns-spi-service-call] 'ns-spi-service-call)
    (define-key global-map [ns-new-frame] 'make-frame)
    (define-key global-map [ns-toggle-toolbar] 'ns-toggle-toolbar)
    (define-key global-map [ns-show-prefs] '
    

    Some of them look quite convenient (easy to press), so I might add a few to my daily work. I’m shocked I never trying any of the standard macOS keybindings for things like adjusting text size in Emacs. Or perhaps I tried them and then I forgot about them… :D

    Still, even though I’m a macOS users (at least for the time being), I doubt I’ll end up using many of them. The reason for this is that I learned Emacs on Linux and I’m extremely used to the default keybindings. Between remembering all of those, and trying to master Vim (as of late), it’s hard to teach this old dog any new tricks. That being sad, I can imagine those keybindings being useful to many other people, especially if they haven’t learned Emacs on Linux 20 years ago.

    Tip: Do a M-x find-library RET ns-win to see what else the library has in store for macOS users.

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

    P.S. After writing this article I was really amused that I’ve been using macOS on and off for over 10 years and I never bothered to try whether something like Command-s or Command-z works in Emacs! Oh, well… habits!

    1. Emacs stubbornly keeps refering to macOS by its ancient name NextStep in much of the code and its documentation. 

  • Let's make keyboard-quit smarter

    I’ll be pretty brief today. keyboard-quit (C-g) is one of the most used commands, but unfortunately it’s not very smart. Most annoyingly, it doesn’t work as expected when the minibuffer is active.

    Fortunately, fixing such problems (and then some) is trivial in Emacs:

    (defun er-keyboard-quit ()
      "Smater version of the built-in `keyboard-quit'.
    
    The generic `keyboard-quit' does not do the expected thing when
    the minibuffer is open.  Whereas we want it to close the
    minibuffer, even without explicitly focusing it."
      (interactive)
      (if (active-minibuffer-window)
          (if (minibufferp)
              (minibuffer-keyboard-quit)
            (abort-recursive-edit))
        (keyboard-quit)))
    

    I’d suggest to just remap keyboard-quit to our improved version:

    (global-set-key [remap keyboard-quit] #'er-keyboard-quit)
    

    There are other ways to tackle this particular issue, of course, and different people might prefer an even more complicated version of the smarter keyboard-quit or one that does fewer things. One of my readers suggested in the comments a similar solution using an advice:

    (define-advice keyboard-quit
        (:around (quit) quit-current-context)
      "Quit the current context.
    
    When there is an active minibuffer and we are not inside it close
    it.  When we are inside the minibuffer use the regular
    `minibuffer-keyboard-quit' which quits any active region before
    exiting.  When there is no minibuffer `keyboard-quit' unless we
    are defining or executing a macro."
      (if (active-minibuffer-window)
          (if (minibufferp)
              (minibuffer-keyboard-quit)
            (abort-recursive-edit))
        (unless (or defining-kbd-macro
                    executing-kbd-macro)
          (funcall-interactively quit))))
    

    This has the benefit of directly modifying the original command, so you don’t really need to rebind anything. On the other hand - advices are arguably a bit more complicated to understand and debug. Personally, I like to replace functions in my own setup with versions that I prefer, as I think this makes the modifications more obvious.

    Another option is a similar function from Prot:1

    (defun prot/keyboard-quit-dwim ()
      "Do-What-I-Mean behaviour for a general `keyboard-quit'.
    
    The generic `keyboard-quit' does not do the expected thing when
    the minibuffer is open.  Whereas we want it to close the
    minibuffer, even without explicitly focusing it.
    
    The DWIM behaviour of this command is as follows:
    
    - When the region is active, disable it.
    - When a minibuffer is open, but not focused, close the minibuffer.
    - When the Completions buffer is selected, close it.
    - In every other case use the regular `keyboard-quit'."
      (interactive)
      (cond
       ((region-active-p)
        (keyboard-quit))
       ((derived-mode-p 'completion-list-mode)
        (delete-completion-window))
       ((> (minibuffer-depth) 0)
        (abort-recursive-edit))
       (t
        (keyboard-quit))))
    

    I know this version of the command is quite popular in the wild, as many people follow Prot’s work, but looking at the code of the actual keyboard-quit it seems to me that Prot’s version is more complicated than it needs to be:

    ;; This executes C-g typed while Emacs is waiting for a command.
    ;; Quitting out of a program does not go through here;
    ;; that happens in the maybe_quit function at the C code level.
    (defun keyboard-quit ()
      "Signal a `quit' condition.
    During execution of Lisp code, this character causes a quit directly.
    At top-level, as an editor command, this simply beeps."
      (interactive)
      ;; Avoid adding the region to the window selection.
      (setq saved-region-selection nil)
      (let (select-active-regions)
        (deactivate-mark))
      (if (fboundp 'kmacro-keyboard-quit)
          (kmacro-keyboard-quit))
      (when completion-in-region-mode
        (completion-in-region-mode -1))
      ;; Force the next redisplay cycle to remove the "Def" indicator from
      ;; all the mode lines.
      (if defining-kbd-macro
          (force-mode-line-update t))
      (setq defining-kbd-macro nil)
      (let ((debug-on-quit nil))
        (signal 'quit nil)))
    

    As you can see it already handles things like the selected region and completion in region. But perhaps I’m missing what Prot was trying to achieve with his version.

    Which of the three approaches do you prefer? How would you improve er-keyboard-quit-dwim further?

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

  • Goodbye setq, hello setopt!

    For many years most Emacs users used setq to set the various configuration options of Emacs and the packages that they were using. This probably wasn’t the best option (read on), but it was the most popular way of doing things. Now, however, it’s finally time for a change!1

    Why do we need setopt?

    In Emacs 29, a new macro setopt was introduced to provide a more appropriate method for setting user options (variables defined with defcustom).

    As mentioned above, traditionally, Emacs users have employed setq to assign values to variables. However, setq does not invoke any custom setter functions associated with user options, which can lead to unexpected behavior. Here’s example of such a setter function from copilot.el:

    (defun copilot--lsp-settings-changed (symbol value)
      "Restart the Copilot LSP due to SYMBOL changed to VALUE.
    
    This function will be called by the customization framework when the
    `copilot-lsp-settings' is changed.  When changed with `setq', then this function
    will not be called."
      (let ((was-bound (boundp symbol)))
        (set-default symbol value)
        (when was-bound
          ;; Notifying the agent with the new value does only work if we include the
          ;; last value (as nil) as well. For example, having the value
          ;; '(:github-enterprise (:uri "https://example2.ghe.com")) and setting it
          ;; to nil would require to send the value '(:github-enterprise (:uri nil))
          ;; to the server. Otherwise, the value is ignored, since sending nil is
          ;; not enough.
          (copilot--start-agent))))
    
    (defcustom copilot-lsp-settings nil
      "Settings for the Copilot LSP server.
    
    This value will always be sent to the server when the server starts or the value
    changes.  See
    https://github.com/github/copilot-language-server-release?tab=readme-ov-file#configuration-management
    for complete documentation.
    
    To change the value of this variable, the customization framework provided by
    Emacs must be used.  Either use `setopt' or `customize' to change the value.  If
    the value was set without the customization mechanism, then the LSP has to be
    manually restarted with `copilot-diagnose'.  Otherwise, the change will not be
    applied.
    
    For example to use GitHub Enterprise use the following configuration:
     '(:github-enterprise (:uri \"https://example.ghe.com\"))
    
    Exchange the URI with the correct URI of your organization."
      :set #'copilot--lsp-settings-changed
      :type 'sexp
      :group 'copilot
      :package-version '(copilot . "0.2"))
    

    In case it’s not obvious - the important thing is the :set property of copilot-lsp-settings. Basically, every time this option is changed, a callback function should be invoked, but this won’t happen if you make the change using setq.

    The setopt macro addresses this by ensuring that when you set a user option, any associated setter functions are properly called, maintaining the integrity of the option’s behavior.

    Even more importantly for me - setopt also checks whether the value is valid for the user option. For instance, using setopt to set a user option defined with a number type to a string will signal an error. I’m pretty sure this will prevent a lot of (weird) configuration issues going forward! (and inspire more package authors to declare their defcustoms properly)

    Now let’s update a bit of legacy code to use setopt:

    (setq user-full-name "Bozhidar Batsov"
          user-mail-address "bozhidar@emacsninja.com")
    
    ;; Always load newest byte code
    (setq load-prefer-newer t)
    
    ;; reduce the frequency of garbage collection by making it happen on
    ;; each 50MB of allocated data (the default is on every 0.76MB)
    (setq gc-cons-threshold 50000000)
    
    ;; warn when opening files bigger than 100MB
    (setq large-file-warning-threshold 100000000)
    
    ;; quit Emacs directly even if there are running processes
    (setq confirm-kill-processes nil)
    

    This will be become:

    (setopt user-full-name "Bozhidar Batsov"
            user-mail-address "bozhidar@emacsninja.com")
    
    ;; Always load newest byte code
    (setopt load-prefer-newer t)
    
    ;; reduce the frequency of garbage collection by making it happen on
    ;; each 50MB of allocated data (the default is on every 0.76MB)
    (setopt gc-cons-threshold 50000000)
    
    ;; warn when opening files bigger than 100MB
    (setopt large-file-warning-threshold 100000000)
    
    ;; quit Emacs directly even if there are running processes
    (setopt confirm-kill-processes nil)
    

    Pretty shocking, right?

    When to Use What?

    The introduction of setopt has sparked discussions within the Emacs community regarding the best practices for setting variables. Some users have expressed uncertainty about when to use setq, customize-set-variable, or the new setopt. My take on the subject is pretty simple:

    • Use setopt for user options to ensure that any custom setter functions are invoked.
      • It has shorter name then customize-set-variable and can be used to set multiple options just like setq.
      • Shows a warning when a configuration value does not match its :type specification.
      • Unlike setq, it does not complain when a variable is not declared. (which is quite normal when dealing with a lot of autoloaded packages)
    • Use setq only for variables that are not defined in terms of defcustom.
      • Amusingly, setopt will work with regular variables as well, but it won’t be as efficient as setq. Not to mention using it in such a way will be super confusing!

    The way I see it, unless you’re running an older Emacs version, and you’re not using setopt extensively in your Emacs config, you’re missing out!

    Further Reading

    For more detailed discussions and perspectives on this topic, check out:

    Check out the official Emacs docs on setopt as well.

    Closing Thoughts

    I always knew that setq was flawed, but I kept using it for ages mostly because of inertia. I didn’t like the long name of customize-set-variable and I never use the M-x customize directly. I guess that’s why I rarely bothered to have setter callbacks in the packages that I wrote and maintain. Going forward I’ll certainly reconsider this.

    That’s all I have for you today. If you haven’t adopted setopt already, go wild and setopt all the things!

    1. How big of a change? Depends on whether you’re using use-package and how exactly are you using it! :D (in case you’re wondering - :custom settings are handled with customize-set-variable internally) 

  • Essential Flyspell

    I’ve been a long time user of flyspell-mode and flyspell-prog-mode, but admittedly I keep forgetting some of it’s keybindings. And there aren’t that many of them to begin with!

    This article is my n-th attempt to help me memorize anything besides C-c $. So, here we go:

    • M-t (flyspell-auto-correct-word) - press this while in word with typo in it to trigger auto-correct. You can press it repeatedly to cycle through the list of candidates.
    • C-, (flyspell-goto-next-error) - go to the next typo in the current buffer
    • C-. (flyspell-auto-correct-word) - same as M-t
    • C-; (flyspell-auto-correct-previous-word) - automatically correct the last misspelled word. (you can cycle here as well)

    Last, but not least, there’s the only command I never forget - C-c $ (flyspell-correct-word-before-point). Why is this my go-to command? Well, its name is a bit misleading, as it can do two things:

    • auto-correct the word at (or before) point
    • add the same word to your personal dictionary, so Flyspell and ispell will stop flagging it

    Good stuff!

    There are more commands, but those are the ones you really need to know.1

    If you ever forget any of them, just do a quick C-h m RET flyspell-mode.

    Do you have some tips to share about using flyspell-mode (and maybe ispell as well)?

    That’s all I have for you today! Keep fixing those typos!

    P.S. If you’re completely new to flyspell-mode, you may want to check this article on the subject as well.

    1. Unless you’d like to trigger auto-correct with your mouse, that is. 

  • Speed up Emacs Startup by Tweaking the GC Settings

    A well-known Emacs performance optimization advice is to boost the garbage collector threshold (so GC collections happen less frequently). That’s something I’ve had in my Emacs config for ages:

    ;; reduce the frequency of garbage collection by making it happen on
    ;; each 50MB of allocated data (the default is on every 0.76MB)
    (setq gc-cons-threshold 50000000)
    

    Probably I should increase it to 100MB+ these days, given the proliferation of more resource-hungry tooling (e.g. LSP). On the other hand there are also some counter arguments to consider when it comes to setting a high GC threshold:

    The GC threshold setting after init is too high, IMNSHO, and its value seems arbitrary.

    If the OP thinks that Emacs will GC as soon as it allocates 100 MiB, then that’s a grave mistake! What really happens is the first time Emacs considers doing GC, if at that time more than 100 MiB have been allocated for Lisp objects, Emacs will GC. And since neither Lisp programs nor the user have any control on how soon Emacs will decide to check whether GC is needed, the actual amount of memory by the time Emacs checks could be many times the value of the threshold.

    My advice is to spend some time measuring the effect of increased GC threshold on operations that you care about and that take a long enough time to annoy, and use the lowest threshold value which produces a tangible improvement. Start with the default value, then enlarge it by a factor of 2 until you see only insignificant speedups. I would not expect the value you arrive at to be as high as 100 MiB.

    – Eli Zaretskii, Emacs maintainer

    One thing that’s not so common knowledge is that removing the GC limits during Emacs startup might improve the speedup quite a lot (the actual results will be highly dependent on your setup). Here’s what you need to do - just add the following bit to your early-init.el:

    ;; Temporarily increase GC threshold during startup
    (setq gc-cons-threshold most-positive-fixnum)
    
    ;; Restore to normal value after startup (e.g. 50MB)
    (add-hook 'emacs-startup-hook
              (lambda () (setq gc-cons-threshold (* 50 1024 1024))))
    

    most-positive-fixnum is a neat constant that represents the biggest positive integer that Emacs can handle. There’s also most-negative-fixnum that you might find handy in some cases.

    As for early-init.el - it was introduced in version 27 and is executed before init.el. Its primary purpose is to allow users to configure settings that need to take effect early in the startup process, such as disabling GUI elements or optimizing performance. This file is loaded before the package system and GUI initialization, giving it a unique role in customizing Emacs startup behavior.

    Here are some other settings that people like to tweak in early-init.el:

    ;; Disable toolbars, menus, and other visual elements for faster startup:
    (menu-bar-mode -1)
    (tool-bar-mode -1)
    (scroll-bar-mode -1)
    
    (setq inhibit-startup-screen t)
    
    ;; Load themes early to avoid flickering during startup (you need a built-in theme, though)
    (load-theme 'modus-operandi t)
    
    ;; tweak native compilation settings
    (setq native-comp-speed 2)
    

    I hope you get the idea! If you have any other tips on speeding up the Emacs startup time, I’d love to hear them!

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

Subscribe via RSS | View Older Posts