From: Yuan Fu Date: Sat, 24 Dec 2022 01:12:32 +0000 (-0800) Subject: Fix block comment indent and filling for c-ts-mode (bug#59763) X-Git-Tag: emacs-29.0.90~1024 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=6a43af58802d46555d692d0934d85d22711e0b56;p=emacs.git 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. --- diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi index c44938f3929..736c2d6841f 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 ea9891f3345..901b22e3c01 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 ec5b3e399f9..845e6ab3883 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.