Files
doomemacs/modules/tools/tree-sitter/config.el
Henrik Lissner d545fccf47 fix(:lang): missing grammar recipes
I removed the grammar recipes in 3b58741 to avoid redundancy with the
upstream recipes, but didn't realize that those upstream recipes weren't
added until Emacs 31, so users on 30 and older would get errors when
trying to install any missing grammars.

This also establishes a hard dependency between :lang (php +tree-sitter)
and :lang ({javascript,web} +tree-sitter).

Amend: 3b58741522
2025-08-31 14:39:38 +02:00

167 lines
8.5 KiB
EmacsLisp

;;; tools/tree-sitter/config.el -*- lexical-binding: t; -*-
;;
;;; Packages
(use-package! treesit
:when (fboundp 'treesit-available-p)
:when (treesit-available-p)
:defer t
:preface
(setq treesit-enabled-modes t)
;; HACK: The *-ts-mode major modes are inconsistent about how they treat
;; missing language grammars (some error out, some respect
;; `treesit-auto-install-grammar', some fall back to `fundamental-mode').
;; I'd like to address this poor UX using `major-mode-remap-alist' entries
;; created by `set-tree-sitter!' (which will fall back to the non-treesit
;; modes), but most *-ts-mode's clobber `auto-mode-alist' and/or
;; `interpreter-mode-alist' each time the major mode is activated, so those
;; must be undone too so they don't overwrite user config.
;; TODO: Handle this during the 'doom sync' process instead.
(save-match-data
(dolist (sym '(auto-mode-alist interpreter-mode-alist))
(set
sym (cl-loop for (src . fn) in (symbol-value sym)
unless (and (functionp fn)
(string-match "-ts-mode\\(?:-maybe\\)?$" (symbol-name fn)))
collect (cons src fn)))))
;; HACK: These *-ts-modes change `auto-mode-alist' and/or
;; `interpreter-mode-alist' every time they are activated, running the risk
;; of overwriting user (or Doom) config.
;; REVIEW: Should be addressed upstream.
(dolist (mode '(csharp-ts-mode
python-ts-mode))
(advice-add mode :around #'+tree-sitter-ts-mode-inhibit-side-effects-a))
;; HACK: Intercept all ts-mode major mode remappings so grammars can be
;; dynamically checked and `treesit-auto-install-grammar' can be
;; consistently respected (which isn't currently the case with the majority
;; of ts-modes, even the built-in ones).
(defadvice! +tree-sitter--maybe-remap-major-mode-a (fn mode)
:around #'major-mode-remap
(let ((mode (funcall fn mode)))
(if-let* ((ts (get mode '+tree-sitter))
(fallback-mode (car ts)))
(cond ((not (fboundp mode))
(message "Couldn't find `%S', falling back to `%S'" mode fallback-mode)
fallback-mode)
((and (or (eq treesit-enabled-modes t)
(memq fallback-mode treesit-enabled-modes))
;; Lazily load autoloaded `treesit-language-source-alist'
;; entries.
(let ((fn (symbol-function mode)))
(or (not (autoloadp fn))
(autoload-do-load fn mode)))
;; Only prompt once, and log other times.
(or (null (cdr ts))
(cl-every (if (get mode '+tree-sitter-ensured)
(doom-rpartial #'treesit-ready-p 'message)
#'treesit-ensure-installed)
(cdr ts))))
(put mode '+tree-sitter-ensured t)
mode)
(fallback-mode))
mode)))
:config
;; 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"))
(defadvice! +tree-sitter--install-grammar-to-local-dir-a (fn &rest args)
"Write grammars to `doom-profile-data-dir'."
:around #'treesit-install-language-grammar
:around #'treesit--build-grammar
(let ((user-emacs-directory doom-profile-data-dir))
(apply fn args)))
;; TODO: Move most of these out to modules
(dolist (map '((awk "https://github.com/Beaglefoot/tree-sitter-awk" nil nil nil nil)
(bibtex "https://github.com/latex-lsp/tree-sitter-bibtex" nil nil nil nil)
(blueprint "https://github.com/huanie/tree-sitter-blueprint" nil nil nil nil)
(commonlisp "https://github.com/tree-sitter-grammars/tree-sitter-commonlisp" nil nil nil nil)
(latex "https://github.com/latex-lsp/tree-sitter-latex" nil nil nil nil)
(make "https://github.com/tree-sitter-grammars/tree-sitter-make" nil nil nil nil)
(nu "https://github.com/nushell/tree-sitter-nu" nil nil nil nil)
(org "https://github.com/milisims/tree-sitter-org" nil nil nil nil)
(perl "https://github.com/ganezdragon/tree-sitter-perl" nil nil nil nil)
(proto "https://github.com/mitchellh/tree-sitter-proto" nil nil nil nil)
(r "https://github.com/r-lib/tree-sitter-r" nil nil nil nil)
(sql "https://github.com/DerekStride/tree-sitter-sql" "gh-pages" nil nil nil)
(surface "https://github.com/connorlay/tree-sitter-surface" nil nil nil nil)
(toml "https://github.com/tree-sitter/tree-sitter-toml" nil nil nil nil)
(typst "https://github.com/uben0/tree-sitter-typst" "master" "src" nil nil)
(verilog "https://github.com/gmlarumbe/tree-sitter-verilog" nil nil nil nil)
(vhdl "https://github.com/alemuller/tree-sitter-vhdl" nil nil nil nil)
(vue "https://github.com/tree-sitter-grammars/tree-sitter-vue" nil nil nil nil)
(wast "https://github.com/wasm-lsp/tree-sitter-wasm" nil "wast/src" nil nil)
(wat "https://github.com/wasm-lsp/tree-sitter-wasm" nil "wat/src" nil nil)
(wgsl "https://github.com/mehmetoguzderin/tree-sitter-wgsl" nil nil nil nil)))
(cl-pushnew map treesit-language-source-alist :test #'eq :key #'car)))
;; TODO: combobulate or evil-textobj-tree-sitter
;; (use-package! combobulate
;; :commands combobulate-query-builder
;; :hook (prog-mode . combobulate-mode))
;; (use-package! evil-textobj-tree-sitter
;; :when (modulep! :editor evil +everywhere)
;; :defer t
;; :init (after! tree-sitter (require 'evil-textobj-tree-sitter))
;; :after-call doom-first-input-hook
;; :config
;; (defvar +tree-sitter-inner-text-objects-map (make-sparse-keymap))
;; (defvar +tree-sitter-outer-text-objects-map (make-sparse-keymap))
;; (defvar +tree-sitter-goto-previous-map (make-sparse-keymap))
;; (defvar +tree-sitter-goto-next-map (make-sparse-keymap))
;; (evil-define-key '(visual operator) 'tree-sitter-mode
;; "i" +tree-sitter-inner-text-objects-map
;; "a" +tree-sitter-outer-text-objects-map)
;; (evil-define-key 'normal 'tree-sitter-mode
;; "[g" +tree-sitter-goto-previous-map
;; "]g" +tree-sitter-goto-next-map)
;; (map! (:map +tree-sitter-inner-text-objects-map
;; "A" (+tree-sitter-get-textobj '("parameter.inner" "call.inner"))
;; "f" (+tree-sitter-get-textobj "function.inner")
;; "F" (+tree-sitter-get-textobj "call.inner")
;; "C" (+tree-sitter-get-textobj "class.inner")
;; "v" (+tree-sitter-get-textobj "conditional.inner")
;; "l" (+tree-sitter-get-textobj "loop.inner"))
;; (:map +tree-sitter-outer-text-objects-map
;; "A" (+tree-sitter-get-textobj '("parameter.outer" "call.outer"))
;; "f" (+tree-sitter-get-textobj "function.outer")
;; "F" (+tree-sitter-get-textobj "call.outer")
;; "C" (+tree-sitter-get-textobj "class.outer")
;; "c" (+tree-sitter-get-textobj "comment.outer")
;; "v" (+tree-sitter-get-textobj "conditional.outer")
;; "l" (+tree-sitter-get-textobj "loop.outer"))
;; (:map +tree-sitter-goto-previous-map
;; "a" (+tree-sitter-goto-textobj "parameter.outer" t)
;; "f" (+tree-sitter-goto-textobj "function.outer" t)
;; "F" (+tree-sitter-goto-textobj "call.outer" t)
;; "C" (+tree-sitter-goto-textobj "class.outer" t)
;; "c" (+tree-sitter-goto-textobj "comment.outer" t)
;; "v" (+tree-sitter-goto-textobj "conditional.outer" t)
;; "l" (+tree-sitter-goto-textobj "loop.outer" t))
;; (:map +tree-sitter-goto-next-map
;; "a" (+tree-sitter-goto-textobj "parameter.outer")
;; "f" (+tree-sitter-goto-textobj "function.outer")
;; "F" (+tree-sitter-goto-textobj "call.outer")
;; "C" (+tree-sitter-goto-textobj "class.outer")
;; "c" (+tree-sitter-goto-textobj "comment.outer")
;; "v" (+tree-sitter-goto-textobj "conditional.outer")
;; "l" (+tree-sitter-goto-textobj "loop.outer")))
;; (after! which-key
;; (setq which-key-allow-multiple-replacements t)
;; (pushnew!
;; which-key-replacement-alist
;; '(("" . "\\`+?evil-textobj-tree-sitter-function--\\(.*\\)\\(?:.inner\\|.outer\\)") . (nil . "\\1")))))