Finish xref backend

This commit is contained in:
2026-04-13 17:03:46 -07:00
parent e0e946d3b1
commit 6d69322351

View File

@@ -108,6 +108,7 @@ command.")
(special_variable_name) @font-lock-variable-use-face (special_variable_name) @font-lock-variable-use-face
(expansion_with_modifier (simple_variable_name) (expansion_with_modifier (simple_variable_name)
@font-lock-variable-use-face) @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) (expansion_substring (simple_variable_name) @font-lock-variable-use-face)
(variable_ref (simple_variable_name) @font-lock-variable-use-face) (variable_ref (simple_variable_name) @font-lock-variable-use-face)
;; variables in math expansions ;; variables in math expansions
@@ -261,6 +262,13 @@ Return non-nil if NODE is not an anonymous function."
name: ([(simple_variable_name) name: ([(simple_variable_name)
(special_variable_name)] (special_variable_name)]
@var)) @var))
(variable_ref ([(simple_variable_name)
(special_variable_name)]
@var))
(expansion_pattern
name: ([(simple_variable_name)
(special_variable_name)]
@var))
((word) @func ((word) @func
(:pred zsh-ts-mode--not-let-predicate @func)))) (:pred zsh-ts-mode--not-let-predicate @func))))
"Query for `zsh-ts-mode' `xref-backend-identifier-at-point'.") "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))) (when-let* ((func (alist-get 'func nodes)))
(propertize (treesit-node-text func t) 'type 'function)))))) (propertize (treesit-node-text func t) 'type 'function))))))
(defun zsh-ts-mode--treesit-node-location (node) (cl-defmethod xref-backend-identifier-completion-table ((_backend (eql zsh-ts)))
"Return an xref location pointing to NODE." "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 (save-excursion
(goto-char (treesit-node-start node)) (goto-char (treesit-node-start node))
(make-xref-file-location :file (buffer-file-name) (buffer-substring (pos-bol) (pos-eol))))
:line (line-number-at-pos)
:column (- (point) (pos-bol)))))
(defun zsh-ts-mode--make-function-definition-xref (identifier node) (cl-defstruct zsh-ts-mode--location
"Make an xref object pointing to IDENTIFIER. "Xref locaton object for tree-sitter nodes."
NODE is IDENTIFIER's tree-sitter node." node)
(xref-make (format "%s %s()"
(propertize "function" (cl-defmethod xref-location-line ((location zsh-ts-mode--location))
'face 'font-lock-keyword-face) "Return the line number of LOCATION."
(propertize identifier (line-number-at-pos (treesit-node-start
'face 'font-lock-function-name-face)) (zsh-ts-mode--location-node location))))
(zsh-ts-mode--treesit-node-location node)))
(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 (defvar zsh-ts-mode--xref-function-definitions-query
(treesit-query-compile 'zsh '((function_definition (treesit-query-compile 'zsh '((function_definition
@@ -314,11 +347,7 @@ NODE is IDENTIFIER's tree-sitter node."
zsh-ts-mode--xref-function-definitions-query zsh-ts-mode--xref-function-definitions-query
nil nil t) nil nil t)
when (equal identifier (treesit-node-text node)) when (equal identifier (treesit-node-text node))
collect (zsh-ts-mode--make-function-definition-xref identifier node) collect (zsh-ts-mode--make-xref-for-node node)))
into out
finally return (if (length= out 1)
(car out)
out)))
(defvar zsh-ts-mode--xref-variable-definitions-query (defvar zsh-ts-mode--xref-variable-definitions-query
(treesit-query-compile (treesit-query-compile
@@ -331,58 +360,6 @@ NODE is IDENTIFIER's tree-sitter node."
@let-arg))))) @let-arg)))))
"Variable query for `zsh-ts-mode--xref-variable-definitions'.") "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) (defun zsh-ts-mode--xref-variable-definitions (identifier)
"Find all definitions of variable IDENTIFIER in the current buffer." "Find all definitions of variable IDENTIFIER in the current buffer."
(let (out) (let (out)
@@ -393,45 +370,96 @@ NODE is the argument to let (possibly with a = in it)."
(cl-case tag (cl-case tag
(non-let (non-let
(when (equal (treesit-node-text node) identifier) (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 (let-arg
(when (string-match-p (rx bos (literal identifier) (or eos "=")) (when (string-match-p (rx bos (literal identifier) (or eos "="))
(treesit-node-text node)) (treesit-node-text node))
(push (zsh-ts-mode--make-xref-for-let-assignment node) out)))))) (push (zsh-ts-mode--make-xref-for-node node) out))))))
(if (length= out 1) (car out) out))) out))
(cl-defmethod xref-backend-definitions ((_backend (eql zsh-ts)) identifier) (cl-defmethod xref-backend-definitions ((_backend (eql zsh-ts)) identifier)
"`zsh-ts-mode' implementation for finding xref definitions for IDENTIFIER." "`zsh-ts-mode' implementation for finding xref definitions for IDENTIFIER."
(cl-case (get-text-property 0 'type identifier) (when identifier
(function (zsh-ts-mode--xref-function-definitions identifier)) (let ((type (get-text-property 0 'type identifier)))
(variable (zsh-ts-mode--xref-variable-definitions 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) (defun zsh-ts-mode--xref-function-references (identifier)
"Find all references for the function IDENTIFIER in the current buffer." "Find all references for the function IDENTIFIER in the current buffer."
(let ((nodes (treesit-query-capture (let ((nodes (treesit-query-capture
(treesit-buffer-root-node) (treesit-buffer-root-node)
`((command name: (command_name (word) @name))) zsh-ts-mode--function-references-query
nil nil t)) nil nil t))
(ident-copy (copy-sequence identifier))) (ident-copy (copy-sequence identifier))
(set-text-properties 0 (length ident-copy) nil ident-copy) (quote-ident (regexp-quote identifier)))
(set-text-properties 0 (length ident-copy) () ident-copy)
(cl-loop for node in nodes (cl-loop for node in nodes
collect (xref-make when (save-excursion
(propertize ident-copy 'face (goto-char (treesit-node-start node))
'font-lock-function-name-face) (looking-at-p quote-ident))
(zsh-ts-mode--treesit-node-location node))))) 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) (defun zsh-ts-mode--xref-variable-references (identifier)
"Find all references for the variable IDENTIFIER in the current buffer." "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) (cl-defmethod xref-backend-references ((_backend (eql zsh-ts)) identifier)
"`zsh-ts-mode' implementation for finding xref references for IDENTIFIER." "`zsh-ts-mode' implementation for finding xref references for IDENTIFIER."
(append (let ((type (get-text-property 0 'type identifier)))
;; definitions are references (append
(xref-backend-definitions 'zsh-ts identifier) ;; definitions are references
(cl-case (get-text-property 0 'type identifier) (ensure-list (xref-backend-definitions 'zsh-ts identifier))
(function (zsh-ts-mode--xref-function-references identifier)) (when (memq type '(nil function))
(variable (zsh-ts-mode--xref-variable-references identifier))))) (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 ;;;###autoload
(define-derived-mode zsh-ts-mode sh-base-mode "Zsh" (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 (setq-local treesit-simple-imenu-settings zsh-ts-mode--simple-imenu-settings
treesit-defun-name-function #'zsh-ts-mode--defun-name treesit-defun-name-function #'zsh-ts-mode--defun-name
imenu-create-index-function #'treesit-simple-imenu) imenu-create-index-function #'treesit-simple-imenu)
(add-hook 'xref-backend-functions #'zsh-ts-mode--xref-backend nil t)
(treesit-major-mode-setup))) (treesit-major-mode-setup)))
(provide 'zsh-ts-mode) (provide 'zsh-ts-mode)