From: Yuan Fu Date: Tue, 18 Oct 2022 20:06:25 +0000 (-0700) Subject: Install new toggle scheme for tree-sitter X-Git-Tag: emacs-29.0.90~1813 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=17b65f52921b6a9314a2709f0e5c3ef88b37f2ab;p=emacs.git Install new toggle scheme for tree-sitter Basically we now have treesit-mode and global-treesit-mode. Major modes set major-mode-backend-function which treesit-mode calls to activate/deactivate tree-sitter. js.el needs a bit explanation: I'm so sorry for messing up the history, but basically now js-mode and js-json-mode should be exactly the same as before any tree-sitter change was introduced, sans some initialization code that are moved to js(-json)--backend-toggle. js-mode and js-json-mode now just sets major-mode-backend-function and initialize with js(-json)--backend-toggle. * lisp/treesit.el (treesit-mode-inhibit-message): New option. (treesit-can-enable-p): Remove function. (major-mode-backend-function) (treesit-remapped-major-mode-alist): New variables. (treesit-mode) (global-treesit-mode): New minor modes. (global-treesit-mode--turn-on) (treesit-ready-p): New functions. * lisp/progmodes/python.el: Remove option. (python--backend-toggle): New function. (python-mode): Remove the if-form, all the initialization code are moved to python--backend-toggle, python-mode now just sets major-mode-backend-function and initialize with python--backend-toggle. * lisp/progmodes/js.el (js--treesit-can-enable-p) (js--json-treesit-can-enable-p) (js--treesit-enable) (js--json-treesit-enable): Remove functions. (js--backend-toggle) (js-json--backend-toggle): New function. * lisp/progmodes/ts-mode.el (ts-mode): Use treesit-ready-p. --- diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el index 667416852ef..51f5b720479 100644 --- a/lisp/progmodes/js.el +++ b/lisp/progmodes/js.el @@ -3639,28 +3639,39 @@ ARG is the same as in `end-of-defun." 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 @@ -3668,98 +3679,79 @@ ARG is the same as in `end-of-defun." (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 @@ -3791,37 +3783,29 @@ indentation." ((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 @@ -3848,11 +3832,9 @@ could set `js-jsx-syntax' to t in your init file, or in a `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) diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index c48f9e95e98..374bc02a78c 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -286,13 +286,6 @@ :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' @@ -6382,6 +6375,32 @@ 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'." + (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. @@ -6398,22 +6417,9 @@ Add import for undefined name `%s' (empty to skip): " (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) @@ -6442,22 +6448,9 @@ Add import for undefined name `%s' (empty to skip): " (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 diff --git a/lisp/progmodes/ts-mode.el b/lisp/progmodes/ts-mode.el index 6e4aeebde87..dffe9fcfcf2 100644 --- a/lisp/progmodes/ts-mode.el +++ b/lisp/progmodes/ts-mode.el @@ -338,8 +338,7 @@ ARG is the same as in `end-of-defun." :syntax-table ts-mode--syntax-table (cond - ((and (treesit-can-enable-p) - (treesit-language-available-p 'tsx)) + ((treesit-ready-p nil 'tsx) ;; Comments (setq-local comment-start "// ") (setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *") @@ -351,15 +350,13 @@ ARG is the same as in `end-of-defun." (setq-local beginning-of-defun-function #'ts-mode--beginning-of-defun) (setq-local end-of-defun-function #'ts-mode--end-of-defun) - (unless font-lock-defaults - (setq font-lock-defaults '(nil t))) - + (setq font-lock-keywords-only t) (setq-local treesit-font-lock-settings ts-mode--settings) (setq treesit-font-lock-feature-list '((basic))) (treesit-font-lock-enable)) (t - (message "Tree sitter for TypeScript isn't available, defaulting to js-mode") + (message "Tree-sitter for TypeScript isn't available, falling back to `js-mode'") (js-mode)))) (provide 'ts-mode) diff --git a/lisp/treesit.el b/lisp/treesit.el index 86da9e15866..91ffb1eff15 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -45,14 +45,103 @@ :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