(Hopefully) Finished zsh-ts-mode.el

This commit is contained in:
2026-04-14 00:11:04 -07:00
parent 6d69322351
commit be733cb11a
2 changed files with 109 additions and 37 deletions

View File

@@ -8,6 +8,11 @@
(require 'treesit) (require 'treesit)
(require 'rx) (require 'rx)
(defgroup zsh-ts-mode nil
"Tree-sitter powered Zsh editing."
:group 'sh
:prefix "zsh-ts-mode-")
;;; ################# ;;; #################
;;; # Fontification # ;;; # Fontification #
;;; ################# ;;; #################
@@ -43,15 +48,26 @@ Fontify NODE, an argument to a Zsh \"let\" statement."
'font-lock-variable-name-face)))))) 'font-lock-variable-name-face))))))
(defvar zsh-ts-mode--arithmetic-variable-query (defvar zsh-ts-mode--arithmetic-variable-query
(treesit-query-compile 'zsh '((word) @name)) (treesit-query-compile
"Query used in `zsh-ts-mode--fontify-arithmetic-variables'.") 'zsh '(((word) @use)
(binary_expression left: (variable_name) @assign
operator: "=")))
"Query used in `zsh-ts-mode--arithmatic-variables'.")
(defun zsh-ts-mode--arithmatic-variables (parent also-define)
"Return a list of arithmetic variables in the arithmetic_expansion PARENT.
If ALSO-DEFINE is non-nil, also capture defined (e.g. x=2)."
(cl-loop for entry in (treesit-query-capture
parent zsh-ts-mode--arithmetic-variable-query)
when (or also-define
(eq (car entry) 'use))
collect (cdr entry)))
(defun zsh-ts-mode--fontify-arithmetic-variables (defun zsh-ts-mode--fontify-arithmetic-variables
(node _override _start _end &rest _r) (node _override _start _end &rest _r)
"Fontify all arithmetic variables below NODE. "Fontify all arithmetic variables below NODE.
NODE should be an `arithmetic_expansion' node." NODE should be an `arithmetic_expansion' node."
(dolist (child (treesit-query-capture (dolist (child (zsh-ts-mode--arithmatic-variables node nil))
node zsh-ts-mode--arithmetic-variable-query nil nil t))
(let ((start (treesit-node-start child)) (let ((start (treesit-node-start child))
(end (treesit-node-end child))) (end (treesit-node-end child)))
(font-lock-append-text-property start end 'face (font-lock-append-text-property start end 'face
@@ -104,13 +120,8 @@ command.")
:feature 'variable-reference :feature 'variable-reference
:language 'zsh :language 'zsh
:override t :override t
'((expansion (simple_variable_name) @font-lock-variable-use-face) '((simple_variable_name) @font-lock-variable-use-face
(special_variable_name) @font-lock-variable-use-face (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 ;; variables in math expansions
((arithmetic_expansion :anchor "$((") ((arithmetic_expansion :anchor "$((")
@zsh-ts-mode--fontify-arithmetic-variables)) @zsh-ts-mode--fontify-arithmetic-variables))
@@ -269,28 +280,41 @@ 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))
(expansion_default
name: ([(simple_variable_name)
(special_variable_name)]
@var))
((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'.")
(cl-defun zsh-ts-mode--extract-variable-from-face-at (&optional (pos (point)))
"Extract the variable under POS from its face."
(cl-flet ((var-face-p (pos)
(memq 'font-lock-variable-name-face
(ensure-list (get-text-property pos 'face)))))
(when (var-face-p pos)
(let ((start (if (and (not (bobp)) (var-face-p (1- pos)))
(or (previous-single-property-change pos 'face)
(point-min))
(point)))
(end (or (next-single-property-change (point) 'face)
(point-max))))
(propertize
(buffer-substring-no-properties start end) 'type 'variable)))))
(cl-defmethod xref-backend-identifier-at-point ((_backend (eql zsh-ts))) (cl-defmethod xref-backend-identifier-at-point ((_backend (eql zsh-ts)))
"`zsh-ts-mode' implementation for xref's identifier at point function." "`zsh-ts-mode' implementation for xref's identifier at point function."
(or (when (memq 'font-lock-variable-name-face (or (zsh-ts-mode--extract-variable-from-face-at)
(ensure-list (get-text-property (point) 'face)))
(let ((start (previous-single-property-change (point) 'face))
(end (next-single-property-change (point) 'face)))
(propertize
(buffer-substring-no-properties (if start (1+ start) (point-min))
(or end (point-max)))
'type 'variable)))
(when-let* ((nodes (treesit-query-capture (treesit-buffer-root-node) (when-let* ((nodes (treesit-query-capture (treesit-buffer-root-node)
zsh-ts-mode--identifier-query zsh-ts-mode--identifier-query
(point) (1+ (point))))) (point) (1+ (point)))))
(if-let* ((var (alist-get 'var nodes))) (if-let* ((func (alist-get 'func nodes)))
(propertize (treesit-node-text var t) 'type 'variable) (propertize (treesit-node-text func t) 'type 'function)
(when-let* ((func (alist-get 'func nodes))) (when-let* ((var (alist-get 'var nodes)))
(propertize (treesit-node-text func t) 'type 'function)))))) (propertize (treesit-node-text var t) 'type 'variable))))))
(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql zsh-ts))) (cl-defmethod xref-backend-identifier-completion-table ((_backend (eql zsh-ts)))
"Return nil." "Return nil."
@@ -353,6 +377,8 @@ Return non-nil if NODE is not an anonymous function."
(treesit-query-compile (treesit-query-compile
'zsh `((variable_assignment name: (variable_name) @non-let) 'zsh `((variable_assignment name: (variable_name) @non-let)
(declaration_command argument: (word) @non-let) (declaration_command argument: (word) @non-let)
(binary_expression left: (variable_name) @non-let
operator: "=")
(command name: ((command_name) @let.name (command name: ((command_name) @let.name
(:equal @let.name "let")) (:equal @let.name "let"))
argument: ((word) @let-arg argument: ((word) @let-arg
@@ -405,11 +431,24 @@ Return non-nil if NODE is not an anonymous function."
when (save-excursion when (save-excursion
(goto-char (treesit-node-start node)) (goto-char (treesit-node-start node))
(looking-at-p quote-ident)) (looking-at-p quote-ident))
collect (zsh-ts-mode--make-xref-for-node node)))) collect (zsh-ts-mode--make-xref-for-node node ))))
(defvar zsh-ts-mode--string-query
(treesit-query-compile 'zsh '((string) @s))
"Query for `zsh-ts-mode--node-not-in-string-p'.")
(defun zsh-ts-mode--node-in-string-p (node)
"Return non-nil if NODE is in a string."
(treesit-query-capture (treesit-buffer-root-node)
zsh-ts-mode--string-query
(treesit-node-start node)
(treesit-node-start node)))
(defvar zsh-ts-mode--variable-references-query (defvar zsh-ts-mode--variable-references-query
(treesit-query-compile (treesit-query-compile
'zsh '(([(simple_variable_name) (special_variable_name)] @var))) 'zsh '(([(simple_variable_name) (special_variable_name)] @var)
((arithmetic_expansion :anchor "$((") @arith)
((arithmetic_expansion :anchor "((") @arith-check)))
"Query used by `zsh-ts-mode--xref-variable-references'.") "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)
@@ -418,14 +457,21 @@ Return non-nil if NODE is not an anonymous function."
(quote-ident (regexp-quote identifier))) (quote-ident (regexp-quote identifier)))
(set-text-properties 0 (length ident-copy) () ident-copy) (set-text-properties 0 (length ident-copy) () ident-copy)
(mapcan (mapcan
#'(lambda (node) #'(lambda (ent)
(when (save-excursion (cl-destructuring-bind (tag . node) ent
(cl-case tag
(var (when (save-excursion
(goto-char (treesit-node-start node)) (goto-char (treesit-node-start node))
(looking-at-p quote-ident)) (looking-at-p quote-ident))
(list (zsh-ts-mode--make-xref-for-node node)))) (list (zsh-ts-mode--make-xref-for-node node))))
(arith (mapcar #'zsh-ts-mode--make-xref-for-node
(zsh-ts-mode--arithmatic-variables node nil)))
(arith-check
(unless (zsh-ts-mode--node-in-string-p node)
(mapcar #'zsh-ts-mode--make-xref-for-node
(zsh-ts-mode--arithmatic-variables node nil)))))))
(treesit-query-capture (treesit-buffer-root-node) (treesit-query-capture (treesit-buffer-root-node)
zsh-ts-mode--variable-references-query 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."
@@ -443,11 +489,14 @@ Return non-nil if NODE is not an anonymous function."
'zsh '(((simple_variable_name) @ident) 'zsh '(((simple_variable_name) @ident)
((special_variable_name) @ident) ((special_variable_name) @ident)
((variable_name) @ident) ((variable_name) @ident)
(command name: (command_name ( (word) @cname (:equal @cname "let"))) (command name: (command_name ((word) @cname (:equal @cname "let")))
argument: (word) @ident) argument: (word) @ident)
(declaration_command argument: (word) @ident) (declaration_command argument: (word) @ident)
(command name: (command_name (word) @ident))
((variable_name) @ident) ((variable_name) @ident)
(function_definition name: (word) @ident))) (function_definition name: (word) @ident)
((arithmetic_expansion :anchor "$((") @arith)
((arithmetic_expansion :anchor "((") @arith-check)))
"Query used by `xref-backend-apropos'.") "Query used by `xref-backend-apropos'.")
(cl-defmethod xref-backend-apropos ((_backend (eql zsh-ts)) pattern) (cl-defmethod xref-backend-apropos ((_backend (eql zsh-ts)) pattern)
@@ -456,9 +505,14 @@ Return non-nil if NODE is not an anonymous function."
(dolist (ent (treesit-query-capture (treesit-buffer-root-node) (dolist (ent (treesit-query-capture (treesit-buffer-root-node)
zsh-ts-mode--xref-apropos-query)) zsh-ts-mode--xref-apropos-query))
(cl-destructuring-bind (type . node) ent (cl-destructuring-bind (type . node) ent
(when (and (eq type 'ident) (cond
(string-match-p pattern (treesit-node-text node))) ((eq type 'ident) (when (string-match-p pattern (treesit-node-text node))
(push (zsh-ts-mode--make-xref-for-node node) out)))) (push (zsh-ts-mode--make-xref-for-node node) out)))
((or (eq type 'arith)
(not (zsh-ts-mode--node-in-string-p node)))
(dolist (var-node (zsh-ts-mode--arithmatic-variables node nil))
(when (string-match-p pattern (treesit-node-text var-node))
(push (zsh-ts-mode--make-xref-for-node var-node) out)))))))
out)) out))
;;;###autoload ;;;###autoload
@@ -488,5 +542,19 @@ not written in Zsh."
(add-hook 'xref-backend-functions #'zsh-ts-mode--xref-backend nil t) (add-hook 'xref-backend-functions #'zsh-ts-mode--xref-backend nil t)
(treesit-major-mode-setup))) (treesit-major-mode-setup)))
(derived-mode-add-parents 'zsh-ts-mode '(sh-mode))
;;;###autoload
(add-to-list 'auto-mode-alist
`(,(rx (or ".zsh"
(seq (or "/" bos)
(or ".zshrc" ".zprofile" ".zlogin"
".zshenv")))
eos)
. zsh-ts-mode))
;;;###autoload
(add-to-list 'interpreter-mode-alist '("zsh" . zsh-ts-mode))
(provide 'zsh-ts-mode) (provide 'zsh-ts-mode)
;;; zsh-ts-mode.el ends here ;;; zsh-ts-mode.el ends here

View File

@@ -2134,7 +2134,7 @@ This is :around advice, so OLDFUN is the real function
:config :config
(my/setup-c-style-newline-keys typescript-ts-mode-map)) (my/setup-c-style-newline-keys typescript-ts-mode-map))
;; shell-mode ;; sh-mode
(use-package sh-script (use-package sh-script
:ensure nil :ensure nil
:hook (sh-mode . my/-setup-sh-mode) :hook (sh-mode . my/-setup-sh-mode)
@@ -2143,6 +2143,10 @@ This is :around advice, so OLDFUN is the real function
(defun my/-setup-sh-mode () (defun my/-setup-sh-mode ()
(add-hook 'completion-at-point-functions #'cape-file nil t))) (add-hook 'completion-at-point-functions #'cape-file nil t)))
;; zsh-ts-mode
(use-package zsh-ts-mode
:ensure nil)
;; go mode ;; go mode
(use-package go-mode (use-package go-mode
:defer nil :defer nil