Skip to content

Commit 7e4303b

Browse files
authored
Merge pull request #648 from Kuuuube/subtext
Add support for Discord style subtext
2 parents e00c9e8 + 04287c0 commit 7e4303b

File tree

14 files changed

+252
-6
lines changed

14 files changed

+252
-6
lines changed

examples/s-expr.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ fn dump(source: &str) -> io::Result<()> {
8585
.math_code(true)
8686
.wikilinks_title_after_pipe(true)
8787
.wikilinks_title_before_pipe(true)
88+
.subtext(true)
8889
.build();
8990

9091
let opts = Options {

fuzz/fuzz_targets/all_options.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ fuzz_target!(|s: &str| {
3333
image_url_rewriter: Some(Arc::new(url_rewriter)),
3434
link_url_rewriter: Some(Arc::new(url_rewriter)),
3535
cjk_friendly_emphasis: true,
36+
subtext: true,
3637
};
3738

3839
let cb = |link_ref: options::BrokenLinkReference| {

src/cm.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ impl<'a, 'o, 'c> CommonMarkFormatter<'a, 'o, 'c> {
420420
NodeValue::SpoileredText => self.format_spoiler()?,
421421
NodeValue::EscapedTag(ref net) => self.format_escaped_tag(net)?,
422422
NodeValue::Alert(ref alert) => self.format_alert(alert, entering)?,
423+
NodeValue::Subtext => self.format_subtext(entering)?,
423424
};
424425
Ok(true)
425426
}
@@ -998,6 +999,18 @@ impl<'a, 'o, 'c> CommonMarkFormatter<'a, 'o, 'c> {
998999
}
9991000
Ok(())
10001001
}
1002+
1003+
fn format_subtext(&mut self, entering: bool) -> fmt::Result {
1004+
if entering {
1005+
write!(self, "-# ")?;
1006+
self.begin_content = true;
1007+
self.no_linebreaks = true;
1008+
} else {
1009+
self.no_linebreaks = false;
1010+
self.blankline();
1011+
}
1012+
Ok(())
1013+
}
10011014
}
10021015

10031016
fn longest_byte_sequence(buffer: &[u8], ch: u8) -> usize {

src/html.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,7 @@ pub fn format_node_default<'a, T>(
464464
NodeValue::Superscript => render_superscript(context, node, entering),
465465
NodeValue::Underline => render_underline(context, node, entering),
466466
NodeValue::WikiLink(ref nwl) => render_wiki_link(context, node, entering, nwl),
467+
NodeValue::Subtext => render_subtext(context, node, entering),
467468
}
468469
}
469470

@@ -1443,6 +1444,23 @@ fn render_subscript<'a, T>(
14431444
Ok(ChildRendering::HTML)
14441445
}
14451446

1447+
fn render_subtext<'a, T>(
1448+
context: &mut Context<T>,
1449+
node: Node<'a>,
1450+
entering: bool,
1451+
) -> Result<ChildRendering, fmt::Error> {
1452+
if entering {
1453+
context.cr()?;
1454+
context.write_str("<p><sub")?;
1455+
render_sourcepos(context, node)?;
1456+
context.write_str(">")?;
1457+
} else {
1458+
writeln!(context, "</sub></p>")?;
1459+
}
1460+
1461+
Ok(ChildRendering::HTML)
1462+
}
1463+
14461464
fn render_superscript<'a, T>(
14471465
context: &mut Context<T>,
14481466
node: Node<'a>,

src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ enum Extension {
195195
Greentext,
196196
Alerts,
197197
CjkFriendlyEmphasis,
198+
Subtext,
198199
}
199200

200201
#[derive(Clone, Copy, Debug, ValueEnum)]
@@ -282,7 +283,8 @@ fn main() -> Result<(), Box<dyn Error>> {
282283
.greentext(exts.contains(&Extension::Greentext))
283284
.alerts(exts.contains(&Extension::Alerts))
284285
.maybe_front_matter_delimiter(cli.front_matter_delimiter)
285-
.cjk_friendly_emphasis(exts.contains(&Extension::CjkFriendlyEmphasis));
286+
.cjk_friendly_emphasis(exts.contains(&Extension::CjkFriendlyEmphasis))
287+
.subtext(exts.contains(&Extension::Subtext));
286288

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

src/nodes.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,10 @@ pub enum NodeValue {
225225
/// **Block**. GitHub style alert boxes which uses a modified blockquote syntax.
226226
/// Enabled with the `alerts` option.
227227
Alert(Box<NodeAlert>),
228+
229+
/// **Block**. Block scoped subscript that acts similar to a header.
230+
/// Enabled with `subtext` option.
231+
Subtext,
228232
}
229233

230234
/// Alignment of a single table cell.
@@ -561,14 +565,18 @@ impl NodeValue {
561565
| NodeValue::TaskItem(..)
562566
| NodeValue::MultilineBlockQuote(_)
563567
| NodeValue::Alert(_)
568+
| NodeValue::Subtext
564569
)
565570
}
566571

567572
/// Whether the type the node is of can contain inline nodes.
568573
pub fn contains_inlines(&self) -> bool {
569574
matches!(
570575
*self,
571-
NodeValue::Paragraph | NodeValue::Heading(..) | NodeValue::TableCell
576+
NodeValue::Paragraph
577+
| NodeValue::Heading(..)
578+
| NodeValue::TableCell
579+
| NodeValue::Subtext
572580
)
573581
}
574582

@@ -595,7 +603,10 @@ impl NodeValue {
595603
pub(crate) fn accepts_lines(&self) -> bool {
596604
matches!(
597605
*self,
598-
NodeValue::Paragraph | NodeValue::Heading(..) | NodeValue::CodeBlock(..)
606+
NodeValue::Paragraph
607+
| NodeValue::Heading(..)
608+
| NodeValue::CodeBlock(..)
609+
| NodeValue::Subtext
599610
)
600611
}
601612

@@ -644,6 +655,7 @@ impl NodeValue {
644655
NodeValue::SpoileredText => "spoiler",
645656
NodeValue::EscapedTag(_) => "escaped_tag",
646657
NodeValue::Alert(_) => "alert",
658+
NodeValue::Subtext => "subtext",
647659
}
648660
}
649661
}
@@ -888,6 +900,7 @@ impl<'a> arena_tree::Node<'a, RefCell<Ast>> {
888900
| NodeValue::SpoileredText
889901
| NodeValue::Underline
890902
| NodeValue::Subscript
903+
| NodeValue::Subtext
891904
// XXX: this is quite a hack: the EscapedTag _contains_ whatever was
892905
// possibly going to fall into the spoiler. This should be fixed in
893906
// inlines.

src/parser/mod.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,10 @@ where
364364
break;
365365
}
366366
}
367-
NodeValue::Heading(..) | NodeValue::TableRow(..) | NodeValue::TableCell => {
367+
NodeValue::Heading(..)
368+
| NodeValue::TableRow(..)
369+
| NodeValue::TableCell
370+
| NodeValue::Subtext => {
368371
break;
369372
}
370373
NodeValue::FootnoteDefinition(..) => {
@@ -613,6 +616,7 @@ where
613616
|| self.handle_multiline_blockquote(container, line)
614617
|| self.handle_blockquote(container, line)
615618
|| self.handle_atx_heading(container, line)
619+
|| self.handle_atx_subtext(container, line)
616620
|| self.handle_code_fence(container, line)
617621
|| self.handle_html_block(container, line)
618622
|| self.handle_setext_heading(container, line)
@@ -782,6 +786,26 @@ where
782786
scanners::atx_heading_start(&line[self.first_nonspace..])
783787
}
784788

789+
fn handle_atx_subtext(&mut self, container: &mut Node<'a>, line: &str) -> bool {
790+
let Some(matched) = self.detect_atx_subtext(line) else {
791+
return false;
792+
};
793+
794+
let heading_startpos = self.first_nonspace;
795+
let offset = self.offset;
796+
self.advance_offset(line, heading_startpos + matched - offset, false);
797+
*container = self.add_child(container, NodeValue::Subtext, heading_startpos + 1);
798+
799+
let container_ast = &mut container.data_mut();
800+
container_ast.value = NodeValue::Subtext;
801+
802+
true
803+
}
804+
805+
fn detect_atx_subtext(&self, line: &str) -> Option<usize> {
806+
scanners::atx_subtext_start(&line[self.first_nonspace..])
807+
}
808+
785809
fn handle_code_fence(&mut self, container: &mut Node<'a>, line: &str) -> bool {
786810
let Some(matched) = self.detect_code_fence(line) else {
787811
return false;
@@ -1328,7 +1352,10 @@ where
13281352

13291353
container.data_mut().last_line_blank = self.blank
13301354
&& match container.data().value {
1331-
NodeValue::BlockQuote | NodeValue::Heading(..) | NodeValue::ThematicBreak => false,
1355+
NodeValue::BlockQuote
1356+
| NodeValue::Heading(..)
1357+
| NodeValue::ThematicBreak
1358+
| NodeValue::Subtext => false,
13321359
NodeValue::CodeBlock(ref ncb) => !ncb.fenced,
13331360
NodeValue::Item(..) => {
13341361
container.first_child().is_some()

src/parser/options.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,22 @@ pub struct Extension<'c> {
509509
/// ```
510510
#[cfg_attr(feature = "bon", builder(default))]
511511
pub cjk_friendly_emphasis: bool,
512+
513+
/// Enables block scoped subscript that acts similar to a header.
514+
///
515+
/// ```md
516+
/// -# subtext
517+
/// ```
518+
///
519+
/// ```rust
520+
/// # use comrak::{markdown_to_html, Options};
521+
/// let mut options = Options::default();
522+
/// options.extension.subscript = true;
523+
///
524+
/// assert_eq!(markdown_to_html("-# subtext", &options),
525+
/// "<p><sub>subtext</sub></p>\n");
526+
/// ```
527+
pub subtext: bool,
512528
}
513529

514530
impl<'c> Extension<'c> {

src/scanners.re

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ pub fn atx_heading_start(s: &str) -> Option<usize> {
7070
*/
7171
}
7272

73+
pub fn atx_subtext_start(s: &str) -> Option<usize> {
74+
let mut cursor = 0;
75+
let mut marker = 0;
76+
let len = s.len();
77+
/*!re2c
78+
[-][#] ([ \t]+|[\r\n]) { return Some(cursor); }
79+
* { return None; }
80+
*/
81+
}
82+
7383
pub fn html_block_end_1(s: &str) -> bool {
7484
let mut cursor = 0;
7585
let mut marker = 0;

src/scanners.rs

Lines changed: 123 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)