branch: elpa/adoc-mode
commit b60321d7330d4fc7b0390f3c6227252100c5d0d9
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>

    Preserve AsciiDoc hard line breaks when filling
    
    A line ending in a space and a plus is a forced line break in AsciiDoc;
    fill-paragraph used to merge it with the following line. Add an
    adoc-fill-paragraph as fill-paragraph-function that marks the newline
    after each hard break as a hard newline and fills with use-hard-newlines
    bound, so the break is kept. Stale hard newlines are cleared first, so
    removing a break and refilling merges the lines again.
---
 CHANGELOG.md           |  1 +
 README.adoc            |  1 +
 adoc-mode.el           | 22 ++++++++++++++++++++++
 test/adoc-mode-test.el | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 72 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 14136b0a6a..0231779258 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
 ### New features
 
 - Honour backslash escapes in inline formatting: a backslash before a 
formatting delimiter (e.g. `\*not bold*`, `\**nor this**`, `` \`nor code` ``) 
now de-emphasises the backslash and leaves the escaped span as literal text 
instead of fontifying it as markup. Previously the unconstrained forms still 
leaked an inner constrained match (`\**x**` highlighted `x`).
+- `fill-paragraph` (and auto-fill) now preserve AsciiDoc hard line breaks: a 
line ending in a space and a `+` is no longer merged with the following line. 
Filling still joins ordinary soft-wrapped lines and indents list-item 
continuations.
 - Add region-aware text-styling commands under the `C-c C-s` prefix, modelled 
on `markdown-mode`: `adoc-insert-bold` (`C-c C-s b`, `*text*`), 
`adoc-insert-italic` (`i`, `_text_`), `adoc-insert-monospace` (`m`, `` `text` 
``), `adoc-insert-highlight` (`h`, `#text#`), `adoc-insert-superscript` (`^`, 
`^text^`), `adoc-insert-subscript` (`~`, `~text~`), and `adoc-insert-link` 
(`l`). Each wraps the active region or the word at point, removes the markup 
again when it is already wrapped, and inse [...]
 - Add outline cycling commands modelled on `org-mode` and `markdown-mode`: 
`adoc-cycle` (`TAB`) rotates the visibility of the section subtree at point 
(folded / child titles / fully shown) when point is on a one-line title, and 
otherwise indents as usual; `adoc-cycle-buffer` (`S-TAB`) rotates the whole 
buffer between overview, contents, and show-all. Both build on the 
`outline-cycle` / `outline-cycle-buffer` primitives.
 - Add list editing: `adoc-promote` (`M-left`) and `adoc-demote` (`M-right`) 
now nest the list item at point one level deeper or shallower (for unordered 
`*`/`-` and implicitly-numbered `.` lists) in addition to acting on section 
titles, and the new `adoc-insert-list-item` (`M-RET`) inserts a sibling item 
below the current one, keeping its indentation and marker and incrementing the 
number/letter of explicitly-numbered items.
diff --git a/README.adoc b/README.adoc
index 7c43a20370..fb6b1eeb6a 100644
--- a/README.adoc
+++ b/README.adoc
@@ -47,6 +47,7 @@ Here are some of the main features of `adoc-mode`:
 - nested `imenu` index with hierarchical heading structure
 - outline folding built on `outline-minor-mode` (enabled out of the box): 
`TAB` cycles the subtree at point, `S-TAB` cycles the whole buffer (overview / 
contents / show all), one-line title style only
 - integration with `flyspell-mode` (skips non-prose regions), comment commands 
(`M-;`), and compilation mode
+- filling that respects AsciiDoc hard line breaks (a line ending in ` +`) and 
section titles
 
 === Demo
 
diff --git a/adoc-mode.el b/adoc-mode.el
index bcf82428e9..a807a39108 100644
--- a/adoc-mode.el
+++ b/adoc-mode.el
@@ -4153,6 +4153,7 @@ Turning on Adoc mode runs the normal hook 
`adoc-mode-hook'."
 
   ;; fill
   (add-hook 'fill-nobreak-predicate #'adoc-fill-nobreak-p nil t)
+  (setq-local fill-paragraph-function #'adoc-fill-paragraph)
 
   ;; misc
   (setq-local page-delimiter "^<<<+$")
@@ -4187,6 +4188,27 @@ from breaking section title lines."
     (beginning-of-line)
     (looking-at-p "=\\{1,6\\}[ \t]")))
 
+(defun adoc-fill-paragraph (&optional justify)
+  "Fill the current paragraph, preserving AsciiDoc hard line breaks.
+A line ending in a space followed by `+' is a forced line break;
+filling must not merge it with the following line.  Such breaks
+are marked as hard newlines so the filler keeps them.  Serves as
+the buffer's `fill-paragraph-function'."
+  (save-excursion
+    (let ((beg (progn (backward-paragraph) (point)))
+          (end (progn (forward-paragraph) (point))))
+      ;; Drop any stale hard newlines first (e.g. from a ` +' the user has
+      ;; since deleted), then mark the breaks that are still present.
+      (remove-text-properties beg end '(hard nil))
+      (goto-char beg)
+      (while (re-search-forward " \\+$" end t)
+        (when (eq (char-after) ?\n)
+          (put-text-property (point) (1+ (point)) 'hard t)))))
+  (let ((use-hard-newlines t)
+        (fill-paragraph-function nil))
+    (fill-paragraph justify))
+  t)
+
 ;; Flyspell
 
 (defun adoc-flyspell-p ()
diff --git a/test/adoc-mode-test.el b/test/adoc-mode-test.el
index 42c4d6d89d..a21877682f 100644
--- a/test/adoc-mode-test.el
+++ b/test/adoc-mode-test.el
@@ -1136,6 +1136,54 @@ on top of the `<mark>' default)."
   (adoctest-faces "escaped-passthrough"
                   "\\" 'adoc-meta-hide-face "+++p+++" 'no-face))
 
+;;;; Filling
+
+(ert-deftest adoctest-test-fill-preserves-hard-break ()
+  (with-temp-buffer
+    (adoc-mode)
+    (setq fill-column 40)
+    (insert "alpha beta gamma +\ndelta epsilon\neta theta iota\n")
+    (goto-char (point-min))
+    (fill-paragraph)
+    (should (string-equal (buffer-string)
+                          "alpha beta gamma +\ndelta epsilon eta theta 
iota\n"))))
+
+(ert-deftest adoctest-test-fill-joins-plain-lines ()
+  (with-temp-buffer
+    (adoc-mode)
+    (setq fill-column 40)
+    (insert "word1 word2 word3\nword4 word5\n")
+    (goto-char (point-min))
+    (fill-paragraph)
+    (should (string-equal (buffer-string) "word1 word2 word3 word4 word5\n"))))
+
+(ert-deftest adoctest-test-fill-list-item-indents-continuation ()
+  (with-temp-buffer
+    (adoc-mode)
+    (setq fill-column 40)
+    (insert "* long list item that should wrap nicely when filled to a narrow 
width indeed yes\n")
+    (goto-char (point-min))
+    (fill-paragraph)
+    (should (string-equal
+             (buffer-string)
+             "* long list item that should wrap nicely\n  when filled to a 
narrow width indeed\n  yes\n"))))
+
+(ert-deftest adoctest-test-fill-clears-stale-hard-break ()
+  ;; once the user removes a ` +', a refill must merge the lines again
+  (with-temp-buffer
+    (adoc-mode)
+    (setq fill-column 30)
+    (insert "alpha beta gamma delta epsilon zeta eta theta iota +\nkappa 
lambda mu nu\n")
+    (goto-char (point-min))
+    (fill-paragraph)
+    (goto-char (point-min))
+    (re-search-forward " \\+$")
+    (delete-char -2)                    ; drop the trailing " +"
+    (goto-char (point-min))
+    (fill-paragraph)
+    (should-not (string-match-p "iota +\n" (buffer-string)))
+    (should (string-match-p "iota kappa" (buffer-string)))))
+
 ;;;; List editing
 
 (ert-deftest adoctest-test-list-promote-demote-unordered ()

Reply via email to