Skip to main content

comrak/
cm.rs

1use crate::ctype::{isalpha, isdigit, ispunct, isspace};
2use crate::nodes::{
3    AstNode, ListDelimType, ListType, NodeCodeBlock, NodeHeading, NodeHtmlBlock, NodeLink,
4    NodeMath, NodeTable, NodeValue, NodeWikiLink,
5};
6use crate::nodes::{NodeList, TableAlignment};
7#[cfg(feature = "shortcodes")]
8use crate::parser::shortcodes::NodeShortCode;
9use crate::parser::{Options, WikiLinksMode};
10use crate::scanners;
11use crate::strings::trim_start_match;
12use crate::{nodes, Plugins};
13
14use std::cmp::max;
15use std::io::{self, Write};
16
17/// Formats an AST as CommonMark, modified by the given options.
18pub fn format_document<'a>(
19    root: &'a AstNode<'a>,
20    options: &Options,
21    output: &mut dyn Write,
22) -> io::Result<()> {
23    // Formatting an ill-formed AST might lead to invalid output. However, we don't want to pay for
24    // validation in normal workflow. As a middleground, we validate the AST in debug builds. See
25    // https://github.com/kivikakk/comrak/issues/371.
26    #[cfg(debug_assertions)]
27    root.validate().unwrap_or_else(|e| {
28        panic!("The document to format is ill-formed: {:?}", e);
29    });
30
31    format_document_with_plugins(root, options, output, &Plugins::default())
32}
33
34/// Formats an AST as CommonMark, modified by the given options. Accepts custom plugins.
35pub fn format_document_with_plugins<'a>(
36    root: &'a AstNode<'a>,
37    options: &Options,
38    output: &mut dyn Write,
39    _plugins: &Plugins,
40) -> io::Result<()> {
41    let mut f = CommonMarkFormatter::new(root, options);
42    f.format(root);
43    if !f.v.is_empty() && f.v[f.v.len() - 1] != b'\n' {
44        f.v.push(b'\n');
45    }
46    output.write_all(&f.v)?;
47    Ok(())
48}
49
50struct CommonMarkFormatter<'a, 'o, 'c> {
51    node: &'a AstNode<'a>,
52    options: &'o Options<'c>,
53    v: Vec<u8>,
54    prefix: Vec<u8>,
55    column: usize,
56    need_cr: u8,
57    last_breakable: usize,
58    begin_line: bool,
59    begin_content: bool,
60    no_linebreaks: bool,
61    in_tight_list_item: bool,
62    custom_escape: Option<fn(&'a AstNode<'a>, u8) -> bool>,
63    footnote_ix: u32,
64    ol_stack: Vec<usize>,
65}
66
67#[derive(PartialEq, Clone, Copy)]
68enum Escaping {
69    Literal,
70    Normal,
71    Url,
72    Title,
73}
74
75impl<'a, 'o, 'c> Write for CommonMarkFormatter<'a, 'o, 'c> {
76    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
77        self.output(buf, false, Escaping::Literal);
78        Ok(buf.len())
79    }
80
81    fn flush(&mut self) -> std::io::Result<()> {
82        Ok(())
83    }
84}
85
86impl<'a, 'o, 'c> CommonMarkFormatter<'a, 'o, 'c> {
87    fn new(node: &'a AstNode<'a>, options: &'o Options<'c>) -> Self {
88        CommonMarkFormatter {
89            node,
90            options,
91            v: vec![],
92            prefix: vec![],
93            column: 0,
94            need_cr: 0,
95            last_breakable: 0,
96            begin_line: true,
97            begin_content: true,
98            no_linebreaks: false,
99            in_tight_list_item: false,
100            custom_escape: None,
101            footnote_ix: 0,
102            ol_stack: vec![],
103        }
104    }
105
106    fn output(&mut self, buf: &[u8], wrap: bool, escaping: Escaping) {
107        let wrap = wrap && !self.no_linebreaks;
108
109        if self.in_tight_list_item && self.need_cr > 1 {
110            self.need_cr = 1;
111        }
112
113        let mut k = self.v.len() as i32 - 1;
114        while self.need_cr > 0 {
115            if k < 0 || self.v[k as usize] == b'\n' {
116                k -= 1;
117            } else {
118                self.v.push(b'\n');
119                if self.need_cr > 1 {
120                    self.v.extend(&self.prefix);
121                }
122            }
123            self.column = 0;
124            self.last_breakable = 0;
125            self.begin_line = true;
126            self.begin_content = true;
127            self.need_cr -= 1;
128        }
129
130        let mut i = 0;
131        while i < buf.len() {
132            if self.begin_line {
133                self.v.extend(&self.prefix);
134                self.column = self.prefix.len();
135            }
136
137            if self.custom_escape.is_some() && self.custom_escape.unwrap()(self.node, buf[i]) {
138                self.v.push(b'\\');
139            }
140
141            let nextc = buf.get(i + 1);
142            if buf[i] == b' ' && wrap {
143                if !self.begin_line {
144                    let last_nonspace = self.v.len();
145                    self.v.push(b' ');
146                    self.column += 1;
147                    self.begin_line = false;
148                    self.begin_content = false;
149                    while buf.get(i + 1) == Some(&(b' ')) {
150                        i += 1;
151                    }
152                    if !buf.get(i + 1).map_or(false, |&c| isdigit(c)) {
153                        self.last_breakable = last_nonspace;
154                    }
155                }
156            } else if escaping == Escaping::Literal {
157                if buf[i] == b'\n' {
158                    self.v.push(b'\n');
159                    self.column = 0;
160                    self.begin_line = true;
161                    self.begin_content = true;
162                    self.last_breakable = 0;
163                } else {
164                    self.v.push(buf[i]);
165                    self.column += 1;
166                    self.begin_line = false;
167                    self.begin_content = self.begin_content && isdigit(buf[i]);
168                }
169            } else {
170                self.outc(buf[i], escaping, nextc);
171                self.begin_line = false;
172                self.begin_content = self.begin_content && isdigit(buf[i]);
173            }
174
175            if self.options.render.width > 0
176                && self.column > self.options.render.width
177                && !self.begin_line
178                && self.last_breakable > 0
179            {
180                let remainder = self.v[self.last_breakable + 1..].to_vec();
181                self.v.truncate(self.last_breakable);
182                self.v.push(b'\n');
183                self.v.extend(&self.prefix);
184                self.v.extend(&remainder);
185                self.column = self.prefix.len() + remainder.len();
186                self.last_breakable = 0;
187                self.begin_line = false;
188                self.begin_content = false;
189            }
190
191            i += 1;
192        }
193    }
194
195    fn outc(&mut self, c: u8, escaping: Escaping, nextc: Option<&u8>) {
196        let follows_digit = !self.v.is_empty() && isdigit(self.v[self.v.len() - 1]);
197
198        let nextc = nextc.map_or(0, |&c| c);
199
200        let needs_escaping = c < 0x80
201            && escaping != Escaping::Literal
202            && ((escaping == Escaping::Normal
203                && (c < 0x20
204                    || c == b'*'
205                    || c == b'_'
206                    || c == b'['
207                    || c == b']'
208                    || c == b'#'
209                    || c == b'<'
210                    || c == b'>'
211                    || c == b'\\'
212                    || c == b'`'
213                    || c == b'!'
214                    || (c == b'&' && isalpha(nextc))
215                    || (c == b'!' && nextc == 0x5b)
216                    || (self.begin_content
217                        && (c == b'-' || c == b'+' || c == b'=')
218                        && !follows_digit)
219                    || (self.begin_content
220                        && (c == b'.' || c == b')')
221                        && follows_digit
222                        && (nextc == 0 || isspace(nextc)))))
223                || (escaping == Escaping::Url
224                    && (c == b'`'
225                        || c == b'<'
226                        || c == b'>'
227                        || isspace(c)
228                        || c == b'\\'
229                        || c == b')'
230                        || c == b'('))
231                || (escaping == Escaping::Title
232                    && (c == b'`' || c == b'<' || c == b'>' || c == b'"' || c == b'\\')));
233
234        if needs_escaping {
235            if escaping == Escaping::Url && isspace(c) {
236                write!(self.v, "%{:2X}", c).unwrap();
237                self.column += 3;
238            } else if ispunct(c) {
239                write!(self.v, "\\{}", c as char).unwrap();
240                self.column += 2;
241            } else {
242                let s = format!("&#{};", c);
243                self.write_all(s.as_bytes()).unwrap();
244                self.column += s.len();
245            }
246        } else {
247            self.v.push(c);
248            self.column += 1;
249        }
250    }
251
252    fn cr(&mut self) {
253        self.need_cr = max(self.need_cr, 1);
254    }
255
256    fn blankline(&mut self) {
257        self.need_cr = max(self.need_cr, 2);
258    }
259
260    fn format(&mut self, node: &'a AstNode<'a>) {
261        enum Phase {
262            Pre,
263            Post,
264        }
265        let mut stack = vec![(node, Phase::Pre)];
266
267        while let Some((node, phase)) = stack.pop() {
268            match phase {
269                Phase::Pre => {
270                    if self.format_node(node, true) {
271                        stack.push((node, Phase::Post));
272                        for ch in node.reverse_children() {
273                            stack.push((ch, Phase::Pre));
274                        }
275                    }
276                }
277                Phase::Post => {
278                    self.format_node(node, false);
279                }
280            }
281        }
282    }
283
284    fn get_in_tight_list_item(&self, node: &'a AstNode<'a>) -> bool {
285        let tmp = match nodes::containing_block(node) {
286            Some(tmp) => tmp,
287            None => return false,
288        };
289
290        match tmp.data.borrow().value {
291            NodeValue::Item(..) | NodeValue::TaskItem(..) => {
292                if let NodeValue::List(ref nl) = tmp.parent().unwrap().data.borrow().value {
293                    return nl.tight;
294                }
295                return false;
296            }
297            _ => {}
298        }
299
300        let parent = match tmp.parent() {
301            Some(parent) => parent,
302            None => return false,
303        };
304
305        match parent.data.borrow().value {
306            NodeValue::Item(..) | NodeValue::TaskItem(..) => {
307                if let NodeValue::List(ref nl) = parent.parent().unwrap().data.borrow().value {
308                    return nl.tight;
309                }
310            }
311            _ => {}
312        }
313
314        false
315    }
316
317    fn format_node(&mut self, node: &'a AstNode<'a>, entering: bool) -> bool {
318        self.node = node;
319        let allow_wrap = self.options.render.width > 0 && !self.options.render.hardbreaks;
320
321        let parent_node = node.parent();
322        if entering {
323            if parent_node.is_some()
324                && matches!(
325                    parent_node.unwrap().data.borrow().value,
326                    NodeValue::Item(..) | NodeValue::TaskItem(..)
327                )
328            {
329                self.in_tight_list_item = self.get_in_tight_list_item(node);
330            }
331        } else if matches!(node.data.borrow().value, NodeValue::List(..)) {
332            self.in_tight_list_item = parent_node.is_some()
333                && matches!(
334                    parent_node.unwrap().data.borrow().value,
335                    NodeValue::Item(..) | NodeValue::TaskItem(..)
336                )
337                && self.get_in_tight_list_item(node);
338        }
339        let next_is_block = node
340            .next_sibling()
341            .map_or(true, |next| next.data.borrow().value.block());
342
343        match node.data.borrow().value {
344            NodeValue::Document => (),
345            NodeValue::FrontMatter(ref fm) => self.format_front_matter(fm.as_bytes(), entering),
346            NodeValue::BlockQuote => self.format_block_quote(entering),
347            NodeValue::List(..) => self.format_list(node, entering),
348            NodeValue::Item(..) => self.format_item(node, entering),
349            NodeValue::DescriptionList => (),
350            NodeValue::DescriptionItem(..) => (),
351            NodeValue::DescriptionTerm => (),
352            NodeValue::DescriptionDetails => self.format_description_details(entering),
353            NodeValue::Heading(ref nch) => self.format_heading(nch, entering),
354            NodeValue::CodeBlock(ref ncb) => self.format_code_block(node, ncb, entering),
355            NodeValue::HtmlBlock(ref nhb) => self.format_html_block(nhb, entering),
356            NodeValue::ThematicBreak => self.format_thematic_break(entering),
357            NodeValue::Paragraph => self.format_paragraph(entering),
358            NodeValue::Text(ref literal) => {
359                self.format_text(literal.as_bytes(), allow_wrap, entering)
360            }
361            NodeValue::LineBreak => self.format_line_break(entering, next_is_block),
362            NodeValue::SoftBreak => self.format_soft_break(allow_wrap, entering),
363            NodeValue::Code(ref code) => {
364                self.format_code(code.literal.as_bytes(), allow_wrap, entering)
365            }
366            NodeValue::HtmlInline(ref literal) => {
367                self.format_html_inline(literal.as_bytes(), entering)
368            }
369            NodeValue::Raw(ref literal) => self.format_raw(literal.as_bytes(), entering),
370            NodeValue::Strong => {
371                if parent_node.is_none()
372                    || !matches!(parent_node.unwrap().data.borrow().value, NodeValue::Strong)
373                {
374                    self.format_strong();
375                }
376            }
377            NodeValue::Emph => self.format_emph(node),
378            NodeValue::TaskItem(symbol) => self.format_task_item(symbol, node, entering),
379            NodeValue::Strikethrough => self.format_strikethrough(),
380            NodeValue::Superscript => self.format_superscript(),
381            NodeValue::Link(ref nl) => return self.format_link(node, nl, entering),
382            NodeValue::Image(ref nl) => self.format_image(nl, allow_wrap, entering),
383            #[cfg(feature = "shortcodes")]
384            NodeValue::ShortCode(ref ne) => self.format_shortcode(ne, entering),
385            NodeValue::Table(..) => self.format_table(entering),
386            NodeValue::TableRow(..) => self.format_table_row(entering),
387            NodeValue::TableCell => self.format_table_cell(node, entering),
388            NodeValue::FootnoteDefinition(ref nfd) => {
389                self.format_footnote_definition(&nfd.name, entering)
390            }
391            NodeValue::FootnoteReference(ref nfr) => {
392                self.format_footnote_reference(nfr.name.as_bytes(), entering)
393            }
394            NodeValue::MultilineBlockQuote(..) => self.format_block_quote(entering),
395            NodeValue::Escaped => {
396                // noop - automatic escaping is already being done
397            }
398            NodeValue::Math(ref math) => self.format_math(math, allow_wrap, entering),
399            NodeValue::WikiLink(ref nl) => return self.format_wikilink(nl, entering),
400            NodeValue::Underline => self.format_underline(),
401            NodeValue::Subscript => self.format_subscript(),
402            NodeValue::SpoileredText => self.format_spoiler(),
403            NodeValue::EscapedTag(ref net) => self.format_escaped_tag(net),
404        };
405        true
406    }
407
408    fn format_front_matter(&mut self, front_matter: &[u8], entering: bool) {
409        if entering {
410            self.output(front_matter, false, Escaping::Literal);
411        }
412    }
413
414    fn format_block_quote(&mut self, entering: bool) {
415        if entering {
416            write!(self, "> ").unwrap();
417            self.begin_content = true;
418            write!(self.prefix, "> ").unwrap();
419        } else {
420            let new_len = self.prefix.len() - 2;
421            self.prefix.truncate(new_len);
422            self.blankline();
423        }
424    }
425
426    fn format_list(&mut self, node: &'a AstNode<'a>, entering: bool) {
427        let ol_start = match node.data.borrow().value {
428            NodeValue::List(NodeList {
429                list_type: ListType::Ordered,
430                start,
431                ..
432            }) => Some(start),
433            _ => None,
434        };
435
436        if entering {
437            if let Some(start) = ol_start {
438                self.ol_stack.push(start);
439            }
440        } else {
441            if ol_start.is_some() {
442                self.ol_stack.pop();
443            }
444
445            if match node.next_sibling() {
446                Some(next_sibling) => matches!(
447                    next_sibling.data.borrow().value,
448                    NodeValue::CodeBlock(..) | NodeValue::List(..)
449                ),
450                _ => false,
451            } {
452                self.cr();
453                write!(self, "<!-- end list -->").unwrap();
454                self.blankline();
455            }
456        }
457    }
458
459    fn format_item(&mut self, node: &'a AstNode<'a>, entering: bool) {
460        let parent = match node.parent().unwrap().data.borrow().value {
461            NodeValue::List(ref nl) => *nl,
462            _ => unreachable!(),
463        };
464
465        let mut listmarker = vec![];
466
467        let marker_width = if parent.list_type == ListType::Bullet {
468            2
469        } else {
470            let list_number = if let Some(last_stack) = self.ol_stack.last_mut() {
471                let list_number = *last_stack;
472                if entering {
473                    *last_stack += 1;
474                };
475                list_number
476            } else {
477                match node.data.borrow().value {
478                    NodeValue::Item(ref ni) => ni.start,
479                    NodeValue::TaskItem(_) => parent.start,
480                    _ => unreachable!(),
481                }
482            };
483            let list_delim = parent.delimiter;
484            write!(
485                listmarker,
486                "{}{} ",
487                list_number,
488                if list_delim == ListDelimType::Paren {
489                    ")"
490                } else {
491                    "."
492                }
493            )
494            .unwrap();
495            let mut current_len = listmarker.len();
496
497            while current_len < self.options.render.ol_width {
498                write!(listmarker, " ").unwrap();
499                current_len += 1;
500            }
501
502            listmarker.len()
503        };
504
505        if entering {
506            if parent.list_type == ListType::Bullet {
507                let bullet = char::from(self.options.render.list_style as u8);
508                write!(self, "{} ", bullet).unwrap();
509            } else {
510                self.write_all(&listmarker).unwrap();
511            }
512            self.begin_content = true;
513            for _ in 0..marker_width {
514                write!(self.prefix, " ").unwrap();
515            }
516        } else {
517            let new_len = if self.prefix.len() > marker_width {
518                self.prefix.len() - marker_width
519            } else {
520                0
521            };
522            self.prefix.truncate(new_len);
523            self.cr();
524        }
525    }
526
527    fn format_description_details(&mut self, entering: bool) {
528        if entering {
529            write!(self, ": ").unwrap()
530        }
531    }
532
533    fn format_heading(&mut self, nch: &NodeHeading, entering: bool) {
534        if entering {
535            for _ in 0..nch.level {
536                write!(self, "#").unwrap();
537            }
538            write!(self, " ").unwrap();
539            self.begin_content = true;
540            self.no_linebreaks = true;
541        } else {
542            self.no_linebreaks = false;
543            self.blankline();
544        }
545    }
546
547    fn format_code_block(&mut self, node: &'a AstNode<'a>, ncb: &NodeCodeBlock, entering: bool) {
548        if entering {
549            let first_in_list_item = node.previous_sibling().is_none()
550                && match node.parent() {
551                    Some(parent) => {
552                        matches!(
553                            parent.data.borrow().value,
554                            NodeValue::Item(..) | NodeValue::TaskItem(..)
555                        )
556                    }
557                    _ => false,
558                };
559
560            if !first_in_list_item {
561                self.blankline();
562            }
563
564            let info = ncb.info.as_bytes();
565            let literal = ncb.literal.as_bytes();
566
567            #[allow(clippy::len_zero)]
568            if !(info.len() > 0
569                || literal.len() <= 2
570                || isspace(literal[0])
571                || first_in_list_item
572                || self.options.render.prefer_fenced
573                || isspace(literal[literal.len() - 1]) && isspace(literal[literal.len() - 2]))
574            {
575                write!(self, "    ").unwrap();
576                write!(self.prefix, "    ").unwrap();
577                self.write_all(literal).unwrap();
578                let new_len = self.prefix.len() - 4;
579                self.prefix.truncate(new_len);
580            } else {
581                let fence_char = if info.contains(&b'`') { b'~' } else { b'`' };
582                let numticks = max(3, longest_char_sequence(literal, fence_char) + 1);
583                for _ in 0..numticks {
584                    write!(self, "{}", fence_char as char).unwrap();
585                }
586                if !info.is_empty() {
587                    write!(self, " ").unwrap();
588                    self.write_all(info).unwrap();
589                }
590                self.cr();
591                self.write_all(literal).unwrap();
592                self.cr();
593                for _ in 0..numticks {
594                    write!(self, "{}", fence_char as char).unwrap();
595                }
596            }
597            self.blankline();
598        }
599    }
600
601    fn format_html_block(&mut self, nhb: &NodeHtmlBlock, entering: bool) {
602        if entering {
603            self.blankline();
604            self.write_all(nhb.literal.as_bytes()).unwrap();
605            self.blankline();
606        }
607    }
608
609    fn format_thematic_break(&mut self, entering: bool) {
610        if entering {
611            self.blankline();
612            write!(self, "-----").unwrap();
613            self.blankline();
614        }
615    }
616
617    fn format_paragraph(&mut self, entering: bool) {
618        if !entering {
619            self.blankline();
620        }
621    }
622
623    fn format_text(&mut self, literal: &[u8], allow_wrap: bool, entering: bool) {
624        if entering {
625            self.output(literal, allow_wrap, Escaping::Normal);
626        }
627    }
628
629    fn format_line_break(&mut self, entering: bool, next_is_block: bool) {
630        if entering {
631            if !self.options.render.hardbreaks && !next_is_block {
632                // If the next element is a block, a backslash means a
633                // literal backslash instead of a line break. In this case
634                // we can just skip the line break since it's meaningless
635                // before a block.
636                write!(self, "\\").unwrap();
637            }
638            self.cr();
639        }
640    }
641
642    fn format_soft_break(&mut self, allow_wrap: bool, entering: bool) {
643        if entering {
644            if !self.no_linebreaks
645                && self.options.render.width == 0
646                && !self.options.render.hardbreaks
647            {
648                self.cr();
649            } else if self.options.render.hardbreaks {
650                self.output(&[b'\n'], allow_wrap, Escaping::Literal);
651            } else {
652                self.output(&[b' '], allow_wrap, Escaping::Literal);
653            }
654        }
655    }
656
657    fn format_code(&mut self, literal: &[u8], allow_wrap: bool, entering: bool) {
658        if entering {
659            let numticks = shortest_unused_sequence(literal, b'`');
660            for _ in 0..numticks {
661                write!(self, "`").unwrap();
662            }
663
664            let all_space = literal
665                .iter()
666                .all(|&c| c == b' ' || c == b'\r' || c == b'\n');
667            let has_edge_space = literal[0] == b' ' || literal[literal.len() - 1] == b' ';
668            let has_edge_backtick = literal[0] == b'`' || literal[literal.len() - 1] == b'`';
669
670            let pad = literal.is_empty() || has_edge_backtick || (!all_space && has_edge_space);
671            if pad {
672                write!(self, " ").unwrap();
673            }
674            self.output(literal, allow_wrap, Escaping::Literal);
675            if pad {
676                write!(self, " ").unwrap();
677            }
678            for _ in 0..numticks {
679                write!(self, "`").unwrap();
680            }
681        }
682    }
683
684    fn format_html_inline(&mut self, literal: &[u8], entering: bool) {
685        if entering {
686            self.write_all(literal).unwrap();
687        }
688    }
689
690    fn format_raw(&mut self, literal: &[u8], entering: bool) {
691        if entering {
692            self.write_all(literal).unwrap();
693        }
694    }
695
696    fn format_strong(&mut self) {
697        write!(self, "**").unwrap();
698    }
699
700    fn format_emph(&mut self, node: &'a AstNode<'a>) {
701        let emph_delim = if match node.parent() {
702            Some(parent) => matches!(parent.data.borrow().value, NodeValue::Emph),
703            _ => false,
704        } && node.next_sibling().is_none()
705            && node.previous_sibling().is_none()
706        {
707            b'_'
708        } else {
709            b'*'
710        };
711
712        self.write_all(&[emph_delim]).unwrap();
713    }
714
715    fn format_task_item(&mut self, symbol: Option<char>, node: &'a AstNode<'a>, entering: bool) {
716        self.format_item(node, entering);
717        if entering {
718            write!(self, "[{}] ", symbol.unwrap_or(' ')).unwrap();
719        }
720    }
721
722    fn format_strikethrough(&mut self) {
723        write!(self, "~~").unwrap();
724    }
725
726    fn format_superscript(&mut self) {
727        write!(self, "^").unwrap();
728    }
729
730    fn format_underline(&mut self) {
731        write!(self, "__").unwrap();
732    }
733
734    fn format_subscript(&mut self) {
735        write!(self, "~").unwrap();
736    }
737
738    fn format_spoiler(&mut self) {
739        write!(self, "||").unwrap();
740    }
741
742    fn format_escaped_tag(&mut self, net: &String) {
743        self.output(net.as_bytes(), false, Escaping::Literal);
744    }
745
746    fn format_link(&mut self, node: &'a AstNode<'a>, nl: &NodeLink, entering: bool) -> bool {
747        if is_autolink(node, nl) {
748            if entering {
749                write!(self, "<{}>", trim_start_match(&nl.url, "mailto:")).unwrap();
750                return false;
751            }
752        } else if entering {
753            write!(self, "[").unwrap();
754        } else {
755            write!(self, "](").unwrap();
756            self.output(nl.url.as_bytes(), false, Escaping::Url);
757            if !nl.title.is_empty() {
758                write!(self, " \"").unwrap();
759                self.output(nl.title.as_bytes(), false, Escaping::Title);
760                write!(self, "\"").unwrap();
761            }
762            write!(self, ")").unwrap();
763        }
764
765        true
766    }
767
768    fn format_wikilink(&mut self, nl: &NodeWikiLink, entering: bool) -> bool {
769        if entering {
770            write!(self, "[[").unwrap();
771            if self.options.extension.wikilinks() == Some(WikiLinksMode::UrlFirst) {
772                self.output(nl.url.as_bytes(), false, Escaping::Url);
773                write!(self, "|").unwrap();
774            }
775        } else {
776            if self.options.extension.wikilinks() == Some(WikiLinksMode::TitleFirst) {
777                write!(self, "|").unwrap();
778                self.output(nl.url.as_bytes(), false, Escaping::Url);
779            }
780            write!(self, "]]").unwrap();
781        }
782
783        true
784    }
785
786    fn format_image(&mut self, nl: &NodeLink, allow_wrap: bool, entering: bool) {
787        if entering {
788            write!(self, "![").unwrap();
789        } else {
790            write!(self, "](").unwrap();
791            self.output(nl.url.as_bytes(), false, Escaping::Url);
792            if !nl.title.is_empty() {
793                self.output(&[b' ', b'"'], allow_wrap, Escaping::Literal);
794                self.output(nl.title.as_bytes(), false, Escaping::Title);
795                write!(self, "\"").unwrap();
796            }
797            write!(self, ")").unwrap();
798        }
799    }
800
801    #[cfg(feature = "shortcodes")]
802    fn format_shortcode(&mut self, ne: &NodeShortCode, entering: bool) {
803        if entering {
804            write!(self, ":").unwrap();
805            self.output(ne.code.as_bytes(), false, Escaping::Literal);
806            write!(self, ":").unwrap();
807        }
808    }
809
810    fn format_table(&mut self, entering: bool) {
811        if entering {
812            self.custom_escape = Some(table_escape);
813        } else {
814            self.custom_escape = None;
815        }
816        self.blankline();
817    }
818
819    fn format_table_row(&mut self, entering: bool) {
820        if entering {
821            self.cr();
822            write!(self, "|").unwrap();
823        }
824    }
825
826    fn format_table_cell(&mut self, node: &'a AstNode<'a>, entering: bool) {
827        if entering {
828            write!(self, " ").unwrap();
829        } else {
830            write!(self, " |").unwrap();
831
832            let row = &node.parent().unwrap().data.borrow().value;
833            let in_header = match *row {
834                NodeValue::TableRow(header) => header,
835                _ => panic!(),
836            };
837
838            if in_header && node.next_sibling().is_none() {
839                let table = &node.parent().unwrap().parent().unwrap().data.borrow().value;
840                let alignments = match *table {
841                    NodeValue::Table(NodeTable { ref alignments, .. }) => alignments,
842                    _ => panic!(),
843                };
844
845                self.cr();
846                write!(self, "|").unwrap();
847                for a in alignments {
848                    write!(
849                        self,
850                        " {} |",
851                        match *a {
852                            TableAlignment::Left => ":--",
853                            TableAlignment::Center => ":-:",
854                            TableAlignment::Right => "--:",
855                            TableAlignment::None => "---",
856                        }
857                    )
858                    .unwrap();
859                }
860                self.cr();
861            }
862        }
863    }
864    fn format_footnote_definition(&mut self, name: &str, entering: bool) {
865        if entering {
866            self.footnote_ix += 1;
867            writeln!(self, "[^{}]:", name).unwrap();
868            write!(self.prefix, "    ").unwrap();
869        } else {
870            let new_len = self.prefix.len() - 4;
871            self.prefix.truncate(new_len);
872        }
873    }
874
875    fn format_footnote_reference(&mut self, r: &[u8], entering: bool) {
876        if entering {
877            self.write_all(b"[^").unwrap();
878            self.write_all(r).unwrap();
879            self.write_all(b"]").unwrap();
880        }
881    }
882
883    fn format_math(&mut self, math: &NodeMath, allow_wrap: bool, entering: bool) {
884        if entering {
885            let literal = math.literal.as_bytes();
886            let start_fence = if math.dollar_math {
887                if math.display_math {
888                    "$$"
889                } else {
890                    "$"
891                }
892            } else {
893                "$`"
894            };
895
896            let end_fence = if start_fence == "$`" {
897                "`$"
898            } else {
899                start_fence
900            };
901
902            self.output(start_fence.as_bytes(), false, Escaping::Literal);
903            self.output(literal, allow_wrap, Escaping::Literal);
904            self.output(end_fence.as_bytes(), false, Escaping::Literal);
905        }
906    }
907}
908
909fn longest_char_sequence(literal: &[u8], ch: u8) -> usize {
910    let mut longest = 0;
911    let mut current = 0;
912    for c in literal {
913        if *c == ch {
914            current += 1;
915        } else {
916            if current > longest {
917                longest = current;
918            }
919            current = 0;
920        }
921    }
922    if current > longest {
923        longest = current;
924    }
925    longest
926}
927
928fn shortest_unused_sequence(literal: &[u8], f: u8) -> usize {
929    let mut used = 1;
930    let mut current = 0;
931    for c in literal {
932        if *c == f {
933            current += 1;
934        } else {
935            if current > 0 {
936                used |= 1 << current;
937            }
938            current = 0;
939        }
940    }
941
942    if current > 0 {
943        used |= 1 << current;
944    }
945
946    let mut i = 0;
947    while used & 1 != 0 {
948        used >>= 1;
949        i += 1;
950    }
951    i
952}
953
954fn is_autolink<'a>(node: &'a AstNode<'a>, nl: &NodeLink) -> bool {
955    if nl.url.is_empty() || scanners::scheme(nl.url.as_bytes()).is_none() {
956        return false;
957    }
958
959    if !nl.title.is_empty() {
960        return false;
961    }
962
963    let link_text = match node.first_child() {
964        None => return false,
965        Some(child) => match child.data.borrow().value {
966            NodeValue::Text(ref t) => t.clone(),
967            _ => return false,
968        },
969    };
970
971    trim_start_match(&nl.url, "mailto:") == link_text
972}
973
974fn table_escape<'a>(node: &'a AstNode<'a>, c: u8) -> bool {
975    match node.data.borrow().value {
976        NodeValue::Table(..) | NodeValue::TableRow(..) | NodeValue::TableCell => false,
977        _ => c == b'|',
978    }
979}