[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.