Tree-sitter powered textobjects for Emacs. You can use them with evil-mode
or with thing-at-point
.
This package will let you create evil textobjects using tree-sitter
grammars. You can easily create function,class,comment etc textobjects
in multiple languages. It also make additional things
available in
thing-at-point
like function
, class
, loop
, comment
etc.
It can work with either elisp-tree-sitter or the builtin treesit library.
You can install evil-textobj-tree-sitter
from melpa. Here is how you would do it using use-package
and package.el
:
(use-package evil-textobj-tree-sitter :ensure t)
Or you can use straight.el
:
(use-package evil-textobj-tree-sitter :straight t)
Or using straight.el
and el-get
to pull from source:
(use-package evil-textobj-tree-sitter
:straight (evil-textobj-tree-sitter :type git
:host github
:repo "meain/evil-textobj-tree-sitter"
:files (:defaults "queries" "treesit-queries")))
You will also have to setup
tree-sitter
.
The package adds more "things" to thing-at-point
. You can find all
the things that it adds in the
source.
You can use these like any other thing-at-point
entries. For example
in case of functions, we make the following available:
(thing-at-point 'function)
(function-at-point)
By default, the library does not provide any keybindings, but it should be relatively easy to add them.
;; bind `function.outer`(entire function block) to `f` for use in things like `vaf`, `yaf`
(define-key evil-outer-text-objects-map "f" (evil-textobj-tree-sitter-get-textobj "function.outer"))
;; bind `function.inner`(function block without name and args) to `f` for use in things like `vif`, `yif`
(define-key evil-inner-text-objects-map "f" (evil-textobj-tree-sitter-get-textobj "function.inner"))
;; You can also bind multiple items and we will match the first one we can find
(define-key evil-outer-text-objects-map "a" (evil-textobj-tree-sitter-get-textobj ("conditional.outer" "loop.outer")))
If you are not able to find the text object that you are looking for in the builtin list, you can create custom text objects by passing the a custom query with captures.
For example if you want to create text object to select import
statements, you can write something like below. You will have to
provide the tree-sitter queries for all the languages that you want it
to work for
;; The first arguemnt to `evil-textobj-tree-sitter-get-textobj' will be the capture group to use
;; and the second arg will be an alist mapping major-mode to the corresponding query to use.
(define-key evil-outer-text-objects-map "m" (evil-textobj-tree-sitter-get-textobj "import"
'((python-mode . ((import_statement) @import)) ;; default modes (using tree-sitter)
(python-ts-mode . ((import_statement) @import)) ;; treesit modes
(rust-mode . ((use_declaration) @import)))))
We have also added support for for a fancier version of
goto-char
. You can use this to go to the next function, previous
class or do any motions like that.
You can use the evil-textobj-tree-sitter-goto-textobj
function to
invoke goto. You can either use this in other function or just bound
to a key. The first argument is the textobj that you want to use, the
second one specifies if you want to search forward or backward and the
last one is for specifying weather to go to the start or end of the
textobj.
Below are some sample binding that you can do. You can use any textobj that is available here.
;; Goto start of next function
(define-key evil-normal-state-map
(kbd "]f")
(lambda ()
(interactive)
(evil-textobj-tree-sitter-goto-textobj "function.outer")))
;; Goto start of previous function
(define-key evil-normal-state-map
(kbd "[f")
(lambda ()
(interactive)
(evil-textobj-tree-sitter-goto-textobj "function.outer" t)))
;; Goto end of next function
(define-key evil-normal-state-map
(kbd "]F")
(lambda ()
(interactive)
(evil-textobj-tree-sitter-goto-textobj "function.outer" nil t)))
;; Goto end of previous function
(define-key evil-normal-state-map
(kbd "[F")
(lambda ()
(interactive)
(evil-textobj-tree-sitter-goto-textobj "function.outer" t t)))
evil-textobj-tree-sitter
work with both builtin treesit
and
elisp-tree-sitter
. The queries in use are a bit different in both
cases with the elisp-tree-sitter
version currently being more feature
complete. In both cases we pull the queries from external sources. For
elisp-tree-sitter
, we source them from
nvim-treesitter/nvim-treesitter-textobjects
and is places into
queries
directory. And for treesit
queries, it is sourced from
helix
and placed in
treesit-queries.
You can check these files to see what all is available. If you are
interesting in contributing additional textobjects, you can do so by
submitting to the respective projects. If there is enough interest, I
don't mind starting to manage queries ourselves.
If you are adding a completely new language, there is two other things that you will have to do to make sure everything will work well.
- Make sure the lang is available in emacs-tree-sitter/tree-sitter-langs or
treesit
. - Make sure we have a
major-mode
mapping in evil-textobj-tree-sitter-major-mode-language-alist
If you would like to test out new textobjects, I would suggest using
custom textobjects. If you want to edit the
query files, you can edit them in
evil-textobj-tree-sitter--queries-dir
or by forking the repo, and
using the forked version with your edits.
The primary codebase is licensed under Apache-2.0
. The queries have
be taken from
nvim-treesitter/nvim-treesitter-textobjects
which is also licensed under the same license.