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

    Highlight backslash-escaped inline formatting
    
    A backslash before an inline formatting delimiter escapes it in
    AsciiDoc, so the markup should render literally. Add a high-priority
    font-lock keyword that de-emphasises the backslash and marks the
    escaped delimiter run as reserved literal text, so the quote keywords
    skip it. This fixes the case where an escaped unconstrained span
    (\**x**) still leaked an inner constrained match.
---
 CHANGELOG.md           |  1 +
 README.adoc            |  5 +++--
 adoc-mode.el           | 26 ++++++++++++++++++++++++++
 test/adoc-mode-test.el | 23 +++++++++++++++++++++++
 4 files changed, 53 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a28584aafd..14136b0a6a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,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`).
 - 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 1d253115fb..7c43a20370 100644
--- a/README.adoc
+++ b/README.adoc
@@ -34,7 +34,7 @@ be easily ignored.
 
 Here are some of the main features of `adoc-mode`:
 
-- comprehensive syntax highlighting for all AsciiDoc elements
+- comprehensive syntax highlighting for all AsciiDoc elements, including 
backslash-escaped inline formatting (e.g. `\*not bold*`)
 - native fontification of source code blocks using language-appropriate major 
modes
 - font-lock support for Asciidoctor inline macros (`kbd:[]`, `btn:[]`, 
`menu:[]`, etc.)
 - inline image preview with right-click context menus and remote image support
@@ -234,7 +234,8 @@ These days all upstream packages (e.g. on MELPA) are built 
from Bozhidar's fork.
 
 Here are some features that we're considering to add in the future:
 
-- Correctly highlighting backslash escapes
+- Table editing and alignment
+- Verifying and improving compliance with the latest AsciiDoc language 
specification
 
 Check out the issue tracker for more details.
 
diff --git a/adoc-mode.el b/adoc-mode.el
index c3001eda4e..bcf82428e9 100644
--- a/adoc-mode.el
+++ b/adoc-mode.el
@@ -1955,6 +1955,28 @@ face-spec expression (e.g. from 
`adoc-facespec-subscript')."
        '(3 nil)) ; grumbl, I don't know how to get rid of it
      `(4 '(face ,(or del-face 'adoc-meta-hide-face) adoc-reserved t) t)))); 
close del
 
+(defconst adoc-re-escaped-formatting
+  "\\(\\\\\\)\\(\\([*_`#+^~]\\)\\3*\\)"
+  "Regexp matching a backslash-escaped inline formatting delimiter.
+Group 1 is the escaping backslash; group 2 is the escaped
+delimiter run, a maximal run of one formatting delimiter
+character (`*', `**', `+++', …).  A lone `$' is intentionally
+excluded: single dollars are not markup (only `$$' is), and a
+literal `\\$' is far more common in prose.")
+
+(defun adoc-kw-escaped-formatting ()
+  "Return a font-lock keyword for backslash-escaped inline formatting.
+In AsciiDoc a backslash before an inline formatting delimiter
+escapes it, so the markup is rendered literally.  The keyword
+de-emphasises the backslash and marks the escaped delimiters as
+reserved literal text, so the quote keywords (which refuse to
+match across `adoc-reserved' text) leave the span alone."
+  (list
+   `(lambda (end)
+      (adoc-kwf-std end adoc-re-escaped-formatting '(2)))
+   '(1 '(face adoc-meta-hide-face adoc-reserved t) t)  ; the backslash
+   '(2 '(face nil adoc-reserved t) t)))                ; the escaped delimiters
+
 (defun adoc-kw-inline-macro (&optional cmd-name unconstrained 
attribute-list-constraints cmd-face target-faces target-meta-p attribute-list 
textprops)
   "Returns a kewyword which highlights an inline macro.
 
@@ -2573,6 +2595,10 @@ for multiline constructs to be matched."
    ;; 7. Replacements2
 
 
+   ;; backslash-escaped inline formatting (must precede the quote keywords
+   ;; below so the escaped delimiters are reserved before they are scanned)
+   (adoc-kw-escaped-formatting)
+
    ;; Inline code and passthroughs
    ;; ----------------------------
    ;; Asciidoctor: `text` is inline code (monospace literal)
diff --git a/test/adoc-mode-test.el b/test/adoc-mode-test.el
index d0135005b6..42c4d6d89d 100644
--- a/test/adoc-mode-test.el
+++ b/test/adoc-mode-test.el
@@ -1113,6 +1113,29 @@ on top of the `<mark>' default)."
     (adoc-insert-link "https://x.com"; "here")
     (should (string-equal (buffer-string) "see https://x.com[here] now"))))
 
+;;;; Escaped formatting
+
+(ert-deftest adoctest-test-escaped-formatting ()
+  ;; constrained: escaped markup is literal, the backslash is de-emphasised
+  (adoctest-faces "escaped-bold"
+                  "\\" 'adoc-meta-hide-face "*foo*" 'no-face)
+  ;; unconstrained escape (previously leaked an inner constrained match)
+  (adoctest-faces "escaped-unconstrained-bold"
+                  "\\" 'adoc-meta-hide-face "**foo**" 'no-face)
+  (adoctest-faces "escaped-monospace"
+                  "\\" 'adoc-meta-hide-face "`foo`" 'no-face)
+  ;; a backslash before a non-formatting char is left untouched
+  (adoctest-faces "non-escape-backslash" "a\\b" 'no-face)
+  ;; normal markup after an escaped span still fontifies
+  (adoctest-faces "escape-then-bold"
+                  "\\" 'adoc-meta-hide-face "*x* " 'no-face
+                  "*" 'adoc-meta-hide-face "y" 'adoc-bold-face "*" 
'adoc-meta-hide-face)
+  ;; a lone dollar is not markup, so a literal \$ is left untouched
+  (adoctest-faces "escaped-dollar" "x \\$5 y" 'no-face)
+  ;; a triple-delimiter passthrough is escaped in full
+  (adoctest-faces "escaped-passthrough"
+                  "\\" 'adoc-meta-hide-face "+++p+++" 'no-face))
+
 ;;;; List editing
 
 (ert-deftest adoctest-test-list-promote-demote-unordered ()

Reply via email to