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.
This commit is contained in:
Yuan Fu 2022-12-23 17:12:32 -08:00
parent e492c21e81
commit 6a43af5880
No known key found for this signature in database
GPG key ID: 56E19BC57664A442
3 changed files with 128 additions and 1 deletions

View 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

View 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))

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