From f6be9b5c3b25caf28fa3efde6b3b9eeb046a7c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emin=20Can=20=C3=96zen?= Date: Mon, 29 Jun 2026 19:57:24 +0300 Subject: [PATCH] feat(tree-sitter): restore evil-textobj-tree-sitter integration Replace the dead `tree-sitter-mode' minor mode with a new `+tree-sitter-textobjs-mode' minor mode, scoped to all `*-ts-mode' buffers registered via `set-tree-sitter!'. This restores: - Structural text objects: A(parameter), f(function), F(call) etc. - Goto navigation with [g/]g prefix with the same keys. - which-key label cleanup for generated fucntion names. - the documented `+tree-sitter-*-map' rebinding API The minor mode is defined eagerly via :init so that `set-tree-sitter!' (called from :lang module :init blocks) can register hooks to the correct mode hooks via `fboundp'. Updates README.org with package and scoping documentation and minor typos. --- modules/tools/tree-sitter/README.org | 10 +- .../tools/tree-sitter/autoload/tree-sitter.el | 30 +++-- modules/tools/tree-sitter/config.el | 124 +++++++++--------- modules/tools/tree-sitter/packages.el | 15 +-- 4 files changed, 85 insertions(+), 94 deletions(-) diff --git a/modules/tools/tree-sitter/README.org b/modules/tools/tree-sitter/README.org index 9760d92bf..f40889bf7 100644 --- a/modules/tools/tree-sitter/README.org +++ b/modules/tools/tree-sitter/README.org @@ -27,7 +27,8 @@ It includes: /This module has no flags./ ** Packages -/This module doesn't install any packages./ +- if [[doom-module::editor evil +everywhere]] + - [[doom-package:evil-textobj-tree-sitter]] ** Hacks - ~treesit-auto-install-grammar~, ~treesit-ensure-installed~, ~treesit-enabled-modes~, @@ -88,7 +89,8 @@ Not all languages support all text objects (yet). [[https://github.com/nvim-tree object languages support]]. #+begin_quote -  Only languages with parsers in Emacs have text object support at the moment. +  Text objects are only enabled in ~*-ts-mode~ buffers, via the + ~+tree-sitter-textobjs-mode~ minor mode, and require [[doom-module::editor evil +everywhere]]. #+end_quote Currently text objects are bound to: @@ -127,8 +129,8 @@ Currently keys are bound to: ** Rebind text objects Rebinding keys works the same as rebinding any other key, but text-object keys -must be bound on one or both of the ~+tree-sitter-inner-text-object-map~ or -~+tree-sitter-outer-text-object-map~ keymaps: +must be bound on one or both of the ~+tree-sitter-inner-text-objects-map~ or +~+tree-sitter-outer-text-objects-map~ keymaps: #+begin_src emacs-lisp (map! (:map +tree-sitter-outer-text-objects-map "f" (evil-textobj-tree-sitter-get-textobj "call.inner") diff --git a/modules/tools/tree-sitter/autoload/tree-sitter.el b/modules/tools/tree-sitter/autoload/tree-sitter.el index 5c747397e..6d92cff23 100644 --- a/modules/tools/tree-sitter/autoload/tree-sitter.el +++ b/modules/tools/tree-sitter/autoload/tree-sitter.el @@ -31,6 +31,8 @@ pre-Emacs 31." (when m (setf (alist-get m major-mode-remap-defaults) ts-mode)) (put ts-mode '+tree-sitter (cons m (mapcar #'car recipes)))) + (when (fboundp '+tree-sitter-textobjs-mode) + (add-hook (intern (format "%s-hook" ts-mode)) #'+tree-sitter-textobjs-mode)) (when-let* ((fn (intern-soft (format "%s-maybe" ts-mode)))) (cl-callf2 rassq-delete-all fn auto-mode-alist) (cl-callf2 rassq-delete-all fn interpreter-mode-alist)) @@ -49,21 +51,21 @@ pre-Emacs 31." 'many) (list commit)))))))))) -;; ;; HACK: Remove and refactor when `use-package' eager macro expansion is solved or `use-package!' is removed -;; ;;;###autoload -;; (defun +tree-sitter-get-textobj (group &optional query) -;; "A wrapper around `evil-textobj-tree-sitter-get-textobj' to -;; prevent eager expansion." -;; (eval `(evil-textobj-tree-sitter-get-textobj ,group ,query))) +;; HACK: Remove and refactor when `use-package' eager macro expansion is solved or `use-package!' is removed +;;;###autoload +(defun +tree-sitter-get-textobj (group &optional query) + "A wrapper around `evil-textobj-tree-sitter-get-textobj' to +prevent eager expansion." + (eval `(evil-textobj-tree-sitter-get-textobj ,group ,query))) -;; ;;;###autoload -;; (defun +tree-sitter-goto-textobj (group &optional previous end query) -;; "Thin wrapper that returns the symbol of a named function, used in keybindings." -;; (let ((sym (intern (format "+goto%s%s-%s" (if previous "-previous" "") (if end "-end" "") group)))) -;; (fset sym (lambda () -;; (interactive) -;; (evil-textobj-tree-sitter-goto-textobj group previous end query))) -;; sym)) +;;;###autoload +(defun +tree-sitter-goto-textobj (group &optional previous end query) + "Thin wrapper that returns the symbol of a named function, used in keybindings." + (let ((sym (intern (format "+goto%s%s-%s" (if previous "-previous" "") (if end "-end" "") group)))) + (fset sym (lambda () + (interactive) + (evil-textobj-tree-sitter-goto-textobj group previous end query))) + sym)) ;;;###autoload (defun +tree-sitter-ts-mode-inhibit-side-effects-a (fn &rest args) diff --git a/modules/tools/tree-sitter/config.el b/modules/tools/tree-sitter/config.el index 9763bd20f..95fd91f7c 100644 --- a/modules/tools/tree-sitter/config.el +++ b/modules/tools/tree-sitter/config.el @@ -139,68 +139,64 @@ (wgsl "https://github.com/mehmetoguzderin/tree-sitter-wgsl" nil nil nil nil))) (cl-pushnew map treesit-language-source-alist :test #'eq :key #'car))) +(use-package! evil-textobj-tree-sitter + :when (modulep! :editor evil +everywhere) + :defer t + :init + ;; Define the minor mode to replace the dead tree-sitter-mode + (define-minor-mode +tree-sitter-textobjs-mode + "Enable evil tree-sitter text objects in this buffer." + :lighter "") -;; 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"))))) + :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-textobjs-mode + "i" +tree-sitter-inner-text-objects-map + "a" +tree-sitter-outer-text-objects-map) + + (evil-define-key 'normal '+tree-sitter-textobjs-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"))))) diff --git a/modules/tools/tree-sitter/packages.el b/modules/tools/tree-sitter/packages.el index c5b7fa3c2..94dcda3ce 100644 --- a/modules/tools/tree-sitter/packages.el +++ b/modules/tools/tree-sitter/packages.el @@ -3,16 +3,7 @@ (package! treesit :built-in t) (when (> emacs-major-version 28) - ;; (package! combobulate - ;; :recipe '(;; If pulled from emacsmirror, this would otherwise pull in test - ;; ;; repos that users don't need. - ;; :nonrecursive t - ;; ;; HACK: This package has terrible autoload ettiquette, eagerly - ;; ;; loading a number of expensive packages at startup, so - ;; ;; autoloads are handled manually in config.el - ;; :build (:not autoloads)) - ;; :pin "59b64d66d66eb84da6a2cedd152b1692378af674") - ;; (when (modulep! :editor evil +everywhere) - ;; (package! evil-textobj-tree-sitter - ;; :pin "bce236e5d2cc2fa4eae7d284ffd19ad18d46349a")) + (when (modulep! :editor evil +everywhere) + (package! evil-textobj-tree-sitter + :pin "fecc0e11615df31a6651ce11b012388e53cad4e9")) )