Skip to content

Commit c8960aa

Browse files
committed
feat: add ==highlight== inline extension behind ENABLE_HIGHLIGHT
1 parent 6020b06 commit c8960aa

11 files changed

Lines changed: 485 additions & 10 deletions

File tree

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ It is designed to be:
2020

2121
Further, it optionally supports parsing footnotes,
2222
[Github flavored tables](https://github.github.com/gfm/#tables-extension-),
23-
[Github flavored task lists](https://github.github.com/gfm/#task-list-items-extension-) and
24-
[strikethrough](https://github.github.com/gfm/#strikethrough-extension-).
23+
[Github flavored task lists](https://github.github.com/gfm/#task-list-items-extension-),
24+
[strikethrough](https://github.github.com/gfm/#strikethrough-extension-) and
25+
[highlight](https://github.com/markdown-it/markdown-it-mark) (`==marked==`,
26+
rendered as `<mark>`).
2527

2628
Rustc 1.71.1 or newer is required to build the crate.
2729

pulldown-cmark/examples/parser-map-tag-print.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ fn main() {
6969
Tag::Subscript => println!("Subscript (this is a span tag)"),
7070
Tag::Strong => println!("Strong (this is a span tag)"),
7171
Tag::Strikethrough => println!("Strikethrough (this is a span tag)"),
72+
Tag::Highlight => println!("Highlight (this is a span tag)"),
7273
Tag::BlockQuote(kind) => println!("BlockQuote ({:?})", kind),
7374
Tag::CodeBlock(code_block_kind) => {
7475
println!("CodeBlock code_block_kind: {:?}", code_block_kind)

pulldown-cmark/specs/highlight.txt

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
Highlighted text uses `==` delimiters, mirroring the markdown-it / pandoc
2+
extension. The pairing rules mirror GFM's `~~` strikethrough.
3+
4+
# Basic
5+
6+
```````````````````````````````` example
7+
==hi==
8+
.
9+
<p><mark>hi</mark></p>
10+
````````````````````````````````
11+
12+
```````````````````````````````` example
13+
==hello world==
14+
.
15+
<p><mark>hello world</mark></p>
16+
````````````````````````````````
17+
18+
# Nesting with emphasis
19+
20+
```````````````````````````````` example
21+
==*hi*==
22+
.
23+
<p><mark><em>hi</em></mark></p>
24+
````````````````````````````````
25+
26+
```````````````````````````````` example
27+
*==hi==*
28+
.
29+
<p><em><mark>hi</mark></em></p>
30+
````````````````````````````````
31+
32+
```````````````````````````````` example
33+
==**bold** and *em*==
34+
.
35+
<p><mark><strong>bold</strong> and <em>em</em></mark></p>
36+
````````````````````````````````
37+
38+
# Single `=` is literal
39+
40+
```````````````````````````````` example
41+
=hi=
42+
.
43+
<p>=hi=</p>
44+
````````````````````````````````
45+
46+
```````````````````````````````` example
47+
a = b
48+
.
49+
<p>a = b</p>
50+
````````````````````````````````
51+
52+
# Whitespace adjacent to inner edges fails flanking
53+
54+
```````````````````````````````` example
55+
== hi ==
56+
.
57+
<p>== hi ==</p>
58+
````````````````````````````````
59+
60+
```````````````````````````````` example
61+
==hi ==
62+
.
63+
<p>==hi ==</p>
64+
````````````````````````````````
65+
66+
```````````````````````````````` example
67+
== hi==
68+
.
69+
<p>== hi==</p>
70+
````````````````````````````````
71+
72+
# Triple and quadruple runs do not pair
73+
74+
```````````````````````````````` example
75+
===hi===
76+
.
77+
<p>===hi===</p>
78+
````````````````````````````````
79+
80+
```````````````````````````````` example
81+
====hi====
82+
.
83+
<p>====hi====</p>
84+
````````````````````````````````
85+
86+
```````````````````````````````` example
87+
====
88+
.
89+
<p>====</p>
90+
````````````````````````````````
91+
92+
# Intraword highlight (mirrors `~~`)
93+
94+
```````````````````````````````` example
95+
a==b==c
96+
.
97+
<p>a<mark>b</mark>c</p>
98+
````````````````````````````````
99+
100+
```````````````````````````````` example
101+
==This==is==highlighted==
102+
.
103+
<p><mark>This</mark>is<mark>highlighted</mark></p>
104+
````````````````````````````````
105+
106+
# Across a soft line break
107+
108+
```````````````````````````````` example
109+
==a
110+
b==
111+
.
112+
<p><mark>a
113+
b</mark></p>
114+
````````````````````````````````
115+
116+
# Backslash escapes
117+
118+
```````````````````````````````` example
119+
==hi \== there==
120+
.
121+
<p><mark>hi == there</mark></p>
122+
````````````````````````````````
123+
124+
```````````````````````````````` example
125+
\==not highlighted==
126+
.
127+
<p>==not highlighted==</p>
128+
````````````````````````````````
129+
130+
# Inside other block contexts
131+
132+
```````````````````````````````` example
133+
- ==in a list==
134+
.
135+
<ul>
136+
<li><mark>in a list</mark></li>
137+
</ul>
138+
````````````````````````````````
139+
140+
```````````````````````````````` example
141+
> ==in a quote==
142+
.
143+
<blockquote>
144+
<p><mark>in a quote</mark></p>
145+
</blockquote>
146+
````````````````````````````````
147+
148+
```````````````````````````````` example
149+
# ==in a heading==
150+
.
151+
<h1><mark>in a heading</mark></h1>
152+
````````````````````````````````
153+
154+
# Combined with strikethrough
155+
156+
```````````````````````````````` example
157+
==~~both~~==
158+
.
159+
<p><mark><del>both</del></mark></p>
160+
````````````````````````````````
161+
162+
```````````````````````````````` example
163+
~~==both==~~
164+
.
165+
<p><del><mark>both</mark></del></p>
166+
````````````````````````````````

pulldown-cmark/src/firstpass.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,7 +1032,7 @@ impl<'a, 'b> FirstPass<'a, 'b> {
10321032
LoopInstruction::ContinueAndSkip(1)
10331033
}
10341034
}
1035-
c @ b'*' | c @ b'_' | c @ b'~' | c @ b'^' => {
1035+
c @ b'*' | c @ b'_' | c @ b'~' | c @ b'^' | c @ b'=' => {
10361036
let string_suffix = &self.text[ix..];
10371037
let count = 1 + scan_ch_repeat(&string_suffix.as_bytes()[1..], c);
10381038
let can_open = delim_run_can_open(
@@ -1051,7 +1051,11 @@ impl<'a, 'b> FirstPass<'a, 'b> {
10511051
mode,
10521052
self.options,
10531053
);
1054-
let is_valid_seq = (c != b'~' || count <= 2) || (c == b'~' && count == 2);
1054+
let is_valid_seq = match c {
1055+
b'~' => count <= 2,
1056+
b'=' => count == 2,
1057+
_ => true,
1058+
};
10551059

10561060
if (can_open || can_close) && is_valid_seq {
10571061
self.tree.append_text(begin_text, ix, backslash_escaped);
@@ -2428,6 +2432,9 @@ fn delim_run_can_open(
24282432
if delim == b'~' && run_len > 1 {
24292433
return true;
24302434
}
2435+
if delim == b'=' && run_len == 2 {
2436+
return true;
2437+
}
24312438
let prev_char = s[..ix].chars().last().unwrap();
24322439
if delim == b'~'
24332440
&& (prev_char == '~' || options.contains(Options::ENABLE_SUBSCRIPT))
@@ -2473,7 +2480,11 @@ fn delim_run_can_close(
24732480
}
24742481
let delim = suffix.bytes().next().unwrap();
24752482
// `*`, `~~`, and `^` can be intraword, `~` can only be interword if it's subscript, `_` cannot
2476-
if (delim == b'*' || (delim == b'~' && run_len > 1)) && !is_punctuation(prev_char) {
2483+
if (delim == b'*'
2484+
|| (delim == b'~' && run_len > 1)
2485+
|| (delim == b'=' && run_len == 2))
2486+
&& !is_punctuation(prev_char)
2487+
{
24772488
return true;
24782489
}
24792490
if delim == b'^' && !is_punctuation(prev_char) {
@@ -2520,6 +2531,9 @@ fn special_bytes(options: &Options) -> [bool; 256] {
25202531
if options.contains(Options::ENABLE_SUPERSCRIPT) {
25212532
bytes[b'^' as usize] = true;
25222533
}
2534+
if options.contains(Options::ENABLE_HIGHLIGHT) {
2535+
bytes[b'=' as usize] = true;
2536+
}
25232537
if options.contains(Options::ENABLE_MATH) {
25242538
bytes[b'$' as usize] = true;
25252539
bytes[b'{' as usize] = true;
@@ -2551,8 +2565,13 @@ struct LookupTable {
25512565
type LookupTable = [bool; 256];
25522566

25532567
/// This function walks the byte slices from the given index and
2554-
/// calls the callback function on all bytes (and their indices) that are in the following set:
2555-
/// `` ` ``, `\`, `&`, `*`, `_`, `~`, `!`, `<`, `[`, `]`, `|`, `\r`, `\n`
2568+
/// calls the callback function on all bytes (and their indices) that are in the
2569+
/// special-bytes set defined by [`special_bytes`]/[`simd::compute_lookup`].
2570+
/// The always-included bytes are
2571+
/// `` ` ``, `\`, `&`, `*`, `_`, `!`, `<`, `[`, `]`, `\r`, `\n`; additional bytes
2572+
/// are added when their corresponding option is enabled (e.g. `|` with tables,
2573+
/// `~` with strikethrough/subscript, `^` with superscript, `=` with highlight,
2574+
/// `$`/`{`/`}` with math, and `.`/`-`/`"`/`'` with smart punctuation).
25562575
/// It is guaranteed not call the callback on other bytes.
25572576
/// Whenever `callback(ix, byte)` returns a `ContinueAndSkip(n)` value, the callback
25582577
/// will not be called with an index that is less than `ix + n + 1`.
@@ -2766,6 +2785,9 @@ mod simd {
27662785
if options.contains(Options::ENABLE_SUPERSCRIPT) {
27672786
add_lookup_byte(&mut lookup, b'^');
27682787
}
2788+
if options.contains(Options::ENABLE_HIGHLIGHT) {
2789+
add_lookup_byte(&mut lookup, b'=');
2790+
}
27692791
if options.contains(Options::ENABLE_MATH) {
27702792
add_lookup_byte(&mut lookup, b'$');
27712793
add_lookup_byte(&mut lookup, b'{');

pulldown-cmark/src/html.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ where
355355
Tag::Emphasis => self.write("<em>"),
356356
Tag::Strong => self.write("<strong>"),
357357
Tag::Strikethrough => self.write("<del>"),
358+
Tag::Highlight => self.write("<mark>"),
358359
Tag::Link {
359360
link_type: LinkType::Email,
360361
dest_url,
@@ -496,6 +497,9 @@ where
496497
TagEnd::Strikethrough => {
497498
self.write("</del>")?;
498499
}
500+
TagEnd::Highlight => {
501+
self.write("</mark>")?;
502+
}
499503
TagEnd::Link => {
500504
self.write("</a>")?;
501505
}

pulldown-cmark/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,12 @@ pub enum Tag<'a> {
278278
/// ~strike through~
279279
/// ```
280280
Strikethrough,
281+
/// Only parsed and emitted with [`Options::ENABLE_HIGHLIGHT`].
282+
///
283+
/// ```markdown
284+
/// ==highlighted==
285+
/// ```
286+
Highlight,
281287
/// Only parsed and emitted with [`Options::ENABLE_SUPERSCRIPT`].
282288
///
283289
/// ```markdown
@@ -336,6 +342,7 @@ impl<'a> Tag<'a> {
336342
Tag::Emphasis => TagEnd::Emphasis,
337343
Tag::Strong => TagEnd::Strong,
338344
Tag::Strikethrough => TagEnd::Strikethrough,
345+
Tag::Highlight => TagEnd::Highlight,
339346
Tag::Link { .. } => TagEnd::Link,
340347
Tag::Image { .. } => TagEnd::Image,
341348
Tag::MetadataBlock(kind) => TagEnd::MetadataBlock(*kind),
@@ -376,6 +383,7 @@ impl<'a> Tag<'a> {
376383
Tag::Emphasis => Tag::Emphasis,
377384
Tag::Strong => Tag::Strong,
378385
Tag::Strikethrough => Tag::Strikethrough,
386+
Tag::Highlight => Tag::Highlight,
379387
Tag::Superscript => Tag::Superscript,
380388
Tag::Subscript => Tag::Subscript,
381389
Tag::Link {
@@ -438,6 +446,7 @@ pub enum TagEnd {
438446
Emphasis,
439447
Strong,
440448
Strikethrough,
449+
Highlight,
441450
Superscript,
442451
Subscript,
443452

@@ -768,6 +777,10 @@ bitflags::bitflags! {
768777
const ENABLE_WIKILINKS = 1 << 15;
769778
/// Colon-delimited Container Extension Blocks.
770779
const ENABLE_CONTAINER_EXTENSIONS = 1 << 16;
780+
/// Allow highlighting text via `==highlight==` syntax, rendered as
781+
/// `<mark>highlight</mark>`. Originates from markdown-it / pandoc; not part
782+
/// of CommonMark or GFM.
783+
const ENABLE_HIGHLIGHT = 1 << 17;
771784
}
772785
}
773786

pulldown-cmark/src/main.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ pub fn main() -> std::io::Result<()> {
114114
"enable-container-extensions",
115115
"enable container extensions",
116116
);
117+
opts.optflag("", "enable-highlight", "enable highlight");
117118

118119
let matches = match opts.parse(&args[1..]) {
119120
Ok(m) => m,
@@ -173,6 +174,9 @@ pub fn main() -> std::io::Result<()> {
173174
if matches.opt_present("enable-container-extensions") {
174175
opts.insert(Options::ENABLE_CONTAINER_EXTENSIONS);
175176
}
177+
if matches.opt_present("enable-highlight") {
178+
opts.insert(Options::ENABLE_HIGHLIGHT);
179+
}
176180

177181
let mut input = String::new();
178182
let mut broken_links = vec![];

0 commit comments

Comments
 (0)