New tree-sitter toggle scheme

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.
This commit is contained in:
Yuan Fu 2022-10-25 13:54:12 -07:00
parent 06b5ec4bbd
commit 7c5d434833
No known key found for this signature in database
GPG key ID: 56E19BC57664A442
5 changed files with 232 additions and 197 deletions

View 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

View 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

View file

@ -260,29 +260,26 @@
: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'"))))

View 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'.")
(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)))))
(defvar-local treesit-required-languages nil
"A list of languages this major mode need for tree-sitter.")
(defun treesit-ready-p (mode language &optional quiet)
"Check that tree-sitter is ready to be used for MODE.
(defun treesit-ready-p (languages &optional report)
"Check that tree-sitter is ready to be used.
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 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'.
If MODE is nil, don't check for user setting and assume the
setting is t.
LANGUAGES is a list of languages we want check for availability."
(let (msg)
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)))
(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))))
(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
(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)
))
If `treesit-font-lock-settings' is non-nil, setup fontification and
enable `font-lock-mode'.
(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)))
If `treesit-simple-indent-rules' is non-nil, setup indentation.
;;;###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-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

View 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