branch: elpa/markdown-mode
commit e4be4c1ab8b96e1effbc3b378da51e4650b838cf
Merge: 3933f282d0 a94cb48c35
Author: Shohei YOSHIDA <[email protected]>
Commit: GitHub <[email protected]>

    Merge pull request #933 from systemfreund/fix/support-4plus-backtick-fences
    
    Support fenced code blocks with 4+ backticks
---
 markdown-mode.el       | 25 ++++++++++++++--------
 tests/markdown-test.el | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 74 insertions(+), 9 deletions(-)

diff --git a/markdown-mode.el b/markdown-mode.el
index 21acc05b06..160975cbb2 100644
--- a/markdown-mode.el
+++ b/markdown-mode.el
@@ -847,9 +847,9 @@ Groups 1 and 3 match the opening and closing tags.
 Group 2 matches the key sequence.")
 
 (defconst markdown-regex-gfm-code-block-open
-  
"^[[:blank:]]*\\(?1:```\\)\\(?2:[[:blank:]]*{?[[:blank:]]*\\)\\(?3:[^`[:space:]]+?\\)?\\(?:[[:blank:]]+\\(?4:.+?\\)\\)?\\(?5:[[:blank:]]*}?[[:blank:]]*\\)$"
+  
"^[[:blank:]]*\\(?1:`\\{3,\\}\\)\\(?2:[[:blank:]]*{?[[:blank:]]*\\)\\(?3:[^`[:space:]]+?\\)?\\(?:[[:blank:]]+\\(?4:.+?\\)\\)?\\(?5:[[:blank:]]*}?[[:blank:]]*\\)$"
   "Regular expression matching opening of GFM code blocks.
-Group 1 matches the opening three backquotes and any following whitespace.
+Group 1 matches the opening three or more backquotes.
 Group 2 matches the opening brace (optional) and surrounding whitespace.
 Group 3 matches the language identifier (optional).
 Group 4 matches the info string (optional).
@@ -857,9 +857,9 @@ Group 5 matches the closing brace (optional), whitespace, 
and newline.
 Groups need to agree with `markdown-regex-tilde-fence-begin'.")
 
 (defconst markdown-regex-gfm-code-block-close
-  "^[[:blank:]]*\\(?1:```\\)\\(?2:\\s *?\\)$"
+  "^[[:blank:]]*\\(?1:`\\{3,\\}\\)\\(?2:\\s *?\\)$"
   "Regular expression matching closing of GFM code blocks.
-Group 1 matches the closing three backquotes.
+Group 1 matches the closing three or more backquotes.
 Group 2 matches any whitespace and the final newline.")
 
 (defconst markdown-regex-pre
@@ -1008,6 +1008,13 @@ Group 3 matches the mathematical expression contained 
within.
 Group 2 matches the opening slashes, and is used internally to
 match the closing slashes.")
 
+(defsubst markdown-make-gfm-fence-regex (num-backticks &optional end-of-line)
+  "Return regexp matching a GFM code fence at least NUM-BACKTICKS long.
+END-OF-LINE is the regexp construct to indicate end of line; $ if
+missing."
+  (format "%s%d%s%s" "^[[:blank:]]*\\([`]\\{" num-backticks ",\\}\\)"
+          (or end-of-line "$")))
+
 (defsubst markdown-make-tilde-fence-regex (num-tildes &optional end-of-line)
   "Return regexp matching a tilde code fence at least NUM-TILDES long.
 END-OF-LINE is the regexp construct to indicate end of line; $ if
@@ -1421,7 +1428,7 @@ giving the bounds of the current and parent list items."
      (markdown-get-yaml-metadata-end-border markdown-yaml-metadata-end)
      markdown-yaml-metadata-section)
     ((,markdown-regex-gfm-code-block-open markdown-gfm-block-begin)
-     (,markdown-regex-gfm-code-block-close markdown-gfm-block-end)
+     (markdown-make-gfm-fence-regex markdown-gfm-block-end)
      markdown-gfm-code))
   "Mapping of regular expressions to \"fenced-block\" constructs.
 These constructs are distinguished by having a distinctive start
@@ -1687,12 +1694,12 @@ MIDDLE-BEGIN is the start of the \"middle\" section of 
the block."
       (put-text-property close-begin close-end
                          (cl-cadadr fence-spec) close-data))))
 
-(defun markdown--triple-quote-single-line-p (begin)
+(defun markdown--code-fence-single-line-p (begin)
   (save-excursion
     (goto-char begin)
     (save-match-data
-      (and (search-forward "```" nil t)
-           (search-forward "```" (line-end-position) t)))))
+      (and (re-search-forward "`\\{3,\\}" nil t)
+           (re-search-forward "`\\{3,\\}" (line-end-position) t)))))
 
 (defun markdown-syntax-propertize-fenced-block-constructs (start end)
   "Propertize according to `markdown-fenced-block-pairs' from START to END.
@@ -1749,7 +1756,7 @@ start which was previously propertized."
                    0)))
                (prop (cl-cadar correct-entry)))
           (when (or (not (eq prop 'markdown-gfm-block-begin))
-                    (not (markdown--triple-quote-single-line-p block-start)))
+                    (not (markdown--code-fence-single-line-p block-start)))
             ;; get correct match data
             (save-excursion
               (beginning-of-line)
diff --git a/tests/markdown-test.el b/tests/markdown-test.el
index 1a19f179fe..3778adb530 100644
--- a/tests/markdown-test.el
+++ b/tests/markdown-test.el
@@ -4125,6 +4125,64 @@ echo hey
     (should (equal (markdown-code-block-at-pos 34) '(1 35)))
     (should (equal (markdown-code-block-at-pos 35) nil))))
 
+(ert-deftest test-markdown-parsing/code-block-at-pos-gfm-fenced-4-backticks ()
+  "Ensure `markdown-code-block-at-pos' works with 4-backtick fenced blocks."
+  (markdown-test-string
+      "```` markdown
+# Not a heading
+```
+inner
+```
+````"
+    (should (markdown-code-block-at-pos 1))
+    ;; Inside the block, on the "# Not a heading" line
+    (should (markdown-code-block-at-pos 15))
+    ;; On the inner ``` line — should still be inside the outer block
+    (should (markdown-code-block-at-pos 33))
+    ;; After the closing ```` — should not be in a code block
+    (should-not (markdown-code-block-at-pos (point-max)))))
+
+(ert-deftest test-markdown-parsing/code-block-at-pos-gfm-fenced-5-backticks ()
+  "Ensure `markdown-code-block-at-pos' works with 5-backtick fenced blocks."
+  (markdown-test-string
+      "````` text
+# Not a heading
+`````"
+    (should (markdown-code-block-at-pos 1))
+    (should (markdown-code-block-at-pos 12))
+    (should-not (markdown-code-block-at-pos (point-max)))))
+
+(ert-deftest test-markdown-outline/heading-in-4-backtick-code-block ()
+  "Headers inside 4+ backtick code blocks should not be outline headings.
+See GitHub issue jrblevin/markdown-mode#932."
+  (markdown-test-string-gfm
+      "# Real heading
+
+```` markdown
+# Not a heading
+```
+inner block
+```
+````
+
+## Another real heading
+"
+    ;; The 4-backtick block should be detected as a code block
+    (should (markdown-code-block-at-pos 35))
+    ;; Navigate forward through headings — the fake heading should be skipped
+    (goto-char (point-min))
+    (markdown-next-visible-heading 1)
+    (should (looking-at "^## Another real heading"))))
+
+(ert-deftest test-markdown-font-lock/gfm-code-block-4-backticks ()
+  "Test font-lock for 4-backtick GFM code blocks."
+  (markdown-test-string-gfm
+      "```` markdown
+code line
+````"
+    ;; The code line should have markdown-pre-face
+    (markdown-test-range-has-face 15 23 'markdown-pre-face)))
+
 (ert-deftest test-markdown-parsing/code-block-at-pos-yaml-metadata ()
   "Ensure `markdown-code-block-at-pos' works in YAML metadata blocks."
   (let ((markdown-use-pandoc-style-yaml-metadata t))

Reply via email to