From: Yuan Fu Date: Wed, 19 Oct 2022 23:44:04 +0000 (-0700) Subject: Revise the toggle scheme of tree-sitter (again) X-Git-Tag: emacs-29.0.90~1807 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=5159789e55d64c7482dff3dc1a621d01f530f83c;p=emacs.git Revise the toggle scheme of tree-sitter (again) Now instead of a toggle function (major-mode-backend-function), we let major mode set local variables like treesit-font-lock-settings, treesit-imenu-function, then treesit-mode takes care of activating those things (clearing font-lock-keywords, setting imenu-create-index-function to treesit-imenu-function, etc). js.el and python.el: I've returned js-mode and python-mode to exactly what they were before tree-sitter change, plus lines at the end setting up tree-sitter variables. Sorry about all these fuss :-D * lisp/treesit.el (treesit-mode-inhibit-message): Remove option. (major-mode-backend-function) (treesit-remapped-major-mode-alist): Remove variables. (treesit-mode): Move down to the end of buffer. Do more things. (global-treesit-mode): Move down to the end of buffer. Don't handle major-mode-remap-alist anymore. (global-treesit-mode--turn-on): Move down to the end of buffer. (treesit-ready-p): Move down to the end of buffer. Changed signature. (treesit-font-lock-enable): Remove function. (treesit-defun-type-regexp): New variable. (treesit-beginning-of-defun) (treesit-end-of-defun): New function. (treesit-imenu-function): New variable. (treesit-mode-supported) (treesit-required-languages) (treesit--local-variable-backup): New variables. (treesit--backup-local-variable): New function * lisp/progmodes/js.el (js-use-tree-sitter): Remove option. (js--treesit-defun-type-regexp): Remove variable. (Now set inline in js-mode.) (js--treesit-beginning-of-defun) (js--treesit-end-of-defun): Remove functions. (Now use treesit-beginning/end-of-defun.) (js--backend-toggle) (js--json-backend-toggle): Remove function. (js-mode) (js-json-mode): Restore back to before tree-sitter changes. Add tree-sitter setup at the end. * lisp/progmodes/python.el (python--backend-toggle): Remove function. (python-mode): Restore back to before tree-sitter changes. Add tree-sitter setup at the end. * lisp/progmodes/ts-mode.el (ts-mode--font-lock-settings): Use js--fontify-template-string. (ts-mode--fontify-template-string): Remove function (because we can just use js--fontify-template-string). (ts-mode--defun-type-regexp): Remove variable (now set inline in ts-mode). (ts-mode--beginning-of-defun) (ts-mode--end-of-defun): Remove functions (now using treesit-beginning/end-of-defun). (ts-mode): Setup tree-sitter variables and then turn on treesit-mode or move to js-mode. --- diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el index fa548e4df7e..52160fbb5ee 100644 --- a/lisp/progmodes/js.el +++ b/lisp/progmodes/js.el @@ -3403,13 +3403,6 @@ This function is intended for use in `after-change-functions'." js-mode "\\(@[[:alpha:]]+\\>\\|$\\)") ;;; Tree sitter integration -(defcustom js-use-tree-sitter nil - "If non-nil, `js-mode' tries to use tree-sitter. -Currently `js-mode' uses tree-sitter for font-locking, -indentation, which-function and movement functions." - :version "29.1" - :type 'boolean - :safe 'booleanp) (defun js--treesit-backward-up-list () (lambda (_node _parent _bol &rest _) @@ -3608,79 +3601,6 @@ This function can be used as a value in `which-func-functions'" do (setq node (treesit-node-parent node)) finally return (string-join name-list ".")))) -(defvar js--treesit-defun-type-regexp - (rx (or "class_declaration" - "method_definition" - "function_declaration" - "lexical_declaration")) - "Regular expression that matches type of defun nodes. -Used in `js--treesit-beginning-of-defun' and friends.") - -(defun js--treesit-beginning-of-defun (&optional arg) - "Tree-sitter `beginning-of-defun' function. -ARG is the same as in `beginning-of-defun." - (let ((arg (or arg 1))) - (if (> arg 0) - ;; Go backward. - (while (and (> arg 0) - (treesit-search-forward-goto - js--treesit-defun-type-regexp 'start nil t)) - (setq arg (1- arg))) - ;; Go forward. - (while (and (< arg 0) - (treesit-search-forward-goto - js--treesit-defun-type-regexp 'start)) - (setq arg (1+ arg)))))) - -(defun js--treesit-end-of-defun (&optional arg) - "Tree-sitter `end-of-defun' function. -ARG is the same as in `end-of-defun." - (let ((arg (or arg 1))) - (if (< arg 0) - ;; Go backward. - (while (and (< arg 0) - (treesit-search-forward-goto - js--treesit-defun-type-regexp 'end nil t)) - (setq arg (1+ arg))) - ;; Go forward. - (while (and (> arg 0) - (treesit-search-forward-goto - js--treesit-defun-type-regexp 'end)) - (setq arg (1- arg)))))) - -(defun js--backend-toggle (backend warn) - "Toggle backend for `js-mode'. -For BACKEND and WARN see `treesit-mode-function'." - (cond - ;; Tree-sitter. - ((and (eq backend 'treesit) (treesit-ready-p warn 'javascript)) - (setq-local treesit-simple-indent-rules js--treesit-indent-rules) - (setq-local indent-line-function #'treesit-indent) - - (setq-local beginning-of-defun-function #'js--treesit-beginning-of-defun) - (setq-local end-of-defun-function #'js--treesit-end-of-defun) - - (add-hook 'which-func-functions #'js-treesit-current-defun nil t) - - (setq-local font-lock-keywords-only t) - (setq-local treesit-font-lock-settings js--treesit-font-lock-settings) - (setq-local treesit-font-lock-feature-list '((minimal) (moderate) (full))) - (treesit-font-lock-enable)) - ;; Elisp. - ((eq backend 'elisp) - (setq-local indent-line-function #'js-indent-line) - (setq-local beginning-of-defun-function #'js-beginning-of-defun) - (setq-local end-of-defun-function #'js-end-of-defun) - - (setq-local font-lock-defaults - (list js--font-lock-keywords nil nil nil nil - '(font-lock-syntactic-face-function - . js-font-lock-syntactic-face-function))) - - ;; Imenu - (setq imenu-case-fold-search nil) - (setq imenu-create-index-function #'js--imenu-create-index)))) - ;;; Main Function ;;;###autoload @@ -3689,7 +3609,14 @@ For BACKEND and WARN see `treesit-mode-function'." :group 'js ;; Ensure all CC Mode "lang variables" are set to valid values. (c-init-language-vars js-mode) + (setq-local indent-line-function #'js-indent-line) + (setq-local beginning-of-defun-function #'js-beginning-of-defun) + (setq-local end-of-defun-function #'js-end-of-defun) (setq-local open-paren-in-column-0-is-defun-start nil) + (setq-local font-lock-defaults + (list js--font-lock-keywords nil nil nil nil + '(font-lock-syntactic-face-function + . js-font-lock-syntactic-face-function))) (setq-local syntax-propertize-function #'js-syntax-propertize) (add-hook 'syntax-propertize-extend-region-functions #'syntax-propertize-multiline 'append 'local) @@ -3704,6 +3631,8 @@ For BACKEND and WARN see `treesit-mode-function'." (setq-local comment-start "// ") (setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *") (setq-local comment-end "") + (setq-local fill-paragraph-function #'js-fill-paragraph) + (setq-local normal-auto-fill-function #'js-do-auto-fill) ;; Parse cache (add-hook 'before-change-functions #'js--flush-caches t t) @@ -3716,6 +3645,10 @@ For BACKEND and WARN see `treesit-mode-function'." (add-hook 'after-change-functions #'js-jsx--detect-after-change nil t)) (js-use-syntactic-mode-name) + ;; Imenu + (setq imenu-case-fold-search nil) + (setq imenu-create-index-function #'js--imenu-create-index) + ;; for filling, pretend we're cc-mode (c-foreign-init-lit-pos-cache) (add-hook 'before-change-functions #'c-foreign-truncate-lit-pos-cache nil t) @@ -3726,9 +3659,6 @@ For BACKEND and WARN see `treesit-mode-function'." (setq-local electric-layout-rules '((?\; . after) (?\{ . after) (?\} . before))) - (setq-local fill-paragraph-function #'js-fill-paragraph) - (setq-local normal-auto-fill-function #'js-do-auto-fill) - (let ((c-buffer-is-cc-mode t)) ;; FIXME: These are normally set by `c-basic-common-init'. Should ;; we call it instead? (Bug#6071) @@ -3756,10 +3686,19 @@ For BACKEND and WARN see `treesit-mode-function'." ;; will mysteriously disappear. ;; FIXME: We should instead do this fontification lazily by adding ;; calls to syntax-propertize wherever it's really needed. - ;; (syntax-propertize (point-max)) - - (js--backend-toggle 'elisp nil) - (setq-local major-mode-backend-function #'js--backend-toggle)) + ;;(syntax-propertize (point-max)) + + ;; Tree-sitter support. + (setq-local treesit-mode-supported t) + (setq-local treesit-required-languages '(javascript)) + (setq-local treesit-simple-indent-rules js--treesit-indent-rules) + (setq-local treesit-defun-type-regexp + (rx (or "class_declaration" + "method_definition" + "function_declaration" + "lexical_declaration"))) + (setq-local treesit-font-lock-settings js--treesit-font-lock-settings) + (setq-local treesit-font-lock-feature-list '((minimal) (moderate) (full)))) (defvar js-json--treesit-font-lock-settings (treesit-font-lock-rules @@ -3791,29 +3730,18 @@ For BACKEND and WARN see `treesit-mode-function'." ((parent-is "object") parent-bol ,js-indent-level) ))) -(defun js--json-backend-toggle (backend warn) - "Toggle backend for `json-mode'. -For BACKEND and WARN see `treesit-mode-function'." - (cond - ;; Tree-sitter. - ((and (eq backend 'treesit) (treesit-ready-p warn 'json)) - (setq-local treesit-simple-indent-rules js--json-treesit-indent-rules) - (setq-local indent-line-function #'treesit-indent) - - (setq-local font-lock-keywords-only t) - (setq-local treesit-font-lock-settings js-json--treesit-font-lock-settings) - (treesit-font-lock-enable)) - ;; Elisp. - ((eq backend 'elisp) - (js--backend-toggle 'elisp nil)))) - ;;;###autoload (define-derived-mode js-json-mode js-mode "JSON" (setq-local js-enabled-frameworks nil) ;; Speed up `syntax-ppss': JSON files can be big but can't hold ;; regexp matchers nor #! thingies (and `js-enabled-frameworks' is nil). (setq-local syntax-propertize-function #'ignore) - (setq-local major-mode-backend-function #'js--json-backend-toggle)) + + ;; Tree-sitter support. + (setq-local treesit-mode-supported t) + (setq-local treesit-required-languages '(json)) + (setq-local treesit-simple-indent-rules js--json-treesit-indent-rules) + (setq-local treesit-font-lock-settings js-json--treesit-font-lock-settings)) ;; Since we made JSX support available and automatically-enabled in ;; the base `js-mode' (for ease of use), now `js-jsx-mode' simply diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index 834e8036a1b..4ac5f2e3b0a 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -6375,35 +6375,6 @@ Add import for undefined name `%s' (empty to skip): " (defvar electric-indent-inhibit) (defvar prettify-symbols-alist) -(defun python--backend-toggle (backend warn) - "Toggle backend for `python-mode'. -BACKEND and WARN are explained in `treesit-mode-function'." - (cond - ;; Tree-sitter. - ((and (eq backend 'treesit) (treesit-ready-p warn 'python)) - (setq-local font-lock-keywords-only t) - (setq-local treesit-font-lock-feature-list - '((basic) (moderate) (elaborate))) - (setq-local treesit-font-lock-settings - python--treesit-settings) - (treesit-font-lock-enable) - (setq-local imenu-create-index-function - #'python-imenu-treesit-create-index) - (add-hook 'which-func-functions - #'python-info-treesit-current-defun nil t)) - ;; Elisp. - ((eq backend 'elisp) - (setq-local font-lock-defaults - `(,python-font-lock-keywords - nil nil nil nil - (font-lock-syntactic-face-function - . python-font-lock-syntactic-face-function) - (font-lock-extend-after-change-region-function - . python-font-lock-extend-region))) - (setq-local imenu-create-index-function - #'python-imenu-create-index) - (add-hook 'which-func-functions #'python-info-current-defun nil t)))) - ;;;###autoload (define-derived-mode python-mode prog-mode "Python" "Major mode for editing Python files. @@ -6420,9 +6391,11 @@ BACKEND and WARN are explained in `treesit-mode-function'." (setq-local forward-sexp-function python-forward-sexp-function) - (python--backend-toggle 'elisp nil) - - (setq-local major-mode-backend-function #'python--backend-toggle) + (setq-local font-lock-defaults + `(,python-font-lock-keywords + nil nil nil nil + (font-lock-syntactic-face-function + . python-font-lock-syntactic-face-function))) (setq-local syntax-propertize-function python-syntax-propertize-function) @@ -6451,9 +6424,14 @@ BACKEND and WARN are explained in `treesit-mode-function'." (add-hook 'post-self-insert-hook #'python-indent-post-self-insert-function 'append 'local) + (setq-local imenu-create-index-function + #'python-imenu-create-index) + (setq-local add-log-current-defun-function #'python-info-current-defun) + (add-hook 'which-func-functions #'python-info-current-defun nil t) + (setq-local skeleton-further-elements '((abbrev-mode nil) (< '(backward-delete-char-untabify (min python-indent-offset @@ -6462,13 +6440,13 @@ BACKEND and WARN are explained in `treesit-mode-function'." (with-no-warnings ;; suppress warnings about eldoc-documentation-function being obsolete - (if (null eldoc-documentation-function) - ;; Emacs<25 - (setq-local eldoc-documentation-function #'python-eldoc-function) - (if (boundp 'eldoc-documentation-functions) - (add-hook 'eldoc-documentation-functions #'python-eldoc-function nil t) - (add-function :before-until (local 'eldoc-documentation-function) - #'python-eldoc-function)))) + (if (null eldoc-documentation-function) + ;; Emacs<25 + (setq-local eldoc-documentation-function #'python-eldoc-function) + (if (boundp 'eldoc-documentation-functions) + (add-hook 'eldoc-documentation-functions #'python-eldoc-function nil t) + (add-function :before-until (local 'eldoc-documentation-function) + #'python-eldoc-function)))) (add-to-list 'hs-special-modes-alist @@ -6499,7 +6477,15 @@ BACKEND and WARN are explained in `treesit-mode-function'." (when python-indent-guess-indent-offset (python-indent-guess-indent-offset)) - (add-hook 'flymake-diagnostic-functions #'python-flymake nil t)) + (add-hook 'flymake-diagnostic-functions #'python-flymake nil t) + + (setq-local treesit-mode-supported t) + (setq-local treesit-required-languages '(python)) + (setq-local treesit-font-lock-feature-list + '((basic) (moderate) (elaborate))) + (setq-local treesit-font-lock-settings python--treesit-settings) + (setq-local treesit-imenu-function + #'python-imenu-treesit-create-index)) ;;; Completion predicates for M-x ;; Commands that only make sense when editing Python code diff --git a/lisp/progmodes/ts-mode.el b/lisp/progmodes/ts-mode.el index a3a2d9f78e7..c23f2bec05b 100644 --- a/lisp/progmodes/ts-mode.el +++ b/lisp/progmodes/ts-mode.el @@ -134,7 +134,7 @@ (string) @font-lock-string-face - (template_string) @ts-mode--fontify-template-string + (template_string) @js--fontify-template-string (template_substitution ["${" "}"] @font-lock-builtin-face) (comment) @font-lock-comment-face) @@ -248,61 +248,6 @@ (jsx_attribute (property_identifier) @font-lock-constant-face))) "Tree-sitter font-lock settings.") -(defun ts-mode--fontify-template-string (beg end node) - "Fontify template string but not substitution inside it. -BEG, END, NODE refers to the template_string node." - (ignore end) - ;; Stolen from `js--fontify-template-string' - (let ((child (treesit-node-child node 0))) - (while child - (if (equal (treesit-node-type child) "template_substitution") - (put-text-property beg (treesit-node-start child) - 'face 'font-lock-string-face) - (put-text-property beg (treesit-node-end child) - 'face 'font-lock-string-face)) - (setq beg (treesit-node-end child) - child (treesit-node-next-sibling child))))) - -(defvar ts-mode--defun-type-regexp - (rx (or "class_declaration" - "method_definition" - "function_declaration" - "lexical_declaration")) - "Regular expression that matches type of defun nodes. -Used in `ts-mode--beginning-of-defun' and friends.") - -(defun ts-mode--beginning-of-defun (&optional arg) - "Tree-sitter `beginning-of-defun' function. -ARG is the same as in `beginning-of-defun." - (let ((arg (or arg 1))) - (if (> arg 0) - ;; Go backward. - (while (and (> arg 0) - (treesit-search-forward-goto - ts-mode--defun-type-regexp 'start nil t)) - (setq arg (1- arg))) - ;; Go forward. - (while (and (< arg 0) - (treesit-search-forward-goto - ts-mode--defun-type-regexp 'start)) - (setq arg (1+ arg)))))) - -(defun ts-mode--end-of-defun (&optional arg) - "Tree-sitter `end-of-defun' function. -ARG is the same as in `end-of-defun." - (let ((arg (or arg 1))) - (if (< arg 0) - ;; Go backward. - (while (and (< arg 0) - (treesit-search-forward-goto - ts-mode--defun-type-regexp 'end nil t)) - (setq arg (1+ arg))) - ;; Go forward. - (while (and (> arg 0) - (treesit-search-forward-goto - ts-mode--defun-type-regexp 'end)) - (setq arg (1- arg)))))) - ;;;###autoload (add-to-list 'auto-mode-alist '("\\.ts\\'" . ts-mode)) @@ -315,26 +260,31 @@ ARG is the same as in `end-of-defun." :group 'typescript :syntax-table ts-mode--syntax-table + ;; Treesit-mode. + (setq-local treesit-mode-supported t) + (setq-local treesit-required-languages '(tsx)) + ;; Comments. + (setq-local comment-start "// ") + (setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *") + (setq-local comment-end "") + ;; Indent. + (setq-local treesit-simple-indent-rules ts-mode--indent-rules) + ;; Navigation. + (setq-local treesit-defun-type-regexp + (rx (or "class_declaration" + "method_definition" + "function_declaration" + "lexical_declaration"))) + ;; Font-lock. + (setq-local treesit-font-lock-settings ts-mode--font-lock-settings) + (setq-local treesit-font-lock-feature-list '((minimal) (moderate) (full))) + (cond - ((treesit-ready-p nil 'tsx) - ;; Comments - (setq-local comment-start "// ") - (setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *") - (setq-local comment-end "") - - (setq-local treesit-simple-indent-rules ts-mode--indent-rules) - (setq-local indent-line-function #'treesit-indent) - - (setq-local beginning-of-defun-function #'ts-mode--beginning-of-defun) - (setq-local end-of-defun-function #'ts-mode--end-of-defun) - - (setq font-lock-keywords-only t) - (setq-local treesit-font-lock-settings ts-mode--font-lock-settings) - (setq treesit-font-lock-feature-list '((minimal) (moderate) (full))) - (treesit-font-lock-enable)) + ((treesit-ready-p '(tsx)) + (treesit-mode)) (t - (message "Tree-sitter for TypeScript isn't available, falling back to `js-mode'") - (js-mode)))) + (js-mode) + (message "Tree-sitter for TypeScript isn't available, falling back to `js-mode'")))) (provide 'ts-mode) diff --git a/lisp/treesit.el b/lisp/treesit.el index aadcc729d48..136b756badd 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -33,7 +33,7 @@ (require 'cl-seq) (require 'font-lock) -;;; Activating tree-sitter +;;; Custom options ;; Tree-sitter always appear as treesit in symbols. (defgroup treesit nil @@ -48,105 +48,8 @@ indent, imenu, etc." :type 'integer :version "29.1") -(defcustom treesit-mode-inhibit-message nil - "If non-nil, don't print message when tree-sitter can't activate." - :type 'boolean - :version "29.1") - (declare-function treesit-available-p "treesit.c") -(defvar-local major-mode-backend-function nil - "A function that toggles the \"backend\" for a major mode. - -The function is passed two argument BACKEND and WARN. BACKEND is -a symbol representing the backend we want to activate. Currently -it can be `treesit' or `elisp'. - -If WARN is non-nil, display a warning if tree-sitter can't -activate, if WARN is nil, just print an message and don't display -warning.") - -;;;###autoload -(define-minor-mode treesit-mode - "Activate tree-sitter to power major-mode features. - -This mode is merely a SUGGESTION of turning on tree-sitter, -actual activation of tree-sitter functionalities depends on -whether the major mode supports tree-sitter, availability of -specific tree-sitter language definition, etc." - :version "29.1" - :group 'treesit - (when major-mode-backend-function - (funcall major-mode-backend-function - (if treesit-mode 'treesit 'elisp) - (eq this-command 'treesit-mode)))) - -(defvar treesit-remapped-major-mode-alist nil - "A list like `major-mode-remap-alist'. - -When major modes activate tree-sitter by switching to another -major mode and `global-treesit-mode' is on, they add an -entry (MODE . NEW-MODE) to `major-mode-remap-alist', so next time -Emacs uses NEW-MODE directly. - -These remaps should be removed when `global-treesit-mode' is -turned off, so we record each (MODE . NEW-MODE) in this variable.") - -;;;###autoload -(define-globalized-minor-mode global-treesit-mode treesit-mode - global-treesit-mode--turn-on - :version "29.1" - :group 'treesit - :predicate t - (when (not global-treesit-mode) - (dolist (map treesit-remapped-major-mode-alist) - (setq major-mode-remap-alist - (remove map major-mode-remap-alist))))) - -(defun global-treesit-mode--turn-on () - "Function used to determine whether to turn on `treesit-mode'. -Called in every buffer if `global-treesit-mode' is on." - (treesit-mode)) - -(defun treesit-ready-p (warn &rest languages) - "Check that tree-sitter is ready to be used. - -If tree-sitter is not ready and WARN is nil-nil, emit a warning; -if WARN is nil, print a message. But if -`treesit-mode-inhibit-message' is non-nil, don't even print the -message. - -LANGUAGES are languages we want check for availability." - (let (msg) - ;; Check for each condition and set MSG. - (catch 'term - (when (not (treesit-available-p)) - (setq msg "tree-sitter library is not built with Emacs") - (throw 'term nil)) - (when (> (buffer-size) treesit-max-buffer-size) - (setq msg "buffer larger than `treesit-max-buffer-size'") - (throw 'term nil)) - (dolist (lang languages) - (pcase-let ((`(,available . ,err) - (treesit-language-available-p lang t))) - (when (not available) - (setq msg (format "language definition for %s is unavailable (%s): %s" - lang (nth 0 err) - (string-join - (mapcar (lambda (x) (format "%s" x)) - (cdr err)) - " "))) - (throw 'term nil))))) - ;; Decide if all conditions met and whether emit a warning. - (if (not msg) - t - (setq msg (concat "Cannot activate tree-sitter, because " msg)) - (if warn - (display-warning 'treesit msg) - (unless treesit-mode-inhibit-message - (message "%s" msg))) - nil))) - ;;; Parser API supplement (defun treesit-parse-string (string language) @@ -605,18 +508,6 @@ If LOUDLY is non-nil, message some debugging information." (let ((font-lock-unfontify-region-function #'ignore)) (funcall #'font-lock-default-fontify-region start end loudly))) -(defun treesit-font-lock-enable () - "Enable tree-sitter font-locking for the current buffer." - (treesit-font-lock-recompute-features) - (setq-local font-lock-fontify-region-function - #'treesit-font-lock-fontify-region) - ;; If we don't set `font-lock-defaults' to some non-nil value, - ;; font-lock doesn't enable properly (`font-lock-mode-internal' - ;; doesn't run). See `font-lock-specified-p'. - (when (null font-lock-defaults) - (setq font-lock-defaults '(nil))) - (font-lock-mode 1)) - ;;; Indent (defvar treesit--indent-verbose nil @@ -945,6 +836,177 @@ ALL, BACKWARD, and UP are the same as in `treesit-search-forward'." (goto-char start)) node)) +;;; Navigation + +(defvar-local treesit-defun-type-regexp nil + "A regexp that matches the node type of defun nodes. +For example, \"(function|class)_definition\". + +This is used by `treesit-beginning-of-defun' and friends. Bind +it buffer-locally and `treesit-mode' will use it for navigating +defun's.") + +(defun treesit-beginning-of-defun (&optional arg) + "Tree-sitter `beginning-of-defun' function. +ARG is the same as in `beginning-of-defun." + (let ((arg (or arg 1))) + (if (> arg 0) + ;; Go backward. + (while (and (> arg 0) + (treesit-search-forward-goto + treesit-defun-type-regexp 'start nil t)) + (setq arg (1- arg))) + ;; Go forward. + (while (and (< arg 0) + (treesit-search-forward-goto + treesit-defun-type-regexp 'start)) + (setq arg (1+ arg)))))) + +(defun treesit-end-of-defun () + "Tree-sitter `end-of-defun' function." + (treesit-search-forward-goto treesit-defun-type-regexp 'end)) + +;;; Imenu + +(defvar-local treesit-imenu-function nil + "Tree-sitter version of `imenu-create-index-function'. + +Set this variable to a function and `treesit-mode' will bind it +to `imenu-create-index-function'.") + +;;; Activating tree-sitter + +(defvar-local treesit-mode-supported nil + "Set this variable to t to indicate support for `treesit-mode'.") + +(defvar-local treesit-required-languages nil + "A list of languages this major mode need for tree-sitter.") + +(defun treesit-ready-p (languages &optional report) + "Check that tree-sitter is ready to be used. + +If tree-sitter is not ready and REPORT non-nil, emit a warning or +message. Emit a warning if REPORT is `warn', message if REPORT +is `message'. + +LANGUAGES is a list of languages we want check for availability." + (let (msg) + ;; Check for each condition and set MSG. + (catch 'term + (when (not treesit-mode-supported) + (setq msg "this major mode doesn't support it") + (throw 'term nil)) + (when (not (treesit-available-p)) + (setq msg "tree-sitter library is not built with Emacs") + (throw 'term nil)) + (when (> (buffer-size) treesit-max-buffer-size) + (setq msg "buffer larger than `treesit-max-buffer-size'") + (throw 'term nil)) + (dolist (lang languages) + (pcase-let ((`(,available . ,err) + (treesit-language-available-p lang t))) + (when (not available) + (setq msg (format "language definition for %s is unavailable (%s): %s" + lang (nth 0 err) + (string-join + (mapcar (lambda (x) (format "%s" x)) + (cdr err)) + " "))) + (throw 'term nil))))) + ;; Decide if all conditions met and whether emit a warning. + (if (not msg) + t + (setq msg (concat "Cannot activate tree-sitter, because " msg)) + (pcase report + ('warn (display-warning 'treesit msg)) + ('message (message "%s" msg))) + nil))) + +(defvar-local treesit--local-variable-backup nil + "An alist of (VAR . VALUE) that backs up pre-treesit-mode values.") + +(defun treesit--backup-local-variable (variable value &optional restore) + "Backup VARIABLE's value and set it to VALUE. +If RESTORE is non-nil, ignore VALUE and restore the backup (if +there is any)." + (if restore + (when-let ((value (alist-get variable + treesit--local-variable-backup))) + (set variable value)) + ;; Set. + (make-variable-buffer-local variable) + (setf (alist-get variable treesit--local-variable-backup) + (symbol-value variable)) + (set variable value))) + +;;;###autoload +(define-minor-mode treesit-mode + "Activate tree-sitter to power major-mode features. + +This mode is merely a SUGGESTION of turning on tree-sitter, +actual activation of tree-sitter functionalities depends on +whether the major mode supports tree-sitter, availability of +specific tree-sitter language definition, etc." + :version "29.1" + :group 'treesit + (if treesit-mode + (when (treesit-ready-p treesit-required-languages 'warn) + ;; Font-lock. + (setq-local treesit--local-variable-backup nil) + ;; NOTE: If anyone other than the major mode added stuff to + ;; `font-lock-keywords', they would need to re-add them after + ;; `treesit-mode' turns on. + (when treesit-font-lock-settings + (treesit--backup-local-variable 'font-lock-keywords-only t) + (treesit--backup-local-variable 'font-lock-keywords nil) + (treesit--backup-local-variable + 'font-lock-fontify-region-function + #'treesit-font-lock-fontify-region) + (treesit-font-lock-recompute-features)) + ;; Indent. + (when treesit-simple-indent-rules + (treesit--backup-local-variable + 'indent-line-function #'treesit-indent)) + ;; Navigation. + (when treesit-defun-type-regexp + (treesit--backup-local-variable + 'beginning-of-defun-function #'treesit-beginning-of-defun) + (treesit--backup-local-variable + 'end-of-defun-function #'treesit-end-of-defun)) + ;; Imenu. + (when treesit-imenu-function + (treesit--backup-local-variable + 'imenu-create-index-function treesit-imenu-function))) + ;; Font-lock. + (treesit--backup-local-variable 'font-lock-keywords nil t) + (treesit--backup-local-variable 'font-lock-keywords-only nil t) + (treesit--backup-local-variable + 'font-lock-fontify-region-function nil t) + ;; Indent. + (treesit--backup-local-variable 'indent-line-function nil t) + ;; Navigation. + (treesit--backup-local-variable 'beginning-of-defun-function nil t) + (treesit--backup-local-variable 'end-of-defun-function nil t) + ;; Imenu. + (treesit--backup-local-variable 'imenu-create-index-function nil t) + )) + +(defun global-treesit-mode--turn-on () + "Function used to determine whether to turn on `treesit-mode'. +Called in every buffer if `global-treesit-mode' is on." + ;; Check tree-sitter readiness and don't emit warnings. + (when (and treesit-mode-supported + (treesit-ready-p treesit-required-languages)) + (treesit-mode))) + +;;;###autoload +(define-globalized-minor-mode global-treesit-mode treesit-mode + global-treesit-mode--turn-on + :version "29.1" + :group 'treesit + :predicate t + nil) + ;;; Debugging (defvar-local treesit--inspect-name nil