Skip to content

Commit 3a5bdcc

Browse files
authored
✨ NEW: Add colon_fence plugin (#91)
1 parent 7e90750 commit 3a5bdcc

File tree

4 files changed

+608
-0
lines changed

4 files changed

+608
-0
lines changed

markdown_it/extensions/colon_fence.py

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
from markdown_it import MarkdownIt
2+
from markdown_it.common.utils import unescapeAll, escapeHtml, stripEscape
3+
from markdown_it.rules_block import StateBlock
4+
5+
6+
def colon_fence_plugin(md: MarkdownIt):
7+
"""This plugin directly mimics regular fences, but with `:` colons.
8+
9+
Example::
10+
11+
:::name
12+
contained text
13+
:::
14+
15+
"""
16+
17+
md.block.ruler.before(
18+
"fence",
19+
"colon_fence",
20+
_rule,
21+
{"alt": ["paragraph", "reference", "blockquote", "list", "footnote_def"]},
22+
)
23+
md.add_render_rule("colon_fence", _render)
24+
25+
26+
def _rule(state: StateBlock, startLine: int, endLine: int, silent: bool):
27+
28+
haveEndMarker = False
29+
pos = state.bMarks[startLine] + state.tShift[startLine]
30+
maximum = state.eMarks[startLine]
31+
32+
# if it's indented more than 3 spaces, it should be a code block
33+
if state.sCount[startLine] - state.blkIndent >= 4:
34+
return False
35+
36+
if pos + 3 > maximum:
37+
return False
38+
39+
marker = state.srcCharCode[pos]
40+
41+
# /* : */
42+
if marker != 0x3A:
43+
return False
44+
45+
# scan marker length
46+
mem = pos
47+
pos = state.skipChars(pos, marker)
48+
49+
length = pos - mem
50+
51+
if length < 3:
52+
return False
53+
54+
markup = state.src[mem:pos]
55+
params = state.src[pos:maximum]
56+
57+
# Since start is found, we can report success here in validation mode
58+
if silent:
59+
return True
60+
61+
# search end of block
62+
nextLine = startLine
63+
64+
while True:
65+
nextLine += 1
66+
if nextLine >= endLine:
67+
# unclosed block should be autoclosed by end of document.
68+
# also block seems to be autoclosed by end of parent
69+
break
70+
71+
pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]
72+
maximum = state.eMarks[nextLine]
73+
74+
if pos < maximum and state.sCount[nextLine] < state.blkIndent:
75+
# non-empty line with negative indent should stop the list:
76+
# - ```
77+
# test
78+
break
79+
80+
if state.srcCharCode[pos] != marker:
81+
continue
82+
83+
if state.sCount[nextLine] - state.blkIndent >= 4:
84+
# closing fence should be indented less than 4 spaces
85+
continue
86+
87+
pos = state.skipChars(pos, marker)
88+
89+
# closing code fence must be at least as long as the opening one
90+
if pos - mem < length:
91+
continue
92+
93+
# make sure tail has spaces only
94+
pos = state.skipSpaces(pos)
95+
96+
if pos < maximum:
97+
continue
98+
99+
haveEndMarker = True
100+
# found!
101+
break
102+
103+
# If a fence has heading spaces, they should be removed from its inner block
104+
length = state.sCount[startLine]
105+
106+
state.line = nextLine + (1 if haveEndMarker else 0)
107+
108+
token = state.push("colon_fence", "code", 0)
109+
token.info = stripEscape(params)
110+
token.content = state.getLines(startLine + 1, nextLine, length, True)
111+
token.markup = markup
112+
token.map = [startLine, state.line]
113+
114+
return True
115+
116+
117+
def _render(self, tokens, idx, options, env):
118+
token = tokens[idx]
119+
info = unescapeAll(token.info).strip() if token.info else ""
120+
content = escapeHtml(token.content)
121+
block_name = ""
122+
123+
if info:
124+
block_name = info.split()[0]
125+
126+
return (
127+
"<pre><code"
128+
+ (f' class="block-{block_name}" ' if block_name else "")
129+
+ ">"
130+
+ content
131+
+ "</code></pre>\n"
132+
)

0 commit comments

Comments
 (0)