Skip to content

Commit de96106

Browse files
committed
Add a Highlight Extension ==for highlights==
1 parent 3af1ead commit de96106

File tree

10 files changed

+104
-4
lines changed

10 files changed

+104
-4
lines changed

src/cm.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ impl<'a, 'o, 'c> CommonMarkFormatter<'a, 'o, 'c> {
397397
NodeValue::Emph => self.format_emph(node)?,
398398
NodeValue::TaskItem(symbol) => self.format_task_item(symbol, node, entering)?,
399399
NodeValue::Strikethrough => self.format_strikethrough()?,
400+
NodeValue::Highlight => self.format_highlight()?,
400401
NodeValue::Superscript => self.format_superscript()?,
401402
NodeValue::Link(ref nl) => return self.format_link(node, nl, entering),
402403
NodeValue::Image(ref nl) => self.format_image(nl, allow_wrap, entering)?,
@@ -783,6 +784,11 @@ impl<'a, 'o, 'c> CommonMarkFormatter<'a, 'o, 'c> {
783784
Ok(())
784785
}
785786

787+
fn format_highlight(&mut self) -> fmt::Result {
788+
write!(self, "==")?;
789+
Ok(())
790+
}
791+
786792
fn format_superscript(&mut self) -> fmt::Result {
787793
write!(self, "^")?;
788794
Ok(())

src/html.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@ pub fn format_node_default<'a, T>(
440440
render_footnote_reference(context, node, entering, nfr)
441441
}
442442
NodeValue::Strikethrough => render_strikethrough(context, node, entering),
443+
NodeValue::Highlight => render_highlight(context, node, entering),
443444
NodeValue::Table(_) => render_table(context, node, entering),
444445
NodeValue::TableCell => render_table_cell(context, node, entering),
445446
NodeValue::TableRow(thead) => render_table_row(context, node, entering, thead),
@@ -1034,6 +1035,22 @@ fn render_strikethrough<'a, T>(
10341035
Ok(ChildRendering::HTML)
10351036
}
10361037

1038+
fn render_highlight<'a, T>(
1039+
context: &mut Context<T>,
1040+
node: Node<'a>,
1041+
entering: bool,
1042+
) -> Result<ChildRendering, fmt::Error> {
1043+
if entering {
1044+
context.write_str("<mark")?;
1045+
render_sourcepos(context, node)?;
1046+
context.write_str(">")?;
1047+
} else {
1048+
context.write_str("</mark>")?;
1049+
}
1050+
1051+
Ok(ChildRendering::HTML)
1052+
}
1053+
10371054
fn render_table<'a, T>(
10381055
context: &mut Context<T>,
10391056
node: Node<'a>,

src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ enum Extension {
196196
Alerts,
197197
CjkFriendlyEmphasis,
198198
Subtext,
199+
Highlight,
199200
}
200201

201202
#[derive(Clone, Copy, Debug, ValueEnum)]
@@ -284,7 +285,8 @@ fn main() -> Result<(), Box<dyn Error>> {
284285
.alerts(exts.contains(&Extension::Alerts))
285286
.maybe_front_matter_delimiter(cli.front_matter_delimiter)
286287
.cjk_friendly_emphasis(exts.contains(&Extension::CjkFriendlyEmphasis))
287-
.subtext(exts.contains(&Extension::Subtext));
288+
.subtext(exts.contains(&Extension::Subtext))
289+
.highlight(exts.contains(&Extension::Highlight));
288290

289291
#[cfg(feature = "shortcodes")]
290292
let extension = extension.shortcodes(cli.gemoji);

src/nodes.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ pub enum NodeValue {
159159
/// per the GFM spec.
160160
Strikethrough,
161161

162+
/// ***Inline**. [Highlight](https://www.markdownlang.com/extended/highlight.html) / mark text
163+
Highlight,
164+
162165
/// **Inline**. Superscript. Enabled with `ext_superscript` option.
163166
Superscript,
164167

@@ -646,6 +649,7 @@ impl NodeValue {
646649
NodeValue::HtmlInline(..) => "html_inline",
647650
NodeValue::Raw(..) => "raw",
648651
NodeValue::Strikethrough => "strikethrough",
652+
NodeValue::Highlight => "highlight",
649653
NodeValue::FrontMatter(_) => "frontmatter",
650654
NodeValue::TaskItem { .. } => "taskitem",
651655
NodeValue::Superscript => "superscript",
@@ -925,6 +929,7 @@ impl<'a> arena_tree::Node<'a, RefCell<Ast>> {
925929
| NodeValue::Image(..)
926930
| NodeValue::WikiLink(..)
927931
| NodeValue::Strikethrough
932+
| NodeValue::Highlight
928933
| NodeValue::Superscript
929934
| NodeValue::SpoileredText
930935
| NodeValue::Underline
@@ -947,6 +952,7 @@ impl<'a> arena_tree::Node<'a, RefCell<Ast>> {
947952
| NodeValue::Link(..)
948953
| NodeValue::Image(..)
949954
| NodeValue::Strikethrough
955+
| NodeValue::Highlight
950956
| NodeValue::HtmlInline(..)
951957
| NodeValue::Math(..)
952958
| NodeValue::WikiLink(..)
@@ -969,6 +975,7 @@ impl<'a> arena_tree::Node<'a, RefCell<Ast>> {
969975
| NodeValue::Link(..)
970976
| NodeValue::Image(..)
971977
| NodeValue::Strikethrough
978+
| NodeValue::Highlight
972979
| NodeValue::HtmlInline(..)
973980
| NodeValue::Math(..)
974981
| NodeValue::WikiLink(..)

src/parser/inlines.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ impl<'a, 'r, 'o, 'd, 'c, 'p> Subject<'a, 'r, 'o, 'd, 'c, 'p> {
107107
s.skip_char_bytes[b'~' as usize] = true;
108108
s.emph_delim_bytes[b'~' as usize] = true;
109109
}
110+
if options.extension.highlight {
111+
s.special_char_bytes[b'=' as usize] = true;
112+
s.skip_char_bytes[b'=' as usize] = true;
113+
s.emph_delim_bytes[b'=' as usize] = true;
114+
}
110115
if options.extension.superscript || options.extension.inline_footnotes {
111116
s.special_char_bytes[b'^' as usize] = true;
112117
}
@@ -198,6 +203,7 @@ impl<'a, 'r, 'o, 'd, 'c, 'p> Subject<'a, 'r, 'o, 'd, 'c, 'p> {
198203
let new_inl: Option<Node<'a>> = match b {
199204
b'\r' | b'\n' => Some(self.handle_newline()),
200205
b'`' => Some(self.handle_backticks(&ast.line_offsets)),
206+
b'=' if self.options.extension.highlight => Some(self.handle_delim(b'=')),
201207
b'\\' => Some(self.handle_backslash()),
202208
b'&' => Some(self.handle_entity()),
203209
b'<' => Some(self.handle_pointy_brace(&ast.line_offsets)),
@@ -684,15 +690,18 @@ impl<'a, 'r, 'o, 'd, 'c, 'p> Subject<'a, 'r, 'o, 'd, 'c, 'p> {
684690
self.scanner.pos - 1,
685691
);
686692

687-
let is_valid_strikethrough_delim = if b == b'~' && self.options.extension.strikethrough {
693+
let is_valid_double_delim_if_required = if b == b'~' && self.options.extension.strikethrough
694+
{
688695
numdelims <= 2
696+
} else if b == b'=' && self.options.extension.highlight {
697+
numdelims == 2
689698
} else {
690699
true
691700
};
692701

693702
if (can_open || can_close)
694703
&& (!(b == b'\'' || b == b'"') || self.options.parse.smart)
695-
&& is_valid_strikethrough_delim
704+
&& is_valid_double_delim_if_required
696705
{
697706
self.push_delimiter(b, can_open, can_close, inl);
698707
}
@@ -1202,7 +1211,7 @@ impl<'a, 'r, 'o, 'd, 'c, 'p> Subject<'a, 'r, 'o, 'd, 'c, 'p> {
12021211
// This array is an important optimization that prevents searching down
12031212
// the stack for openers we've previously searched for and know don't
12041213
// exist, preventing exponential blowup on pathological cases.
1205-
let mut openers_bottom: [usize; 12] = [stack_bottom; 12];
1214+
let mut openers_bottom: [usize; 13] = [stack_bottom; 13];
12061215

12071216
// This is traversing the stack from the top to the bottom, setting `closer` to
12081217
// the delimiter directly above `stack_bottom`. In the case where we are processing
@@ -1233,6 +1242,7 @@ impl<'a, 'r, 'o, 'd, 'c, 'p> Subject<'a, 'r, 'o, 'd, 'c, 'p> {
12331242
b'\'' => 4,
12341243
b'_' => 5,
12351244
b'*' => 6 + (if c.can_open { 3 } else { 0 }) + (c.length % 3),
1245+
b'=' => 12,
12361246
_ => unreachable!(),
12371247
};
12381248

@@ -1459,6 +1469,12 @@ impl<'a, 'r, 'o, 'd, 'c, 'p> Subject<'a, 'r, 'o, 'd, 'c, 'p> {
14591469
}
14601470
} else if self.options.extension.superscript && opener_byte == b'^' {
14611471
NodeValue::Superscript
1472+
} else if opener_byte == b'=' {
1473+
if self.options.extension.highlight {
1474+
NodeValue::Highlight
1475+
} else {
1476+
NodeValue::EscapedTag("==".to_owned())
1477+
}
14621478
} else if self.options.extension.spoiler && opener_byte == b'|' {
14631479
if use_delims == 2 {
14641480
NodeValue::SpoileredText

src/parser/options.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,23 @@ pub struct Extension<'c> {
526526
/// ```
527527
#[cfg_attr(feature = "bon", builder(default))]
528528
pub subtext: bool,
529+
530+
/// Enables highlighting (mark) using `==`.
531+
///
532+
/// ```md
533+
/// Hey, ==this is important!==
534+
/// ```
535+
///
536+
/// ```rust
537+
/// # use comrak::{markdown_to_html, Options};
538+
/// let mut options = Options::default();
539+
/// options.extension.highlight = true;
540+
///
541+
/// assert_eq!(markdown_to_html("Hey, ==this is important!==", &options),
542+
/// "<p>Hey, <mark>this is important!</mark></p>\n");
543+
/// ```
544+
#[cfg_attr(feature = "bon", builder(default))]
545+
pub highlight: bool,
529546
}
530547

531548
impl<'c> Extension<'c> {

src/tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ mod front_matter;
2121
mod fuzz;
2222
mod greentext;
2323
mod header_ids;
24+
mod highlight;
2425
#[path = "tests/html.rs"]
2526
mod html_;
2627
mod inline_footnotes;

src/tests/highlight.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use super::*;
2+
3+
#[test]
4+
fn highlight() {
5+
html_opts!(
6+
[extension.highlight],
7+
concat!(
8+
"This is ==important!==.\n",
9+
"\n",
10+
"This is ==very important== OK?\n",
11+
"\n",
12+
"==It is a \n shrubbery==\n",
13+
"\n",
14+
"Vendo ==Opel Corsa **em bom estado**==\n",
15+
"\n",
16+
"Ceci n'est pas =important=\n"
17+
),
18+
concat!(
19+
"<p>This is <mark>important!</mark>.</p>\n",
20+
"<p>This is <mark>very important</mark> OK?</p>\n",
21+
"<p><mark>It is a\nshrubbery</mark></p>\n",
22+
"<p>Vendo <mark>Opel Corsa <strong>em bom estado</strong></mark></p>\n",
23+
"<p>Ceci n'est pas =important=</p>\n"
24+
),
25+
);
26+
}

src/tests/sourcepos.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,11 @@ const STRIKETHROUGH: TestCase = (
298298
"hello ~~world~~ between ~~wo\nrld~~ after",
299299
);
300300

301+
const HIGHLIGHT: TestCase = (
302+
&[sourcepos!((1:7-1:15)), sourcepos!((1:25-2:5))],
303+
"hello ==world== between ==wo\nrld== after",
304+
);
305+
301306
const SUPERSCRIPT: TestCase = (
302307
&[sourcepos!((1:7-1:13)), sourcepos!((1:23-2:4))],
303308
"hello ^world^ between ^wo\nrld^ after",
@@ -475,6 +480,7 @@ fn node_values() -> HashMap<NodeValueDiscriminants, TestCase> {
475480
Emph => EMPH,
476481
Strong => STRONG,
477482
Strikethrough => STRIKETHROUGH,
483+
Highlight => HIGHLIGHT,
478484
Superscript => SUPERSCRIPT,
479485
Subscript => SUBSCRIPT,
480486
Link => LINK,
@@ -508,6 +514,7 @@ fn sourcepos() {
508514
options.extension.table = true;
509515
options.extension.tasklist = true;
510516
options.extension.strikethrough = true;
517+
options.extension.highlight = true;
511518
options.extension.superscript = true;
512519
options.extension.subscript = true;
513520
options.extension.autolink = true;

src/xml.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ impl<'o, 'c> XmlFormatter<'o, 'c> {
223223
NodeValue::Strong => {}
224224
NodeValue::Emph => {}
225225
NodeValue::Strikethrough => {}
226+
NodeValue::Highlight => {}
226227
NodeValue::Superscript => {}
227228
NodeValue::Link(ref nl) | NodeValue::Image(ref nl) => {
228229
self.output.write_str(" destination=\"")?;

0 commit comments

Comments
 (0)