Start work on zsh-ts-mode
This commit is contained in:
214
elisp/zsh-ts-mode.el
Normal file
214
elisp/zsh-ts-mode.el
Normal file
@@ -0,0 +1,214 @@
|
||||
;;; zsh-ts-mode.el --- TreeSitter based mode for editing Zsh sripts -*- lexical-binding: t; -*-
|
||||
;;; Commentary:
|
||||
;;; Code:
|
||||
|
||||
(require 'sh-script)
|
||||
(require 'treesit)
|
||||
(require 'rx)
|
||||
|
||||
(defun zsh-ts-mode--fontify-let-arg (node _override _start _end &rest _r)
|
||||
"Internal function used from `zsh-ts-mode--treesit-settings'.
|
||||
Fontify NODE, an argument to a Zsh \"let\" statement."
|
||||
(let ((ns (treesit-node-start node))
|
||||
(ne (treesit-node-end node)))
|
||||
(save-excursion
|
||||
(goto-char ns)
|
||||
(if (re-search-forward (rx (or "=" "[")) ne t)
|
||||
(put-text-property ns (1- (point)) 'face
|
||||
'font-lock-variable-name-face)
|
||||
(put-text-property ns ne 'face 'font-lock-variable-name-face)))))
|
||||
|
||||
(defvar zsh-ts-mode--arithmetic-variable-query
|
||||
(treesit-query-compile 'zsh '((word) @name))
|
||||
"Query used in `zsh-ts-mode--fontify-arithmetic-variables'.")
|
||||
|
||||
(defun zsh-ts-mode--fontify-arithmetic-variables
|
||||
(node _override _start _end &rest _r)
|
||||
"Fontify all arithmetic variables below NODE.
|
||||
NODE should be an `arithmetic_expansion' node."
|
||||
(dolist (child (treesit-query-capture
|
||||
node zsh-ts-mode--arithmetic-variable-query))
|
||||
(let ((start (treesit-node-start (cdr child)))
|
||||
(end (treesit-node-end (cdr child))))
|
||||
(font-lock-append-text-property start end 'face
|
||||
'font-lock-variable-use-face))))
|
||||
|
||||
(defun zsh-ts-mode--other-keywords ()
|
||||
"Return a list of `other' keywords for Zsh.
|
||||
This just appends some more stuff to `sh-mode--treesit-other-keywords'."
|
||||
(append (sh-mode--treesit-other-keywords) '("let")))
|
||||
|
||||
(defvar zsh-ts-mode--declaration-commands
|
||||
'("typeset" "local" "integer" "float" "readonly" "export" "declare")
|
||||
"Return a list of keywords that declare a parameter in Zsh.
|
||||
Note that \"let\" isn't included here as the parser treats it as a normal
|
||||
command.")
|
||||
|
||||
;; from `bash-ts-mode'
|
||||
(defvar zsh-ts-mode--treesit-settings
|
||||
(treesit-font-lock-rules
|
||||
:feature 'comment
|
||||
:language 'zsh
|
||||
'((comment) @font-lock-comment-face)
|
||||
|
||||
:feature 'function
|
||||
:language 'zsh
|
||||
'((function_definition name: (word) @font-lock-function-name-face))
|
||||
|
||||
:feature 'string
|
||||
:language 'zsh
|
||||
'(((raw_string) @font-lock-string-face)
|
||||
((string :anchor ("\"" @font-lock-string-face)
|
||||
[(string_content)
|
||||
(arithmetic_expansion :anchor "((")]
|
||||
:* @font-lock-string-face
|
||||
("\"" @font-lock-string-face) :anchor)))
|
||||
|
||||
:feature 'string-interpolation
|
||||
:language 'zsh
|
||||
:override t
|
||||
'((command_substitution :anchor ("$(" @sh-quoted-exec)
|
||||
(")" @sh-quoted-exec) :anchor)
|
||||
;; work around parser error of "(( ))" (no $) being an arithmetic expansion
|
||||
(string (arithmetic_expansion :anchor ("$((" @sh-quoted-exec)
|
||||
("))" @sh-quoted-exec) :anchor)))
|
||||
|
||||
:feature 'variable-reference
|
||||
:language 'zsh
|
||||
:override t
|
||||
'((expansion (simple_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_substring (simple_variable_name) @font-lock-variable-use-face)
|
||||
(variable_ref (simple_variable_name) @font-lock-variable-use-face)
|
||||
;; variables in math expansions
|
||||
((arithmetic_expansion :anchor "$((")
|
||||
@zsh-ts-mode--fontify-arithmetic-variables))
|
||||
|
||||
:feature 'math-function-call
|
||||
:language 'zsh
|
||||
:override t
|
||||
`((arithmetic_call name: (word) @font-lock-function-name-face))
|
||||
|
||||
:feature 'heredoc
|
||||
:language 'zsh
|
||||
'([(heredoc_start) (heredoc_body)] @sh-heredoc)
|
||||
|
||||
:feature 'keyword
|
||||
:language 'zsh
|
||||
`(;; keywords
|
||||
[ ,@sh-mode--treesit-keywords ] @font-lock-keyword-face
|
||||
;; reserved words
|
||||
(command_name
|
||||
((word) @font-lock-keyword-face
|
||||
(:match ,(rx-to-string
|
||||
`(seq bos
|
||||
(or ,@(zsh-ts-mode--other-keywords))
|
||||
eos))
|
||||
@font-lock-keyword-face))))
|
||||
|
||||
:feature 'command
|
||||
:language 'zsh
|
||||
`(;; builtin commands
|
||||
(command_name
|
||||
((word) @font-lock-builtin-face
|
||||
(:match ,(let ((builtins
|
||||
(sh-feature sh-builtins)))
|
||||
(rx-to-string
|
||||
`(seq bos
|
||||
(or ,@builtins)
|
||||
eos)))
|
||||
@font-lock-builtin-face)))
|
||||
;; function/non-builtin command calls
|
||||
(command_name (word) @font-lock-function-name-face))
|
||||
|
||||
:feature 'declaration-command
|
||||
:language 'zsh
|
||||
;; the parser doesn't treat "let" as a keyword
|
||||
`([,@zsh-ts-mode--declaration-commands] @font-lock-keyword-face)
|
||||
|
||||
;; variable declarations in the form of a=b
|
||||
;; e.g. local x=2
|
||||
:feature 'decl-equal-variable
|
||||
:language 'zsh
|
||||
'((variable_name) @font-lock-variable-name-face)
|
||||
|
||||
;; variable declarations in the form of a
|
||||
;; e.g. local y z
|
||||
:feature 'decl-solo-variable
|
||||
:language 'zsh
|
||||
:override 'prepend
|
||||
`((declaration_command
|
||||
argument: ((_) @font-lock-variable-name-face
|
||||
(:match ,(rx-to-string '(seq bos (not "-")))
|
||||
@font-lock-variable-name-face))))
|
||||
|
||||
|
||||
;; let variable declarations (handled differently by the parser)
|
||||
:feature 'decl-let-variable
|
||||
:language 'zsh
|
||||
:override 'prepend
|
||||
`((command
|
||||
(command_name ((word) @ignore
|
||||
(:equal "let" @ignore)))
|
||||
argument: ((_) @zsh-ts-mode--fontify-let-arg
|
||||
(:match ,(rx-to-string '(seq bos (not "-")))
|
||||
@zsh-ts-mode--fontify-let-arg))))
|
||||
|
||||
:feature 'for-variable
|
||||
:language 'zsh
|
||||
`("for" variable: ((simple_variable_name) @font-lock-variable-name-face))
|
||||
|
||||
:feature 'constant
|
||||
:language 'zsh
|
||||
'((case_item value: (word) @font-lock-constant-face)
|
||||
(file_descriptor) @font-lock-constant-face)
|
||||
|
||||
:feature 'operator
|
||||
:language 'zsh
|
||||
`([,@sh-mode--treesit-operators] @font-lock-operator-face)
|
||||
|
||||
:feature 'number
|
||||
:language 'zsh
|
||||
`(((word) @font-lock-number-face
|
||||
(:match "\\`[0-9]+\\'" @font-lock-number-face))
|
||||
((number) @font-lock-number-face))
|
||||
|
||||
:feature 'bracket
|
||||
:language 'zsh
|
||||
'((["(" ")" "((" "))" "[" "]" "[[" "]]" "{" "}"]) @font-lock-bracket-face)
|
||||
|
||||
:feature 'delimiter
|
||||
:language 'zsh
|
||||
'(([";" ";;"]) @font-lock-delimiter-face)
|
||||
|
||||
:feature 'misc-punctuation
|
||||
:language 'zsh
|
||||
'((["$"]) @font-lock-misc-punctuation-face)))
|
||||
|
||||
;;;###autoload
|
||||
(define-derived-mode zsh-ts-mode sh-base-mode "Zsh"
|
||||
;; This function is based mostly on `bash-ts-mode'.
|
||||
"Major mode for editing Zsh shell scripts.
|
||||
This mode automatically falls back to `sh-mode' if the buffer is
|
||||
not written in Zsh."
|
||||
:syntax-table sh-mode-syntax-table
|
||||
(when (treesit-ready-p 'zsh)
|
||||
(sh-set-shell "zsh")
|
||||
(add-hook 'hack-local-variables-hook
|
||||
#'sh-after-hack-local-variables nil t)
|
||||
(treesit-parser-create 'zsh)
|
||||
(setq-local treesit-font-lock-feature-list
|
||||
'((comment function)
|
||||
(command declaration-command keyword string math-function-call)
|
||||
(constant heredoc number string-interpolation
|
||||
decl-equal-variable decl-solo-variable decl-let-variable
|
||||
for-variable variable-reference)
|
||||
(bracket delimiter misc-punctuation operator)))
|
||||
(setq-local treesit-font-lock-settings
|
||||
zsh-ts-mode--treesit-settings)
|
||||
(treesit-major-mode-setup)))
|
||||
|
||||
(provide 'zsh-ts-mode)
|
||||
;;; zsh-ts-mode.el ends here
|
||||
Reference in New Issue
Block a user