From f46b31c129bde369cc2d02586d59edb24e3ebefa Mon Sep 17 00:00:00 2001 From: Kiana Sheibani Date: Sat, 14 Feb 2026 18:30:08 -0500 Subject: [PATCH] compat(indent): update EditorConfig indent guess function The new API is actually a lot nicer for us, as we can offload more of the work onto EditorConfig and cover a wider range of cases. --- config.org | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/config.org b/config.org index baaf5e0..e159e61 100644 --- a/config.org +++ b/config.org @@ -2045,30 +2045,32 @@ The =indent-bars= package (provided by the above Doom module) provides some nice *** Offset Detection -Unfortunately for packages like these, indentation in Emacs is a notoriously awful pain point. Every major mode uses a different variable to configure the number of spaces in its indents (typically referred to in Emacs lingo as the "offset"), which makes it a massive pain if you want to, say, set every language to use 2-space indents. You can't even really /read/; =indent-bars= has a hacky system to guess what offset variable to pull from, but it fails in many cases. +Unfortunately for packages like these, indentation in Emacs is a notoriously awful pain point. Every major mode uses a different variable to configure the number of spaces in its indents (typically referred to in Emacs lingo as the "offset"), which makes it a massive pain if you want to, say, set every language to use a certain indent size. You can't even really /read/ the value; =indent-bars= has a hacky system to guess what offset variable to pull from, but it fails in many cases. -There isn't really a fix for this, but there is a better, more stable hack. The built-in and officially supported =editorconfig= package, which also requires manipulating indentation, maintains a variable ~editorconfig-indentation-alist~ which maps major modes to their offset variables. This won't work for every mode listed there, since =editorconfig= only has to set an offset value where we have to read one, but we can use it for the majority of cases and fall back to the default behavior if it doesn't work. +There isn't really a fix for this, but there is a better, more stable hack. The built-in and officially supported =editorconfig= package, which also requires manipulating indentation, has a system for obtaining the offset variables for each major mode, via the function ~editorconfig--get-indentation~ and the variable ~editorconfig-indentation-alist~. This system lets us get the symbol for the offset variable, which can be read to find the indentation. + +Problem solved, right? Not quite. We can't just read the variable it returns, because sometimes offset values in these variables need to be modified from the actual column offset, such as to be halved or doubled. We can account for this by using the most ridiculous hack I've ever devised: by telling =editorconfig= to set the indentation to 720720 spaces (a [[https://en.wikipedia.org/wiki/Superior_highly_composite_number][superior highly composite number]]) and analyzing the variables it modifies, we can reverse engineer the actual offset. For example, if it sets a certain variable to 360360, we then know that twice that variable's original value was the correct offset. We use a highly composite number since the offset must be an integer, and we must be able to recover the original value from a division operation without rounding error. #+begin_src emacs-lisp (defun ~/guess-offset-from-editorconfig () "Guess the indent offset of the current buffer based on -`editorconfig-indentation-alist'." +`editorconfig--get-indentation'." (require 'editorconfig) - (let ((parent major-mode) - entry) - ;; Find the closet parent mode of `major-mode' in - ;; `editorconfig-indentation-alist'. - (while (and (not (setq entry (alist-get parent editorconfig-indentation-alist))) - (setq parent (get parent 'derived-mode-parent)))) - (when (consp entry) - (when-let ((elem (cl-find-if (lambda (elem) - (or (symbolp elem) - (and (consp elem) - (symbolp (car elem)) - (integerp (cdr elem))))) - entry))) - (cond ((symbolp elem) (symbol-value elem)) - ((consp elem) (/ (symbol-value (car elem)) (cdr elem)))))))) + (require 'dash) + (let* ((indent-value 720720) + (alist (editorconfig--get-indentation nil (number-to-string indent-value)))) + (--some (pcase it + ;; Non-mode-specific variables - ignore + (`(tab-width . ,_) nil) + (`(evil-shift-width . ,_) nil) + (`(,var . ,val) + (cond ((zerop val) nil) + ((= val indent-value) (symbol-value var)) + ((zerop (mod indent-value val)) + (* (symbol-value var) (/ indent-value val))) + ((zerop (mod val indent-value)) + (/ (symbol-value var) (/ val indent-value)))))) + alist))) (advice-add #'indent-bars--guess-spacing :before-until #'~/guess-offset-from-editorconfig) @@ -2090,7 +2092,8 @@ As a special case for exclusively this config file, we override the offset to ma The =indent-bars= package works by overriding the face used for displaying the whitespace characters before each line. This works in most cases, but sometimes (like in Org files) the major mode uses a face to change the background color. When the bars override the face, the default background color peeks through them. We can fix this by patching one of the display functions to add the face on top rather than overriding. #+begin_src emacs-lisp -(deftemplate! indent-bars (defun indent-bars--draw-line) +(deftemplate! indent-bars + (defun indent-bars--draw-line) (let* ... (when (<= bar nbars) (el-patch-remove