From ac5b0761489d9a6e42faa2573fa5b1d94cdd2a93 Mon Sep 17 00:00:00 2001 From: Juri Linkov Date: Wed, 9 Apr 2025 21:21:53 +0300 Subject: [PATCH] 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. (cherry picked from commit 99a2cb05a41c66565d417e11e60f4324aaa5c5b5) --- lisp/progmodes/c-ts-mode.el | 6 +++- lisp/progmodes/elixir-ts-mode.el | 57 ++++++++++++++++++++----------- lisp/progmodes/heex-ts-mode.el | 42 +++++++++++++---------- lisp/progmodes/java-ts-mode.el | 22 +++++------- lisp/progmodes/php-ts-mode.el | 9 ++++- lisp/progmodes/ruby-ts-mode.el | 58 +++++++------------------------- lisp/textmodes/css-mode.el | 10 ++++-- lisp/textmodes/html-ts-mode.el | 18 +++++----- lisp/treesit.el | 44 ++++++++++++++++++++---- 9 files changed, 150 insertions(+), 116 deletions(-) diff --git a/lisp/progmodes/c-ts-mode.el b/lisp/progmodes/c-ts-mode.el index c49e928884a..a9af2eb679a 100644 --- a/lisp/progmodes/c-ts-mode.el +++ b/lisp/progmodes/c-ts-mode.el @@ -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" diff --git a/lisp/progmodes/elixir-ts-mode.el b/lisp/progmodes/elixir-ts-mode.el index 9a0418eaf29..9d938e10fde 100644 --- a/lisp/progmodes/elixir-ts-mode.el +++ b/lisp/progmodes/elixir-ts-mode.el @@ -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)) diff --git a/lisp/progmodes/heex-ts-mode.el b/lisp/progmodes/heex-ts-mode.el index 65aaa0d488d..93b87d3a29d 100644 --- a/lisp/progmodes/heex-ts-mode.el +++ b/lisp/progmodes/heex-ts-mode.el @@ -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 diff --git a/lisp/progmodes/java-ts-mode.el b/lisp/progmodes/java-ts-mode.el index a36ea4f62ab..e1de96febd0 100644 --- a/lisp/progmodes/java-ts-mode.el +++ b/lisp/progmodes/java-ts-mode.el @@ -444,19 +444,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" diff --git a/lisp/progmodes/php-ts-mode.el b/lisp/progmodes/php-ts-mode.el index cc5f02a74c2..1a0b7707034 100644 --- a/lisp/progmodes/php-ts-mode.el +++ b/lisp/progmodes/php-ts-mode.el @@ -1454,7 +1454,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" diff --git a/lisp/progmodes/ruby-ts-mode.el b/lisp/progmodes/ruby-ts-mode.el index 6fb71a75a58..f646e5e9385 100644 --- a/lisp/progmodes/ruby-ts-mode.el +++ b/lisp/progmodes/ruby-ts-mode.el @@ -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 diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el index 17da056f41e..e715a11a03b 100644 --- a/lisp/textmodes/css-mode.el +++ b/lisp/textmodes/css-mode.el @@ -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 diff --git a/lisp/textmodes/html-ts-mode.el b/lisp/textmodes/html-ts-mode.el index 30d1f080b96..ff79945b291 100644 --- a/lisp/textmodes/html-ts-mode.el +++ b/lisp/textmodes/html-ts-mode.el @@ -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 "" " arg 0) 1 -1))) @@ -3115,7 +3114,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)) @@ -3139,7 +3139,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))) @@ -3166,7 +3166,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)) @@ -3179,6 +3180,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'. -- 2.39.5