diff --git a/CHANGELOG.md b/CHANGELOG.md index d9b5803..6d6c50c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 `<>` even when a captioned `<>` appears later on the same or an adjacent line, and ignores the whitespace in forms like `<>`. 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) diff --git a/adoc-mode.el b/adoc-mode.el index f883276..3af2ec6 100644 --- a/adoc-mode.el +++ b/adoc-mode.el @@ -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." @@ -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) @@ -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. @@ -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. @@ -3545,7 +3554,7 @@ 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 @@ -3553,7 +3562,11 @@ trailing delimiter ('== my title =='). ;; 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) @@ -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)) @@ -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 @@ -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) diff --git a/test/adoc-mode-editing-test.el b/test/adoc-mode-editing-test.el index be73ce0..49faef8 100644 --- a/test/adoc-mode-editing-test.el +++ b/test/adoc-mode-editing-test.el @@ -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 "======") diff --git a/test/adoc-mode-font-lock-test.el b/test/adoc-mode-font-lock-test.el index f25d82a..72b45e5 100644 --- a/test/adoc-mode-font-lock-test.el +++ b/test/adoc-mode-font-lock-test.el @@ -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))))