emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

emacs-29 6a43af5880 2/5: Fix block comment indent and filling for c-ts-m


From: Yuan Fu
Subject: emacs-29 6a43af5880 2/5: Fix block comment indent and filling for c-ts-mode (bug#59763)
Date: Sat, 24 Dec 2022 03:33:27 -0500 (EST)

branch: emacs-29
commit 6a43af58802d46555d692d0934d85d22711e0b56
Author: Yuan Fu <casouri@gmail.com>
Commit: Yuan Fu <casouri@gmail.com>

    Fix block comment indent and filling for c-ts-mode (bug#59763)
    
    Now indent and filling works like in c-mode.  The only noticeable
    missing piece is that the "*/" is not attached to the last sentence
    when filling.  c-mode does it by replacing whitespaces between the
    "*/" and the end of the last sentence with xxx, fill it, then change
    the xxx back. I don't know if we should do that in c-ts-mode's filling.
    
    * doc/lispref/modes.texi (Parser-based Indentation): Add new preset.
    * lisp/progmodes/c-ts-mode.el (c-ts-mode--indent-styles): Add new
    indent rule.
    (c-ts-mode--fill-paragraph): New function.
    (c-ts-base-mode): Setup paragraph-start, adaptive-fill, etc.
    * lisp/treesit.el (treesit-simple-indent-presets): Add new preset.
---
 doc/lispref/modes.texi      |  8 ++++
 lisp/progmodes/c-ts-mode.el | 96 +++++++++++++++++++++++++++++++++++++++++++++
 lisp/treesit.el             | 25 +++++++++++-
 3 files changed, 128 insertions(+), 1 deletion(-)

diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi
index c44938f392..736c2d6841 100644
--- a/doc/lispref/modes.texi
+++ b/doc/lispref/modes.texi
@@ -5024,6 +5024,14 @@ comment-start token.  Comment-start tokens are defined 
by regular
 expression @code{comment-start-skip}.  This function assumes
 @var{parent} is the comment node.
 
+@item prev-adaptive-prefix
+This anchor is a function that is called with 3 arguments: @var{node},
+@var{parent}, and @var{bol}.  It tries to go to the beginning of the
+previous non-empty line, and matches @code{adaptive-fill-regexp}.  If
+there is a match, this function returns the end of the match,
+otherwise it returns nil.  This anchor is useful for a
+@code{indent-relative}-like indent behavior for block comments.
+
 @end ftable
 @end defvar
 
diff --git a/lisp/progmodes/c-ts-mode.el b/lisp/progmodes/c-ts-mode.el
index ea9891f334..901b22e3c0 100644
--- a/lisp/progmodes/c-ts-mode.el
+++ b/lisp/progmodes/c-ts-mode.el
@@ -103,6 +103,7 @@ MODE is either `c' or `cpp'."
            ((node-is "case") parent-bol 0)
            ((node-is "preproc_arg") no-indent)
            ((and (parent-is "comment") comment-end) comment-start -1)
+           ((parent-is "comment") prev-adaptive-prefix 0)
            ((node-is "labeled_statement") parent-bol 0)
            ((parent-is "labeled_statement") parent-bol c-ts-mode-indent-offset)
            ((match "preproc_ifdef" "compound_statement") point-min 0)
@@ -562,6 +563,70 @@ the semicolon.  This function skips the semicolon."
                    (treesit-node-end node))
     (goto-char orig-point)))
 
+(defun c-ts-mode--fill-paragraph (&optional arg)
+  "Fillling function for `c-ts-mode'.
+ARG is passed to `fill-paragraph'."
+  (interactive "*P")
+  (save-restriction
+    (widen)
+    (let* ((node (treesit-node-at (point)))
+           (start (treesit-node-start node))
+           (end (treesit-node-end node))
+           ;; Bind to nil to avoid infinite recursion.
+           (fill-paragraph-function nil)
+           (orig-point (point-marker))
+           (start-marker nil)
+           (end-marker nil)
+           (end-len 0))
+      (when (equal (treesit-node-type node) "comment")
+        ;; We mask "/*" and the space before "*/" like
+        ;; `c-fill-paragraph' does.
+        (atomic-change-group
+          ;; Mask "/*".
+          (goto-char start)
+          (when (looking-at (rx (* (syntax whitespace))
+                                (group "/") "*"))
+            (goto-char (match-beginning 1))
+            (setq start-marker (point-marker))
+            (replace-match " " nil nil nil 1))
+          ;; Mask spaces before "*/" if it is attached at the end
+          ;; of a sentence rather than on its own line.
+          (goto-char end)
+          (when (looking-back (rx (not (syntax whitespace))
+                                  (group (+ (syntax whitespace)))
+                                  "*/")
+                              (line-beginning-position))
+            (goto-char (match-beginning 1))
+            (setq end-marker (point-marker))
+            (setq end-len (- (match-end 1) (match-beginning 1)))
+            (replace-match (make-string end-len ?x)
+                           nil nil nil 1))
+          ;; If "*/" is on its own line, don't included it in the
+          ;; filling region.
+          (when (not end-marker)
+            (goto-char end)
+            (when (looking-back "*/" 2)
+              (backward-char 2)
+              (skip-syntax-backward "-")
+              (setq end (point))))
+          ;; Let `fill-paragraph' do its thing.
+          (goto-char orig-point)
+          (narrow-to-region start end)
+          (funcall #'fill-paragraph arg)
+          ;; Unmask.
+          (when start-marker
+            (goto-char start-marker)
+            (delete-char 1)
+            (insert "/"))
+          (when end-marker
+            (goto-char end-marker)
+            (delete-region (point) (+ end-len (point)))
+            (insert (make-string end-len ?\s))))
+        (goto-char orig-point))
+      ;; Return t so `fill-paragraph' doesn't attempt to fill by
+      ;; itself.
+      t)))
+
 (defvar-keymap c-ts-mode-map
   :doc "Keymap for the C language with tree-sitter"
   :parent prog-mode-map
@@ -593,6 +658,37 @@ the semicolon.  This function skips the semicolon."
   (when (eq c-ts-mode-indent-style 'linux)
     (setq-local indent-tabs-mode t))
 
+  (setq-local adaptive-fill-mode t)
+  ;; This matches (1) empty spaces (the default), (2) "//", (3) "*",
+  ;; but do not match "/*", because we don't want to use "/*" as
+  ;; prefix when filling.  (Actually, it doesn't matter, because
+  ;; `comment-start-skip' matches "/*" which will cause
+  ;; `fill-context-prefix' to use "/*" as a prefix for filling, that's
+  ;; why we mask the "/*" in `c-ts-mode--fill-paragraph'.)
+  (setq-local adaptive-fill-regexp
+              (concat (rx (* (syntax whitespace))
+                          (group (or (seq "/" (+ "/")) (* "*"))))
+                      adaptive-fill-regexp))
+  ;; Same as `adaptive-fill-regexp'.
+  (setq-local adaptive-fill-first-line-regexp
+              (rx bos
+                  (seq (* (syntax whitespace))
+                       (group (or (seq "/" (+ "/")) (* "*")))
+                       (* (syntax whitespace)))
+                  eos))
+  ;; Same as `adaptive-fill-regexp'.
+  (setq-local paragraph-start
+              (rx (or (seq (* (syntax whitespace))
+                           (group (or (seq "/" (+ "/")) (* "*")))
+                           (* (syntax whitespace))
+                           ;; Add this eol so that in
+                           ;; `fill-context-prefix', `paragraph-start'
+                           ;; doesn't match the prefix.
+                           eol)
+                      "\f")))
+  (setq-local paragraph-separate paragraph-start)
+  (setq-local fill-paragraph-function #'c-ts-mode--fill-paragraph)
+
   ;; Electric
   (setq-local electric-indent-chars
               (append "{}():;," electric-indent-chars))
diff --git a/lisp/treesit.el b/lisp/treesit.el
index ec5b3e399f..845e6ab388 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -1107,6 +1107,22 @@ See `treesit-simple-indent-presets'.")
                   (re-search-forward comment-start-skip)
                   (skip-syntax-backward "-")
                   (point))))
+        (cons 'prev-adaptive-prefix
+              (lambda (_n parent &rest _)
+                (save-excursion
+                  (re-search-backward
+                   (rx (not (or " " "\t" "\n"))) nil t)
+                  (beginning-of-line)
+                  (and (>= (point) (treesit-node-start parent))
+                       ;; `adaptive-fill-regexp' will not match "/*",
+                       ;; so we need to also try `comment-start-skip'.
+                       (or (and adaptive-fill-regexp
+                                (looking-at adaptive-fill-regexp)
+                                (> (- (match-end 0) (match-beginning 0)) 0)
+                                (match-end 0))
+                           (and comment-start-skip
+                                (looking-at comment-start-skip)
+                                (match-end 0)))))))
         ;; TODO: Document.
         (cons 'grand-parent
               (lambda (_n parent &rest _)
@@ -1229,7 +1245,14 @@ comment-start
 
     Goes to the position that `comment-start-skip' would return,
     skips whitespace backwards, and returns the resulting
-    position.  Assumes PARENT is a comment node.")
+    position.  Assumes PARENT is a comment node.
+
+prev-adaptive-prefix
+
+    Goes to the beginning of previous non-empty line, and tries
+    to match `adaptive-fill-regexp'.  If it matches, return the
+    end of the match, otherwise return nil.  This is useful for a
+    `indent-relative'-like indent behavior for block comments.")
 
 (defun treesit--simple-indent-eval (exp)
   "Evaluate EXP.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]