]> git.eshelyaron.com Git - emacs.git/commitdiff
Fix block comment indent and filling for c-ts-mode (bug#59763)
authorYuan Fu <casouri@gmail.com>
Sat, 24 Dec 2022 01:12:32 +0000 (17:12 -0800)
committerYuan Fu <casouri@gmail.com>
Sat, 24 Dec 2022 08:33:17 +0000 (00:33 -0800)
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
lisp/progmodes/c-ts-mode.el
lisp/treesit.el

index c44938f392987908c2b2c7316da71aa8a04c3153..736c2d6841f19645fd44f7feca77ae5d3ca4b33f 100644 (file)
@@ -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
 
index ea9891f3345b2df5ced56adee0c9c12ff7fc4cb6..901b22e3c01dcd725254bbf87ff465b6bd7c0533 100644 (file)
@@ -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))
index ec5b3e399f9b441def174012ab19dc3fc8a58e5b..845e6ab3883c3fbd3731d135d1995a7d82ee6998 100644 (file)
@@ -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.