refactor(tree-sitter): major mode remapping

This approach simplifies the hacks and uses fewer moving parts to be
less error prone (or prone to potentially overwriting user/module
configuration).
This commit is contained in:
Henrik Lissner
2025-08-30 03:01:44 +02:00
parent 3044812ac1
commit 7dcedc16b5
2 changed files with 29 additions and 47 deletions

View File

@@ -1,8 +1,5 @@
;;; tools/tree-sitter/autoload/tree-sitter.el -*- lexical-binding: t; -*- ;;; tools/tree-sitter/autoload/tree-sitter.el -*- lexical-binding: t; -*-
;;;###autoload
(defvar +tree-sitter--major-mode-remaps-alist nil)
;;;###autodef (fset 'tree-sitter! #'ignore) ;;;###autodef (fset 'tree-sitter! #'ignore)
(defun tree-sitter! () (defun tree-sitter! ()
(message "Old tree-sitter.el support is deprecated!")) (message "Old tree-sitter.el support is deprecated!"))
@@ -25,9 +22,8 @@ pre-Emacs 31."
(cl-check-type ts-mode symbol) (cl-check-type ts-mode symbol)
(setq recipes (mapcar #'ensure-list (ensure-list recipes))) (setq recipes (mapcar #'ensure-list (ensure-list recipes)))
(dolist (m (ensure-list mode)) (dolist (m (ensure-list mode))
(add-to-list (setf (alist-get m major-mode-remap-defaults) ts-mode)
'+tree-sitter--major-mode-remaps-alist (put ts-mode '+tree-sitter (cons m (mapcar #'car recipes))))
(list m ts-mode (mapcar #'car recipes) nil)))
(when (setq recipes (cl-remove-if-not #'cdr recipes)) (when (setq recipes (cl-remove-if-not #'cdr recipes))
(with-eval-after-load 'treesit (with-eval-after-load 'treesit
(dolist (recipe recipes) (dolist (recipe recipes)

View File

@@ -35,51 +35,37 @@
python-ts-mode)) python-ts-mode))
(advice-add mode :around #'+tree-sitter-ts-mode-inhibit-side-effects-a)) (advice-add mode :around #'+tree-sitter-ts-mode-inhibit-side-effects-a))
;; HACK: Some *-ts-mode packages modify `major-mode-remap-defaults' ;; HACK: Intercept all ts-mode major mode remappings so grammars can be
;; inconsistently. Playing whack-a-mole to undo those changes is more hassle ;; dynamically checked and `treesit-auto-install-grammar' can be
;; then simply ignoring them (by overriding `major-mode-remap-defaults' for ;; consistently respected (which isn't currently the case with the majority
;; any modes remapped with `set-tree-sitter!'). The user shouldn't touch ;; of ts-modes, even the built-in ones).
;; `major-mode-remap-defaults' anyway; `major-mode-remap-alist' will always
;; have precedence.
(defadvice! +tree-sitter--maybe-remap-major-mode-a (fn mode) (defadvice! +tree-sitter--maybe-remap-major-mode-a (fn mode)
:around #'major-mode-remap :around #'major-mode-remap
(let ((major-mode-remap-defaults (let ((mode (funcall fn mode)))
;; Because standard major-mode remapping doesn't offer graceful (if-let* ((ts (get mode '+tree-sitter))
;; failure in some cases, I implement it myself: (fallback-mode (car ts)))
(cons (when-let* ((spec (assq mode +tree-sitter--major-mode-remaps-alist)) (cond ((not (fboundp mode))
(ts-mode (nth 1 spec)) (message "Couldn't find %S, falling back to %S" mode fallback-mode)
(langs (nth 2 spec))) fallback-mode)
(if (not (fboundp ts-mode)) ((and (or (eq treesit-enabled-modes t)
(ignore (message "Couldn't find %S, falling back to %S" ts-mode mode)) (memq fallback-mode treesit-enabled-modes))
(prog1 (and (or (eq treesit-enabled-modes t) ;; Lazily load autoloaded `treesit-language-source-alist'
(memq ts-mode treesit-enabled-modes)) ;; entries.
;; Lazily load autoload so (let ((fn (symbol-function mode)))
;; `treesit-language-source-alist' is (or (not (autoloadp fn))
;; initialized. (autoload-do-load fn mode)))
(let ((fn (symbol-function ts-mode))) ;; Only prompt once, and log other times.
(or (not (autoloadp fn)) (or (null (cdr ts))
(autoload-do-load fn ts-mode))) (cl-every (if (get mode '+tree-sitter-ensured)
;; Only prompt once, and log other times. (doom-rpartial #'treesit-ready-p 'message)
(cl-every (if (get ts-mode 'ensured?) #'treesit-ensure-installed)
(doom-rpartial #'treesit-ready-p 'message) (cdr ts))))
#'treesit-ensure-installed) (put mode '+tree-sitter-ensured t)
langs) mode)
`((,mode . ,ts-mode))) (fallback-mode))
(put ts-mode 'ensured? t)))) mode)))
major-mode-remap-defaults)))
(funcall fn mode)))
:config :config
;; HACK: The implementation of `treesit-enabled-modes's setter and
;; `treesit-major-mode-remap-alist' is intrusively opinionated, so disable
;; it ato avoid untimely (and overriding) modifications of
;; `major-mode-remap-alist' at runtime. What's more, this was only
;; introduced in 31, so ignoring them is more consistent for pre-31 users.
(when major-mode-remap-alist
(dolist (m treesit-major-mode-remap-alist)
(setq major-mode-remap-alist (delete m major-mode-remap-alist))))
(setq treesit-major-mode-remap-alist nil)
;; HACK: Keep $EMACSDIR clean by installing grammars to the active profile. ;; HACK: Keep $EMACSDIR clean by installing grammars to the active profile.
(add-to-list 'treesit-extra-load-path (concat doom-profile-data-dir "tree-sitter")) (add-to-list 'treesit-extra-load-path (concat doom-profile-data-dir "tree-sitter"))
(defadvice! +tree-sitter--install-grammar-to-local-dir-a (fn &rest args) (defadvice! +tree-sitter--install-grammar-to-local-dir-a (fn &rest args)