Posts
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!1Why 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 withdefcustom
).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 fromcopilot.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 ofcopilot-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 usingsetq
.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, usingsetopt
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 theirdefcustom
s properly)Now let’s update a bit of legacy code to use
setopt
:(setq user-full-name "Bozhidar Batsov" user-mail-address "[email protected]") ;; 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 "[email protected]") ;; 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 usesetq
,customize-set-variable
, or the newsetopt
. 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 likesetq
. - 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)
- It has shorter name then
- Use
setq
only for variables that are not defined in terms ofdefcustom
.- Amusingly,
setopt
will work with regular variables as well, but it won’t be as efficient assetq
. Not to mention using it in such a way will be super confusing!
- Amusingly,
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:
- Reddit Thread: Why was
setopt
introduced in Emacs 29? - Emacs Stack Exchange: Which option to use for setting a variable
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 ofcustomize-set-variable
and I never use theM-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 andsetopt
all the things!-
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 withcustomize-set-variable
internally) ↩
- Use
Essential Flyspell
I’ve been a long time user of
flyspell-mode
andflyspell-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 bufferC-.
(flyspell-auto-correct-word
) - same asM-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 maybeispell
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.-
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 alsomost-negative-fixnum
that you might find handy in some cases.As for
early-init.el
- it was introduced in version 27 and is executed beforeinit.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!
Relative Line Numbers
Relative line numbers (relative to the current line) are super popular in the world of Vim, because there it’s super easy to move
n
lines up or down wihtj
andk
. In the world of Emacs most of us tend to just go some line usingM-g g
using a absolute line number or usingavy
(avy-goto-line
).That being said, relative line numbers are easy to enable in Emacs and quite handy if you’re into
evil-mode
:(setq display-line-numbers-type 'relative) (global-display-line-numbers-mode +1)
Relative line numbers are useful with the Emacs core commands
forward-line
(C-n
) andprevious-line
(C-p
) as well. Just trigger them with the universal prefixC-u
and you can move quickly around:C-u 5 C-n
(move 5 lines forward)C-u 10 C-p
(move 10 lines backward)
Easy-peasy!
That’s all I have for you today! Keep hacking!
You have no idea how powerful isearch is!
isearch
is probably one of the most widely known Emacs commands. Every Emacs user knows that they can run it usingC-s
(to search forward) andC-r
to search backwards. Everyone also knows they can keep pressingC-s
andC-r
to go over the list of matches in the current buffer. Even at this point that’s a very useful command. But that doesn’t even scratch the surface of whatisearch
can do!After you’ve started
isearch
you can actually do a lot more than pressingC-s
andC-r
:- Type
DEL
to cancel last input item from end of search string. - Type
RET
to exit, leaving point at location found. - Type LFD (
C-j
) to match end of line. - Type
M-s M-<
to go to the first match,M-s M->
to go to the last match. (super handy) - Type
C-w
to yank next word or character in buffer onto the end of the search string, and search for it. (very handy) - Type
C-M-d
to delete character from end of search string. - Type
C-M-y
to yank char from buffer onto end of search string and search for it. - Type
C-M-z
to yank from point until the next instance of a specified character onto end of search string and search for it. - Type
M-s C-e
to yank rest of line onto end of search string and search for it. - Type
C-y
to yank the last string of killed text. - Type
M-y
to replace string just yanked into search prompt with string killed before it. - Type
C-q
to quote control character to search for it. - Type
C-x 8 RET
to add a character to search by Unicode name, with completion. C-g
while searching or when search has failed cancels input back to what has been found successfully.C-g
when search is successful aborts and moves point to starting point.
You can also toggle some settings write
isearch
is active:- Type
M-s c
to toggle search case-sensitivity. - Type
M-s i
to toggle search in invisible text. - Type
M-s r
to toggle regular-expression mode. - Type
M-s w
to toggle word mode. - Type
M-s _
to toggle symbol mode. - Type
M-s '
to toggle character folding.
Type
M-s SPC
to toggle whitespace matching. In incremental searches, a space or spaces normally matches any whitespace defined by the variablesearch-whitespace-regexp
; see also the variablesisearch-lax-whitespace
andisearch-regexp-lax-whitespace
.Type
M-s e
to edit the search string in the minibuffer. That one is super useful!Also supported is a search ring of the previous 16 search strings:
- Type
M-n
to search for the next item in the search ring. - Type
M-p
to search for the previous item in the search ring. - Type
C-M-i
to complete the search string using the search ring.
Last, but not least - you can directly search for the symbol/thing at point:
- Type
M-s .
to search for the symbol at point. (useful in the context of programming languages) - Type
M-s M-.
to search for the thing (e.g. word or symbol) at point.
One of the most useful parts of that is the fact that a region is a thing. So you can mark a region (e.g. with
expand-region
ormark-*
) andM-s M-.
to immediately search for other instances of that text. Powerful stuff!Tip: You don’t really have to remember all those keybindings - just remember you can press
C-h b
to show them. (after you’ve startedisearch
)Most of the above text is coming straight from the docstring of
isearch
. It’s funny that I’ve been using Emacs for almost 20 years, I useisearch
numerous times every day and I still often forget about much of its functionality.There’s more to
isearch
, though. Did you know it’s widely customizable as well? If you check its options withM-x customize-group isearch
you’ll see there are over 30 (!!!) options there! Admittedly, I never used any of them, but you’ve got quite a lot of opportunities to tweak the behavior ofisearch
if you want to. Here’s an example of a customization some of you might find useful:;; When isearching, enable M-<, M->, C-v and M-v to skip between matches ;; in an intuitive fashion. Note that the `cua-selection-mode' bindings ;; for C-v and M-v bindings are not supported. (setq isearch-allow-motion t isearch-motion-changes-direction t)
I hope you learned something useful today! Keep searching (the Emacs docs)!
- Type
Subscribe via RSS | View Older Posts