diff --git a/lisp/progmodes/sh-script.el b/lisp/progmodes/sh-script.el index 558b62b20a..c7cc676843 100644 --- a/lisp/progmodes/sh-script.el +++ b/lisp/progmodes/sh-script.el @@ -148,6 +148,7 @@ (require 'let-alist) (require 'subr-x)) (require 'executable) +(require 'treesit)  (autoload 'comint-completion-at-point "comint") (autoload 'comint-filename-completion "comint") @@ -170,6 +171,12 @@ sh-script :group 'sh :prefix "sh-")  +(defcustom sh-script-use-tree-sitter nil + "If non-nil, `sh-script-mode' tries to use tree-sitter. +Currently `sh-script-mode' uses tree-sitter for font-locking, imenu, +and movement functions." + :type 'boolean + :version "29.1")  (defcustom sh-ancestor-alist '((ash . sh) @@ -1534,13 +1541,24 @@ sh-mode ;; we can't look if previous line ended with `\' (setq-local comint-prompt-regexp "^[ \t]*") (setq-local imenu-case-fold-search nil) - (setq font-lock-defaults - `((sh-font-lock-keywords - sh-font-lock-keywords-1 sh-font-lock-keywords-2) - nil nil - ((?/ . "w") (?~ . "w") (?. . "w") (?- . "w") (?_ . "w")) nil - (font-lock-syntactic-face-function - . ,#'sh-font-lock-syntactic-face-function))) + + (if (and sh-script-use-tree-sitter + (treesit-can-enable-p)) + (progn + (setq-local font-lock-keywords-only t) + (setq-local treesit-font-lock-feature-list + '((basic) (moderate) (elaborate))) + (setq-local treesit-font-lock-settings + sh-script--treesit-settings) + (treesit-font-lock-enable)) + (setq font-lock-defaults + `((sh-font-lock-keywords + sh-font-lock-keywords-1 sh-font-lock-keywords-2) + nil nil + ((?/ . "w") (?~ . "w") (?. . "w") (?- . "w") (?_ . "w")) nil + (font-lock-syntactic-face-function + . ,#'sh-font-lock-syntactic-face-function)))) + (setq-local syntax-propertize-function #'sh-syntax-propertize-function) (add-hook 'syntax-propertize-extend-region-functions #'syntax-propertize-multiline 'append 'local) @@ -3191,6 +3209,51 @@ sh-shellcheck-flymake (process-send-region sh--shellcheck-process (point-min) (point-max)) (process-send-eof sh--shellcheck-process))))  -(provide 'sh-script) +;;; Tree-sitter font-lock + +(defvar sh-script--treesit-bash-keywords + '("case" "do" "done" "elif" "else" "esac" "export" "fi" "for" + "function" "if" "in" "unset" "while" "then")) + +(defun sh-script--treesit-filtered-keywords (blacklist) + "Docstring goes here" + (let ((keywords (append (sh-feature sh-leading-keywords) + (sh-feature sh-other-keywords))) + (filtered-list)) + (dolist (item keywords filtered-list) + (if (not (member item blacklist)) + (setq filtered-list (cons item filtered-list)) + nil)))) + +(defvar sh-script--treesit-blacklisted-keywords + "Docstring goes here" + '("time" "coproc" "type" "trap" "exit" "exec" "continue" "break" + "return" "logout" "bye")) + +(defvar sh-script--treesit-settings + (treesit-font-lock-rules + :language 'bash + :feature 'basic + '(;; Queries for function, strings, comments, and heredocs + (function_definition name: (word) @font-lock-function-name-face) + (comment) @font-lock-comment-face + [ (string) (raw_string)(heredoc_body) (heredoc_start) ] @font-lock-string-face) + :language 'bash + :feature 'moderate + :override t + `(;; Queries for keywords and builtin commands + [ ,@(sh-script--treesit-filtered-keywords sh-script--blacklisted-keywords) ] @font-lock-keyword-face + (command name: (command_name + ((word) @font-lock-builtin-face + (:match ,(let ((builtins (sh-feature sh-builtins))) + (rx-to-string + `(seq bol + (or ,@builtins) + eol))) + @font-lock-builtin-face)))) + ) + ) + "Tree-sitter font-lock settings.")  +(provide 'sh-script) ;;; sh-script.el ends here