From f8e219ebfaa286f4e7240640799020bb5b6e07b3 Mon Sep 17 00:00:00 2001 From: Yuan Fu Date: Sat, 24 Dec 2022 16:33:35 -0800 Subject: [PATCH] Add treesit-defun-name and friends 1. We now have treesit-defun-name, powered by treesit-defun-name-function. 2. We now have treesit-add-log-current-defun, which powers add-log-current-defun. 3. c-ts-mode updates its code to take advantage of these new features. 4. Manual updates. * doc/lispref/parsing.texi (Tree-sitter major modes): Add manual for new functions. * lisp/progmodes/c-ts-mode.el (c-ts-mode--defun-name): New function. (c-ts-mode--imenu-1): Extract out into c-ts-mode--defun-name. (c-ts-base-mode): Setup treesit-defun-name-function. * lisp/treesit.el (treesit-defun-name-function) (treesit-add-log-defun-delimiter): New variables. (treesit-defun-at-point) (treesit-defun-name): New functions. (treesit-major-mode-setup): Setup add-log-current-defun-function. --- doc/lispref/parsing.texi | 38 +++++++++++++++++++++++++++++++ lisp/progmodes/c-ts-mode.el | 37 +++++++++++++++++------------- lisp/treesit.el | 45 ++++++++++++++++++++++++++++++++++++- 3 files changed, 103 insertions(+), 17 deletions(-) diff --git a/doc/lispref/parsing.texi b/doc/lispref/parsing.texi index e213363298d..918e197676e 100644 --- a/doc/lispref/parsing.texi +++ b/doc/lispref/parsing.texi @@ -1727,6 +1727,9 @@ indentation. If @code{treesit-defun-type-regexp} is non-@code{nil}, it sets up navigation functions for @code{beginning-of-defun} and @code{end-of-defun}. +@item +If @code{treesit-defun-name-function} is non-@code{nil}, it sets up +add-log functions used by @code{add-log-current-defun}. @end itemize @end defun @@ -1737,6 +1740,41 @@ For more information of these built-in tree-sitter features, For supporting mixing of multiple languages in a major mode, @pxref{Multiple Languages}. +Besides @code{beginning-of-defun} and @code{end-of-defun}, Emacs +provides some additional functions for working with defuns: +@code{treesit-defun-at-point} returns the defun node at point, and +@code{treesit-defun-name} returns the name of a defun node. + +@defun treesit-defun-at-point +This function returns the defun node at point, or @code{nil} if none +is found. It respects @code{treesit-defun-tactic}: it returns the +top-level defun if the value is @code{top-level}, and returns the +immediate enclosing defun if the value is @code{nested}. + +This function requires @code{treesit-defun-type-regexp} to work. If +it is @code{nil}, this function simply returns @code{nil}. +@end defun + +@defun treesit-defun-name node +This function returns the defun name of @var{node}. It returns +@code{nil} if there is no defun name for @var{node}, or if @var{node} +is not a defun node, or if @var{node} is @code{nil}. + +The defun name is names like function name, class name, struct name, +etc. + +If @code{treesit-defun-name-function} is @code{nil}, this function +always returns @code{nil}. +@end defun + +@defvar treesit-defun-name-function +If non-@code{nil}, this variable should store a function that is +called with a node and returns the defun name of it. The function +should have the same semantic as @code{treesit-defun-name}: if the +node is not a defun node, or the node is a defun node but doesn't have +a name, or the node is @code{nil}, return @code{nil}. +@end defvar + @node Tree-sitter C API @section Tree-sitter C API Correspondence diff --git a/lisp/progmodes/c-ts-mode.el b/lisp/progmodes/c-ts-mode.el index d3291722331..28e99732fe2 100644 --- a/lisp/progmodes/c-ts-mode.el +++ b/lisp/progmodes/c-ts-mode.el @@ -481,6 +481,25 @@ For NODE, OVERRIDE, START, and END, see ;;; Imenu +(defun c-ts-mode--defun-name (node) + "Return the name of the defun NODE. +Return nil if NODE is not a defun node, return an empty string if +NODE doesn't have a name." + (treesit-node-text + (pcase (treesit-node-type node) + ("function_definition" + (treesit-node-child-by-field-name + (treesit-node-child-by-field-name node "declarator") + "declarator")) + ("declaration" + (let ((child (treesit-node-child node -1 t))) + (pcase (treesit-node-type child) + ("identifier" child) + (_ (treesit-node-child-by-field-name child "declarator"))))) + ("struct_specifier" + (treesit-node-child-by-field-name node "name"))) + t)) + (defun c-ts-mode--imenu-1 (node) "Helper for `c-ts-mode--imenu'. Find string representation for NODE and set marker, then recurse @@ -488,22 +507,7 @@ the subtrees." (let* ((ts-node (car node)) (subtrees (mapcan #'c-ts-mode--imenu-1 (cdr node))) (name (when ts-node - (treesit-node-text - (pcase (treesit-node-type ts-node) - ("function_definition" - (treesit-node-child-by-field-name - (treesit-node-child-by-field-name - ts-node "declarator") - "declarator")) - ("declaration" - (let ((child (treesit-node-child ts-node -1 t))) - (pcase (treesit-node-type child) - ("identifier" child) - (_ (treesit-node-child-by-field-name - child "declarator"))))) - ("struct_specifier" - (treesit-node-child-by-field-name - ts-node "name")))))) + (treesit-defun-name ts-node))) (marker (when ts-node (set-marker (make-marker) (treesit-node-start ts-node))))) @@ -682,6 +686,7 @@ ARG is passed to `fill-paragraph'." "class_specifier")) #'c-ts-mode--defun-valid-p)) (setq-local treesit-defun-skipper #'c-ts-mode--defun-skipper) + (setq-local treesit-defun-name-function #'c-ts-mode--defun-name) ;; Nodes like struct/enum/union_specifier can appear in ;; function_definitions, so we need to find the top-level node. diff --git a/lisp/treesit.el b/lisp/treesit.el index 2b30da4be7a..355c6b6b99a 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -1612,6 +1612,17 @@ newline after a defun, or the beginning of a defun. If the value is nil, no skipping is performed.") +(defvar-local treesit-defun-name-function nil + "A function called with a node and returns the name of it. +If the node is a defun node, return the defun name. E.g., the +function name of a function. If the node is not a defun node, or +the defun node doesn't have a name, or the node is nil, return +nil.") + +(defvar-local treesit-add-log-defun-delimiter "." + "The delimiter used to connect several defun names. +This is used in `treesit-add-log-current-defun'.") + (defun treesit-beginning-of-defun (&optional arg) "Move backward to the beginning of a defun. @@ -1885,6 +1896,34 @@ is `top-level', return the immediate parent defun if it is (if (eq treesit-defun-tactic 'top-level) (treesit--top-level-defun node regexp pred) node))) +(defun treesit-defun-name (node) + "Return the defun name of NODE. + +Return nil if there is no name, or if NODE is not a defun node, +or if NODE is nil. + +If `treesit-defun-name-function' is nil, always return nil." + (when treesit-defun-name-function + (funcall treesit-defun-name-function node))) + +(defun treesit-add-log-current-defun () + "Return the name of the defun at point. + +Used for `add-log-current-defun-function'. + +The delimiter between nested defun names is controlled by +`treesit-add-log-defun-delimiter'." + (let ((node (treesit-defun-at-point)) + (name nil)) + (while node + (when-let ((new-name (treesit-defun-name node))) + (if name + (setq name (concat new-name + treesit-add-log-defun-delimiter + name)) + (setq name new-name))) + (setq node (treesit-node-parent node))) + name)) ;;; Activating tree-sitter @@ -1979,7 +2018,11 @@ before calling this function." ;; the variables. In future we should update `end-of-defun' to ;; work with nested defuns. (setq-local beginning-of-defun-function #'treesit-beginning-of-defun) - (setq-local end-of-defun-function #'treesit-end-of-defun))) + (setq-local end-of-defun-function #'treesit-end-of-defun)) + ;; Defun name. + (when treesit-defun-name-function + (setq-local add-log-current-defun-function + #'treesit-add-log-current-defun))) ;;; Debugging -- 2.39.2