From 6d693223515b436fbc1e92191ae83ea929a163dc Mon Sep 17 00:00:00 2001 From: Alexander Rosenberg Date: Mon, 13 Apr 2026 17:03:46 -0700 Subject: [PATCH] Finish xref backend --- elisp/zsh-ts-mode.el | 211 ++++++++++++++++++++++++------------------- 1 file changed, 120 insertions(+), 91 deletions(-) diff --git a/elisp/zsh-ts-mode.el b/elisp/zsh-ts-mode.el index 2396e68..4c31337 100644 --- a/elisp/zsh-ts-mode.el +++ b/elisp/zsh-ts-mode.el @@ -108,6 +108,7 @@ command.") (special_variable_name) @font-lock-variable-use-face (expansion_with_modifier (simple_variable_name) @font-lock-variable-use-face) + (expansion_pattern (simple_variable_name) @font-lock-variable-use-face) (expansion_substring (simple_variable_name) @font-lock-variable-use-face) (variable_ref (simple_variable_name) @font-lock-variable-use-face) ;; variables in math expansions @@ -261,6 +262,13 @@ Return non-nil if NODE is not an anonymous function." name: ([(simple_variable_name) (special_variable_name)] @var)) + (variable_ref ([(simple_variable_name) + (special_variable_name)] + @var)) + (expansion_pattern + name: ([(simple_variable_name) + (special_variable_name)] + @var)) ((word) @func (:pred zsh-ts-mode--not-let-predicate @func)))) "Query for `zsh-ts-mode' `xref-backend-identifier-at-point'.") @@ -284,23 +292,48 @@ Return non-nil if NODE is not an anonymous function." (when-let* ((func (alist-get 'func nodes))) (propertize (treesit-node-text func t) 'type 'function)))))) -(defun zsh-ts-mode--treesit-node-location (node) - "Return an xref location pointing to NODE." +(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql zsh-ts))) + "Return nil." + nil) + +(cl-defmethod xref-backend-identifier-completion-ignore-case + ((_backend (eql zsh-ts))) + "Return `completion-ignore-case'." + completion-ignore-case) + +(defun zsh-ts-mode--treesit-node-summary (node) + "Return the contents of the line of NODE." (save-excursion (goto-char (treesit-node-start node)) - (make-xref-file-location :file (buffer-file-name) - :line (line-number-at-pos) - :column (- (point) (pos-bol))))) + (buffer-substring (pos-bol) (pos-eol)))) -(defun zsh-ts-mode--make-function-definition-xref (identifier node) - "Make an xref object pointing to IDENTIFIER. -NODE is IDENTIFIER's tree-sitter node." - (xref-make (format "%s %s()" - (propertize "function" - 'face 'font-lock-keyword-face) - (propertize identifier - 'face 'font-lock-function-name-face)) - (zsh-ts-mode--treesit-node-location node))) +(cl-defstruct zsh-ts-mode--location + "Xref locaton object for tree-sitter nodes." + node) + +(cl-defmethod xref-location-line ((location zsh-ts-mode--location)) + "Return the line number of LOCATION." + (line-number-at-pos (treesit-node-start + (zsh-ts-mode--location-node location)))) + +(cl-defmethod xref-location-group ((location zsh-ts-mode--location)) + "Return the file name of the buffer of LOCATION." + (with-slots (node) location + (or (buffer-file-name (treesit-node-buffer node)) + ""))) + +(cl-defmethod xref-location-marker ((location zsh-ts-mode--location)) + "Return a marker for LOCATION." + (let ((node (zsh-ts-mode--location-node location)) + (marker (make-marker))) + (set-marker marker (treesit-node-start node) + (treesit-node-buffer node)) + marker)) + +(defun zsh-ts-mode--make-xref-for-node (node) + "Create an xref for a tree-sitter node NODE." + (xref-make (zsh-ts-mode--treesit-node-summary node) + (make-zsh-ts-mode--location :node node))) (defvar zsh-ts-mode--xref-function-definitions-query (treesit-query-compile 'zsh '((function_definition @@ -314,11 +347,7 @@ NODE is IDENTIFIER's tree-sitter node." zsh-ts-mode--xref-function-definitions-query nil nil t) when (equal identifier (treesit-node-text node)) - collect (zsh-ts-mode--make-function-definition-xref identifier node) - into out - finally return (if (length= out 1) - (car out) - out))) + collect (zsh-ts-mode--make-xref-for-node node))) (defvar zsh-ts-mode--xref-variable-definitions-query (treesit-query-compile @@ -331,58 +360,6 @@ NODE is IDENTIFIER's tree-sitter node." @let-arg))))) "Variable query for `zsh-ts-mode--xref-variable-definitions'.") -(defvar zsh-ts-mode--non-let-declaration-type-query - (treesit-query-compile 'zsh '((declaration_command :anchor _ @name))) - "Helper query for `zsh-ts-mode--get-non-let-assignment-type'.") - -(defun zsh-ts-mode--get-assignment-parent (node) - "Get the node that is a parent of NODE that is a variable assignment. -This only looks up two levels." - (cond ((equal (treesit-node-type - (treesit-node-parent node)) - "declaration_command") - (treesit-node-parent node)) - ((equal (treesit-node-type - (treesit-node-parent (treesit-node-parent node))) - "declaration_command") - (treesit-node-parent (treesit-node-parent node))))) - -(defun zsh-ts-mode--get-non-let-assignment-type (node) - "Return the assignment command for NODE." - (when-let* ((type-node (zsh-ts-mode--get-assignment-parent node))) - (propertize (treesit-node-text - (car (treesit-query-capture - type-node - zsh-ts-mode--non-let-declaration-type-query - nil nil t)) - t) - 'face 'font-lock-keyword-face))) - -(defun zsh-ts-mode--make-xref-for-non-let-assignment (node) - "Create an xref for an \"other\" assignment for NODE. -An \"other\" assignment is an assignment using local, typeset, etc." - (let ((type (zsh-ts-mode--get-non-let-assignment-type node))) - (xref-make (format "%s%s" (if type (concat type " ") "") - (propertize (treesit-node-text node t) - 'face 'font-lock-variable-name-face)) - (zsh-ts-mode--treesit-node-location node)))) - -(defun zsh-ts-mode--make-xref-for-let-assignment (node) - "Create an xref node for a list assingment. -NODE is the argument to let (possibly with a = in it)." - (save-excursion - (goto-char (treesit-node-start node)) - (xref-make - (format "%s %s" (propertize "let" 'face 'font-lock-keyword-face) - (propertize (buffer-substring-no-properties - (treesit-node-start node) - (if (re-search-forward (rx (or "=" "[")) - (treesit-node-end node) - t) - (1- (point)) - (treesit-node-end node))))) - (zsh-ts-mode--treesit-node-location node)))) - (defun zsh-ts-mode--xref-variable-definitions (identifier) "Find all definitions of variable IDENTIFIER in the current buffer." (let (out) @@ -393,45 +370,96 @@ NODE is the argument to let (possibly with a = in it)." (cl-case tag (non-let (when (equal (treesit-node-text node) identifier) - (push (zsh-ts-mode--make-xref-for-non-let-assignment node) out))) + (push (zsh-ts-mode--make-xref-for-node node) out))) (let-arg (when (string-match-p (rx bos (literal identifier) (or eos "=")) (treesit-node-text node)) - (push (zsh-ts-mode--make-xref-for-let-assignment node) out)))))) - (if (length= out 1) (car out) out))) + (push (zsh-ts-mode--make-xref-for-node node) out)))))) + out)) (cl-defmethod xref-backend-definitions ((_backend (eql zsh-ts)) identifier) "`zsh-ts-mode' implementation for finding xref definitions for IDENTIFIER." - (cl-case (get-text-property 0 'type identifier) - (function (zsh-ts-mode--xref-function-definitions identifier)) - (variable (zsh-ts-mode--xref-variable-definitions identifier)))) + (when identifier + (let ((type (get-text-property 0 'type identifier))) + (append + (when (memq type '(nil function)) + (zsh-ts-mode--xref-function-definitions identifier)) + (when (memq type '(nil variable)) + (zsh-ts-mode--xref-variable-definitions identifier)))))) + +(defvar zsh-ts-mode--function-references-query + (treesit-query-compile + 'zsh '((command name: (command_name (word) @name)))) + "Query used by `zsh-ts-mode--xref-function-references'.") (defun zsh-ts-mode--xref-function-references (identifier) "Find all references for the function IDENTIFIER in the current buffer." (let ((nodes (treesit-query-capture (treesit-buffer-root-node) - `((command name: (command_name (word) @name))) + zsh-ts-mode--function-references-query nil nil t)) - (ident-copy (copy-sequence identifier))) - (set-text-properties 0 (length ident-copy) nil ident-copy) + (ident-copy (copy-sequence identifier)) + (quote-ident (regexp-quote identifier))) + (set-text-properties 0 (length ident-copy) () ident-copy) (cl-loop for node in nodes - collect (xref-make - (propertize ident-copy 'face - 'font-lock-function-name-face) - (zsh-ts-mode--treesit-node-location node))))) + when (save-excursion + (goto-char (treesit-node-start node)) + (looking-at-p quote-ident)) + collect (zsh-ts-mode--make-xref-for-node node)))) + +(defvar zsh-ts-mode--variable-references-query + (treesit-query-compile + 'zsh '(([(simple_variable_name) (special_variable_name)] @var))) + "Query used by `zsh-ts-mode--xref-variable-references'.") (defun zsh-ts-mode--xref-variable-references (identifier) "Find all references for the variable IDENTIFIER in the current buffer." - ()) + (let ((ident-copy (copy-sequence identifier)) + (quote-ident (regexp-quote identifier))) + (set-text-properties 0 (length ident-copy) () ident-copy) + (mapcan + #'(lambda (node) + (when (save-excursion + (goto-char (treesit-node-start node)) + (looking-at-p quote-ident)) + (list (zsh-ts-mode--make-xref-for-node node)))) + (treesit-query-capture (treesit-buffer-root-node) + zsh-ts-mode--variable-references-query + nil nil t)))) (cl-defmethod xref-backend-references ((_backend (eql zsh-ts)) identifier) "`zsh-ts-mode' implementation for finding xref references for IDENTIFIER." - (append - ;; definitions are references - (xref-backend-definitions 'zsh-ts identifier) - (cl-case (get-text-property 0 'type identifier) - (function (zsh-ts-mode--xref-function-references identifier)) - (variable (zsh-ts-mode--xref-variable-references identifier))))) + (let ((type (get-text-property 0 'type identifier))) + (append + ;; definitions are references + (ensure-list (xref-backend-definitions 'zsh-ts identifier)) + (when (memq type '(nil function)) + (zsh-ts-mode--xref-function-references identifier)) + (when (memq type '(nil variable)) + (zsh-ts-mode--xref-variable-references identifier))))) + +(defvar zsh-ts-mode--xref-apropos-query + (treesit-query-compile + 'zsh '(((simple_variable_name) @ident) + ((special_variable_name) @ident) + ((variable_name) @ident) + (command name: (command_name ( (word) @cname (:equal @cname "let"))) + argument: (word) @ident) + (declaration_command argument: (word) @ident) + ((variable_name) @ident) + (function_definition name: (word) @ident))) + "Query used by `xref-backend-apropos'.") + +(cl-defmethod xref-backend-apropos ((_backend (eql zsh-ts)) pattern) + "Search though the buffer for PATTERN." + (let (out) + (dolist (ent (treesit-query-capture (treesit-buffer-root-node) + zsh-ts-mode--xref-apropos-query)) + (cl-destructuring-bind (type . node) ent + (when (and (eq type 'ident) + (string-match-p pattern (treesit-node-text node))) + (push (zsh-ts-mode--make-xref-for-node node) out)))) + out)) ;;;###autoload (define-derived-mode zsh-ts-mode sh-base-mode "Zsh" @@ -457,6 +485,7 @@ not written in Zsh." (setq-local treesit-simple-imenu-settings zsh-ts-mode--simple-imenu-settings treesit-defun-name-function #'zsh-ts-mode--defun-name imenu-create-index-function #'treesit-simple-imenu) + (add-hook 'xref-backend-functions #'zsh-ts-mode--xref-backend nil t) (treesit-major-mode-setup))) (provide 'zsh-ts-mode)