1# ~~strike through~~ (and optionally ~single tilde~)
2from __future__ import annotations
3
4from .state_inline import Delimiter, StateInline
5
6
7def tokenize(state: StateInline, silent: bool) -> bool:
8 """Insert each marker as a separate text token, and add it to delimiter list.
9
10 When the ``strikethrough_single_tilde`` option is enabled on the
11 ``MarkdownIt`` instance, single ``~`` delimiters are also accepted and
12 runs of three or more tildes are rejected (matching GitHub's rendering behaviour).
13 """
14 start = state.pos
15 ch = state.src[start]
16
17 if silent:
18 return False
19
20 if ch != "~":
21 return False
22
23 scanned = state.scanDelims(state.pos, True)
24 length = scanned.length
25
26 single_tilde = state.md.options.get("strikethrough_single_tilde", False)
27
28 if single_tilde:
29 # GitHub mode: only accept exactly 1 or 2 tildes.
30 if length < 1:
31 return False
32 if length > 2:
33 # Consume 3+ tildes as plain text so the parser doesn't
34 # re-enter and match a subset of them. This intentionally
35 # matches GitHub's rendering, where ≥3 tildes are literal text.
36 token = state.push("text", "", 0)
37 token.content = ch * length
38 state.pos += scanned.length
39 return True
40
41 token = state.push("text", "", 0)
42 token.content = ch * length
43 state.delimiters.append(
44 Delimiter(
45 marker=ord(ch),
46 length=0, # disable "rule of 3" length checks
47 token=len(state.tokens) - 1,
48 end=-1,
49 open=scanned.can_open,
50 close=scanned.can_close,
51 )
52 )
53 else:
54 # Original markdown-it behaviour: minimum 2, split odd runs.
55 if length < 2:
56 return False
57
58 if length % 2:
59 token = state.push("text", "", 0)
60 token.content = ch
61 length -= 1
62
63 i = 0
64 while i < length:
65 token = state.push("text", "", 0)
66 token.content = ch + ch
67 state.delimiters.append(
68 Delimiter(
69 marker=ord(ch),
70 length=0, # disable "rule of 3" length checks
71 token=len(state.tokens) - 1,
72 end=-1,
73 open=scanned.can_open,
74 close=scanned.can_close,
75 )
76 )
77
78 i += 2
79
80 state.pos += scanned.length
81
82 return True
83
84
85def _postProcess(state: StateInline, delimiters: list[Delimiter]) -> None:
86 loneMarkers = []
87 maximum = len(delimiters)
88 single_tilde = state.md.options.get("strikethrough_single_tilde", False)
89
90 i = 0
91 while i < maximum:
92 startDelim = delimiters[i]
93
94 if startDelim.marker != 0x7E: # /* ~ */
95 i += 1
96 continue
97
98 if startDelim.end == -1:
99 i += 1
100 continue
101
102 endDelim = delimiters[startDelim.end]
103
104 # In single-tilde mode, opener and closer must have the same width
105 # (both `~` or both `~~`). The width is stored in the text token.
106 if single_tilde:
107 opener_content = state.tokens[startDelim.token].content
108 closer_content = state.tokens[endDelim.token].content
109 if opener_content != closer_content:
110 i += 1
111 continue
112
113 markup = state.tokens[startDelim.token].content
114
115 token = state.tokens[startDelim.token]
116 token.type = "s_open"
117 token.tag = "s"
118 token.nesting = 1
119 token.markup = markup
120 token.content = ""
121
122 token = state.tokens[endDelim.token]
123 token.type = "s_close"
124 token.tag = "s"
125 token.nesting = -1
126 token.markup = markup
127 token.content = ""
128
129 if (
130 state.tokens[endDelim.token - 1].type == "text"
131 and state.tokens[endDelim.token - 1].content == "~"
132 ):
133 loneMarkers.append(endDelim.token - 1)
134
135 i += 1
136
137 # If a marker sequence has an odd number of characters, it's split
138 # like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the
139 # start of the sequence.
140 #
141 # So, we have to move all those markers after subsequent s_close tags.
142 #
143 while loneMarkers:
144 i = loneMarkers.pop()
145 j = i + 1
146
147 while (j < len(state.tokens)) and (state.tokens[j].type == "s_close"):
148 j += 1
149
150 j -= 1
151
152 if i != j:
153 token = state.tokens[j]
154 state.tokens[j] = state.tokens[i]
155 state.tokens[i] = token
156
157
158def postProcess(state: StateInline) -> None:
159 """Walk through delimiter list and replace text tokens with tags."""
160 tokens_meta = state.tokens_meta
161 maximum = len(state.tokens_meta)
162 _postProcess(state, state.delimiters)
163
164 curr = 0
165 while curr < maximum:
166 try:
167 curr_meta = tokens_meta[curr]
168 except IndexError:
169 pass
170 else:
171 if curr_meta and "delimiters" in curr_meta:
172 _postProcess(state, curr_meta["delimiters"])
173 curr += 1