unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Philip Kaludercic <philipk@posteo.net>
To: Eli Zaretskii <eliz@gnu.org>
Cc: emacs-devel@gnu.org, Yuan Fu <casouri@gmail.com>
Subject: Unifying "foo-mode"s and "foo-ts-mode"s
Date: Fri, 30 Dec 2022 10:58:39 +0000	[thread overview]
Message-ID: <877cy9b1k0.fsf_-_@posteo.net> (raw)
In-Reply-To: <83sfgy6l0n.fsf@gnu.org> (Eli Zaretskii's message of "Thu, 29 Dec 2022 21:57:12 +0200")

[-- Attachment #1: Type: text/plain, Size: 2605 bytes --]

Eli Zaretskii <eliz@gnu.org> writes:

> You can try.  I would like to start a full feature freeze in a day or
> two, so I'm not sure you will have enough time.  And it isn't like we
> didn't try various approaches during the past two months, so frankly I
> don't think that a better way even exists.  But if you come up with
> some very bright idea, who knows?

I have attached a sketch of my proposal with support for Python.
Instead of a separate python-ts-mode, we regulate tree-sitter support
using a user option `treesit-enabled-modes'.  It can either be a list

--8<---------------cut here---------------start------------->8---
  (setq treesit-enabled-modes '(python-mode c-mode))
--8<---------------cut here---------------end--------------->8---

or generally enable tree-sitter

--8<---------------cut here---------------start------------->8---
  (setq treesit-enabled-modes t)
--8<---------------cut here---------------end--------------->8---

All a major modes has to do is pass a parser configuration

--8<---------------cut here---------------start------------->8---
  (define-derived-mode python-mode prog-mode "Python"
    "Major mode for editing Python files.

  \\{python-mode-map}"
    :syntax-table python-mode-syntax-table
    :parser-conf python-mode--treesit-conf
   ...
--8<---------------cut here---------------end--------------->8---

that expands to

--8<---------------cut here---------------start------------->8---
  (when-let
      ((conf python-mode--treesit-conf)
       ((cond
         ((listp treesit-enabled-modes)
          (memq 'python-mode treesit-enabled-modes))
         ((eq treesit-enabled-modes t))))
       ((treesit-ready-p
         (nth 0 conf)))
       (parser
        (treesit-parser-create
         (nth 0 conf))))
    (setq-local treesit-font-lock-feature-list
                (nth 1 conf)
                treesit-font-lock-settings
                (nth 2 conf)
                treesit-defun-name-function
                (nth 3 conf)
                treesit-defun-type-regexp
                (nth 4 conf)
                imenu-create-index-function
                (nth 5 conf))
    (treesit-major-mode-setup))
--8<---------------cut here---------------end--------------->8---

at *the end* of the major mode definition.  Note that if no parser
configuration was parsed, the entire expression is byte-compiled away,
so there is no run-time overhead for other modes.

The parser configuration is currently a list but if might as well be a
vector, a structure or anything else.  This is just a rushed proposal to
meet the deadline.  How does it look like?


[-- Attachment #2: Type: text/plain, Size: 15287 bytes --]

diff --git a/lisp/emacs-lisp/derived.el b/lisp/emacs-lisp/derived.el
index 260fc3bf47..d6a1f4af00 100644
--- a/lisp/emacs-lisp/derived.el
+++ b/lisp/emacs-lisp/derived.el
@@ -143,6 +143,8 @@ define-derived-mode
            :interactive BOOLEAN
                    Whether the derived mode should be `interactive' or not.
                    The default is t.
+           :parser-conf CONF
+                   A tree-sitter parser configuration.
 
 BODY:      forms to execute just before running the
            hooks for the new mode.  Do not use `interactive' here.
@@ -192,6 +194,7 @@ define-derived-mode
 	(hook (derived-mode-hook-name child))
 	(group nil)
         (interactive t)
+        (parser-conf nil)
         (after-hook nil))
 
     ;; Process the keyword args.
@@ -202,6 +205,7 @@ define-derived-mode
 	(:syntax-table (setq syntax (pop body)) (setq declare-syntax nil))
         (:after-hook (setq after-hook (pop body)))
         (:interactive (setq interactive (pop body)))
+        (:parser-conf (setq parser-conf (pop body)))
 	(_ (pop body))))
 
     (setq docstring (derived-mode-make-docstring
@@ -285,7 +289,22 @@ define-derived-mode
 	  ,(when abbrev `(setq local-abbrev-table ,abbrev))
 					; Splice in the body (if any).
 	  ,@body
-	  )
+
+          ;; Activate tree-sitter if requested and available.
+          (when-let ((conf ,parser-conf)
+                      ((cond
+                        ((listp treesit-enabled-modes)
+                         (memq ',child treesit-enabled-modes))
+                        ((eq treesit-enabled-modes t))))
+                      ((treesit-ready-p (nth 0 conf)))
+                      (parser (treesit-parser-create (nth 0 conf))))
+             (setq-local
+              treesit-font-lock-feature-list (nth 1 conf)
+              treesit-font-lock-settings     (nth 2 conf)
+              treesit-defun-name-function    (nth 3 conf)
+              treesit-defun-type-regexp      (nth 4 conf)
+              imenu-create-index-function    (nth 5 conf))
+             (treesit-major-mode-setup)))
 	 ,@(when after-hook
 	     `((push (lambda () ,after-hook) delayed-after-hook-functions)))
 	 ;; Run the hooks (and delayed-after-hook-functions), if any.
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index 9a6f807f4f..5ad90e6d71 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -405,9 +405,6 @@ python-mode-map
     map)
   "Keymap for `python-mode'.")
 
-(defvar python-ts-mode-map (copy-keymap python-mode-map)
-  "Keymap for `(copy-keymap python-mode-map)'.")
-
 \f
 ;;; Python specialized rx
 
@@ -1072,124 +1069,6 @@ python--treesit-fontify-string
     (treesit-fontify-with-override
      string-beg string-end face override start end)))
 
-(defvar python--treesit-settings
-  (treesit-font-lock-rules
-   :feature 'comment
-   :language 'python
-   '((comment) @font-lock-comment-face)
-
-   :feature 'string
-   :language 'python
-   '((string) @python--treesit-fontify-string)
-
-   :feature 'string-interpolation
-   :language 'python
-   :override t
-   '((interpolation (identifier) @font-lock-variable-name-face))
-
-   :feature 'definition
-   :language 'python
-   '((function_definition
-      name: (identifier) @font-lock-function-name-face)
-     (class_definition
-      name: (identifier) @font-lock-type-face))
-
-   :feature 'function
-   :language 'python
-   '((function_definition
-      name: (identifier) @font-lock-function-name-face)
-     (call function: (identifier) @font-lock-function-name-face)
-     (call function: (attribute
-                      attribute: (identifier) @font-lock-function-name-face)))
-
-   :feature 'keyword
-   :language 'python
-   `([,@python--treesit-keywords] @font-lock-keyword-face
-     ((identifier) @font-lock-keyword-face
-      (:match "^self$" @font-lock-keyword-face)))
-
-   :feature 'builtin
-   :language 'python
-   `(((identifier) @font-lock-builtin-face
-      (:match ,(rx-to-string
-                `(seq bol
-                      (or ,@python--treesit-builtins
-                          ,@python--treesit-special-attributes)
-                      eol))
-              @font-lock-builtin-face)))
-
-   :feature 'constant
-   :language 'python
-   '([(true) (false) (none)] @font-lock-constant-face)
-
-   :feature 'assignment
-   :language 'python
-   `(;; Variable names and LHS.
-     (assignment left: (identifier)
-                 @font-lock-variable-name-face)
-     (assignment left: (attribute
-                        attribute: (identifier)
-                        @font-lock-property-face))
-     (pattern_list (identifier)
-                   @font-lock-variable-name-face)
-     (tuple_pattern (identifier)
-                    @font-lock-variable-name-face)
-     (list_pattern (identifier)
-                   @font-lock-variable-name-face)
-     (list_splat_pattern (identifier)
-                         @font-lock-variable-name-face))
-
-   :feature 'decorator
-   :language 'python
-   '((decorator "@" @font-lock-type-face)
-     (decorator (call function: (identifier) @font-lock-type-face))
-     (decorator (identifier) @font-lock-type-face))
-
-   :feature 'type
-   :language 'python
-   `(((identifier) @font-lock-type-face
-      (:match ,(rx-to-string
-                `(seq bol (or ,@python--treesit-exceptions)
-                      eol))
-              @font-lock-type-face))
-     (type (identifier) @font-lock-type-face))
-
-   :feature 'escape-sequence
-   :language 'python
-   :override t
-   '((escape_sequence) @font-lock-escape-face)
-
-   :feature 'number
-   :language 'python
-   '([(integer) (float)] @font-lock-number-face)
-
-   :feature 'property
-   :language 'python
-   '((attribute
-      attribute: (identifier) @font-lock-property-face)
-     (class_definition
-      body: (block
-             (expression_statement
-              (assignment left:
-                          (identifier) @font-lock-property-face)))))
-
-   :feature 'operator
-   :language 'python
-   `([,@python--treesit-operators] @font-lock-operator-face)
-
-   :feature 'bracket
-   :language 'python
-   '(["(" ")" "[" "]" "{" "}"] @font-lock-bracket-face)
-
-   :feature 'delimiter
-   :language 'python
-   '(["," "." ":" ";" (ellipsis)] @font-lock-delimiter-face)
-
-   :feature 'variable
-   :language 'python
-   '((identifier) @python--treesit-fontify-variable))
-  "Tree-sitter font-lock settings.")
-
 (defun python--treesit-variable-p (node)
   "Check whether NODE is a variable.
 NODE's type should be \"identifier\"."
@@ -6541,13 +6420,145 @@ python-electric-pair-string-delimiter
 (defvar electric-indent-inhibit)
 (defvar prettify-symbols-alist)
 
+(defvar python-mode--treesit-conf
+  (list
+   'python
+   ;; font-lock feature list
+   '(( comment definition)
+     ( keyword string type)
+     ( assignment builtin constant decorator
+       escape-sequence number property string-interpolation )
+     ( bracket delimiter function operator variable))
+   ;; font-lock settings
+   (treesit-font-lock-rules
+    :feature 'comment
+    :language 'python
+    '((comment) @font-lock-comment-face)
+
+    :feature 'string
+    :language 'python
+    '((string) @python--treesit-fontify-string)
+
+    :feature 'string-interpolation
+    :language 'python
+    :override t
+    '((interpolation (identifier) @font-lock-variable-name-face))
+
+    :feature 'definition
+    :language 'python
+    '((function_definition
+       name: (identifier) @font-lock-function-name-face)
+      (class_definition
+       name: (identifier) @font-lock-type-face))
+
+    :feature 'function
+    :language 'python
+    '((function_definition
+       name: (identifier) @font-lock-function-name-face)
+      (call function: (identifier) @font-lock-function-name-face)
+      (call function: (attribute
+                       attribute: (identifier) @font-lock-function-name-face)))
+
+    :feature 'keyword
+    :language 'python
+    `([,@python--treesit-keywords] @font-lock-keyword-face
+      ((identifier) @font-lock-keyword-face
+       (:match "^self$" @font-lock-keyword-face)))
+
+    :feature 'builtin
+    :language 'python
+    `(((identifier) @font-lock-builtin-face
+       (:match ,(rx-to-string
+                 `(seq bol
+                       (or ,@python--treesit-builtins
+                           ,@python--treesit-special-attributes)
+                       eol))
+               @font-lock-builtin-face)))
+
+    :feature 'constant
+    :language 'python
+    '([(true) (false) (none)] @font-lock-constant-face)
+
+    :feature 'assignment
+    :language 'python
+    `(;; Variable names and LHS.
+      (assignment left: (identifier)
+                  @font-lock-variable-name-face)
+      (assignment left: (attribute
+                         attribute: (identifier)
+                         @font-lock-property-face))
+      (pattern_list (identifier)
+                    @font-lock-variable-name-face)
+      (tuple_pattern (identifier)
+                     @font-lock-variable-name-face)
+      (list_pattern (identifier)
+                    @font-lock-variable-name-face)
+      (list_splat_pattern (identifier)
+                          @font-lock-variable-name-face))
+
+    :feature 'decorator
+    :language 'python
+    '((decorator "@" @font-lock-type-face)
+      (decorator (call function: (identifier) @font-lock-type-face))
+      (decorator (identifier) @font-lock-type-face))
+
+    :feature 'type
+    :language 'python
+    `(((identifier) @font-lock-type-face
+       (:match ,(rx-to-string
+                 `(seq bol (or ,@python--treesit-exceptions)
+                       eol))
+               @font-lock-type-face))
+      (type (identifier) @font-lock-type-face))
+
+    :feature 'escape-sequence
+    :language 'python
+    :override t
+    '((escape_sequence) @font-lock-escape-face)
+
+    :feature 'number
+    :language 'python
+    '([(integer) (float)] @font-lock-number-face)
+
+    :feature 'property
+    :language 'python
+    '((attribute
+       attribute: (identifier) @font-lock-property-face)
+      (class_definition
+       body: (block
+              (expression_statement
+               (assignment left:
+                           (identifier) @font-lock-property-face)))))
+
+    :feature 'operator
+    :language 'python
+    `([,@python--treesit-operators] @font-lock-operator-face)
+
+    :feature 'bracket
+    :language 'python
+    '(["(" ")" "[" "]" "{" "}"] @font-lock-bracket-face)
+
+    :feature 'delimiter
+    :language 'python
+    '(["," "." ":" ";" (ellipsis)] @font-lock-delimiter-face)
+
+    :feature 'variable
+    :language 'python
+    '((identifier) @python--treesit-fontify-variable))
+   ;; defun name function
+   #'python--treesit-defun-name
+   ;; defun regexp
+   (rx (or "function" "class") "_definition")
+   ;; imenu function
+   #'python-imenu-create-index))
+
 ;;;###autoload
-(define-derived-mode python-base-mode prog-mode "Python"
-  "Generic major mode for editing Python files.
+(define-derived-mode python-mode prog-mode "Python"
+  "Major mode for editing Python files.
 
-This is a generic major mode intended to be inherited by
-concrete implementations.  Currently there are two concrete
-implementations: `python-mode' and `python-ts-mode'."
+\\{python-mode-map}"
+  :syntax-table python-mode-syntax-table
+  :parser-conf python-mode--treesit-conf
   (setq-local tab-width 8)
   (setq-local indent-tabs-mode nil)
 
@@ -6603,20 +6614,19 @@ python-base-mode
                       #'python-eldoc-function))))
 
   ;; TODO: Use tree-sitter to figure out the block in `python-ts-mode'.
-  (dolist (mode '(python-mode python-ts-mode))
-    (add-to-list
-     'hs-special-modes-alist
-     `(,mode
-       ,python-nav-beginning-of-block-regexp
-       ;; Use the empty string as end regexp so it doesn't default to
-       ;; "\\s)".  This way parens at end of defun are properly hidden.
-       ""
-       "#"
-       python-hideshow-forward-sexp-function
-       nil
-       python-nav-beginning-of-block
-       python-hideshow-find-next-block
-       python-info-looking-at-beginning-of-block)))
+  (add-to-list
+   'hs-special-modes-alist
+   `(python-mode
+     ,python-nav-beginning-of-block-regexp
+     ;; Use the empty string as end regexp so it doesn't default to
+     ;; "\\s)".  This way parens at end of defun are properly hidden.
+     ""
+     "#"
+     python-hideshow-forward-sexp-function
+     nil
+     python-nav-beginning-of-block
+     python-hideshow-find-next-block
+     python-info-looking-at-beginning-of-block))
 
   (setq-local outline-regexp (python-rx (* space) block-start))
   (setq-local outline-level
@@ -6630,13 +6640,8 @@ python-base-mode
 
   (make-local-variable 'python-shell-internal-buffer)
 
-  (add-hook 'flymake-diagnostic-functions #'python-flymake nil t))
+  (add-hook 'flymake-diagnostic-functions #'python-flymake nil t)
 
-;;;###autoload
-(define-derived-mode python-mode python-base-mode "Python"
-  "Major mode for editing Python files.
-
-\\{python-mode-map}"
   (setq-local font-lock-defaults
               `(,python-font-lock-keywords
                 nil nil nil nil
@@ -6654,32 +6659,6 @@ python-mode
   (when python-indent-guess-indent-offset
     (python-indent-guess-indent-offset)))
 
-;;;###autoload
-(define-derived-mode python-ts-mode python-base-mode "Python"
-  "Major mode for editing Python files, using tree-sitter library.
-
-\\{python-ts-mode-map}"
-  :syntax-table python-mode-syntax-table
-  (when (treesit-ready-p 'python)
-    (treesit-parser-create 'python)
-    (setq-local treesit-font-lock-feature-list
-                '(( comment definition)
-                  ( keyword string type)
-                  ( assignment builtin constant decorator
-                    escape-sequence number property string-interpolation )
-                  ( bracket delimiter function operator variable)))
-    (setq-local treesit-font-lock-settings python--treesit-settings)
-    (setq-local imenu-create-index-function
-                #'python-imenu-treesit-create-index)
-    (setq-local treesit-defun-type-regexp (rx (or "function" "class")
-                                              "_definition"))
-    (setq-local treesit-defun-name-function
-                #'python--treesit-defun-name)
-    (treesit-major-mode-setup)
-
-    (when python-indent-guess-indent-offset
-      (python-indent-guess-indent-offset))))
-
 ;;; Completion predicates for M-x
 ;; Commands that only make sense when editing Python code
 (dolist (sym '(python-add-import
diff --git a/lisp/treesit.el b/lisp/treesit.el
index 2130cd0061..ba38a7d9b2 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -99,6 +99,15 @@ treesit
   :group 'tools
   :version "29.1")
 
+(defcustom treesit-enabled-modes nil
+  "List of modes to enable tree-sitter support if available.
+When initialising a major mode with potential tree-sitter
+support, this variable is consulted.  The special value t will
+enable tree-sitter support whenever possible."
+  :type '(choice (const :tag "Whenever possible" t)
+                 (repeat :tag "Specific modes" function))
+  :version "29.1")
+
 (defcustom treesit-max-buffer-size
   (let ((mb (* 1024 1024)))
     ;; 40MB for 64-bit systems, 15 for 32-bit.

  parent reply	other threads:[~2022-12-30 10:58 UTC|newest]

Thread overview: 35+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-12-29 17:08 Need for "-ts-mode" modes Philip Kaludercic
2022-12-29 17:42 ` Eli Zaretskii
2022-12-29 18:26   ` Philip Kaludercic
2022-12-29 19:27     ` Yuan Fu
2022-12-29 19:42       ` Eli Zaretskii
2022-12-30  4:05         ` Richard Stallman
2022-12-30  8:49           ` Eli Zaretskii
2022-12-29 19:57     ` Eli Zaretskii
2022-12-29 20:23       ` Eli Zaretskii
2022-12-30 10:58       ` Philip Kaludercic [this message]
2022-12-30 12:50         ` Unifying "foo-mode"s and "foo-ts-mode"s Theodor Thornhill
2022-12-30 13:08           ` Philip Kaludercic
2022-12-30 13:19             ` Theodor Thornhill
2022-12-30 15:02               ` Philip Kaludercic
2022-12-30 15:24                 ` Theodor Thornhill
2022-12-30 15:45                   ` Philip Kaludercic
2022-12-30 15:42                 ` Eli Zaretskii
2022-12-30 15:57                   ` Philip Kaludercic
2022-12-30 16:20                     ` Eli Zaretskii
2022-12-30 16:39                       ` Philip Kaludercic
2022-12-30 17:05                         ` Eli Zaretskii
2022-12-31  0:13                           ` Philip Kaludercic
2022-12-31  6:38                             ` Eli Zaretskii
2022-12-30 15:02         ` Eli Zaretskii
2022-12-30 15:20           ` Philip Kaludercic
2022-12-30 15:52             ` Eli Zaretskii
2022-12-30 16:09               ` Philip Kaludercic
2022-12-30 16:30                 ` Stefan Monnier
2022-12-30 17:03                   ` Eli Zaretskii
2023-01-01  3:03                   ` Richard Stallman
2023-01-01  7:28                     ` Eli Zaretskii
2023-01-03  4:07                       ` Richard Stallman
2023-01-03 12:11                         ` Eli Zaretskii
2023-01-05  3:34                           ` Richard Stallman
2022-12-30 17:10                 ` Gregory Heytings

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=877cy9b1k0.fsf_-_@posteo.net \
    --to=philipk@posteo.net \
    --cc=casouri@gmail.com \
    --cc=eliz@gnu.org \
    --cc=emacs-devel@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).