emacs-elpa-diffs
[Top][All Lists]
Advanced

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

[nongnu] elpa/swift-mode 8f11068d84 1/3: Support `fill-region' and auto


From: ELPA Syncer
Subject: [nongnu] elpa/swift-mode 8f11068d84 1/3: Support `fill-region' and auto filling
Date: Mon, 13 Jun 2022 03:58:51 -0400 (EDT)

branch: elpa/swift-mode
commit 8f11068d84675abbe31a7e8364636bf5e0328beb
Author: taku0 <mxxouy6x3m_github@tatapa.org>
Commit: taku0 <taku0@users.noreply.github.com>

    Support `fill-region' and auto filling
    
    We fill only comments and strings.
    
    Removes `swift-mode:comment-fill-column' in favor of `comment-fill-column'.
---
 src-autoloads.el                    |  10 +-
 swift-mode-fill.el                  | 797 +++++++++++++++++++++++++++++-------
 swift-mode-lexer.el                 |  20 +
 swift-mode.el                       |   7 +-
 test/swift-files/fill/code.swift    |   5 +
 test/swift-files/fill/comment.swift | 249 +++++------
 test/swift-mode-test-fill.el        | 534 ++++++++++++++++++++----
 7 files changed, 1271 insertions(+), 351 deletions(-)

diff --git a/src-autoloads.el b/src-autoloads.el
index a4aeb53a8d..bd2dffbc3c 100644
--- a/src-autoloads.el
+++ b/src-autoloads.el
@@ -15,7 +15,7 @@
   (custom-add-load 'languages 'swift-mode))
 
 (defsubst swift-mode:add-supported-extension-for-speedbar nil "\
-Register .swfit to speedbar." (if (fboundp (quote 
speedbar-add-supported-extension)) (speedbar-add-supported-extension ".swift") 
(add-hook (quote speedbar-load-hook) (lambda nil 
(speedbar-add-supported-extension ".swift")))))
+Register .swift to speedbar." (if (fboundp (quote 
speedbar-add-supported-extension)) (speedbar-add-supported-extension ".swift") 
(add-hook (quote speedbar-load-hook) (lambda nil 
(speedbar-add-supported-extension ".swift")))))
 
 (autoload 'swift-mode "swift-mode" "\
 Major mode for editing Swift code.
@@ -38,6 +38,14 @@ Major mode for editing Swift code.
 
 ;;;***
 
+;;;### (autoloads nil "swift-mode-fill" "swift-mode-fill.el" (0 0
+;;;;;;  0 0))
+;;; Generated autoloads from swift-mode-fill.el
+
+(if (fboundp 'register-definition-prefixes) (register-definition-prefixes 
"swift-mode-fill" '("swift-mode:")))
+
+;;;***
+
 ;;;### (autoloads nil "swift-mode-font-lock" "swift-mode-font-lock.el"
 ;;;;;;  (0 0 0 0))
 ;;; Generated autoloads from swift-mode-font-lock.el
diff --git a/swift-mode-fill.el b/swift-mode-fill.el
index 2b1bef47f0..8481a29083 100644
--- a/swift-mode-fill.el
+++ b/swift-mode-fill.el
@@ -1,8 +1,9 @@
 ;;; swift-mode-fill.el --- Major-mode for Apple's Swift programming language, 
paragraph filling. -*- lexical-binding: t -*-
 
-;; Copyright (C) 2022 Josh Caswell
+;; Copyright (C) 2022 Josh Caswell, taku0
 
-;; Authors: Josh Caswell (https://github.com/woolsweater)
+;; Author: Josh Caswell (https://github.com/woolsweater)
+;;         taku0 (http://github.com/taku0)
 
 ;; This file is not part of GNU Emacs.
 
@@ -27,161 +28,669 @@
 
 (require 'rx)
 (require 'swift-mode-lexer)
+(require 'swift-mode-beginning-of-defun)
 
-(defcustom swift-mode:comment-fill-column 80
-  "Fill column for comment wrapping in Swift code.
-
-This may be different than the fill column for the rest of the source.  See
-also `swift-mode:comment-fill-function'."
-  :type 'integer
+(defcustom swift-mode:fill-paragraph-entire-comment-or-string nil
+  "When non-nil, `fill-paragraph' fills entire comment block or string."
+  :type 'boolean
   :group 'swift
-  :safe 'integerp)
-
-(make-variable-buffer-local 'swift-mode:comment-fill-column)
-
-(defconst swift-mode:doc-comment-annotation-re
-  (let ((bullet '(any ?- ?+ ?*))
-        (any-spacing '(* blank))
-        (identifier '(+ (syntax word))))
-    (rx-to-string
-     `(seq
-       ;; Explicitly include indentation here, for `forward-paragraph'
-       ;; search (see usage of 'parstart' there)
-       ,any-spacing
-       ,bullet ,any-spacing ,identifier
-       ;; For '- parameter foo:'
-       (? ,any-spacing ,identifier)
-       ,any-spacing ?:)))
-  "Regex to match Swift documentation comment markup labels, like '- remark:'.
-
-This is used by `swift-mode:comment-fill-function' to extend
+  :safe 'booleanp)
+
+(defconst swift-mode:doc-comment-paragraph-start
+  (let* ((list-marker '(or (any ?- ?+ ?*) (seq (* (any "0-9")) (any ".)"))))
+         (list-item `(seq ,list-marker (or blank eol)))
+         (atx-heading '(seq (+ "#") blank)))
+    (rx-to-string `(seq
+                    (* blank)
+                    (or ,list-item ,atx-heading))))
+  "Regex to match start of paragraphs in documentation comments.
+
+This is used by `swift-mode:fill-forward-paragraph' to extend
 `paragraph-start' such that the built-in fill functions recognize
 these elements as the beginnings of their own paragraphs.")
 
-(defsubst swift-mode:-find-slash-comment-edges (slashes)
-  "Helper for `swift-mode:comment-fill-function' handling '///' style.
-
-Search backwards and forwards for contiguous lines that
-open (after any indentation) with SLASHES.  Return the buffer
-locations for the beginning and end of the comment contents.  For
-this style of comment, the content beginning is after the first
-delimiter; the end is the end of the last contiguous line found.
-
-Point may be anywhere in the comment when this is called."
-  (let ((orig-point (point))
-        start end)
-    (back-to-indentation)
-    (while (and (not (bobp))
-                (looking-at-p slashes))
-      (forward-line -1)
-      (back-to-indentation))
-    (setq start (progn
-                  (search-forward slashes nil t)
-                  (point)))
-    (goto-char orig-point)
-    (while (and (not (eobp))
-                (looking-at-p slashes))
-      (forward-line 1)
-      (unless (eobp)
-        (back-to-indentation)))
-    (setq end (if (progn
-                    (back-to-indentation)
-                    (looking-at-p slashes))
-                  ;; Edge case: comment is last thing in buffer with no 
trailing
-                  ;; newline.
-                  (point-max)
-                (forward-line -1)
-                (move-end-of-line 1)
-                (point)))
-    (cons start end)))
+(defconst swift-mode:doc-comment-paragraph-separate
+  (let* ((code-fence '(seq (or "```" "~~~") (* not-newline)))
+         (thematic-break '(or (>= 3 "-" (* blank))
+                              (>= 3 "_" (* blank))
+                              (>= 3 "*" (* blank))))
+         (setext-heading-underline '(or (* "=") (* "-"))))
+    (rx-to-string `(seq
+                    (* blank)
+                    (or
+                     ,code-fence
+                     ,thematic-break
+                     ,setext-heading-underline)
+                    (* blank)
+                    eol)))
+  "Regex to match paragraph separators in documentation comments.
+
+This is used by `swift-mode:fill-forward-paragraph' to extend
+`paragraph-separate'.")
+
+(defsubst swift-mode:find-single-line-comment-edges ()
+  "Return start and end of a single-line comment block.
+
+A single-line comment block is a continuous lines with same \"comment level\".
+
+Comment level is:
+
+- Number of slashes after indentation, if the line contains characters
+  other than slashes and spaces.
+
+- Comment level of the following line if the line contains only slashes
+  and spaces, and the following line is a single line comment and it have
+  fewer or equal number of slashes.
+
+- Comment level of the preceding line if the line contains only slashes
+  and spaces, and the preceding line is a single line comment and it have
+  fewer or equal number of slashes.
+
+- Number of slashes after indentation, otherwise.
+
+
+Examples:
+
+Code comment after documentation comment:
+
+/// paragraph 1: aaa bbb
+/// ccc.
+// paragraph 2: aaa bbb
+// ccc.
+
+
+Comment box:
+
+///////////////////////
+// paragraph 1: aaa bbb
+// ccc.
+//
+// paragraph 2: aaa bbb
+// ccc.
+///////////////////////
+
 
-(defsubst swift-mode:-fix-up-star-comment-edges ()
-  "Helper for `swift-mode:comment-fill-function' handling '/**' style.
-
-Ensure each delimiter is on its own line, then return the buffer
-locations for the beginning and end of the comment
-contents (excluding the delimiters).
-
-Point is assumed to be just after the opening delimiter and its
-trailing whitespace (if any) when this is called."
-  (when (not (looking-at-p "\n"))
-    (delete-horizontal-space)
-    (insert-and-inherit "\n")
-    (indent-according-to-mode))
-
-  (let ((start (point))
-        (end (progn (re-search-forward "\\*+/")
-                    (match-beginning 0))))
-    (goto-char end)
-    (skip-chars-backward " \t")
-    (if (bolp)
-        (setq end (- (point) 1))
-      (insert-and-inherit "\n")
-      (indent-according-to-mode))
+Code comment before and after comment box:
+
+// paragraph 1: aaa bbb
+// ccc.
+////////////////////////////
+/// paragraph 2: aaa bbb
+/// ccc.
+////////////////////////////
+// paragraph 3: aaa bbb
+// ccc.
+
+Return tuple (START . END) where START is the point before the first slash of
+the block, and END is the end of the last comment, excluding the last line
+break if any.
+
+Point may be anywhere in a single-line comment when this is called."
+  (let* ((current-comment-level (swift-mode:single-line-comment-level))
+         (start
+          (save-excursion
+            (while (and (not (bobp))
+                        (= (swift-mode:single-line-comment-level)
+                           current-comment-level))
+              (forward-line -1))
+            (unless (= (swift-mode:single-line-comment-level)
+                       current-comment-level)
+              (forward-line 1))
+            (back-to-indentation)
+            (point)))
+         (end
+          (save-excursion
+            (while (and (not (eobp))
+                        (= (swift-mode:single-line-comment-level)
+                           current-comment-level))
+              (when (/= (forward-line 1) 0)
+                (goto-char (point-max))))
+            (when (bolp)
+              (backward-char))
+            (point))))
     (cons start end)))
 
-(defun swift-mode:comment-fill-function (justify)
-  "Handle comment filling in Swift code.
+(defun swift-mode:single-line-comment-level (&optional search-direction)
+  "Return comment level of the current line.
+
+See `swift-mode:find-single-line-comment-edges' for details.
+
+If SEARCH-DIRECTION is `backward', search only backward.
+If SEARCH-DIRECTION is `forward', search only forward.
 
-Delegates to `fill-region' with `fill-column' bound to the value of
-`swift-mode:comment-fill-column' so that comments can be wrapped at
-different width than the rest of the source source.  JUSTIFY is as the
-argument of the same name in `fill-region'.
+Return 1.0e+INF if the line doesn't start with a single-line comment."
+  (save-excursion
+    (save-match-data
+      (back-to-indentation)
+      (if (looking-at "//+")
+          (let ((number-of-slashes (- (match-end 0) (match-beginning 0))))
+            (if (looking-at "//+\\s *$")
+                ;; Only slashes.
+                (or
+                 (let ((following-comment-level
+                        (save-excursion
+                          (if (and (zerop (forward-line 1))
+                                   (bolp)
+                                   (not (eq search-direction 'backward)))
+                              (swift-mode:single-line-comment-level 'forward)
+                            1.0e+INF))))
+                   (and (<= following-comment-level number-of-slashes)
+                        following-comment-level))
+                 (let ((preceding-comment-level
+                        (save-excursion
+                          (if (and (zerop (forward-line -1))
+                                   (not (eq search-direction 'forward)))
+                              (swift-mode:single-line-comment-level 'backward)
+                            1.0e+INF))))
+                   (and (<= preceding-comment-level number-of-slashes)
+                        preceding-comment-level))
+                 number-of-slashes)
+              number-of-slashes))
+        ;; Not a comment
+        1.0e+INF))))
 
-The function determines which style of comment is at or around
-point and does preliminary cleanup as needed (the built-in fill
-functions do not handle the '/**' style of comment particularly
-well)."
+(defun swift-mode:fill-paragraph (justify)
+  "Fill paragraph in Swift code.
+
+JUSTIFY is as the argument of the same name in `fill-region'.
+
+If `swift-mode:fill-paragraph-entire-comment-or-string' is non-nil, fill entire
+comment rather than a paragraph.
+
+Determine which style of comment is at or around point and does preliminary
+cleanup as needed (the built-in fill functions do not handle the '/**' style of
+comment particularly well)."
   ;; TODO A leading star on an empty line screws up paragraph calculation.
-  ;; TODO Recognize fenced code blocks.
   ;; TODO Handle trailing comments.
-  (let ((chunk (swift-mode:chunk-after)))
-    (if (not (or (swift-mode:chunk:comment-p chunk)
-                 (looking-at-p comment-start-skip)))
-        ;; If not in a comment, just let `fill-paragraph' try to handle it
+  (save-excursion
+    (save-match-data
+      (skip-syntax-backward " ")
+      (let ((chunk (or (swift-mode:chunk-after)
+                       (and (looking-at "\\s *\\(/[/*]\\|#*\"\"\"\\)")
+                            (swift-mode:chunk-after (match-end 0)))
+                       (save-excursion
+                         (skip-chars-backward "#")
+                         (skip-chars-backward "\"")
+                         (swift-mode:chunk-after))
+                       (and (eq (char-before) ?/)
+                            (save-excursion
+                              (backward-char)
+                              (skip-chars-backward "*")
+                              (swift-mode:chunk-after))))))
+        (cond
+         ;; Single-line comment
+         ((swift-mode:chunk:single-line-comment-p chunk)
+          (let* ((edges (swift-mode:find-single-line-comment-edges))
+                 (start (car edges))
+                 (end (copy-marker (cdr edges))))
+            (if swift-mode:fill-paragraph-entire-comment-or-string
+                (fill-region start end justify)
+              (let ((fill-paragraph-function nil))
+                (fill-paragraph justify)))
+            (indent-region start end)
+            (set-marker end nil)))
+
+         ;; Multiline comment or string
+         ((or (swift-mode:chunk:multiline-comment-p chunk)
+              (swift-mode:chunk:multiline-string-p chunk))
+          (let* ((start (swift-mode:chunk:start chunk))
+                 (end (swift-mode:chunk:end chunk)))
+            (if swift-mode:fill-paragraph-entire-comment-or-string
+                (fill-region start end justify)
+              (let ((fill-paragraph-function nil))
+                (fill-paragraph justify)))))
+         ;; Otherwise; do nothing
+         (t
+          nil)))
+      t)))
+
+(defun swift-mode:fill-region-as-paragraph-advice
+    (fill-region-as-paragraph from to &rest args)
+  "Advice function for `fill-region-as-paragraph'.
+
+FILL-REGION-AS-PARAGRAPH is the original function, and FROM, TO, and ARGS are
+original arguments.
+
+Fix up multiline comments.
+
+- When the region contains other than one multline comment, fill normally:
+
+  foo() /* abc def ghi */
+  ↓
+  foo() /* abc
+  def ghi */
+
+- Otherwise and when the region fits one line, fill as a line:
+
+  /*
+    abc
+    def
+  */
+  ↓
+  /* abc def */
+
+- Otherwise and when the region was one line, insert breaks before and after
+  the contents:
+
+  /* abc def ghi */
+  ↓
+  /*
+    abc def
+    ghi
+  */
+
+- Otherwise, keep line breaks around the contents and fill the contents:
+
+  /* abc def ghi
+  */
+  ↓
+  /* abc def
+     ghi
+  */"
+  (if (eq major-mode 'swift-mode)
+      (let* ((chunk (save-excursion
+                      (save-match-data
+                        (goto-char from)
+                        (or (swift-mode:chunk-after)
+                            (and (looking-at "\\s *\\(/[/*]\\|#*\"\"\"\\)")
+                                 (swift-mode:chunk-after (match-end 0)))))))
+             comment-start
+             comment-end
+             one-line
+             have-break-after-open-delimiter
+             have-break-before-close-delimiter
+             contents-start
+             contents-end
+             result)
+        (if (swift-mode:chunk:multiline-comment-p chunk)
+            (progn
+              (setq comment-start (swift-mode:chunk:start chunk))
+              (setq comment-end (swift-mode:chunk:end chunk))
+              ;; Is filling the entire comment?
+              (if (and (member (save-excursion
+                                 (goto-char from)
+                                 (skip-syntax-forward " >")
+                                 (point))
+                               (list comment-start
+                                     (save-excursion
+                                       (goto-char comment-start)
+                                       (forward-char)
+                                       (skip-chars-forward "*")
+                                       (skip-syntax-forward " >")
+                                       (point))))
+                       (member (save-excursion
+                                 (goto-char to)
+                                 (skip-syntax-backward " >")
+                                 (point))
+                               (list comment-end
+                                     (save-excursion
+                                       (goto-char comment-end)
+                                       (backward-char)
+                                       (skip-chars-backward "*")
+                                       (skip-syntax-backward " >")
+                                       (point)))))
+                  (progn
+                    (setq one-line
+                          (swift-mode:same-line-p comment-start comment-end))
+                    (setq have-break-after-open-delimiter
+                          (save-excursion
+                            (goto-char comment-start)
+                            (forward-char)
+                            (skip-chars-forward "*")
+                            (skip-syntax-forward " ")
+                            (eolp)))
+                    (setq have-break-before-close-delimiter
+                          (save-excursion
+                            (goto-char comment-end)
+                            (backward-char)
+                            (skip-chars-backward "*")
+                            (skip-syntax-backward " ")
+                            (bolp)))
+                    (setq comment-start (copy-marker comment-start))
+                    (setq comment-end (copy-marker comment-end))
+                    (setq result (apply fill-region-as-paragraph from to args))
+                    ;; If the entire comment fits in one line, do nothing.
+                    ;; Otherwise, insert line breaks before/after the contents
+                    ;; if necessary.  See the documentation comment for 
details.
+                    (unless (swift-mode:same-line-p comment-start comment-end)
+                      (save-excursion
+                        (goto-char comment-start)
+                        (forward-char)
+                        (skip-chars-forward "*")
+                        (skip-syntax-forward " ")
+                        (when (and
+                               (or one-line have-break-after-open-delimiter)
+                               (not (eolp)))
+                          (delete-horizontal-space)
+                          (insert-and-inherit "\n")
+                          (indent-according-to-mode))
+                        (setq contents-start (point)))
+                      (save-excursion
+                        (goto-char comment-end)
+                        (backward-char)
+                        (skip-chars-backward "*")
+                        (skip-syntax-backward " ")
+                        (setq contents-end (point))
+                        (when (and
+                               (or one-line have-break-before-close-delimiter)
+                               (not (bolp)))
+                          (delete-horizontal-space)
+                          (insert-and-inherit "\n")
+                          (indent-according-to-mode)))
+                      (setq result (apply fill-region-as-paragraph
+                                          contents-start
+                                          contents-end
+                                          args)))
+                    (set-marker comment-start nil)
+                    (set-marker comment-end nil)
+                    result)
+                (apply fill-region-as-paragraph from to args)))
+          (apply fill-region-as-paragraph from to args)))
+    (apply fill-region-as-paragraph from to args)))
+
+(defun swift-mode:install-fill-region-as-paragraph-advice ()
+  "Install advice around `fill-region-as-paragraph'."
+  (advice-add 'fill-region-as-paragraph
+              :around
+              #'swift-mode:fill-region-as-paragraph-advice))
+
+
+(defun swift-mode:current-fill-column-advice (current-fill-column)
+  "Advice function for `current-fill-column'.
+
+CURRENT-FILL-COLUMN is the original function.
+
+Use `comment-fill-column' as `fill-column' when filling inside a comment."
+  (if (and (eq major-mode 'swift-mode) comment-fill-column)
+      (let* ((chunk (save-excursion
+                      (save-match-data
+                        (or (swift-mode:chunk-after)
+                            (and (looking-at "\\s *\\(/[/*]\\|#*\"\"\"\\)")
+                                 (swift-mode:chunk-after (match-end 0)))))))
+             (fill-column
+              (if (swift-mode:chunk:comment-p chunk)
+                  comment-fill-column
+                fill-column)))
+        (funcall current-fill-column))
+    (funcall current-fill-column)))
+
+(defun swift-mode:install-current-fill-column-advice ()
+  "Install advice around `current-fill-column'."
+  (advice-add 'current-fill-column
+              :around
+              #'swift-mode:current-fill-column-advice))
+
+(defun swift-mode:fill-forward-paragraph (arg)
+  "Forward ARG paragraphs for filling.
+
+Returns the count of paragraphs left to move."
+  (if (< arg 0)
+      (swift-mode:fill-backward-paragraph (- arg))
+    (let ((done nil))
+      (while (and (< 0 arg)
+                  (not done))
+        (setq done (not (swift-mode:fill-skip-paragraph-1 'forward)))
+        (unless done (setq arg (1- arg)))))
+    arg))
+
+(defun swift-mode:fill-backward-paragraph (arg)
+  "Backward ARG paragraphs for filling.
+
+Returns the count of paragraphs left to move."
+  (if (< arg 0)
+      (swift-mode:fill-forward-paragraph (- arg))
+    (let ((done nil))
+      (while (and (< 0 arg)
+                  (not done))
+        (setq done (not (swift-mode:fill-skip-paragraph-1 'backward)))
+        (unless done (setq arg (1- arg)))))
+    arg))
+
+(defun swift-mode:fill-skip-paragraph-1 (direction)
+  "Skip a paragraph for filling.
+
+If DIRECTION is `backward', skip backward.  Otherwise, skip forward.
+
+Return non-nil if skipped a paragraph.  Return nil otherwise."
+  (save-match-data
+    ;; Skip whitespaces and line breaks.
+    (if (eq direction 'backward)
+        (skip-syntax-backward " >")
+      (skip-syntax-forward " >"))
+    (let ((chunk (or (swift-mode:chunk-after)
+                     (and (looking-at "/[/*]\\|#*\"\"\"")
+                          (swift-mode:chunk-after (match-end 0))))))
+      (cond
+       ((swift-mode:chunk:single-line-comment-p chunk)
+        (swift-mode:fill-skip-paragraph-in-single-line-comment
+         chunk
+         direction))
+       ((swift-mode:chunk:multiline-comment-p chunk)
+        (swift-mode:fill-skip-paragraph-in-multiline-comment
+         chunk
+         direction))
+       ((swift-mode:chunk:string-p chunk)
+        (swift-mode:fill-skip-paragraph-in-string
+         chunk
+         direction))
+       (t
+        (swift-mode:fill-skip-paragraph-in-code direction))))))
+
+(defun swift-mode:fill-skip-paragraph-in-single-line-comment (chunk direction)
+  "Skip a paragraph in a single line comment for filling.
+
+CHUNK is the comment.
+
+If DIRECTION is `backward', skip backward.  Otherwise, skip forward.
+
+Return non-nil if skipped a paragraph.  Return nil otherwise."
+  (let* ((backward (eq direction 'backward))
+         (pos (point))
+         (comment-level (save-excursion
+                          (goto-char (swift-mode:chunk:start chunk))
+                          (swift-mode:single-line-comment-level)))
+         (slashes (make-string comment-level ?/))
+         (edges (swift-mode:find-single-line-comment-edges))
+         ;; Factor the comment markers into paragraph recognition
+         (paragraph-start (concat
+                           "[[:blank:]]*"
+                           slashes "/*"
+                           "\\(?:"
+                           swift-mode:doc-comment-paragraph-start
+                           "\\|"
+                           paragraph-start
+                           "\\)"))
+         (paragraph-separate (concat "[[:blank:]]*"
+                                     slashes "/*"
+                                     "[[:blank:]]*"
+                                     "\\(?:"
+                                     swift-mode:doc-comment-paragraph-separate
+                                     "\\|"
+                                     paragraph-separate
+                                     "\\)")))
+    (if backward (backward-paragraph) (forward-paragraph))
+    (when (and (< (point) (car edges))
+               (< (car edges) pos))
+      (goto-char (car edges)))
+    (when (and (< (cdr edges) (point))
+               (< pos (cdr edges)))
+      (goto-char (cdr edges)))
+    (/= pos (point))))
+
+(defun swift-mode:fill-skip-paragraph-in-multiline-comment (chunk direction)
+  "Skip a paragraph in a multiline comment for filling.
+
+CHUNK is the comment.
+
+If DIRECTION is `backward', skip backward.  Otherwise, skip forward.
+
+Return non-nil if skipped a paragraph.  Return nil otherwise."
+  (swift-mode:fill-skip-paragraph-in-multiline-chunk
+   chunk
+   direction
+   "\\s */\\*+\\s *$\\|\\s *\\*+/\\s *$"
+   (if (eq direction 'backward)
+       (lambda ()
+         (forward-char)
+         (skip-chars-forward "*")
+         (skip-syntax-forward " "))
+     (lambda ()
+       (when (eq (char-before) ?/)
+         (backward-char)
+         (skip-chars-backward "*"))
+       (skip-syntax-backward " ")))))
+
+(defun swift-mode:fill-skip-paragraph-in-string (chunk direction)
+  "Skip a paragraph in a string for filling.
+
+CHUNK is the comment.
+
+If DIRECTION is `backward', skip backward.  Otherwise, skip forward.
+
+Return non-nil if skipped a paragraph.  Return nil otherwise."
+  (swift-mode:fill-skip-paragraph-in-multiline-chunk
+   chunk
+   direction
+   "\\s *#*\"+\\s *$\\|\\s *\"+#*/\\s *$"
+   (if (eq direction 'backward)
+       (lambda ()
+         (skip-chars-forward "#")
+         (skip-chars-forward "\"")
+         (skip-syntax-forward " "))
+     (lambda ()
+       (when (memq (char-before) '(?# ?\"))
+         (skip-chars-backward "#")
+         (skip-chars-backward "\""))
+       (skip-syntax-backward " ")))))
+
+(defun swift-mode:fill-skip-paragraph-in-multiline-chunk
+    (chunk direction extra-paragraph-separate skip-delimiter)
+  "Skip a paragraph in a multiline comment or string for filling.
+
+CHUNK is the comment or string.
+
+EXTRA-PARAGRAPH-SEPARATE is additional `paragraph-separate' regexp.
+
+If DIRECTION is `backward', skip backward.  Otherwise, skip forward.
+
+SKIP-DELIMITER is a function that skips delimiter in opposite direction.
+
+Return non-nil if skipped a paragraph.  Return nil otherwise."
+  (let* ((backward (eq direction 'backward))
+         (pos (point))
+         (limit (save-excursion
+                  (goto-char (if backward
+                                 (swift-mode:chunk:start chunk)
+                               (swift-mode:chunk:end chunk)))
+                  (funcall skip-delimiter)
+                  (if backward
+                      (skip-syntax-forward " ")
+                    (skip-syntax-backward " "))
+                  (if backward
+                      (min pos (point))
+                    (max pos (point)))))
+         (min (if backward limit pos))
+         (max (if backward pos limit))
+         (paragraph-start (concat
+                           swift-mode:doc-comment-paragraph-start
+                           "\\|"
+                           paragraph-start))
+         (paragraph-separate (concat extra-paragraph-separate
+                                     "\\|"
+                                     swift-mode:doc-comment-paragraph-separate
+                                     "\\|"
+                                     paragraph-separate)))
+    (forward-paragraph (if backward -1 1))
+    (when (< (point) min)
+      (if (< min pos)
+          (goto-char min)
+        (goto-char (swift-mode:chunk:start chunk))
+        (unless (zerop (fill-forward-paragraph -1))
+          (goto-char pos))))
+    (when (< max (point))
+      (if (< pos max)
+          (goto-char max)
+        (goto-char (swift-mode:chunk:end chunk))
+        (unless (zerop (fill-forward-paragraph 1))
+          (goto-char pos))))
+    (/= (point) pos)))
+
+(defun swift-mode:fill-skip-paragraph-in-code (direction)
+  "Skip a paragraph in a code for filling.
+
+If DIRECTION is `backward', skip backward.  Otherwise, skip forward.
+
+Return non-nil if skipped a paragraph.  Return nil otherwise."
+  (let ((pos (point))
+        (done nil))
+    (while (not done)
+      (if (or (not (zerop (forward-line (if (eq direction 'backward) -1 1))))
+              (eobp)
+              (bobp))
+          (progn
+            (setq done t)
+            ;; When no paragraph is found, `fill-region' expects to keep
+            ;; position when going backward while go forward anyway when going
+            ;; forward.
+            (when (eq direction 'backward)
+              (goto-char pos)))
+        (back-to-indentation)
+        (when (or (looking-at "/[/*]\\|#*\"\"\"")
+                  (swift-mode:chunk-after))
+          (setq done t)
+          (when (eq direction 'backward)
+            (end-of-line))
+          (swift-mode:fill-skip-paragraph-1 direction))))
+    (/= pos (point))))
+
+(defun swift-mode:do-auto-fill ()
+  "Do auto fill at point.
+
+Do nothing except in a comment.
+
+If point is inside a muitiline style comment (slash-star style comment) which
+is actually in single line, insert line breaks before and after the contents,
+then call `do-auto-fill'.
+
+Example:
+
+/* aaa bbb ccc */
+↓
+/*
+  aaa bbb
+  ccc
+*/"
+  (let ((current-fill-column (current-fill-column))
+        (current-justification (current-justification))
+        chunk)
+    (if (or (null current-justification)
+            (null fill-column)
+            (and (eq current-justification 'left)
+                 (<= (current-column) current-fill-column))
+            (null (setq chunk (swift-mode:chunk-after)))
+            (not (swift-mode:chunk:comment-p chunk)))
+        ;; Do nothing.
         nil
-      (save-match-data
-        (save-excursion
-          ;; Move to opening delimiter if not already there.
-          (let ((start (swift-mode:chunk:start chunk)))
-            (when start
-              (goto-char start)))
-          (skip-syntax-forward " ")
-
-          ;; We want these two bound to their special values when delegating to
-          ;; `fill-region'.
-          (let ((fill-column swift-mode:comment-fill-column)
-                (paragraph-start (concat
-                                  swift-mode:doc-comment-annotation-re
-                                  "\\|"
-                                  paragraph-start)))
-            (cond
-
-             ;; Slash-style comment
-             ((looking-at "/\\{2,\\}")
-              (let* ((slashes (match-string-no-properties 0))
-                     (edges (swift-mode:-find-slash-comment-edges slashes))
-                     (start (car edges))
-                     (end (cdr edges)))
-                ;; Factor the comment markers into paragraph recognition
-                (let ((paragraph-start (concat "[[:blank:]]*" slashes
-                                               "\\(?:" paragraph-start "\\)"))
-                      (paragraph-separate (concat "[[:blank:]]*" slashes
-                                                  "\\(?:" paragraph-separate
-                                                  "\\)")))
-                  (fill-region start end justify :preserve-spaces)
-                  (indent-region start end))))
-
-             ;; Star-style comment
-             ((re-search-forward "/\\*\\{2,\\} *" nil t)
-              (let* ((edges (swift-mode:-fix-up-star-comment-edges))
-                     (start (car edges))
-                     (end (cdr edges)))
-                (fill-region start end justify :preserve-spaces)
-                (indent-region start end))))))
-
-        ;; Make sure `fill-paragraph' does not undo our work.
-        t))))
+      (when (swift-mode:chunk:multiline-comment-p chunk)
+        (let ((comment-start (swift-mode:chunk:start chunk))
+              (comment-end (swift-mode:chunk:end chunk)))
+          (when (swift-mode:same-line-p comment-start comment-end)
+            (save-excursion
+              (goto-char comment-start)
+              (forward-char)
+              (skip-chars-forward "*")
+              (delete-horizontal-space)
+              (insert-and-inherit "\n")
+              (indent-according-to-mode))
+            (save-excursion
+              (goto-char comment-end)
+              (backward-char)
+              (skip-chars-backward "*")
+              (skip-syntax-backward " ")
+              (delete-horizontal-space)
+              (insert-and-inherit "\n")
+              (indent-according-to-mode)))))
+      (do-auto-fill))))
 
 (provide 'swift-mode-fill)
 
diff --git a/swift-mode-lexer.el b/swift-mode-lexer.el
index 407bf64270..bbb4e6afc0 100644
--- a/swift-mode-lexer.el
+++ b/swift-mode-lexer.el
@@ -1398,6 +1398,15 @@ If this line ends with a single-line comment, goto just 
before the comment."
   "Return the start position of the CHUNK."
   (nth 1 chunk))
 
+(defun swift-mode:chunk:end (chunk)
+  "Return the end position of the CHUNK."
+  (save-excursion
+    (goto-char (nth 1 chunk))
+    (if (swift-mode:chunk:comment-p chunk)
+        (forward-comment 1)
+      (swift-mode:forward-token))
+    (point)))
+
 (defun swift-mode:chunk:comment-p (chunk)
   "Return non-nil if the CHUNK is a comment."
   (memq (swift-mode:chunk:type chunk) '(single-line-comment 
multiline-comment)))
@@ -1466,6 +1475,17 @@ If PARSER-STATE is given, it is used instead of 
(syntax-ppss)."
      (t
       nil))))
 
+(defun swift-mode:same-line-p (point1 point2)
+  "Return non-nil if POINT1 and POINT2 is on the same line.
+
+Return nil otherwise."
+  (= (save-excursion
+       (goto-char point1)
+       (line-beginning-position))
+     (save-excursion
+       (goto-char point2)
+       (line-beginning-position))))
+
 (provide 'swift-mode-lexer)
 
 ;;; swift-mode-lexer.el ends here
diff --git a/swift-mode.el b/swift-mode.el
index 0bb0b42b7e..a4d3ec9867 100644
--- a/swift-mode.el
+++ b/swift-mode.el
@@ -194,7 +194,12 @@ Signal `scan-error' if it hits opening parentheses."
   (setq-local fill-indent-according-to-mode t)
   (setq-local comment-multi-line t)
   (setq-local comment-line-break-function #'swift-mode:indent-new-comment-line)
-  (setq-local fill-paragraph-function #'swift-mode:comment-fill-function)
+  (setq-local fill-paragraph-function #'swift-mode:fill-paragraph)
+  (setq-local fill-forward-paragraph-function
+              #'swift-mode:fill-forward-paragraph)
+  (setq-local normal-auto-fill-function #'swift-mode:do-auto-fill)
+  (swift-mode:install-fill-region-as-paragraph-advice)
+  (swift-mode:install-current-fill-column-advice)
 
   (setq-local parse-sexp-lookup-properties t)
   (add-hook 'syntax-propertize-extend-region-functions
diff --git a/test/swift-files/fill/code.swift b/test/swift-files/fill/code.swift
new file mode 100644
index 0000000000..2690859381
--- /dev/null
+++ b/test/swift-files/fill/code.swift
@@ -0,0 +1,5 @@
+public class Foo {
+    func foo() {
+        print("Code should not affected by fill-region")
+    }
+}
diff --git a/test/swift-files/fill/comment.swift 
b/test/swift-files/fill/comment.swift
index e5cc595db4..550db446ff 100644
--- a/test/swift-files/fill/comment.swift
+++ b/test/swift-files/fill/comment.swift
@@ -1,115 +1,134 @@
-// swift-mode:test:case-begin (Slash style doc comment)
-
-/// Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
-/// tempor incididunt ut labore et dolore magna aliqua. Ut
-///
-/// - parameter foo: enim ad minim veniam, quis nostrud exercitation ullamco
-/// laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
-/// reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
-/// pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
-/// officia deserunt mollit anim id est laborum.
-/// - parameter bar: enim ad minim veniam, quis nostrud exercitation ullamco
-/// - returns: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
-/// eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad 
minim
-/// veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
-/// commodo consequat. Duis aute irure dolor in reprehenderit in voluptate 
velit
-/// esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
-/// cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
-/// est laborum.
-///
-/// - warning: reprehenderit in voluptate velit esse
-
-// swift-mode:test:case-input-begin
-
-/// Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 
tempor incididunt ut labore et
-/// dolore magna aliqua. Ut
-///
-/// - parameter foo: enim ad minim veniam, quis nostrud exercitation ullamco 
laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
-/// in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui 
officia deserunt mollit anim id est laborum.
-/// - parameter bar: enim ad minim veniam, quis nostrud exercitation ullamco
-/// - returns: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed 
do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim 
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse 
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non 
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-///
-/// - warning: reprehenderit in voluptate velit esse
-
-// swift-mode:test:case-end (Slash style doc comment)
-
-// swift-mode:test:eval (setq swift-mode:comment-fill-column 96)
-// swift-mode:test:case-begin (Slash line comment)
-
-// Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 
tempor incididunt ut
-// labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud 
exercitation ullamco
-
-// swift-mode:test:case-input-begin
-
-// Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 
tempor incididunt ut labore et dolore magna
-// aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
-
-// swift-mode:test:case-end
-
-// swift-mode:test:eval (setq swift-mode:comment-fill-column 80)
-// swift-mode:test:case-begin (Star style doc comment)
-
-/**
- Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 
tempor
- incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
- nostrud exercitation ullamco
-
- - parameter foo: laboris nisi ut
- - parameter bar: aliquip ex ea commodo consequat. Duis aute irure dolor in
- reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
- pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
- officia deserunt mollit anim id est laborum.
- - parameter baz: enim ad minim veniam, quis nostrud exercitation ullamco
-
- - returns: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
- eiusmod tempor incididunt ut labore et dolore magna aliqua.
-
- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
- aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
- voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
- occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit 
anim
- id est laborum.
-
- - important: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
- eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
- veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea 
commodo
- consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
- cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
- proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
- */
-
-// swift-mode:test:case-input-begin
-
-/**
- Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 
tempor incididunt ut labore et dolore magna aliqua. Ut
- enim ad minim veniam, quis nostrud exercitation ullamco
-
- - parameter foo: laboris nisi ut
- - parameter bar: aliquip ex ea commodo consequat. Duis aute irure dolor in 
reprehenderit
- in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
- occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit 
anim
- id est laborum.
- - parameter baz: enim ad minim veniam, quis nostrud exercitation ullamco
-
- - returns: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do 
eiusmod tempor incididunt ut labore
- et dolore magna aliqua.
-
- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut 
aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in 
voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint 
occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim 
id est laborum.
-
- - important: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do 
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim 
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse 
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non 
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
- */
-
-// swift-mode:test:case-end (Star style doc comment)
-
-// swift-mode:test:case-begin (Star comment, single line)
-
-/**
- Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 
tempor
- incididunt ut
- */
-
-// swift-mode:test:case-input-begin
-
-/** Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 
tempor incididunt ut */
-
-// swift-mode:test:case-end (Star comment, single line)
+// swift-mode:test:paragraph
+public class Foo {
+    // Code comment before and after documentation comment:
+
+    // swift-mode:test:paragraph swift-mode:test:start-block
+    //
+    // swift-mode:test:paragraph swift-mode:test:end-block
+    /// swift-mode:test:paragraph swift-mode:test:start-block
+    ///
+    /// swift-mode:test:paragraph swift-mode:test:end-block
+    // swift-mode:test:paragraph swift-mode:test:start-block
+    //
+    // swift-mode:test:paragraph swift-mode:test:end-block
+
+    // Comment box:
+
+    ///////////////////////
+    // swift-mode:test:paragraph swift-mode:test:start-block
+    //
+    // swift-mode:test:paragraph swift-mode:test:end-block
+    ///////////////////////
+
+    ///////////////////////
+    ///////////////////////
+    //
+    // swift-mode:test:paragraph swift-mode:test:start-block
+    //
+    // swift-mode:test:paragraph swift-mode:test:end-block
+    //
+    ///////////////////////
+    ///////////////////////
+
+    /**********************
+     swift-mode:test:paragraph swift-mode:test:start-block
+
+     swift-mode:test:paragraph swift-mode:test:end-block
+     *********************/
+
+
+    // Code comment before and after comment box:
+
+    // swift-mode:test:paragraph
+    //
+    // swift-mode:test:paragraph
+    ////////////////////////////
+    /// swift-mode:test:paragraph swift-mode:test:start-block
+    ///
+    /// swift-mode:test:paragraph swift-mode:test:end-block
+    ////////////////////////////
+    // swift-mode:test:paragraph
+    //
+    // swift-mode:test:paragraph
+
+    /*
+     swift-mode:test:paragraph
+
+     swift-mode:test:paragraph
+     */
+    /**
+     swift-mode:test:paragraph swift-mode:test:start-block
+
+     swift-mode:test:paragraph swift-mode:test:end-block
+     */
+    /*
+     swift-mode:test:paragraph
+
+     swift-mode:test:paragraph
+     */
+
+
+    // Code and comments
+
+    /// swift-mode:test:paragraph
+    func foo() {}
+
+    /// swift-mode:test:paragraph
+    func foo() {}
+
+    /**
+     swift-mode:test:paragraph
+     */
+    func foo() {}
+
+    /**
+     swift-mode:test:paragraph
+     */
+    func foo() {}
+
+
+    // CommonMark
+
+    // swift-mode:test:paragraph swift-mode:test:start-block
+    // swift-mode:test:heading
+    // swift-mode:test:list-item
+    // swift-mode:test:list-item
+    // swift-mode:test:list-item
+    // ```
+    // swift-mode:test:paragraph
+    // ```
+    // swift-mode:test:paragraph
+    // - - -
+    // swift-mode:test:paragraph
+    // =========================
+    // swift-mode:test:paragraph
+    // -------------------------
+    // swift-mode:test:paragraph swift-mode:test:end-block
+
+    /**
+     swift-mode:test:paragraph swift-mode:test:start-block
+     swift-mode:test:heading
+     swift-mode:test:list-item
+     swift-mode:test:list-item
+     swift-mode:test:list-item
+     ```
+     swift-mode:test:paragraph
+     ```
+     swift-mode:test:paragraph
+     - - -
+     swift-mode:test:paragraph
+     =========================
+     swift-mode:test:paragraph
+     -------------------------
+     swift-mode:test:paragraph swift-mode:test:end-block
+     */
+
+
+    // Strings
+    ###"""
+      swift-mode:test:paragraph swift-mode:test:start-block
+
+      swift-mode:test:paragraph swift-mode:test:end-block
+      """###
+}
+// swift-mode:test:paragraph
diff --git a/test/swift-mode-test-fill.el b/test/swift-mode-test-fill.el
index c75d979d4a..e53e441855 100644
--- a/test/swift-mode-test-fill.el
+++ b/test/swift-mode-test-fill.el
@@ -1,5 +1,4 @@
 ;;; swift-mode-test-fill.el --- Test for swift-mode: filling -*- 
lexical-binding: t -*-
-
 ;; Copyright (C) 2016, 2022 taku0, Josh Caswell
 
 ;; Authors: taku0 (https://github.com/taku0)
@@ -29,6 +28,7 @@
 
 (require 'swift-mode)
 (require 'swift-mode-fill)
+(require 'diff)
 
 (defun swift-mode:run-test:fill
     (&optional error-buffer error-counts progress-reporter)
@@ -41,105 +41,459 @@ interactively."
   (interactive)
   (if (not swift-mode:test:running)
       (swift-mode:run-test '(swift-mode:run-test:fill))
-    (let ((current-line 0))
+    (swift-mode:test-fill-region-as-paragraph error-buffer error-counts)
+    (let (lines)
       (setq default-directory
             (concat (file-name-as-directory swift-mode:test:basedir)
                     (file-name-as-directory "swift-files")
                     "fill"))
-
       (dolist (swift-file (file-expand-wildcards "*.swift"))
         (redisplay)
         (with-temp-buffer
           (switch-to-buffer (current-buffer))
           (insert-file-contents-literally swift-file)
+          (let ((coding-system-for-read 'utf-8))
+            (decode-coding-inserted-region (point-min) (point-max) swift-file))
           (swift-mode)
-          (setq current-line 0)
-          (while (not (eobp))
-            (when (not noninteractive)
-              (progress-reporter-update progress-reporter))
-            (setq current-line (1+ current-line))
-            (cond
-             ((= (line-beginning-position) (line-end-position))
-              ;; Empty line
-              nil)
-
-             ((looking-at ".*//.*swift-mode:test:eval\\(.*\\)")
-              (eval-region (match-beginning 1) (match-end 1)))
-
-             ((looking-at ".*//.*swift-mode:test:case-begin")
-              (progn
-                ;; Marker comments must have a blank line separating them from
-                ;; the test input or they will interfere with filling.
-                (forward-line 2)
-                (let*
-                    ((status (swift-mode:test-current-case-fill
-                              swift-file current-line error-buffer))
-                     (count-assoc (assq status error-counts)))
-                  (setcdr count-assoc (1+ (cdr count-assoc))))))
-
-             (t (swift-mode:show-error
-                 error-buffer swift-file current-line
-                 "warning" "Unexpected test case input")))
-            (forward-line)))))))
-
-(defun swift-mode:test-current-case-fill (swift-file current-line error-buffer)
-  "Run the current fill test case.
-
-This applies `fill-paragraph' to the input string and compares the result
-to the expected value.
-
-SWIFT-FILE is the filename of the current test case.
-CURRENT-LINE is the current line number.
-ERROR-BUFFER is the buffer to output errors."
-  (let (expected
-        computed
-        (status 'ok))
-    (setq expected (swift-mode:test-fill:capture-expected))
-    ;; Marker comments must have a blank line separating them from
-    ;; the test input or they will interfere with filling.
-    (forward-line 2)
-    (setq computed (swift-mode:test-fill:perform-fill))
-    (forward-line)
-
-    (when (not (string= expected computed))
-      (setq status 'error)
-
-      (swift-mode:show-error
-       error-buffer swift-file current-line
-       "error"
-       (concat "Fill region failure\n"
-               "Expected: ```\n" expected "```\n\n"
-               "Actual: ```\n" computed "```")))
-    status))
-
-(defsubst swift-mode:test-fill:capture-expected ()
-  "Collect the expected result for this test case into a string."
-  (let ((start (point))
-        (end (point)))
-    (while (not (looking-at ".*//.*swift-mode:test:case-input-begin"))
-      (setq end (point))
-      (forward-line))
-    (buffer-substring start end)))
-
-(defsubst swift-mode:test-fill:perform-fill ()
-  "Run `fill-paragraph' on the case's input, returning the resulting string."
-  (let ((start (point))
-        end)
-    (while (not (looking-at ".*//.*swift-mode:test:case-end"))
-      (setq end (point))
-      (forward-line))
-
-    ;; Filling should work correctly from any place inside the contents
-    (goto-char start)
-    (forward-char (random (- (1- end) start)))
-    (fill-paragraph)
-
-    ;; Newline characters may have been added; find new end
-    (beginning-of-line)
-    (while (not (looking-at ".*//.*swift-mode:test:case-end"))
-      (setq end (point))
-      (forward-line))
-    (buffer-substring start end)))
+          (syntax-propertize (point-max))
+          (setq lines (swift-mode:test-fill:parse-fill-test)))
+        (dolist (mode '(break join))
+          (swift-mode:test-fill:test-fill-region
+           swift-file
+           lines
+           mode
+           error-buffer
+           error-counts)
+          (swift-mode:test-fill:test-fill-paragraph
+           swift-file
+           lines
+           mode
+           error-buffer
+           error-counts
+           progress-reporter))))))
+
+(defun swift-mode:test-fill-region-as-paragraph (error-buffer error-counts)
+  "Run tests for `fill-region-as-paragraph'.
+
+See `swift-mode:run-test:fill' for ERROR-BUFFER and ERROR-COUNTS."
+  ;; Sinle-line comments, insert breaks:
+  (swift-mode:do-test-fill-region-as-paragraph
+   "// abc def ghi\n"
+   "// abc def\n// ghi\n"
+   8
+   12
+   error-buffer
+   error-counts)
+
+  ;; Sinle-line comments, delete breaks:
+  (swift-mode:do-test-fill-region-as-paragraph
+   "// abc\n// def\n// ghi\n"
+   "// abc def\n// ghi\n"
+   8
+   12
+   error-buffer
+   error-counts)
+
+  ;; Multiline comments, insert breaks:
+  (swift-mode:do-test-fill-region-as-paragraph
+   "/* abc def ghi jkl */\n"
+   "/*\n abc def ghi\n jkl\n */\n"
+   8
+   12
+   error-buffer
+   error-counts)
+
+  ;; Multiline comments, delete breaks:
+  (swift-mode:do-test-fill-region-as-paragraph
+   "/*\n abc\n def\n ghi\n */\n"
+   "/*\n abc def ghi\n */\n"
+   8
+   12
+   error-buffer
+   error-counts)
+
+  ;; Multiline comments, to one-line:
+  (swift-mode:do-test-fill-region-as-paragraph
+   "/*\n abc\n def\n ghi\n */\n"
+   "/* abc def ghi */\n"
+   8
+   80
+   error-buffer
+   error-counts)
+
+  ;; Multiline comments, keep line break after open delimiter:
+  (swift-mode:do-test-fill-region-as-paragraph
+   "/*\n abc\n def\n ghi */\n"
+   "/*\n abc def ghi */\n"
+   8
+   12
+   error-buffer
+   error-counts)
+
+  ;; Multiline comments, keep line break before close delimiter:
+  (swift-mode:do-test-fill-region-as-paragraph
+   "/* abc\n def\n ghi\n */\n"
+   "/* abc def\n   ghi\n */\n"
+   8
+   12
+   error-buffer
+   error-counts))
+
+(defun swift-mode:do-test-fill-region-as-paragraph
+    (input
+     expected
+     fill-column
+     comment-fill-column
+     error-buffer
+     error-counts)
+  "Run a test for `fill-region-as-paragraph'.
+
+INPUT is a text before filling.
+
+EXPECTED is the expected result.
+
+FILL-COLUMN and COMMENT-FILL-COLUMN is used for respective dynamic variables.
+
+See `swift-mode:run-test:fill' for ERROR-BUFFER and ERROR-COUNTS."
+  (with-temp-buffer
+    (switch-to-buffer (current-buffer))
+    (insert input)
+    (swift-mode)
+    (syntax-propertize (point-max))
+    (let ((fill-column fill-column)
+          (comment-fill-column comment-fill-column))
+      (fill-region-as-paragraph (point-min) (point-max)))
+    (let* ((status (if (equal (buffer-string) expected)
+                       'ok
+                     (swift-mode:show-error
+                      error-buffer "swift-mode-test-fill.el" 0
+                      "error"
+                      (concat "`fill-region-as-paragraph' failed\n"
+                              "Expected:\n```\n" expected "```\n\n"
+                              "Actual:\n```\n" (buffer-string) "```"))
+                     'error))
+           (count-assoc (assq status error-counts)))
+      (setcdr count-assoc (1+ (cdr count-assoc))))))
+
+(defun swift-mode:test-fill:parse-fill-test ()
+  "Parse the current buffer as a test file and return its structure.
+
+The result is list of elements, which is one of:
+
+- non-paragraph line, (list 'literal STRING), where STRING is the line
+  excluding a line break,
+
+- paragraph, (list 'paragraph PREFIX BLOCK-BOUNDARY-TYPE), where PREFIX is the
+  fill prefix and BLOCK-BOUNDARY-TYPE is either nil, `start', or `end', or
+
+- list item, (list 'list-item PREFIX).
+
+- heading, (list 'heading PREFIX)."
+  (save-excursion
+    (goto-char (point-min))
+    (let ((result ()))
+      (while (not (eobp))
+        (push
+         (cond
+          ((looking-at "\\(.*\\)swift-mode:test:paragraph")
+           (list 'paragraph
+                 (match-string-no-properties 1)
+                 (cond
+                  ((looking-at ".*swift-mode:test:start-block")
+                   'start)
+                  ((looking-at ".*swift-mode:test:end-block")
+                   'end)
+                  (t
+                   nil))))
+          ((looking-at "\\(.*\\)swift-mode:test:list-item")
+           (list 'list-item (match-string-no-properties 1)))
+          ((looking-at "\\(.*\\)swift-mode:test:heading")
+           (list 'heading (match-string-no-properties 1)))
+          (t
+           (list 'literal
+                 (buffer-substring-no-properties
+                  (line-beginning-position)
+                  (line-end-position)))))
+         result)
+        (forward-line))
+      (reverse result))))
+
+(defun swift-mode:test-fill:test-fill-region
+    (filename lines mode error-buffer error-counts)
+  "Run tests for `fill-region'.
+
+FILENAME is the name of test file.
+
+LINES is the parsed lines of the test file.
+See `swift-mode:test-fill:parse-fill-test' for details.
+
+MODE is either `break' or `join'.  If it is `break', test breaking long lines.
+If it is `join' test joining short lines.
+
+See `swift-mode:run-test:fill' for ERROR-BUFFER and ERROR-COUNTS."
+  (let (regions
+        expected
+        actual
+        start)
+    (with-temp-buffer
+      (switch-to-buffer (current-buffer))
+      (swift-mode)
+      (setq regions (nth 0 (swift-mode:test-fill:insert-test lines mode)))
+      (syntax-propertize (point-max))
+      (dolist (region (reverse regions))
+        (fill-region-as-paragraph (nth 0 region) (nth 1 region)))
+      (setq expected (buffer-substring-no-properties (point-min) (point-max))))
+    (with-temp-buffer
+      (switch-to-buffer (current-buffer))
+      (swift-mode)
+      (swift-mode:test-fill:insert-test lines mode)
+      (syntax-propertize (point-max))
+      (fill-region (point-min) (point-max))
+      (setq actual (buffer-substring-no-properties (point-min) (point-max))))
+    (let* ((status (if (equal actual expected)
+                       'ok
+                     (swift-mode:show-error
+                      error-buffer filename 0
+                      "error"
+                      (concat "`fill-region' failed\n"
+                              (swift-mode:test-fill:diff-strings
+                               expected
+                               actual)))
+                     'error))
+           (count-assoc (assq status error-counts)))
+      (setcdr count-assoc (1+ (cdr count-assoc))))))
+
+(defun swift-mode:test-fill:test-fill-paragraph
+    (filename
+     lines
+     mode
+     error-buffer
+     error-counts
+     progress-reporter)
+  "Run tests for `fill-paragraph'.
+
+FILENAME is the name of test file.
+
+LINES is the parsed lines of the test file.
+See `swift-mode:test-fill:parse-fill-test' for details.
+
+MODE is either `break' or `join'.  If it is `break', test breaking long lines.
+If it is `join' test joining short lines.
+
+See `swift-mode:run-test:fill' for ERROR-BUFFER, ERROR-COUNTS, and
+PROGRESS-REPORTER."
+  (let (regions-and-blocks
+        regions
+        blocks
+        original
+        expected
+        (point-placements '(beginning-of-line
+                            after-indent
+                            after-first-slash
+                            after-slashes
+                            end-of-line)))
+    (with-temp-buffer
+      (switch-to-buffer (current-buffer))
+      (swift-mode)
+      (setq regions-and-blocks (swift-mode:test-fill:insert-test lines mode))
+      (setq regions (nth 0 regions-and-blocks))
+      (setq blocks (nth 1 regions-and-blocks))
+      (setq original (buffer-string))
+      (dolist (region regions)
+        (delete-region (point-min) (point-max))
+        (insert original)
+        (syntax-propertize (point-max))
+        (fill-region-as-paragraph (nth 0 region) (nth 1 region))
+        (setq expected (buffer-substring-no-properties (point-min) 
(point-max)))
+        (dolist (point-placement point-placements)
+          (when (not noninteractive)
+            (progress-reporter-update progress-reporter))
+          (swift-mode:test-fill:do-test-fill-paragraph
+           filename
+           original
+           expected
+           region
+           point-placement
+           mode
+           nil
+           error-buffer
+           error-counts)))
+      (dolist (block blocks)
+        (delete-region (point-min) (point-max))
+        (insert original)
+        (syntax-propertize (point-max))
+        (fill-region (nth 0 block) (nth 1 block))
+        (setq expected (buffer-substring-no-properties (point-min) 
(point-max)))
+        (dolist (point-placement point-placements)
+          (when (not noninteractive)
+            (progress-reporter-update progress-reporter))
+          (swift-mode:test-fill:do-test-fill-paragraph
+           filename
+           original
+           expected
+           block
+           point-placement
+           mode
+           t
+           error-buffer
+           error-counts))))))
+
+(defun swift-mode:test-fill:do-test-fill-paragraph (filename
+                                                    original
+                                                    expected
+                                                    region
+                                                    point-placement
+                                                    mode
+                                                    entire-comment
+                                                    error-buffer
+                                                    error-counts)
+  "Run single test for `fill-paragraph'.
+
+FILENAME is the name of test file.
+
+ORIGINAL is a text before filling.
+
+EXPECTED is the expected result.
+
+REGION is the region to fill.
+
+POINT-PLACEMENT designates where to put the point before filling.  It must be
+one of the following:
+- `beginning-of-line'
+- `after-indent'
+- `after-first-slash'
+- `after-slashes'
+- `end-of-line'
+- `end-of-region'
+
+MODE is either `break' or `join'.  If it is `break', test breaking long lines.
+If it is `join' test joining short lines.
+
+ENTIRE-COMMENT is used for 
`swift-mode:fill-paragraph-entire-comment-or-string'.
+
+See `swift-mode:run-test:fill' for ERROR-BUFFER, ERROR-COUNTS."
+  (let (actual)
+    (delete-region (point-min) (point-max))
+    (insert original)
+    (syntax-propertize (point-max))
+    (cond
+     ((eq point-placement 'beginning-of-line)
+      (goto-char (nth 0 region)))
+     ((eq point-placement 'after-indent)
+      (goto-char (nth 0 region))
+      (back-to-indentation))
+     ((eq point-placement 'after-first-slash)
+      (goto-char (nth 0 region))
+      (back-to-indentation)
+      (when (eq (char-after) ?/)
+        (forward-char)))
+     ((eq point-placement 'after-slashes)
+      (goto-char (nth 0 region))
+      (back-to-indentation)
+      (skip-chars-forward "/"))
+     ((eq point-placement 'end-of-line)
+      (goto-char (nth 0 region))
+      (end-of-line))
+     ((eq point-placement 'end-of-region)
+      (goto-char (1- (nth 1 region)))))
+    (let ((swift-mode:fill-paragraph-entire-comment-or-string entire-comment))
+      (fill-paragraph))
+    (setq actual (buffer-substring-no-properties (point-min) (point-max)))
+    (let* ((status
+            (if (equal actual expected)
+                'ok
+              (swift-mode:show-error
+               error-buffer filename (nth 2 region)
+               "error"
+               (concat "`fill-paragraph' failed\n"
+                       "point-placement: " (symbol-name point-placement) "\n"
+                       "mode: " (prin1-to-string mode) "\n"
+                       "entire-comment: " (prin1-to-string entire-comment) "\n"
+                       (swift-mode:test-fill:diff-strings
+                        expected
+                        actual)))
+              'error))
+           (count-assoc (assq status error-counts)))
+      (setcdr count-assoc (1+ (cdr count-assoc))))))
+
+(defun swift-mode:test-fill:insert-test (lines mode)
+  "Insert parsed lines at point.
+
+LINES is the parsed lines of the test file.
+See `swift-mode:test-fill:parse-fill-test' for details.
+
+MODE is either `break' or `join'.  If it is `break', test breaking long lines.
+If it is `join' test joining short lines."
+  (let (regions
+        blocks
+        start
+        block-start
+        (line-number 0))
+    (dolist (line lines)
+      (setq line-number (1+ line-number))
+      (cond
+       ((eq (car line) 'literal)
+        (insert (nth 1 line) "\n"))
+       ((eq (car line) 'paragraph)
+        (setq start (point))
+        (when (eq (nth 2 line) 'start)
+          (setq block-start (point)))
+        (swift-mode:test-fill:insert-paragraph
+         (nth 1 line)
+         (nth 1 line)
+         mode)
+        (push (list start (point) line-number) regions)
+        (when (eq (nth 2 line) 'end)
+          (push (list block-start (point) line-number) blocks)))
+       ((eq (car line) 'list-item)
+        (setq start (point))
+        (swift-mode:test-fill:insert-paragraph
+         (concat (nth 1 line) "- ")
+         (nth 1 line)
+         mode)
+        (push (list start (point) line-number) regions))
+       ((eq (car line) 'heading)
+        (setq start (point))
+        (swift-mode:test-fill:insert-paragraph
+         (concat (nth 1 line) "## ")
+         (nth 1 line)
+         mode)
+        (push (list start (point) line-number) regions))))
+    (list (reverse regions) (reverse blocks))))
+
+(defun swift-mode:test-fill:insert-paragraph (first-line-prefix prefix mode)
+  "Insert a test paragraph at point.
+
+FIRST-LINE-PREFIX is inserted before the first line, while PREFIX is inserted
+before other lines.
+
+If MODE is `join', insert short multiple lines.  If MODE is `break', insert a
+long line."
+  (cond
+   ((eq mode 'join)
+    (insert first-line-prefix "aaa\n")
+    (dotimes (_ 100)
+      (insert prefix "aaa\n")))
+   ((eq mode 'break)
+    (insert first-line-prefix)
+    (dotimes (_ 100)
+      (insert "aaa "))
+    (insert "\n"))))
+
+(defun swift-mode:test-fill:diff-strings (expected actual)
+  "Return difference of EXPECTED and ACTUAL."
+  (let (expected-buffer actual-buffer)
+    (with-temp-buffer
+      (rename-buffer "expected" t)
+      (setq expected-buffer (current-buffer))
+      (insert expected)
+      (with-temp-buffer
+        (rename-buffer "actual" t)
+        (setq actual-buffer (current-buffer))
+        (insert actual)
+        (with-temp-buffer
+          (diff-no-select expected-buffer actual-buffer nil t (current-buffer))
+          (buffer-string))))))
 
 (provide 'swift-mode-test-fill)
 



reply via email to

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