New command 'treesit-toggle-sexp-mode' (bug#76676).

* lisp/treesit.el (treesit-forward-sexp): Don't use
'treesit-sexp-type-regexp' reserved for list commands
to override their default 'list' thing.
(treesit-down-list, treesit-up-list): Use
'treesit-sexp-type-regexp' instead of 'list'
when it's non-nil.
(treesit-toggle-sexp-mode): New command.

* lisp/progmodes/c-ts-mode.el (c-ts-mode--thing-settings):
Improve 'sexp' thing settings to exclude the top-level
"translation_unit" that just moves to EOF and also "comment".

* lisp/progmodes/elixir-ts-mode.el (elixir-ts--sexp-regexp): Remove.
(elixir-ts--forward-sexp): Remove to use the default 'sexp' navigation.
(elixir-ts--with-parens-0-p, elixir-ts--with-parens-1-p):
New internal functions.
(elixir-ts-mode): Add 'treesit-thing-settings'
instead of 'forward-sexp-function' (bug#76788).

* lisp/progmodes/heex-ts-mode.el (heex-ts--sexp-regexp): Remove.
(heex-ts--forward-sexp): Remove to use the default 'sexp' navigation.
(heex-ts--thing-settings): New variable.
(heex-ts-mode): Use 'heex-ts--thing-settings'
instead of 'forward-sexp-function'.

* lisp/progmodes/java-ts-mode.el (java-ts-mode):
Improve 'sexp' thing to use settings like in c-ts-mode.

* lisp/progmodes/php-ts-mode.el (php-ts-mode):
Improve 'sexp' thing settings to exclude the top-level
"program" that just moves to EOF and also "comment".

* lisp/progmodes/ruby-ts-mode.el (ruby-ts-mode):
Improve 'sexp' thing to use settings like in c-ts-mode.

* lisp/textmodes/css-mode.el (css--treesit-thing-settings):
Add 'sexp' thing.  Add "string_value" to 'text' thing.

* lisp/textmodes/html-ts-mode.el (html-ts-mode--treesit-things-settings):
Improve 'sexp' thing to use settings like in c-ts-mode.
Add "doctype" to the 'list' thing.
This commit is contained in:
Juri Linkov 2025-04-09 21:21:53 +03:00
parent fa247a24a5
commit 99a2cb05a4
10 changed files with 161 additions and 116 deletions

View file

@ -535,6 +535,17 @@ This variable has no effect when Transient Mark mode is off.
* Changes in Specialized Modes and Packages in Emacs 31.1
** Tree-sitter
*** New command 'treesit-toggle-sexp-mode'.
It toggles the mode of navigation for such sexp and list commands as
'treesit-forward-sexp', 'treesit-forward-list', 'treesit-down-list',
'treesit-up-list'.
In sexp mode these commands navigate purely by treesit nodes
defined by the thing sexp.
In list mode they navigate using syntax tables for symbols
and using the treesit thing list for lists.
---
** Text mode

View file

@ -1194,7 +1194,11 @@ if `c-ts-mode-emacs-sources-support' is non-nil."
(defvar c-ts-mode--thing-settings
`(;; It's more useful to include semicolons as sexp so
;; that users can move to the end of a statement.
(sexp (not ,(rx (or "{" "}" "[" "]" "(" ")" ","))))
(sexp (not (or (and named
,(rx bos (or "translation_unit" "comment") eos))
(and anonymous
,(rx (or "{" "}" "[" "]"
"(" ")" ","))))))
(list
,(regexp-opt '("preproc_params"
"preproc_if"

View file

@ -111,13 +111,6 @@
"Face used for attributes in Elixir files."
:group 'elixir-ts)
(defconst elixir-ts--sexp-regexp
(rx bol
(or "call" "stab_clause" "binary_operator" "list" "tuple" "map" "pair"
"sigil" "string" "atom" "alias" "arguments" "identifier"
"boolean" "quoted_content" "bitstring")
eol))
(defconst elixir-ts--test-definition-keywords
'("describe" "test"))
@ -574,21 +567,10 @@
(:match "^[HF]$" @_name)
(quoted_content) @heex)))))
(defvar heex-ts--sexp-regexp)
(defvar heex-ts--thing-settings)
(defvar heex-ts--indent-rules)
(defvar heex-ts--font-lock-settings)
(defun elixir-ts--forward-sexp (&optional arg)
"Move forward across one balanced expression (sexp).
With ARG, do it many times. Negative ARG means move backward."
(or arg (setq arg 1))
(funcall
(if (> arg 0) #'treesit-end-of-thing #'treesit-beginning-of-thing)
(if (eq (treesit-language-at (point)) 'heex)
heex-ts--sexp-regexp
elixir-ts--sexp-regexp)
(abs arg)))
(defun elixir-ts--treesit-anchor-grand-parent-bol (_n parent &rest _)
"Return the beginning of non-space characters for the parent node of PARENT."
(save-excursion
@ -624,6 +606,14 @@ Return nil if NODE is not a defun node or doesn't have a name."
(_ nil))))
(_ nil)))
(defun elixir-ts--with-parens-0-p (node)
(equal (treesit-node-type (treesit-node-child node 0))
"("))
(defun elixir-ts--with-parens-1-p (node)
(equal (treesit-node-type (treesit-node-child node 1))
"("))
(defvar elixir-ts--syntax-propertize-query
(when (treesit-available-p)
(treesit-query-compile
@ -707,7 +697,34 @@ Return nil if NODE is not a defun node or doesn't have a name."
(setq-local treesit-simple-indent-rules elixir-ts--indent-rules)
;; Navigation.
(setq-local forward-sexp-function #'elixir-ts--forward-sexp)
(setq-local treesit-thing-settings
`((elixir
(sexp (not (or (and named
,(rx bos (or "source" "comment") eos))
(and anonymous
,(rx (or "{" "}" "[" "]" "(" ")"
"do" "end"))))))
(list
(or (and "\\`arguments\\'" ,#'elixir-ts--with-parens-0-p)
(and "\\`unary_operator\\'" ,#'elixir-ts--with-parens-1-p)
,(rx bos (or "block"
"quoted_atom"
"string"
"interpolation"
"sigil"
"quoted_keyword"
"list"
"tuple"
"bitstring"
"map"
"do_block"
"anonymous_function")
eos)))
(sentence
,(rx bos (or "call") eos))
(text
,(rx bos (or "string" "sigil" "comment") eos)))
(heex ,@heex-ts--thing-settings)))
(setq-local treesit-defun-type-regexp
'("call" . elixir-ts--defun-p))

View file

@ -56,12 +56,6 @@
:safe 'integerp
:group 'heex-ts)
(defconst heex-ts--sexp-regexp
(rx bol
(or "directive" "tag" "component" "slot"
"attribute" "attribute_value" "quoted_attribute_value" "expression")
eol))
;; There seems to be no parent directive block for tree-sitter-heex,
;; so we ignore them for now until we learn how to query them.
;; https://github.com/phoenixframework/tree-sitter-heex/issues/28
@ -139,14 +133,29 @@ Return nil if NODE is not a defun node or doesn't have a name."
(treesit-node-child (treesit-node-child node 0) 1) nil)))
(_ nil)))
(defun heex-ts--forward-sexp (&optional arg)
"Move forward across one balanced expression (sexp).
With ARG, do it many times. Negative ARG means move backward."
(or arg (setq arg 1))
(funcall
(if (> arg 0) #'treesit-end-of-thing #'treesit-beginning-of-thing)
heex-ts--sexp-regexp
(abs arg)))
(defvar heex-ts--thing-settings
`((sexp
(not (or (and named
,(rx bos (or "fragment" "comment") eos))
(and anonymous
,(rx (or "<!" "<" ">" "{" "}"))))))
(list
,(rx bos (or "doctype"
"tag"
"component"
"slot"
"expression"
"directive"
"comment")
eos))
(sentence
,(rx bos (or "tag_name"
"component_name"
"attribute")
eos))
(text
,(rx bos (or "comment" "text") eos)))
"`treesit-thing-settings' for HEEx.")
;;;###autoload
(define-derived-mode heex-ts-mode html-mode "HEEx"
@ -158,10 +167,7 @@ With ARG, do it many times. Negative ARG means move backward."
;; Comments
(setq-local treesit-thing-settings
`((heex
(text ,(regexp-opt '("comment" "text"))))))
(setq-local forward-sexp-function #'heex-ts--forward-sexp)
`((heex ,@heex-ts--thing-settings)))
;; Navigation.
(setq-local treesit-defun-type-regexp

View file

@ -436,19 +436,15 @@ Return nil if there is no name or if NODE is not a defun node."
(setq-local treesit-thing-settings
`((java
(sexp ,(rx (or "annotation"
"parenthesized_expression"
"argument_list"
"identifier"
"modifiers"
"block"
"body"
"literal"
"access"
"reference"
"_type"
"true"
"false")))
(sexp (not (or (and named
,(rx bos (or "program"
"line_comment"
"block_comment")
eos))
(and anonymous
,(rx (or "{" "}" "[" "]"
"(" ")" "<" ">"
","))))))
(list ,(rx bos (or "inferred_parameters"
"parenthesized_expression"
"argument_list"

View file

@ -1455,7 +1455,14 @@ Depends on `c-ts-common-comment-setup'."
(setq-local treesit-thing-settings
`((php
(defun ,treesit-defun-type-regexp)
(sexp (not ,(rx (or "{" "}" "[" "]" "(" ")" ","))))
(sexp (not (or (and named
,(rx bos (or "program"
"comment")
eos))
(and anonymous
,(rx bos (or "{" "}" "[" "]"
"(" ")" ",")
eos)))))
(list
,(rx bos (or "namespace_use_group"
"enum_declaration_list"

View file

@ -1167,52 +1167,18 @@ leading double colon is not added."
(setq-local treesit-thing-settings
`((ruby
(sexp ,(cons (rx
bos
(or
"class"
"singleton_class"
"module"
"method"
"singleton_method"
"array"
"hash"
"parenthesized_statements"
"method_parameters"
"array_pattern"
"hash_pattern"
"if"
"else"
"then"
"unless"
"case"
"case_match"
"when"
"while"
"until"
"for"
"block"
"do_block"
"begin"
"integer"
"identifier"
"self"
"super"
"constant"
"simple_symbol"
"hash_key_symbol"
"symbol_array"
"string"
"string_array"
"heredoc_body"
"regex"
"argument_list"
"interpolation"
"instance_variable"
"global_variable"
)
eos)
#'ruby-ts--sexp-p))
(sexp (not (or (and named
,(rx bos (or "program"
"body_statement"
"comment"
"then")
eos))
(and anonymous
,(rx (or "do" "begin"
"if" "unless"
"def" "end"
"(" ")" "[" "]"
"{" "}" "|" "," ";"))))))
(list ,(cons (rx
bos
(or

View file

@ -1781,7 +1781,13 @@ rgb()/rgba()."
res)))))))
(defvar css--treesit-thing-settings
`((css (list
`((css (sexp
(not (or (and named
,(rx bos (or "stylesheet" "comment") eos))
(and anonymous
,(rx (or "{" "}" "[" "]"
"(" ")" ","))))))
(list
,(rx bos (or "keyframe_block_list"
"block"
"pseudo_class_arguments"
@ -1804,7 +1810,7 @@ rgb()/rgba()."
"declaration")
eos))
(text
,(rx bos "comment" eos))))
,(rx bos (or "comment" "string_value") eos))))
"Settings for `treesit-thing-settings'.")
(defvar css--treesit-font-lock-feature-list

View file

@ -90,15 +90,15 @@
(defvar html-ts-mode--treesit-things-settings
`((html
(sexp ,(regexp-opt '("element"
"text"
"attribute"
"value")))
(list ,(rx (or
;; Also match script_element and style_element
"element"
;; HTML comments have the element syntax
"comment")))
(sexp (not (or (and named
,(rx bos (or "document" "tag_name") eos))
(and anonymous
,(rx (or "<!" "<" ">" "</"))))))
(list ,(rx (or "doctype"
;; Also match script_element and style_element
"element"
;; HTML comments have the element syntax
"comment")))
(sentence ,(rx (and bos (or "tag_name" "attribute") eos)))
(text ,(regexp-opt '("comment" "text")))))
"Settings for `treesit-thing-settings'.")

View file

@ -3004,8 +3004,7 @@ across atoms (such as symbols or words) inside the list."
(let ((arg (or arg 1))
(pred (or treesit-sexp-type-regexp 'sexp))
(node-at-point
(when (null treesit-sexp-type-regexp)
(treesit-node-at (point) (treesit-language-at (point))))))
(treesit-node-at (point) (treesit-language-at (point)))))
(or (when (and node-at-point
;; Make sure point is strictly inside node.
(< (treesit-node-start node-at-point)
@ -3107,7 +3106,7 @@ redefined by the variable `down-list-function'.
ARG is described in the docstring of `down-list'."
(interactive "^p")
(let* ((pred 'list)
(let* ((pred (or treesit-sexp-type-regexp 'list))
(arg (or arg 1))
(cnt arg)
(inc (if (> arg 0) 1 -1)))
@ -3123,7 +3122,8 @@ ARG is described in the docstring of `down-list'."
(treesit-thing-prev (point) pred)))
(child (when sibling
(treesit-node-child sibling (if (> arg 0) 0 -1)))))
(or (when (and default-pos
(or (when (and (null treesit-sexp-type-regexp)
default-pos
(or (null child)
(if (> arg 0)
(<= default-pos (treesit-node-start child))
@ -3147,7 +3147,7 @@ redefined by the variable `up-list-function'.
ARG is described in the docstring of `up-list'."
(interactive "^p")
(let* ((pred 'list)
(let* ((pred (or treesit-sexp-type-regexp 'list))
(arg (or arg 1))
(cnt arg)
(inc (if (> arg 0) 1 -1)))
@ -3174,7 +3174,8 @@ ARG is described in the docstring of `up-list'."
(treesit-node-at (point) (car parsers)) pred)
parsers (cdr parsers)))))
(or (when (and default-pos
(or (when (and (null treesit-sexp-type-regexp)
default-pos
(or (null parent)
(if (> arg 0)
(<= default-pos (treesit-node-end parent))
@ -3187,6 +3188,37 @@ ARG is described in the docstring of `up-list'."
(user-error "At top level")))
(setq cnt (- cnt inc)))))
(defun treesit-toggle-sexp-mode ()
"Toggle the mode of navigation for sexp and list commands.
This mode toggle affects such navigation commands as
`treesit-forward-sexp', `treesit-forward-list', `treesit-down-list',
`treesit-up-list'.
The list mode uses the `list' thing defined in `treesit-thing-settings'.
In this mode commands use syntax definition to navigate symbols and
treesit definition to navigate lists.
The sexp mode uses the `sexp' thing defined in `treesit-thing-settings'.
In this mode commands use the treesit definition only
without distinction between symbols and lists."
(interactive)
(if (not (treesit-thing-defined-p 'list (treesit-language-at (point))))
(message "No `list' thing is defined in `treesit-thing-settings'")
(setq-local treesit-sexp-type-regexp
(unless treesit-sexp-type-regexp
(if (treesit-thing-defined-p
'sexp (treesit-language-at (point)))
'sexp
#'treesit-node-named))
forward-sexp-function
(if treesit-sexp-type-regexp
#'treesit-forward-sexp
#'treesit-forward-sexp-list))
(message "Toggle to mode where sexp commands navigate %s"
(or (and treesit-sexp-type-regexp
"treesit nodes")
"syntax symbols and treesit lists"))))
(defun treesit-transpose-sexps (&optional arg)
"Tree-sitter `transpose-sexps' function.
ARG is the same as in `transpose-sexps'.