]> git.eshelyaron.com Git - emacs.git/commitdiff
Revise the toggle scheme of tree-sitter (again)
authorYuan Fu <casouri@gmail.com>
Wed, 19 Oct 2022 23:44:04 +0000 (16:44 -0700)
committerYuan Fu <casouri@gmail.com>
Wed, 19 Oct 2022 23:44:04 +0000 (16:44 -0700)
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.

lisp/progmodes/js.el
lisp/progmodes/python.el
lisp/progmodes/ts-mode.el
lisp/treesit.el

index fa548e4df7ea245d56e197b30e69cdd025b80d7b..52160fbb5eecc7857344b6e2f57e4b7a6960628d 100644 (file)
@@ -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
index 834e8036a1b19fd5c93cfeee384e85c62f911608..4ac5f2e3b0ab94590216885045924b7aff76c46b 100644 (file)
@@ -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
index a3a2d9f78e7a508b47b726afcd74fee1642dd141..c23f2bec05b10c1983f74ae00a806afb758ef266 100644 (file)
 
      (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)
      (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)
 
index aadcc729d48a9be95e2bcddd9e94c1ac9ab50830..136b756badde51f70066daa1b076f4738f22b6c0 100644 (file)
@@ -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