]> git.eshelyaron.com Git - emacs.git/commitdiff
New tree-sitter toggle scheme
authorYuan Fu <casouri@gmail.com>
Tue, 25 Oct 2022 20:54:12 +0000 (13:54 -0700)
committerYuan Fu <casouri@gmail.com>
Tue, 25 Oct 2022 21:07:47 +0000 (14:07 -0700)
This version: central variable, everything controlled by
treesit-settings.  Major mode sets up tree-sitter/non-tree-sitter
in a conditional branch, based on the setting.

* lisp/treesit.el (treesit-settings): New option.
(treesit-defun-type-regexp): Change docstring.
(treesit-mode-supported)
(treesit-required-languages)
(treesit--local-variable-backup): Remove variables.
(treesit--backup-local-variable)
(treesit-mode)
(global-treesit-mode--turn-on)
(global-treesit-mode): Remove functions.
(treesit--setting-for-mode): New function.
(treesit-ready-p): New argument MODE, changed REPORT to QUIET, and
LANGUAGEs to LANGUAGE (now it can be a single symbol or a list of
them).
(treesit-major-mode-setup): New function.  Mostly comes from
treesit-mode.

* test/src/treesit-tests.el (treesit-misc): New test.

* lisp/progmodes/python.el (python-mode): Move some setup code into
the conditional branch at the end.

* lisp/progmodes/js.el (js-json-mode)
(js-mode): Move some setup code into the conditional branch at the
end.

* lisp/progmodes/ts-mode.el: Move tree-sitter setup into the
conditional branch.

lisp/progmodes/js.el
lisp/progmodes/python.el
lisp/progmodes/ts-mode.el
lisp/treesit.el
test/src/treesit-tests.el

index 52160fbb5eecc7857344b6e2f57e4b7a6960628d..4ccdab0d9288df3f0f26c8f3717858b7fd302c10 100644 (file)
@@ -3609,19 +3609,7 @@ This function can be used as a value in `which-func-functions'"
   :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)
-  (add-hook 'syntax-propertize-extend-region-functions
-            #'js--syntax-propertize-extend-region 'append 'local)
   (setq-local prettify-symbols-alist js--prettify-symbols-alist)
 
   (setq-local parse-sexp-ignore-comments t)
@@ -3634,9 +3622,6 @@ This function can be used as a value in `which-func-functions'"
   (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)
-
   ;; Frameworks
   (js--update-quick-match-re)
 
@@ -3688,17 +3673,40 @@ This function can be used as a value in `which-func-functions'"
   ;; calls to syntax-propertize wherever it's really needed.
   ;;(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))))
+  (cond
+   ;; Tree-sitter.
+   ((treesit-ready-p 'js-mode 'javascript)
+    ;; Indent.
+    (setq-local treesit-simple-indent-rules js--treesit-indent-rules)
+    ;; Navigation.
+    (setq-local treesit-defun-type-regexp
+                (rx (or "class_declaration"
+                        "method_definition"
+                        "function_declaration"
+                        "lexical_declaration")))
+    ;; Fontification.
+    (setq-local treesit-font-lock-settings js--treesit-font-lock-settings)
+    (setq-local treesit-font-lock-feature-list '((minimal) (moderate) (full)))
+    (treesit-major-mode-setup))
+   ;; Elisp.
+   (t
+    ;; Ensure all CC Mode "lang variables" are set to valid values
+    ;; (continued).
+    (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)))
+    (setq-local syntax-propertize-function #'js-syntax-propertize)
+    (add-hook 'syntax-propertize-extend-region-functions
+              #'syntax-propertize-multiline 'append 'local)
+    (add-hook 'syntax-propertize-extend-region-functions
+              #'js--syntax-propertize-extend-region 'append 'local)
+
+    ;; Parse cache
+    (add-hook 'before-change-functions #'js--flush-caches t t))))
 
 (defvar js-json--treesit-font-lock-settings
   (treesit-font-lock-rules
@@ -3737,11 +3745,12 @@ This function can be used as a value in `which-func-functions'"
   ;; regexp matchers nor #! thingies (and `js-enabled-frameworks' is nil).
   (setq-local syntax-propertize-function #'ignore)
 
-  ;; 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))
+  (cond
+   ;; Tree-sitter.
+   ((treesit-ready-p 'js-json-mode 'json)
+    (setq-local treesit-simple-indent-rules js--json-treesit-indent-rules)
+    (setq-local treesit-font-lock-settings js-json--treesit-font-lock-settings)
+    (treesit-major-mode-setup))))
 
 ;; Since we made JSX support available and automatically-enabled in
 ;; the base `js-mode' (for ease of use), now `js-jsx-mode' simply
index 4ac5f2e3b0ab94590216885045924b7aff76c46b..2c0c35174c2621c58d2acdeef319368b00140c47 100644 (file)
@@ -6391,15 +6391,6 @@ Add import for undefined name `%s' (empty to skip): "
 
   (setq-local forward-sexp-function python-forward-sexp-function)
 
-  (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)
-
   (setq-local indent-line-function #'python-indent-line-function)
   (setq-local indent-region-function #'python-indent-region)
   ;; Because indentation is not redundant, we cannot safely reindent code.
@@ -6424,14 +6415,9 @@ Add import for undefined name `%s' (empty to skip): "
   (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
@@ -6479,13 +6465,27 @@ Add import for undefined name `%s' (empty to skip): "
 
   (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))
+  (cond
+   ;; Tree-sitter.
+   ((treesit-ready-p 'python-mode 'python)
+    (setq-local treesit-font-lock-feature-list
+                '((basic) (moderate) (elaborate)))
+    (setq-local treesit-font-lock-settings python--treesit-settings)
+    (setq-local imenu-create-index-function
+                #'python-imenu-treesit-create-index)
+    (treesit-major-mode-setup))
+   ;; Elisp.
+   (t
+    (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)
+    (setq-local imenu-create-index-function
+                #'python-imenu-create-index)
+    (add-hook 'which-func-functions #'python-info-current-defun nil t))))
 
 ;;; Completion predicates for M-x
 ;; Commands that only make sense when editing Python code
index 4d57edf65b0a1d051486938198772056c9ef5ace..f2002f9ef72a614043eb418061939fd6a337657a 100644 (file)
   :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 font-lock-defaults '(nil))
-  (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 '(tsx))
-    (treesit-mode))
+   ((treesit-ready-p nil 'tsx)
+    ;; Tree-sitter.
+    ;; 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)))
+    (treesit-major-mode-setup))
+   ;; Elisp.
    (t
     (js-mode)
     (message "Tree-sitter for TypeScript isn't available, falling back to `js-mode'"))))
index a3bc1259b13cec08847d921911447cb7d40698f4..86cff52af244eba29a3e81be8f40235fe29be3ce 100644 (file)
@@ -50,6 +50,34 @@ indent, imenu, etc."
 
 (declare-function treesit-available-p "treesit.c")
 
+(defcustom treesit-settings '((t nil t))
+  "Tree-sitter toggle settings for major modes.
+
+A list of (MODE ACTIVATE INHERIT).  MODE is a major mode, ACTIVATE
+can be one of the following:
+
+  demand => Demand the use of tree-sitter, warn if it can't activate
+  t => Enable if available
+  nil => Don't enable
+
+If INHERIT is t, the setting for MODE is inherited by all its
+derived modes.  For a derived mode, closer ancestor mode's
+setting takes higher precedence.
+
+A special MODE, t, is considered the ancestor of every mode, and
+its INHERIT flag is ignored."
+  :type '(repeat
+          (list :tag "Setting"
+                (symbol :tag "Mode")
+                (choice :tag "Activate"
+                        (const :tag "No" nil)
+                        (const :tag "Yes" t)
+                        (const :tag "Demand" demand))
+                (choice :tag "Inherit"
+                        (const :tag "Yes" t)
+                        (const :tag "No" nil))))
+  :version "29.1")
+
 ;;; Parser API supplement
 
 (defun treesit-parse-string (string language)
@@ -891,9 +919,7 @@ BACKWARD and ALL are the same as in `treesit-search-forward'."
   "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.")
+This is used by `treesit-beginning-of-defun' and friends.")
 
 (defun treesit--find-top-level-match (node type)
   "Return the top-level parent of NODE matching TYPE.
@@ -947,136 +973,119 @@ 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)
+(defun treesit--setting-for-mode (mode settings)
+  "Get the setting for MODE in SETTINGS.
+MODE is a major mode symbol.  SETTINGS should be `treesit-settings'."
+  ;;    A setting for exactly this MODE.  The shape is (FLAG INHERIT).
+  (let ((self (alist-get mode settings))
+        ;; Fallback setting, shape is (FLAG INHERIT).
+        (fallback (alist-get t settings))
+        ;; Settings for ancestor modes of MODE.  Its shape is
+        ;; ((MODE . FLAG)...)
+        (applicable (cl-loop for setting in settings
+                             for m = (nth 0 setting)
+                             for flag = (nth 1 setting)
+                             for inherit = (nth 2 setting)
+                             if (and (not (eq m t))
+                                     (not (eq m mode))
+                                     inherit
+                                     (provided-mode-derived-p mode m))
+                             collect (cons m flag))))
+    (cond
+     (self (car self))
+     ((null applicable) (car fallback))
+     (t
+      ;; After sort, the most specific setting is at the top.
+      (setq applicable
+            (cl-sort applicable
+                     (lambda (a b)
+                       ;; Major mode inheritance has a total ordering
+                       ;; right?
+                       (provided-mode-derived-p (car a) (car b)))))
+      (cdar applicable)))))
+
+(defun treesit-ready-p (mode language &optional quiet)
+  "Check that tree-sitter is ready to be used for MODE.
+
+Checks the user setting in `treesit-settings', if user sets
+`demand' for MODE, and tree-sitter is not ready, emit a warning
+and return nil.  If user chose to activate tree-sitter for MODE
+and tree-sitter is ready, return non-nil.  If QUIET is t, no
+warning is emitted in any case, if quiet is `message', message
+instead of emitting warning.
+
+If MODE is nil, don't check for user setting and assume the
+setting is t.
+
+LANGUAGE is languages symbol we want check for availability.  It
+can also be a list of language symbols."
+  (let ((language-list (if (consp language)
+                           language
+                         (list language)))
+        (activate (if mode
+                      (treesit--setting-for-mode mode treesit-settings)
+                    t))
+        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
+    (if (null activate)
+        nil
+      (catch 'term
+        (when (not (treesit-available-p))
+          (setq msg "tree-sitter library is not compiled 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 language-list)
+          (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
+        (when (eq activate 'demand)
+          (setq msg (concat "Cannot activate tree-sitter, because " msg))
+          (pcase quiet
+            ('nil (display-warning 'treesit msg))
+            ('message (message "%s" msg))))
+        nil))))
+
+(defun treesit-major-mode-setup ()
   "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)
+If `treesit-font-lock-settings' is non-nil, setup fontification and
+enable `font-lock-mode'.
+
+If `treesit-simple-indent-rules' is non-nil, setup indentation.
+
+If `treesit-defun-type-regexp' is non-nil, setup
+`beginning/end-of-defun' functions."
+  ;; Font-lock.
+  (when treesit-font-lock-settings
+    ;; `font-lock-mode' wouldn't setup properly if
+    ;; `font-lock-defaults' is nil, see `font-lock-specified-p'.
+    ;; And we disable syntax-table-based font-lock by setting the
+    ;; KEYWORD-ONLY flag to t, so syntax-table-based font-lock
+    ;; doesn't override tree-sitter's fontification.
+    (setq-local font-lock-defaults '(nil t))
+    (setq-local font-lock-fontify-region-function
+                #'treesit-font-lock-fontify-region)
+    (font-lock-mode 1)
+    (treesit-font-lock-recompute-features))
+  ;; Indent.
+  (when treesit-simple-indent-rules
+    (setq-local indent-line-function #'treesit-indent))
+  ;; Navigation.
+  (when treesit-defun-type-regexp
+    (setq-local beginning-of-defun-function #'treesit-beginning-of-defun)
+    (setq-local end-of-defun-function #'treesit-end-of-defun)))
 
 ;;; Debugging
 
index adc17bc079adb063444a8cf1c1a1223757ee36a8..2e26df6925852e7da933ea590704382ebb19841c 100644 (file)
@@ -448,6 +448,26 @@ visible_end.)"
     ;; `treesit-search-forward-goto'
     ))
 
+(ert-deftest treesit-misc ()
+  "Misc helper functions."
+  (let ((settings '((t 0 t)
+                    (c-mode 1 t)
+                    (text-mode 2 nil)
+                    (prog-mode 3 t)
+                    (fundamental-mode 4 t))))
+    ;; `treesit--setting-for-mode'.
+    ;; Exact match.
+    (should (eq 1 (treesit--setting-for-mode 'c-mode settings)))
+    ;; Inherit from t.
+    (should (eq 0 (treesit--setting-for-mode 'non-exist settings)))
+    ;; Inherit from prog-mode rather than fundamental-mode.
+    (require 'elisp-mode)
+    (should (eq 3 (treesit--setting-for-mode 'emacs-lisp-mode settings)))
+    ;; Not inherit from text-mode.
+    (require 'outline)
+    (should (not (eq 2 (treesit--setting-for-mode 'outline-mode settings))))
+    ))
+
 ;; TODO
 ;; - Functions in treesit.el
 ;; - treesit-load-name-override-list