js--treesit-defun-type-regexp 'end))
(setq arg (1- arg))))))
-(defun js--treesit-can-enable-p ()
- (if (and js-use-tree-sitter
- (treesit-can-enable-p)
- (treesit-language-available-p 'javascript))
- t
- (message "Cannot enable Tree Sitter for JavaScript.")
- nil))
+(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)
-(defun js--treesit-enable ()
- (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)
- (setq-local beginning-of-defun-function #'js--treesit-beginning-of-defun)
- (setq-local end-of-defun-function #'js--treesit-end-of-defun)
+ (setq-local font-lock-keywords-only t)
+ (setq-local treesit-font-lock-settings js--treesit-settings)
+ (setq-local treesit-font-lock-feature-list '((basic)))
- (setq-local font-lock-keywords-only t)
- (setq-local treesit-font-lock-settings js--treesit-settings)
- (setq-local treesit-font-lock-feature-list '((basic)))
+ (add-hook 'which-func-functions #'js-treesit-current-defun nil t)
- (add-hook 'which-func-functions #'js-treesit-current-defun nil t)
+ (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)
- (treesit-font-lock-enable))
+ (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
(define-derived-mode js-mode prog-mode "JavaScript"
"Major mode for editing JavaScript."
:group 'js
+ ;; Ensure all CC Mode "lang variables" are set to valid values.
+ (c-init-language-vars js-mode)
+ (setq-local open-paren-in-column-0-is-defun-start nil)
+ (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)
+ (setq-local which-func-imenu-joiner-function #'js--which-func-joiner)
+
;; Comments
(setq-local comment-start "// ")
(setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *")
(setq-local comment-end "")
- (if (js--treesit-can-enable-p)
- (js--treesit-enable)
- ;; 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)
- (setq-local which-func-imenu-joiner-function #'js--which-func-joiner)
-
- (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)
-
- ;; Syntax extensions
- (unless (js-jsx--detect-and-enable)
- (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)
- (setq-local comment-line-break-function #'c-indent-new-comment-line)
- (setq-local comment-multi-line t)
- (setq-local electric-indent-chars
- (append "{}():;," electric-indent-chars)) ;FIXME: js2-mode adds "[]*".
- (setq-local electric-layout-rules
- '((?\; . after) (?\{ . after) (?\} . before)))
-
- (let ((c-buffer-is-cc-mode t))
- ;; FIXME: These are normally set by `c-basic-common-init'. Should
- ;; we call it instead? (Bug#6071)
- (make-local-variable 'paragraph-start)
- (make-local-variable 'paragraph-separate)
- (make-local-variable 'paragraph-ignore-fill-prefix)
- (make-local-variable 'adaptive-fill-mode)
- (make-local-variable 'adaptive-fill-regexp)
- ;; While the full CC Mode style system is not yet in use, set the
- ;; pertinent style variables manually.
- (c-initialize-builtin-style)
- (let ((style (cc-choose-style-for-mode 'js-mode c-default-style)))
- (c-set-style style))
- (setq c-block-comment-prefix "* "
- c-comment-prefix-regexp "//+\\|\\**")
- (c-setup-paragraph-variables))
-
- ;; Important to fontify the whole buffer syntactically! If we don't,
- ;; then we might have regular expression literals that aren't marked
- ;; as strings, which will screw up parse-partial-sexp, scan-lists,
- ;; etc. and produce maddening "unbalanced parenthesis" errors.
- ;; When we attempt to find the error and scroll to the portion of
- ;; the buffer containing the problem, JIT-lock will apply the
- ;; correct syntax to the regular expression literal and the problem
- ;; 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))
- ))
-
-(defcustom js-json-use-tree-sitter nil
- "If non-nil, `js-json-mode' tries to use tree-sitter.
-Currently `js-json-mode' uses tree-sitter for font-locking and
-indentation."
- :version "29.1"
- :type 'boolean
- :safe 'booleanp)
+ ;; Parse cache
+ (add-hook 'before-change-functions #'js--flush-caches t t)
+
+ ;; Frameworks
+ (js--update-quick-match-re)
+
+ ;; Syntax extensions
+ (unless (js-jsx--detect-and-enable)
+ (add-hook 'after-change-functions #'js-jsx--detect-after-change nil t))
+ (js-use-syntactic-mode-name)
+
+ ;; 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)
+ (setq-local comment-line-break-function #'c-indent-new-comment-line)
+ (setq-local comment-multi-line t)
+ (setq-local electric-indent-chars
+ (append "{}():;," electric-indent-chars)) ;FIXME: js2-mode adds "[]*".
+ (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)
+ (make-local-variable 'paragraph-start)
+ (make-local-variable 'paragraph-separate)
+ (make-local-variable 'paragraph-ignore-fill-prefix)
+ (make-local-variable 'adaptive-fill-mode)
+ (make-local-variable 'adaptive-fill-regexp)
+ ;; While the full CC Mode style system is not yet in use, set the
+ ;; pertinent style variables manually.
+ (c-initialize-builtin-style)
+ (let ((style (cc-choose-style-for-mode 'js-mode c-default-style)))
+ (c-set-style style))
+ (setq c-block-comment-prefix "* "
+ c-comment-prefix-regexp "//+\\|\\**")
+ (c-setup-paragraph-variables))
+
+ ;; Important to fontify the whole buffer syntactically! If we don't,
+ ;; then we might have regular expression literals that aren't marked
+ ;; as strings, which will screw up parse-partial-sexp, scan-lists,
+ ;; etc. and produce maddening "unbalanced parenthesis" errors.
+ ;; When we attempt to find the error and scroll to the portion of
+ ;; the buffer containing the problem, JIT-lock will apply the
+ ;; correct syntax to the regular expression literal and the problem
+ ;; 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))
(defvar js--json-treesit-settings
(treesit-font-lock-rules
((parent-is "object") parent-bol ,js-indent-level)
)))
-
-(defun js--json-treesit-can-enable-p ()
- (if (and js-json-use-tree-sitter
- (treesit-can-enable-p)
- (treesit-language-available-p 'json))
- t
- (error "Cannot enable Tree Sitter for JSON.")
- nil))
-
-
-(defun js--json-treesit-enable ()
- (setq-local treesit-simple-indent-rules js--json-treesit-indent-rules)
- (setq-local indent-line-function #'treesit-indent)
-
- (setq-local beginning-of-defun-function #'ignore)
- (setq-local end-of-defun-function #'ignore)
-
- (setq-local font-lock-defaults '(nil t))
- (setq-local treesit-font-lock-settings js--json-treesit-settings)
-
- (treesit-font-lock-enable))
-
+(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-settings)
+ (treesit-font-lock-enable))
+ ;; Elisp.
+ ((eq backend 'elisp)
+ (js--backend-toggle 'elisp nil))))
;;;###autoload
(define-derived-mode js-json-mode js-mode "JSON"
- (if (js--json-treesit-can-enable-p)
- (js--json-treesit-enable)
- (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 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))
;; Since we made JSX support available and automatically-enabled in
;; the base `js-mode' (for ease of use), now `js-jsx-mode' simply
`js-jsx-enable' in `js-mode-hook'. You may be better served by
one of the aforementioned options instead of using this mode."
:group 'js
- (if (js--treesit-can-enable-p)
- (js--treesit-enable)
- (js-jsx-enable)
- (setq-local comment-region-function #'js-jsx--comment-region)
- (js-use-syntactic-mode-name)))
+ (js-jsx-enable)
+ (setq-local comment-region-function #'js-jsx--comment-region)
+ (js-use-syntactic-mode-name))
(defun js-jsx--comment-region (beg end &optional arg)
(if (or (js-jsx--context)
:version "24.3"
:link '(emacs-commentary-link "python"))
-(defcustom python-use-tree-sitter nil
- "If non-nil, `python-mode' tries to use tree-sitter.
-Currently `python-mode' uses tree-sitter for font-locking, imenu,
-and movement functions."
- :type 'boolean
- :version "29.1")
-
(defcustom python-interpreter "python"
"Python interpreter for noninteractive use.
To customize the Python shell, modify `python-shell-interpreter'
(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'."
+ (if (and (eq backend 'treesit) (treesit-ready-p warn 'python))
+ (progn
+ (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))
+ (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.
(setq-local forward-sexp-function python-forward-sexp-function)
- (if (and python-use-tree-sitter
- (treesit-can-enable-p))
- (progn
- (setq-local font-lock-keywords-only t)
- (setq-local treesit-font-lock-feature-list
- '((basic) (moderate) (elaborate)))
- (setq-local treesit-font-lock-settings
- python--treesit-settings)
- (treesit-font-lock-enable))
- (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))))
+ (python--backend-toggle 'elisp nil)
+
+ (setq-local major-mode-backend-function #'python--backend-toggle)
(setq-local syntax-propertize-function
python-syntax-propertize-function)
(add-hook 'post-self-insert-hook
#'python-indent-post-self-insert-function 'append 'local)
- (if (and python-use-tree-sitter
- (treesit-can-enable-p))
- (setq-local imenu-create-index-function
- #'python-imenu-treesit-create-index)
- (setq-local imenu-create-index-function
- #'python-imenu-create-index))
-
(setq-local add-log-current-defun-function
#'python-info-current-defun)
- (if (and python-use-tree-sitter
- (treesit-can-enable-p))
- (add-hook 'which-func-functions
- #'python-info-treesit-current-defun nil t)
- (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
: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")
-(defun treesit-can-enable-p ()
- "Return non-nil if current buffer can activate tree-sitter.
-Currently this function checks whether tree-sitter is available
-and the buffer size."
- (and (treesit-available-p)
- (< (buffer-size) treesit-max-buffer-size)))
+(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 'languages
+ (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
+ :group 'languages
+ :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