master fb5cf238a9d: New variable comment-start-line-regexp (bug#80837)

Sean Whitton <[email protected]> Wed, 29 Jul 2026 07:21:11 -0400 (EDT)
Newsgroups gmane.emacs.diffs
Message-ID <[email protected]>
branch: master
commit fb5cf238a9da9e2b182d27e94fbee63a184fc02f
Author: Yuan Fu <[email protected]>
Commit: Sean Whitton <[email protected]>

    New variable comment-start-line-regexp (bug#80837)
    
    * lisp/newcomment.el (comment-start-line-regexp): New variable
    (bug#80837).
    * lisp/progmodes/c-ts-common.el (c-ts-common-comment-setup):
    * lisp/progmodes/cmake-ts-mode.el (cmake-ts-mode):
    * lisp/progmodes/dockerfile-ts-mode.el (dockerfile-ts-mode):
    * lisp/progmodes/elixir-ts-mode.el (elixir-ts-mode):
    * lisp/progmodes/go-ts-mode.el (go-work-ts-mode):
    * lisp/progmodes/json-ts-mode.el (json-ts-mode):
    * lisp/progmodes/lua-ts-mode.el (lua-ts-mode):
    * lisp/progmodes/php-ts-mode.el (php-ts-mode--comment-setup):
    * lisp/progmodes/python.el (python-base-mode):
    * lisp/progmodes/ruby-mode.el (ruby-base-mode):
    * lisp/progmodes/sh-script.el (sh-base-mode):
    * lisp/textmodes/mhtml-ts-mode.el
    (mhtml-ts-mode--comment-setup):
    * lisp/textmodes/toml-ts-mode.el (toml-ts-mode):
    * lisp/textmodes/yaml-ts-mode.el (yaml-ts-mode): Set it.
    * doc/emacs/programs.texi (Options for Comments):
    * etc/NEWS: Document it.
    * lisp/progmodes/c-ts-common.el
    (c-ts-common-comment-start-line-regexp): New variable.
    * lisp/treesit.el (newcomment): Require.
    (treesit--likely-line-comment-p): New function.
    (treesit-forward-comment): Use it.  Make COUNT parameter
    non-optional.
---
 doc/emacs/programs.texi              |  6 ++++++
 etc/NEWS                             |  6 ++++++
 lisp/newcomment.el                   | 13 +++++++++++++
 lisp/progmodes/c-ts-common.el        |  7 +++++++
 lisp/progmodes/cmake-ts-mode.el      |  1 +
 lisp/progmodes/dockerfile-ts-mode.el |  1 +
 lisp/progmodes/elixir-ts-mode.el     |  1 +
 lisp/progmodes/go-ts-mode.el         |  1 +
 lisp/progmodes/json-ts-mode.el       |  1 +
 lisp/progmodes/lua-ts-mode.el        |  4 ++++
 lisp/progmodes/php-ts-mode.el        |  3 +++
 lisp/progmodes/python.el             |  1 +
 lisp/progmodes/ruby-mode.el          |  1 +
 lisp/progmodes/sh-script.el          |  1 +
 lisp/textmodes/mhtml-ts-mode.el      |  2 ++
 lisp/textmodes/toml-ts-mode.el       |  1 +
 lisp/textmodes/yaml-ts-mode.el       |  1 +
 lisp/treesit.el                      | 26 ++++++++++++++++++++++++--
 18 files changed, 75 insertions(+), 2 deletions(-)

diff --git a/doc/emacs/programs.texi b/doc/emacs/programs.texi
index 7cf7b2ff96f..d5738d684a2 100644
--- a/doc/emacs/programs.texi
+++ b/doc/emacs/programs.texi
@@ -1385,6 +1385,12 @@ comments also.  (Note that @samp{\\} is needed in Lisp syntax to
 include a @samp{\} in the string, which is needed to deny the first
 star its special meaning in regexp syntax.  @xref{Regexp Backslash}.)
 
+@vindex comment-start-line-regexp
+  Modes that support both line and block comments should also set
+@code{comment-start-line-regexp} to a regexp that matches only line
+comment starters.  This lets Emacs distinguish between the two kinds of
+comments.
+
 @vindex comment-start
 @vindex comment-end
   When a comment command makes a new comment, it inserts the value of
diff --git a/etc/NEWS b/etc/NEWS
index 92033851c74..6282d8cf9f6 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -265,6 +265,12 @@ buffer as if it were newly created.
 +++
 ** The new function 'markers-in' returns the set of markers in a region.
 
++++
+** New buffer-local variable 'comment-start-line-regexp'.
+Modes that support both line and block comments should set this
+variable to a regexp that matches only the start of line comments, so
+Emacs can distinguish between line and block comments.
+
 ---
 ** New variable 'completion-frontend-properties'.
 This variable generalizes the 'completion-lazy-hilit' variable added in
diff --git a/lisp/newcomment.el b/lisp/newcomment.el
index 1f8a38b586e..8db8d2969a5 100644
--- a/lisp/newcomment.el
+++ b/lisp/newcomment.el
@@ -128,6 +128,19 @@ by the close of the first pair.")
 ;;;###autoload
 (put 'comment-start-skip 'safe-local-variable 'stringp)
 
+;;;###autoload
+(defvar comment-start-line-regexp nil
+  "Regexp matching the start of a line comment.
+
+Unlike `comment-start-skip', which matches the start of any comment,
+this regexp matches only the start of line comments (as opposed to block
+comments), so it can be used to distinguish between the two.
+
+Modes that support both line and block comments should set this
+variable.")
+;;;###autoload
+(put 'comment-start-line-regexp 'safe-local-variable 'stringp)
+
 ;;;###autoload
 (defvar comment-end-skip nil
   "Regexp to match the end of a comment plus everything back to its body.")
diff --git a/lisp/progmodes/c-ts-common.el b/lisp/progmodes/c-ts-common.el
index d5e6b012fe7..461b6852a26 100644
--- a/lisp/progmodes/c-ts-common.el
+++ b/lisp/progmodes/c-ts-common.el
@@ -60,6 +60,11 @@
       (* (syntax whitespace)))
   "The `comment-start-skip' used by `c-ts-common-comment-setup'.")
 
+(defvar c-ts-common-comment-start-line-regexp
+  (rx (seq "/" (+ "/"))
+      (* (syntax whitespace)))
+  "The `comment-start-line-regexp' used by `c-ts-common-comment-setup'.")
+
 (defun c-ts-common-looking-at-star (_n _p bol &rest _)
   "A tree-sitter simple indent matcher.
 Matches if there is a \"*\" after BOL."
@@ -287,6 +292,7 @@ Set up:
  - `comment-start'
  - `comment-end'
  - `comment-start-skip'
+ - `comment-start-line-regexp'
  - `comment-end-skip'
  - `adaptive-fill-mode'
  - `adaptive-fill-first-line-regexp'
@@ -298,6 +304,7 @@ Set up:
   (setq-local comment-start "// ")
   (setq-local comment-end "")
   (setq-local comment-start-skip c-ts-common-comment-start-skip)
+  (setq-local comment-start-line-regexp c-ts-common-comment-start-line-regexp)
   (setq-local comment-end-skip
               (rx (* (syntax whitespace))
                   (group (or (syntax comment-end)
diff --git a/lisp/progmodes/cmake-ts-mode.el b/lisp/progmodes/cmake-ts-mode.el
index 6abd92b5e1a..678f3b09678 100644
--- a/lisp/progmodes/cmake-ts-mode.el
+++ b/lisp/progmodes/cmake-ts-mode.el
@@ -229,6 +229,7 @@ Return nil if there is no name or if NODE is not a defun node."
     (setq-local comment-start "# ")
     (setq-local comment-end "")
     (setq-local comment-start-skip (rx "#" (* (syntax whitespace))))
+    (setq-local comment-start-line-regexp comment-start-skip)
 
     ;; Defuns.
     (setq-local treesit-defun-type-regexp (rx (or "function" "macro")
diff --git a/lisp/progmodes/dockerfile-ts-mode.el b/lisp/progmodes/dockerfile-ts-mode.el
index b97ec89b99a..551bd1bcaee 100644
--- a/lisp/progmodes/dockerfile-ts-mode.el
+++ b/lisp/progmodes/dockerfile-ts-mode.el
@@ -176,6 +176,7 @@ Return nil if there is no name or if NODE is not a stage node."
     (setq-local comment-start "# ")
     (setq-local comment-end "")
     (setq-local comment-start-skip (rx "#" (* (syntax whitespace))))
+    (setq-local comment-start-line-regexp comment-start-skip)
 
     ;; Imenu.
     (setq-local treesit-simple-imenu-settings
diff --git a/lisp/progmodes/elixir-ts-mode.el b/lisp/progmodes/elixir-ts-mode.el
index 9bda7f0046f..b040113b3dc 100644
--- a/lisp/progmodes/elixir-ts-mode.el
+++ b/lisp/progmodes/elixir-ts-mode.el
@@ -724,6 +724,7 @@ Return nil if NODE is not a defun node or doesn't have a name."
   (setq-local comment-start "# ")
   (setq-local comment-start-skip
               (rx "#" (* (syntax whitespace))))
+  (setq-local comment-start-line-regexp comment-start-skip)
 
   (setq-local comment-end "")
   (setq-local comment-end-skip
diff --git a/lisp/progmodes/go-ts-mode.el b/lisp/progmodes/go-ts-mode.el
index 8de6d0e0700..d7563c3bbf6 100644
--- a/lisp/progmodes/go-ts-mode.el
+++ b/lisp/progmodes/go-ts-mode.el
@@ -725,6 +725,7 @@ what the parent of the node would be if it were a node."
     (setq-local comment-start "// ")
     (setq-local comment-end "")
     (setq-local comment-start-skip (rx "//" (* (syntax whitespace))))
+    (setq-local comment-start-line-regexp comment-start-skip)
 
     ;; Indent.
     (setq-local indent-tabs-mode t
diff --git a/lisp/progmodes/json-ts-mode.el b/lisp/progmodes/json-ts-mode.el
index 9575eaf6cf0..b2ffccecd76 100644
--- a/lisp/progmodes/json-ts-mode.el
+++ b/lisp/progmodes/json-ts-mode.el
@@ -198,6 +198,7 @@ PATH is a list of keys (strings) and indices (numbers)."
   ;; Comments.
   (setq-local comment-start "// ")
   (setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *")
+  (setq-local comment-start-line-regexp "//+\\s *")
   (setq-local comment-end "")
 
   ;; Electric
diff --git a/lisp/progmodes/lua-ts-mode.el b/lisp/progmodes/lua-ts-mode.el
index 2963d5a96af..a9666c97788 100644
--- a/lisp/progmodes/lua-ts-mode.el
+++ b/lisp/progmodes/lua-ts-mode.el
@@ -683,6 +683,10 @@ Calls REPORT-FN directly."
     ;; Comments.
     (setq-local comment-start "--")
     (setq-local comment-start-skip "--\\s-*")
+    (setq-local comment-start-line-regexp
+                (rx (seq "--" (or (not (any "["))
+                                  (seq "[" (zero-or-more "=")
+                                       (not (any "=[")))))))
     (setq-local comment-end "")
 
     ;; Pairs.
diff --git a/lisp/progmodes/php-ts-mode.el b/lisp/progmodes/php-ts-mode.el
index dee26915a31..452a0bffc2f 100644
--- a/lisp/progmodes/php-ts-mode.el
+++ b/lisp/progmodes/php-ts-mode.el
@@ -1405,6 +1405,9 @@ If FORCE is t setup comment for PHP.  Depends on
 						 (seq "/" (+ "/"))
 						 (seq "/" (+ "*")))
 					     (* (syntax whitespace)))
+		      comment-start-line-regexp
+		      (rx (or (seq "#" (or eol (not (any "["))))
+			      (seq "/" (+ "/"))))
 		      ;; reset the state of mhtml-ts-mode--comment-setup
 		      mhtml-ts-mode--comment-current-lang nil))
       ;; otherwise set comment style for other languages.
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index 962f9c5a031..093a8049fdd 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -7358,6 +7358,7 @@ implementations: `python-mode' and `python-ts-mode'."
 
   (setq-local comment-start "# ")
   (setq-local comment-start-skip "#+\\s-*")
+  (setq-local comment-start-line-regexp comment-start-skip)
 
   (setq-local parse-sexp-lookup-properties t)
   (setq-local parse-sexp-ignore-comments t)
diff --git a/lisp/progmodes/ruby-mode.el b/lisp/progmodes/ruby-mode.el
index f2e38e0af46..ff33f0554ba 100644
--- a/lisp/progmodes/ruby-mode.el
+++ b/lisp/progmodes/ruby-mode.el
@@ -2693,6 +2693,7 @@ Currently there are `ruby-mode' and `ruby-ts-mode'."
   (setq-local comment-end "")
   (setq-local comment-column ruby-comment-column)
   (setq-local comment-start-skip "#+ *")
+  (setq-local comment-start-line-regexp comment-start-skip)
 
   (setq-local parse-sexp-ignore-comments t)
   (setq-local parse-sexp-lookup-properties t)
diff --git a/lisp/progmodes/sh-script.el b/lisp/progmodes/sh-script.el
index f9d4310f367..79180da8718 100644
--- a/lisp/progmodes/sh-script.el
+++ b/lisp/progmodes/sh-script.el
@@ -1475,6 +1475,7 @@ implementations.  Currently there are two: `sh-mode' and
   (setq-local paragraph-separate (concat paragraph-start "\\|#!/"))
   (setq-local comment-start "# ")
   (setq-local comment-start-skip "#+[\t ]*")
+  (setq-local comment-start-line-regexp comment-start-skip)
   (setq-local local-abbrev-table sh-mode-abbrev-table)
   (setq-local comint-dynamic-complete-functions
 	      sh-dynamic-complete-functions)
diff --git a/lisp/textmodes/mhtml-ts-mode.el b/lisp/textmodes/mhtml-ts-mode.el
index d53d74e220a..f126fbcac0d 100644
--- a/lisp/textmodes/mhtml-ts-mode.el
+++ b/lisp/textmodes/mhtml-ts-mode.el
@@ -362,11 +362,13 @@ Return nil if there is no name or if NODE is not a defun node."
         ('html
          (setq-local comment-start "<!-- ")
          (setq-local comment-start-skip nil)
+         (setq-local comment-start-line-regexp nil)
          (setq-local comment-end " -->")
          (setq-local comment-end-skip nil))
         ('css
          (setq-local comment-start "/*")
          (setq-local comment-start-skip "/\\*+[ \t]*")
+         (setq-local comment-start-line-regexp nil)
          (setq-local comment-end "*/")
          (setq-local comment-end-skip "[ \t]*\\*+/"))
         ('javascript
diff --git a/lisp/textmodes/toml-ts-mode.el b/lisp/textmodes/toml-ts-mode.el
index 63e3f60edd9..b121d8917d1 100644
--- a/lisp/textmodes/toml-ts-mode.el
+++ b/lisp/textmodes/toml-ts-mode.el
@@ -146,6 +146,7 @@ Return nil if there is no name or if NODE is not a defun node."
     ;; Comments
     (setq-local comment-start "# ")
     (setq-local comment-end "")
+    (setq-local comment-start-line-regexp "#+ *")
 
     ;; Indent.
     (setq-local treesit-simple-indent-rules toml-ts-mode--indent-rules)
diff --git a/lisp/textmodes/yaml-ts-mode.el b/lisp/textmodes/yaml-ts-mode.el
index 37925f69782..95fd4b6a78a 100644
--- a/lisp/textmodes/yaml-ts-mode.el
+++ b/lisp/textmodes/yaml-ts-mode.el
@@ -271,6 +271,7 @@ Calls REPORT-FN directly."
     (setq-local comment-start "# ")
     (setq-local comment-end "")
     (setq-local comment-start-skip "#+ *")
+    (setq-local comment-start-line-regexp comment-start-skip)
 
     ;; Indentation.
     (setq-local indent-tabs-mode nil)
diff --git a/lisp/treesit.el b/lisp/treesit.el
index 107b41abef6..8a1fb1ca86c 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -59,6 +59,7 @@
 (require 'font-lock)
 (require 'seq)
 (require 'prog-mode) ; For `prog--text-at-point-or-region-p'.
+(require 'newcomment) ; For `comment-start-line-regexp'.
 
 ;;; Function declarations
 
@@ -3764,7 +3765,20 @@ by `text' and `sentence' in `treesit-thing-settings'."
                      (max (point-min) (previous-single-char-property-change
                                        (point) 'treesit-parser)))))))
 
-(defun treesit-forward-comment (&optional count)
+(defun treesit--likely-line-comment-p (node)
+  "Return non-nil if NODE is likely a line comment."
+  (save-excursion
+    (goto-char (treesit-node-start node))
+    (if comment-start-line-regexp
+        (looking-at-p comment-start-line-regexp)
+      ;; Without `comment-start-line-regexp', it's kind of best-effort.
+      (and comment-start
+           ;; If `comment-end' is non-empty, `comment-start' must be
+           ;; paired with it.
+           (string-empty-p (string-trim (or comment-end "")))
+           (looking-at-p (regexp-quote (string-trim-right comment-start)))))))
+
+(defun treesit-forward-comment (count)
   "Tree-sitter `forward-comment-function' implementation.
 
 COUNT is the same as in `forward-comment'."
@@ -3774,7 +3788,15 @@ COUNT is the same as in `forward-comment'."
       (setq thing (treesit-thing-at (point) 'comment))
       (if (and thing (eq (point) (treesit-node-start thing)))
           (progn
-            (goto-char (min (1+ (treesit-node-end thing)) (point-max)))
+            (goto-char (treesit-node-end thing))
+            ;; For line comments, go to the next line.  This is
+            ;; important because a) for navigation convenience, and b)
+            ;; many functions expect `forward-comment' to behave this
+            ;; way (bug#80837).
+            (when (treesit--likely-line-comment-p thing)
+              (skip-chars-forward " \t")
+              (when (looking-at-p "\n")
+                (forward-char)))
             (setq count (1- count)))
         (setq count 0 res nil)))
     (while (< count 0)