Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
- Following a cross-reference at point (`C-c C-o` / `M-.`, and the new `xref` commands) now works for a plain `<<id>>` even when a captioned `<<id,caption>>` appears later on the same or an adjacent line, and ignores the whitespace in forms like `<<id >>`. Previously `adoc-xref-id-at-point` could return nil or an id with a trailing space in those cases.
- Heading navigation (`C-c C-n` and friends) and the imenu index no longer get confused by code and other delimited blocks. A `==`-style line inside a listing, source, literal, example, sidebar, quote, or open block, or a code line followed by `----` (which looks just like a two-line title underline), is no longer mistaken for a section title. Navigation and imenu now stay in step with what is actually highlighted as a title.
- Heading navigation and imenu now honour `adoc-enable-two-line-title`. It is nil by default, so two-line (setext) titles are no longer picked up unless you opt in, matching their fontification. Previously they were always recognised, which was the main source of the code-block confusion above.
- [#89](https://github.com/bbatsov/adoc-mode/pull/89): Promoting, demoting or toggling a one-line title (`M-left` / `M-right`, `C-c C-t`) no longer turns it into the enclosed form when it's followed by a newline. `== Section` used to become `=== Section ===`.
- [#89](https://github.com/bbatsov/adoc-mode/pull/89): `adoc-promote-title` and `adoc-demote-title` default to one level when called from Lisp without an argument (`adoc-demote-title` used to signal an error), and the title commands signal a `user-error` instead of an `error` when point isn't on a title.
- [#89](https://github.com/bbatsov/adoc-mode/pull/89): Title editing commands now honour `adoc-enable-two-line-title`, like highlighting, navigation and imenu already did.
- With two-line titles disabled (the default), `M-left` / `M-right` and `C-c C-t` no longer mistake a line above a `----` or `====` delimiter for a title and overwrite the delimiter.
- `C-c C-t` won't convert to a two-line title while they're disabled, or past level 4 (where it used to crash).
- A numeric value now skips underlines of that length, as documented. It used to compare the length of the title text instead.

## 0.9.0 (2026-06-02)

Expand Down
36 changes: 28 additions & 8 deletions adoc-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -1831,6 +1831,13 @@ TEXT-FACE is a face name symbol."

;; TODO: highlight bogous 'two line titles' with warning face
;; TODO: completely remove keyword when adoc-enable-two-line-title is nil
(defun adoc--two-line-title-underline-p (length)
"Return non-nil when a line LENGTH chars long may underline a two-line title.
That takes two-line titles to be enabled, and LENGTH to differ from
a numeric `adoc-enable-two-line-title'."
(and adoc-enable-two-line-title
(not (eql adoc-enable-two-line-title length))))

(defun adoc-kw-two-line-title (del text-face)
"Creates a keyword for font-lock which highlights two line titles.
TEXT-FACE is a face name symbol."
Expand All @@ -1841,8 +1848,7 @@ TEXT-FACE is a face name symbol."
(and adoc-enable-two-line-title
(adoc-kwf-search ,(adoc-re-two-line-title del) end t)
(< (abs (- (- (match-end 2) (match-beginning 2)) (- (match-end 3) (match-beginning 3)))) 3)
(or (not (numberp adoc-enable-two-line-title))
(not (equal adoc-enable-two-line-title (- (match-end 2) (match-beginning 2)))))
(adoc--two-line-title-underline-p (- (match-end 3) (match-beginning 3)))
(not (text-property-not-all (match-beginning 0) (match-end 0) 'adoc-reserved nil))))
;; highlighers
`(2 ,face-form t)
Expand Down Expand Up @@ -3120,12 +3126,12 @@ When ARG is nil (i.e. when no prefix arg is given), it defaults
to 1. When ARG is negative, level is demoted that many levels. If
ARG is 0, see `adoc-adjust-title-del'."
(interactive "p")
(adoc-modify-title arg))
(adoc-modify-title (or arg 1)))

(defun adoc-demote-title (&optional arg)
"Completely analogous to `adoc-promote-title'."
(interactive "p")
(adoc-promote-title (- arg)))
(adoc-promote-title (- (or arg 1))))

(defun adoc-adjust-title-del ()
"Adjusts underline length to match the length of the title's text.
Expand Down Expand Up @@ -3521,6 +3527,9 @@ Returns nil if there was no xref found."
When STRICT-MATCH is t, and 2 line title is used, the lengths of the underline
text and title must not differ by more than 2 characters.

Two line titles are only recognised when `adoc-enable-two-line-title'
is non-nil.

Title descriptor looks like this: (TYPE SUB-TYPE LEVEL TEXT START END)

0 TYPE: 1 fore one line title, 2 for two line title.
Expand All @@ -3545,15 +3554,19 @@ trailing delimiter ('== my title ==').
((looking-at (adoc-re-one-line-title level))
(setq type 1)
(setq text (match-string 2))
(setq sub-type (if (< 0 (length (match-string 3))) 2 1))
(setq sub-type (if (match-beginning 4) 2 1))
(setq found t))
;; WARNING: if you decide to replace adoc-re-two-line-title with a
;; method ensuring the correct length of the underline, be aware that
;; due to adoc-adjust-title-del we sometimes want to find a title which has
;; the wrong underline length.
;; Two-line titles only cover the first N levels (one per entry in
;; adoc-two-line-title-del); skip the check for higher levels.
((and (< level (length adoc-two-line-title-del))
;; They're also only recognised when enabled, like fontification,
;; navigation and imenu do - otherwise a line of text above a
;; block delimiter (`----', `====') would pass for one.
((and adoc-enable-two-line-title
(< level (length adoc-two-line-title-del))
(or (looking-at (adoc-re-two-line-title (nth level adoc-two-line-title-del)))
(save-excursion
(forward-line -1)
Expand All @@ -3564,7 +3577,8 @@ trailing delimiter ('== my title ==').
(<= (abs (- (length (match-string 3))
(length (match-string 2))))
2))
(not (string-prefix-p "[" (match-string 2))))
(not (string-prefix-p "[" (match-string 2)))
(adoc--two-line-title-underline-p (- (match-end 3) (match-beginning 3))))
(setq type 2)
(setq text (match-string 2))
(setq found t))
Expand Down Expand Up @@ -3608,7 +3622,7 @@ BUG: In one line title case: number of spaces between delimiters
and title's text are not preserved, afterwards its always one space."
(let ((descriptor (adoc-title-descriptor)))
(if (or create (not descriptor))
(error "Point is not on a title"))
(user-error "Point is not on a title"))

(let* ((type (nth 0 descriptor))
(new-type-val (cond
Expand Down Expand Up @@ -3652,6 +3666,12 @@ and title's text are not preserved, afterwards its always one space."
(end (nth 5 descriptor))
(saved-col (current-column)))

(when (eq new-type-val 2)
(unless adoc-enable-two-line-title
(user-error "Two-line titles are disabled, see `adoc-enable-two-line-title'"))
(when (>= new-level level-count)
(user-error "Two-line titles only support levels 0-%d" (1- level-count))))

;; set new title descriptor
(setcar (nthcdr 0 descriptor) new-type-val)
(setcar (nthcdr 1 descriptor) new-sub-type-val)
Expand Down
70 changes: 59 additions & 11 deletions test/adoc-mode-editing-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,75 @@
(adoc-test-trans "====== foo ======" "= foo =" '(adoc-promote-title 1))
(adoc-test-trans "== foo ==" "==== foo ====" '(adoc-promote-title 2))
;; two-line titles span five levels (0-4), so promoting level 4 wraps to 0
(adoc-test-trans "foo!\n===!" "foo\n---" '(adoc-promote-title 1))
(adoc-test-trans "foo!\n+++!" "foo\n===" '(adoc-promote-title 1))
(adoc-test-trans "foo!\n---!" "foo\n^^^" '(adoc-promote-title 2)))
(let ((adoc-enable-two-line-title t))
(adoc-test-trans "foo!\n===!" "foo\n---" '(adoc-promote-title 1))
(adoc-test-trans "foo!\n+++!" "foo\n===" '(adoc-promote-title 1))
(adoc-test-trans "foo!\n---!" "foo\n^^^" '(adoc-promote-title 2))))

(it "keeps a title's delimiter style when it is followed by text"
(adoc-test-trans "== Sec!tion\n\nbody\n" "=== Section\n\nbody\n"
'(adoc-promote-title 1))
(adoc-test-trans "=== Sec!tion\n\nbody\n" "== Section\n\nbody\n"
'(adoc-demote-title 1))
(adoc-test-trans "== Sec!tion ==\n\nbody\n" "=== Section ===\n\nbody\n"
'(adoc-promote-title 1))
(adoc-test-trans "== Sec!tion\n\nbody\n" "== Section\n\nbody\n"
'(adoc-adjust-title-del))
(adoc-test-trans "== Sec!tion\n\nbody\n" "== Section ==\n\nbody\n"
'(adoc-toggle-title-type t))
(adoc-test-trans "== Sec!tion ==\n\nbody\n" "== Section\n\nbody\n"
'(adoc-toggle-title-type t)))

(it "demotes titles"
(adoc-test-trans "= foo" "====== foo" '(adoc-demote-title 1))
(adoc-test-trans "= foo =" "====== foo ======" '(adoc-demote-title 1))
(adoc-test-trans "foo!\n===!" "foo\n+++" '(adoc-demote-title 1)))
(let ((adoc-enable-two-line-title t))
(adoc-test-trans "foo!\n===!" "foo\n+++" '(adoc-demote-title 1))))

(it "defaults to one level when called from Lisp without an argument"
(adoc-test-trans "== f!oo\n" "=== foo\n" '(adoc-promote-title))
(adoc-test-trans "== f!oo\n" "= foo\n" '(adoc-demote-title))
(adoc-test-trans "== f!oo\n" "=== foo\n" '(adoc-promote))
(adoc-test-trans "== f!oo\n" "= foo\n" '(adoc-demote)))

(it "toggles the title type"
(adoc-test-trans "= one" "one\n===" '(adoc-toggle-title-type))
(adoc-test-trans "two!\n===!" "= two" '(adoc-toggle-title-type))
(adoc-test-trans "= three!\nbar" "three\n=====\nbar" '(adoc-toggle-title-type))
(adoc-test-trans "four!\n====!\nbar" "= four\nbar" '(adoc-toggle-title-type))
(let ((adoc-enable-two-line-title t))
(adoc-test-trans "= one" "one\n===" '(adoc-toggle-title-type))
(adoc-test-trans "two!\n===!" "= two" '(adoc-toggle-title-type))
(adoc-test-trans "= three!\nbar" "three\n=====\nbar" '(adoc-toggle-title-type))
(adoc-test-trans "four!\n====!\nbar" "= four\nbar" '(adoc-toggle-title-type)))
(adoc-test-trans "= five" "= five =" '(adoc-toggle-title-type t))
(adoc-test-trans "= six =" "= six" '(adoc-toggle-title-type t)))

(it "refuses to make a two-line title when they are disabled or too deep"
(adoc-test-trans "== f!oo\n" "== foo\n"
'(expect (adoc-toggle-title-type) :to-throw 'user-error))
(let ((adoc-enable-two-line-title t))
(adoc-test-trans "====== f!oo\n" "====== foo\n"
'(expect (adoc-toggle-title-type) :to-throw 'user-error))))

(it "leaves block delimiters alone when two-line titles are disabled"
(dolist (command '(adoc-promote adoc-toggle-title-type))
(adoc-test-trans ".Ex!ample\n-!---\ncode\n----\n" ".Example\n----\ncode\n----\n"
`(expect (,command) :to-throw 'user-error))
(adoc-test-trans "Some t!ext\n====\nexample\n====\n" "Some text\n====\nexample\n====\n"
`(expect (,command) :to-throw 'user-error))))

(it "honours a numeric `adoc-enable-two-line-title' when editing"
(let ((adoc-enable-two-line-title 4))
(adoc-test-trans ".Ex!ample\n----\ncode\n----\n" ".Example\n----\ncode\n----\n"
'(expect (adoc-promote) :to-throw 'user-error))
(adoc-test-trans "foo!\n---" "== foo" '(adoc-toggle-title-type))))

(it "signals a user-error when point is not on a title"
(adoc-test-trans "just some pr!ose\n" "just some prose\n"
'(expect (adoc-promote-title 1) :to-throw 'user-error)))

(it "adjusts the two-line title underline length"
(adoc-test-trans "lorem!\n===!" "lorem\n=====" '(adoc-adjust-title-del))
(adoc-test-trans "lorem!\n========!" "lorem\n=====" '(adoc-adjust-title-del))
(adoc-test-trans "lorem!\n=====!" "lorem\n=====" '(adoc-adjust-title-del)))
(let ((adoc-enable-two-line-title t))
(adoc-test-trans "lorem!\n===!" "lorem\n=====" '(adoc-adjust-title-del))
(adoc-test-trans "lorem!\n========!" "lorem\n=====" '(adoc-adjust-title-del))
(adoc-test-trans "lorem!\n=====!" "lorem\n=====" '(adoc-adjust-title-del))))

(it "builds a two-line title underline"
(expect (adoc-make-two-line-title-underline 0 6) :to-equal "======")
Expand Down
5 changes: 5 additions & 0 deletions test/adoc-mode-font-lock-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@
"Document Title\n=============="
'(("Document Title" adoc-title-0-face)))))

(it "skips two-line titles whose underline has the excluded length"
(let ((adoc-enable-two-line-title 5))
(adoc-test--check-face-specs "Titles\n-----\n" '(("Titles" nil)))
(adoc-test--check-face-specs "Title\n------\n" '(("Title" adoc-title-1-face)))))

(when-fontifying-it "fontifies a block title"
(".Block Title\nsome text"
("Block Title" adoc-gen-face))))
Expand Down
Loading