Skip to main content

genemichaels_lib/
whitespace.rs

1use {
2    crate::{
3        Comment,
4        CommentMode,
5        FormatConfig,
6        Whitespace,
7        WhitespaceMode,
8    },
9    loga::{
10        ResultContext,
11        ea,
12    },
13    markdown::mdast::Node,
14    proc_macro2::{
15        Group,
16        LineColumn,
17        TokenStream,
18    },
19    regex::Regex,
20    std::{
21        cell::RefCell,
22        collections::BTreeMap,
23        hash::Hash,
24        rc::Rc,
25        str::FromStr,
26    },
27};
28
29/// Identifies the start/stop locations of whitespace in a chunk of source.
30/// Whitespace is grouped runs, but the `keep_max_blank_lines` parameter allows
31/// splitting the groups.
32pub fn extract_whitespaces(
33    keep_max_blank_lines: usize,
34    source: &str,
35) -> Result<(BTreeMap<HashLineColumn, Vec<Whitespace>>, TokenStream), loga::Error> {
36    let mut line_lookup = vec![];
37    {
38        let mut offset = 0usize;
39        loop {
40            line_lookup.push(offset);
41            offset += match source[offset..].find('\n') {
42                Some(r) => r,
43                None => {
44                    break;
45                },
46            } + 1;
47        }
48    }
49
50    struct State<'a> {
51        block_event_re: Option<Regex>,
52        keep_max_blank_lines: usize,
53        last_offset: usize,
54        // starting offset of each line
55        line_lookup: Vec<usize>,
56        // records the beginning of the last line extracted - this is the destination for
57        // transposed comments
58        line_start: Option<LineColumn>,
59        source: &'a str,
60        start_re: Option<Regex>,
61        whitespaces: BTreeMap<HashLineColumn, Vec<Whitespace>>,
62    }
63
64    impl<'a> State<'a> {
65        fn add_comments(&mut self, end: LineColumn, abs_start: usize, between_ast_nodes: &str) {
66            let start_re = &self.start_re.get_or_insert_with(|| Regex::new(
67                // `//` maybe followed by `[/!.?#]`, `/**/`, or `/*` maybe followed by `[*!]`
68                r#"(?:(//)(/|!|\.|\?|#)?)|(/\*\*/)|(?:(/\*)(\*|!)?)"#,
69            ).unwrap());
70            let block_event_re =
71                &self.block_event_re.get_or_insert_with(|| Regex::new(r#"((?:/\*)|(?:\*/))"#).unwrap());
72
73            struct CommentBuffer {
74                blank_lines: usize,
75                keep_max_blank_lines: usize,
76                lines: Vec<String>,
77                loc: LineColumn,
78                mode: CommentMode,
79                orig_start_offset: Option<usize>,
80                out: Vec<Whitespace>,
81            }
82
83            impl CommentBuffer {
84                fn add(&mut self, mode: CommentMode, line: &str, orig_start_offset: usize) {
85                    if self.mode != mode && !self.lines.is_empty() {
86                        self.flush();
87                    }
88                    self.mode = mode;
89                    self.lines.push(line.to_string());
90                    self.orig_start_offset.get_or_insert(orig_start_offset);
91                }
92
93                fn add_blank_lines(&mut self, text: &str) {
94                    let blank_lines = text.as_bytes().iter().filter(|x| **x == b'\n').count();
95                    if blank_lines > 1 && self.keep_max_blank_lines > 0 {
96                        self.flush();
97                        self.out.push(Whitespace {
98                            loc: self.loc,
99                            mode: crate::WhitespaceMode::BlankLines(
100                                (blank_lines - 1).min(self.keep_max_blank_lines),
101                            ),
102                        });
103                    }
104                }
105
106                fn flush(&mut self) {
107                    if self.lines.is_empty() {
108                        return;
109                    }
110                    self.out.push(Whitespace {
111                        loc: self.loc,
112                        mode: crate::WhitespaceMode::Comment(Comment {
113                            mode: self.mode,
114                            lines: self.lines.split_off(0).join("\n"),
115                            orig_start_offset: self.orig_start_offset.unwrap(),
116                        }),
117                    });
118                    self.blank_lines = 0;
119                    self.orig_start_offset = None;
120                }
121            }
122
123            let mut buffer = CommentBuffer {
124                keep_max_blank_lines: self.keep_max_blank_lines,
125                blank_lines: 0,
126                out: vec![],
127                mode: CommentMode::Normal,
128                lines: vec![],
129                loc: end,
130                orig_start_offset: None,
131            };
132            let mut text = (abs_start, between_ast_nodes);
133            'comment_loop : loop {
134                match start_re.captures(text.1) {
135                    Some(found_start) => {
136                        let orig_start_offset = abs_start + found_start.get(0).unwrap().start();
137                        let start_prefix_match =
138                            found_start
139                                .get(1)
140                                .or_else(|| found_start.get(3))
141                                .or_else(|| found_start.get(4))
142                                .unwrap();
143                        if buffer.out.is_empty() && buffer.lines.is_empty() {
144                            buffer.add_blank_lines(&text.1[..start_prefix_match.start()]);
145                        }
146                        match start_prefix_match.as_str() {
147                            "//" => {
148                                let mode = {
149                                    let start_suffix_match = found_start.get(2);
150                                    let (mut mode, mut match_end) = match start_suffix_match {
151                                        Some(start_suffix_match) => (match start_suffix_match.as_str() {
152                                            "/" => CommentMode::DocOuter,
153                                            "!" => CommentMode::DocInner,
154                                            "." => CommentMode::Verbatim,
155                                            "#" => CommentMode::Directive,
156                                            "?" => CommentMode::ExplicitNormal,
157                                            _ => unreachable!(),
158                                        }, start_suffix_match.end()),
159                                        None => (CommentMode::Normal, start_prefix_match.end()),
160                                    };
161                                    if mode == CommentMode::DocOuter && text.1[match_end..].starts_with("/") {
162                                        // > 3 slashes, so actually not a doc comment
163                                        mode = CommentMode::Normal;
164                                        match_end = start_prefix_match.end();
165                                    }
166                                    text = (text.0 + match_end, &text.1[match_end..]);
167                                    mode
168                                };
169                                let (line, next_start) = match text.1.find('\n') {
170                                    Some(line_end) => (&text.1[..line_end], line_end + 1),
171                                    None => (text.1, text.1.len()),
172                                };
173                                buffer.add(mode, line, orig_start_offset);
174                                text = (text.0 + next_start, &text.1[next_start..]);
175                            },
176                            "/**/" => {
177                                buffer.add(CommentMode::Normal, "".into(), orig_start_offset);
178                                text = (text.0 + start_prefix_match.end(), &text.1[start_prefix_match.end()..]);
179                            },
180                            "/*" => {
181                                let mode = {
182                                    let start_suffix_match = found_start.get(5);
183                                    let (mode, match_end) = match start_suffix_match {
184                                        Some(start_suffix_match) => (match start_suffix_match.as_str() {
185                                            "*" => CommentMode::DocOuter,
186                                            "!" => CommentMode::DocInner,
187                                            _ => unreachable!(),
188                                        }, start_suffix_match.end()),
189                                        None => (CommentMode::Normal, start_prefix_match.end()),
190                                    };
191                                    text = (text.0 + match_end, &text.1[match_end..]);
192                                    mode
193                                };
194                                let mut nesting = 1;
195                                let mut search_end_at = 0usize;
196                                let (lines, next_start) = loop {
197                                    let found_event =
198                                        block_event_re.captures(&text.1[search_end_at..]).unwrap().get(1).unwrap();
199                                    let event_start = search_end_at + found_event.start();
200                                    search_end_at += found_event.end();
201                                    match found_event.as_str() {
202                                        "/*" => {
203                                            nesting += 1;
204                                        },
205                                        "*/" => {
206                                            nesting -= 1;
207                                            if nesting == 0 {
208                                                break (&text.1[..event_start], search_end_at);
209                                            }
210                                        },
211                                        _ => unreachable!(),
212                                    }
213                                };
214                                for line in lines.lines() {
215                                    let mut line = line.trim();
216                                    line = line.strip_prefix("* ").unwrap_or(line);
217                                    buffer.add(mode, line, orig_start_offset);
218                                }
219                                text = (text.0 + next_start, &text.1[next_start..]);
220                            },
221                            _ => unreachable!(),
222                        }
223                    },
224                    None => {
225                        if buffer.out.is_empty() && buffer.lines.is_empty() {
226                            buffer.add_blank_lines(text.1);
227                        }
228                        break 'comment_loop;
229                    },
230                }
231            }
232            buffer.flush();
233            if !buffer.out.is_empty() {
234                let whitespaces = self.whitespaces.entry(HashLineColumn(end)).or_insert(vec![]);
235
236                // Merge with existing comments (basically only if comments come before and after
237                // at the end of the line)
238                'merge : loop {
239                    let Some(previous_whitespace) = whitespaces.last_mut() else {
240                        break;
241                    };
242                    let WhitespaceMode::Comment(previous_comment) = &mut previous_whitespace.mode else {
243                        break;
244                    };
245                    let start = buffer.out.remove(0);
246                    loop {
247                        let WhitespaceMode::Comment(start_comment) = &start.mode else {
248                            break;
249                        };
250                        if previous_comment.mode != start_comment.mode {
251                            break;
252                        }
253                        previous_comment.lines.push_str("\n");
254                        previous_comment.lines.push_str(&start_comment.lines);
255                        break 'merge;
256                    }
257                    buffer.out.insert(0, start);
258                    break;
259                }
260
261                // Add rest without merging
262                whitespaces.extend(buffer.out);
263            }
264        }
265
266        fn extract(&mut self, mut start: usize, end: LineColumn) {
267            // Transpose line-end comments to line-start
268            if loop {
269                let previous_start = match &self.line_start {
270                    // No line start recorded, must be within first line
271                    None => break true,
272                    Some(s) => s,
273                };
274                if end.line <= previous_start.line {
275                    // Not at end of line yet, or at bad element (moved backwards); don't do anything
276                    break false;
277                }
278                let eol = match self.source[start..].find('\n') {
279                    Some(n) => start + n,
280                    None => self.source.len(),
281                };
282                let text = &self.source[start .. eol];
283                if text.trim_start().starts_with("//") {
284                    self.add_comments(*previous_start, start, text);
285                }
286                start = eol;
287                break true;
288            } {
289                self.line_start = Some(end);
290            }
291
292            // Do normal comment extraction
293            let end_offset = self.to_offset(end);
294            if end_offset < start {
295                return;
296            }
297            let whole_text = &self.source[start .. end_offset];
298            self.add_comments(end, start, whole_text);
299        }
300
301        fn to_offset(&self, loc: LineColumn) -> usize {
302            if loc.line == 0 {
303                return 0usize;
304            }
305            let line_start_offset = *self.line_lookup.get(loc.line - 1).unwrap();
306            line_start_offset +
307                self.source[line_start_offset..].chars().take(loc.column).map(char::len_utf8).sum::<usize>()
308        }
309    }
310
311    // Extract comments
312    let mut state = State {
313        source: source,
314        keep_max_blank_lines: keep_max_blank_lines,
315        line_lookup: line_lookup,
316        whitespaces: BTreeMap::new(),
317        last_offset: 0usize,
318        line_start: None,
319        start_re: None,
320        block_event_re: None,
321    };
322
323    fn recurse(state: &mut State, ts: TokenStream) -> TokenStream {
324        let mut out = vec![];
325        let mut ts = ts.into_iter().peekable();
326        while let Some(t) = ts.next() {
327            match t {
328                proc_macro2::TokenTree::Group(g) => {
329                    state.extract(state.last_offset, g.span_open().start());
330                    state.last_offset = state.to_offset(g.span_open().end());
331                    let subtokens = recurse(state, g.stream());
332                    state.extract(state.last_offset, g.span_close().start());
333                    state.last_offset = state.to_offset(g.span_close().end());
334                    let mut new_g = Group::new(g.delimiter(), subtokens);
335                    new_g.set_span(g.span());
336                    out.push(proc_macro2::TokenTree::Group(new_g));
337                },
338                proc_macro2::TokenTree::Ident(g) => {
339                    state.extract(state.last_offset, g.span().start());
340                    state.last_offset = state.to_offset(g.span().end());
341                    out.push(proc_macro2::TokenTree::Ident(g));
342                },
343                proc_macro2::TokenTree::Punct(g) => {
344                    let offset = state.to_offset(g.span().start());
345                    if g.as_char() == '#' && &state.source[offset .. offset + 1] == "/" {
346                        // Syn converts doc comments into doc attrs, work around that here by detecting a
347                        // mismatch between the token and the source (written /, token is #) and skipping
348                        // all tokens within the fake doc attr range
349                        loop {
350                            let in_comment = ts.peek().map(|n| n.span().start() < g.span().end()).unwrap_or(false);
351                            if !in_comment {
352                                break;
353                            }
354                            ts.next();
355                        }
356                    } else {
357                        state.extract(state.last_offset, g.span().start());
358                        state.last_offset = state.to_offset(g.span().end());
359                        out.push(proc_macro2::TokenTree::Punct(g));
360                    }
361                },
362                proc_macro2::TokenTree::Literal(g) => {
363                    state.extract(state.last_offset, g.span().start());
364                    state.last_offset = state.to_offset(g.span().end());
365                    out.push(proc_macro2::TokenTree::Literal(g));
366                },
367            }
368        }
369        TokenStream::from_iter(out)
370    }
371
372    let tokens =
373        recurse(
374            &mut state,
375            TokenStream::from_str(
376                source,
377            ).map_err(
378                |e| loga::err_with(
379                    "Error undoing syn parse transformations",
380                    ea!(
381                        line = e.span().start().line,
382                        column = e.span().start().column,
383                        error = e.to_string(),
384                        source = source.lines().skip(e.span().start().line - 1).next().unwrap()
385                    ),
386                ),
387            )?,
388        );
389    state.add_comments(LineColumn {
390        line: 0,
391        column: 1,
392    }, state.last_offset, &source[state.last_offset..]);
393    Ok((state.whitespaces, tokens))
394}
395
396pub fn format_md(
397    true_out: &mut String,
398    config: &FormatConfig,
399    prefix: &str,
400    source: &str,
401) -> Result<(), loga::Error> {
402    // TODO, due to a bug a bunch of unreachable branches might have had code added.
403    // I'd like to go back and see if some block-level starts can be removed in
404    // contexts they shouldn't appear.
405    match || -> Result<String, loga::Error> {
406        let mut out = String::new();
407        let mut state = State {
408            line_buffer: String::new(),
409            need_nl: false,
410            config: config.clone(),
411        };
412        let ast = markdown::to_mdast(source, &markdown::ParseOptions {
413            constructs: markdown::Constructs { ..Default::default() },
414            ..Default::default()
415        }).map_err(loga::err).context("Error parsing markdown")?;
416        recurse_write(
417            &mut state,
418            &mut out,
419            LineState::new(
420                unicode_len(&prefix),
421                None,
422                prefix.to_string(),
423                VisualLen(config.max_width),
424                config.comment_width.map(VisualLen),
425            ),
426            &ast,
427            false,
428        );
429        Ok(out)
430    }() {
431        Ok(o) => {
432            true_out.push_str(&o);
433            Ok(())
434        },
435        Err(e) => {
436            Err(e)
437        },
438    }
439}
440
441/// Line split points, must be interior indexes (no 0 and no text.len())
442fn get_splits(text: &str) -> Vec<usize> {
443    // let segmenter =
444    // LineBreakSegmenter::try_new_unstable(&icu_testdata::unstable()).unwrap(); match
445    // segmenter .segment_str(&text)
446    text.char_indices().filter(|i| i.1 == ' ').map(|i| i.0 + 1).collect()
447}
448
449#[derive(PartialEq, Eq, Debug, Clone, Copy)]
450pub struct HashLineColumn(pub LineColumn);
451
452impl Hash for HashLineColumn {
453    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
454        (self.0.line, self.0.column).hash(state);
455    }
456}
457
458impl Ord for HashLineColumn {
459    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
460        return self.0.line.cmp(&other.0.line).then(self.0.column.cmp(&other.0.column));
461    }
462}
463
464impl PartialOrd for HashLineColumn {
465    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
466        return Some(self.0.cmp(&other.0));
467    }
468}
469
470struct LineState(Rc<RefCell<LineState_>>);
471
472impl LineState {
473    fn clone_indent(&self, first_prefix: Option<String>, prefix: String) -> LineState {
474        let mut s = self.0.as_ref().borrow_mut();
475        LineState(Rc::new(RefCell::new(LineState_ {
476            base_prefix_len: s.base_prefix_len,
477            first_prefix: match (s.first_prefix.take(), first_prefix) {
478                (None, None) => None,
479                (None, Some(p)) => Some(format!("{}{}", s.prefix, p)),
480                (Some(p), None) => Some(p),
481                (Some(p1), Some(p2)) => Some(format!("{}{}", p1, p2)),
482            },
483            prefix: format!("{}{}", s.prefix, prefix),
484            max_width: s.max_width,
485            rel_max_width: s.rel_max_width,
486            backward_break: None,
487            unbreakable: false,
488        })))
489    }
490
491    fn clone_inline(&self) -> LineState {
492        LineState(self.0.clone())
493    }
494
495    fn clone_unbreakable(&self, first_prefix: Option<String>) -> LineState {
496        let mut s = self.0.as_ref().borrow_mut();
497        LineState(Rc::new(RefCell::new(LineState_ {
498            base_prefix_len: s.base_prefix_len,
499            first_prefix: match (s.first_prefix.take(), first_prefix) {
500                (None, None) => None,
501                (None, Some(p)) => Some(format!("{}{}", s.prefix, p)),
502                (Some(p), None) => Some(p),
503                (Some(p1), Some(p2)) => Some(format!("{}{}", p1, p2)),
504            },
505            prefix: s.prefix.clone(),
506            max_width: s.max_width,
507            rel_max_width: s.rel_max_width,
508            backward_break: None,
509            unbreakable: true,
510        })))
511    }
512
513    fn clone_zero_indent(&self) -> LineState {
514        let mut s = self.0.as_ref().borrow_mut();
515        LineState(Rc::new(RefCell::new(LineState_ {
516            base_prefix_len: s.base_prefix_len,
517            first_prefix: s.first_prefix.take(),
518            prefix: s.prefix.clone(),
519            max_width: s.max_width,
520            rel_max_width: s.rel_max_width,
521            backward_break: None,
522            unbreakable: false,
523        })))
524    }
525
526    fn flush_always(&self, state: &mut State, out: &mut String) {
527        self.0.as_ref().borrow_mut().flush_always(state, out);
528    }
529
530    fn new(
531        base_prefix_len: VisualLen,
532        first_prefix: Option<String>,
533        prefix: String,
534        max_width: VisualLen,
535        rel_max_width: Option<VisualLen>,
536    ) -> LineState {
537        LineState(Rc::new(RefCell::new(LineState_ {
538            base_prefix_len: base_prefix_len,
539            first_prefix,
540            prefix,
541            max_width,
542            rel_max_width,
543            backward_break: None,
544            unbreakable: false,
545        })))
546    }
547
548    fn write(&self, state: &mut State, out: &mut String, text: &str, breaks: &[usize]) {
549        let mut s = self.0.as_ref().borrow_mut();
550        if s.unbreakable {
551            state.line_buffer.push_str(text);
552            return;
553        }
554        let max_len = s.calc_max_width();
555
556        struct FoundWritableLen<'a> {
557            /// The next break after the last break before the max length, 2nd fallback (after
558            /// retro-break)
559            next_break: Option<(usize, &'a [usize])>,
560            /// If a break is used, the break and the remaining unused breaks
561            previous_break: Option<(usize, &'a [usize])>,
562            /// How much of text can be written to the current line. If a break is used, will
563            /// be equal to the break point, but if the whole string is written may be longer
564            writable: usize,
565        }
566
567        fn find_writable_len<
568            'a,
569        >(
570            width: VisualLen,
571            max_len: VisualLen,
572            text: &str,
573            breaks_offset: usize,
574            breaks: &'a [usize],
575        ) -> FoundWritableLen<'a> {
576            let mut previous_break = None;
577            let mut writable = 0;
578            for (i, b) in breaks.iter().enumerate() {
579                let b = *b - breaks_offset;
580                let next_break = Some((b, &breaks[i + 1..]));
581                if width + unicode_len(&text[..b]) > max_len {
582                    return FoundWritableLen {
583                        writable: writable,
584                        previous_break: previous_break,
585                        next_break: next_break,
586                    };
587                }
588                previous_break = next_break;
589                writable = b;
590            }
591            return FoundWritableLen {
592                writable: if width + unicode_len(&text) > max_len {
593                    writable
594                } else {
595                    text.len()
596                },
597                previous_break: previous_break,
598                next_break: None,
599            };
600        }
601
602        /// Write new text following a break, storing the new break point with it
603        fn write_forward(state: &mut State, s: &mut LineState_, text: &str, b: Option<usize>) {
604            if let Some(b) = b {
605                s.backward_break = Some(state.line_buffer.len() + b);
606            }
607            state.line_buffer.push_str(&text);
608        }
609
610        fn write_forward_breaks(
611            state: &mut State,
612            s: &mut LineState_,
613            out: &mut String,
614            max_len: VisualLen,
615            mut first: bool,
616            mut text: String,
617            mut breaks_offset: usize,
618            breaks: &[usize],
619        ) {
620            let mut breaks = breaks;
621            while !text.is_empty() {
622                if first {
623                    first = false;
624                } else {
625                    s.flush(state, out);
626                }
627                let found = find_writable_len(s.calc_current_len(state), max_len, &text, breaks_offset, breaks);
628                if found.writable > 0 {
629                    write_forward(state, s, &text[..found.writable], found.previous_break.map(|b| b.0));
630                    breaks = found.previous_break.map(|b| b.1).unwrap_or(breaks);
631                    text = text.split_off(found.writable);
632                    breaks_offset += found.writable;
633                } else if let Some((b, breaks0)) = found.next_break {
634                    write_forward(state, s, &text[..b], Some(b));
635                    breaks = breaks0;
636                    text = text.split_off(b);
637                    breaks_offset += b;
638                } else {
639                    state.line_buffer.push_str(&text);
640                    return;
641                }
642            }
643        }
644
645        let found = find_writable_len(s.calc_current_len(state), max_len, text, 0, breaks);
646        if found.writable > 0 {
647            write_forward(state, &mut s, &text[..found.writable], found.previous_break.map(|b| b.0));
648            write_forward_breaks(
649                state,
650                &mut s,
651                out,
652                max_len,
653                false,
654                (&text[found.writable..]).to_string(),
655                found.writable,
656                found.previous_break.map(|b| b.1).unwrap_or(breaks),
657            );
658        } else if let Some(at) = s.backward_break.take() {
659            // Couldn't split forward but there's a retroactive split point in previously
660            // written segments
661            let prefix = state.line_buffer.split_off(at);
662            s.flush(state, out);
663            state.line_buffer.push_str(&prefix);
664            write_forward_breaks(state, &mut s, out, max_len, true, text.to_string(), 0, breaks);
665        } else if let Some((b, breaks)) = found.next_break {
666            // No retroactive split, try first split after max len (overflow, but better than
667            // nothing)
668            write_forward(state, &mut s, &text[..b], Some(b));
669            write_forward_breaks(state, &mut s, out, max_len, false, (&text[b..]).to_string(), b, breaks);
670        } else {
671            state.line_buffer.push_str(text);
672        }
673    }
674
675    fn write_breakable(&self, state: &mut State, out: &mut String, text: &str) {
676        self.write(state, out, text, &get_splits(text));
677    }
678
679    fn write_newline(&self, state: &mut State, out: &mut String) {
680        let mut s = self.0.as_ref().borrow_mut();
681        if !state.line_buffer.is_empty() {
682            panic!();
683        }
684        s.flush_always(state, out);
685    }
686
687    fn write_unbreakable(&self, state: &mut State, out: &mut String, text: &str) {
688        self.write(state, out, text, &[]);
689    }
690}
691
692#[derive(Debug)]
693struct LineState_ {
694    backward_break: Option<usize>,
695    base_prefix_len: VisualLen,
696    first_prefix: Option<String>,
697    max_width: VisualLen,
698    prefix: String,
699    rel_max_width: Option<VisualLen>,
700    unbreakable: bool,
701}
702
703impl LineState_ {
704    fn calc_current_len(&self, state: &State) -> VisualLen {
705        self.base_prefix_len + unicode_len(&state.line_buffer)
706    }
707
708    fn calc_max_width(&self) -> VisualLen {
709        match self.rel_max_width {
710            Some(w) => unicode_len(&self.prefix) + w,
711            None => self.max_width,
712        }
713    }
714
715    fn flush(&mut self, state: &mut State, out: &mut String) {
716        if !state.line_buffer.trim().is_empty() {
717            self.flush_always(state, out);
718        }
719    }
720
721    fn flush_always(&mut self, state: &mut State, out: &mut String) {
722        out.push_str(format!(
723            //. .
724            "{}{}{}",
725            if state.need_nl {
726                "\n"
727            } else {
728                ""
729            },
730            match &self.first_prefix.take() {
731                Some(t) => t,
732                None => &*self.prefix,
733            },
734            &state.line_buffer,
735        ).trim_end());
736        state.line_buffer.clear();
737        state.need_nl = true;
738        self.backward_break = None;
739    }
740}
741
742fn recurse_write(state: &mut State, out: &mut String, line: LineState, node: &Node, inline: bool) {
743    fn join_lines(text: &str) -> String {
744        let lines = Regex::new("\r?\n").unwrap().split(text).collect::<Vec<&str>>();
745        let mut joined = String::new();
746        for (i, line) in lines.iter().enumerate() {
747            let mut line = *line;
748            if i > 0 {
749                line = line.trim_start();
750                joined.push(' ');
751            }
752            if i < lines.len() - 1 {
753                line = line.trim_end();
754            }
755            joined.push_str(line);
756        }
757        joined
758    }
759
760    match node {
761        // block->block elements (newline between)
762        Node::Root(x) => {
763            for (i, child) in x.children.iter().enumerate() {
764                if i > 0 {
765                    line.write_newline(state, out);
766                }
767                recurse_write(state, out, line.clone_zero_indent(), child, false);
768            }
769        },
770        Node::Blockquote(x) => {
771            let line = line.clone_indent(None, "> ".into());
772            for (i, child) in x.children.iter().enumerate() {
773                if i > 0 {
774                    line.write_newline(state, out);
775                }
776                recurse_write(state, out, line.clone_inline(), child, false);
777            }
778        },
779        Node::List(x) => {
780            match &x.start {
781                Some(i) => {
782                    // bug in markdown lib, start is actually the number of the last child:
783                    // https://github.com/wooorm/markdown-rs/issues/38
784                    for (j, child) in x.children.iter().enumerate() {
785                        if j > 0 {
786                            line.write_newline(state, out);
787                        }
788                        recurse_write(
789                            state,
790                            out,
791                            line.clone_indent(Some(format!("{}. ", *i as usize + j)), "   ".into()),
792                            child,
793                            false,
794                        );
795                    }
796                },
797                None => {
798                    for (i, child) in x.children.iter().enumerate() {
799                        if i > 0 {
800                            line.write_newline(state, out);
801                        }
802                        recurse_write(state, out, line.clone_indent(Some("* ".into()), "  ".into()), child, false);
803                    }
804                },
805            };
806        },
807        Node::ListItem(x) => {
808            for (i, child) in x.children.iter().enumerate() {
809                if i > 0 {
810                    line.write_newline(state, out);
811                }
812                recurse_write(state, out, line.clone_zero_indent(), child, false);
813            }
814        },
815        // block->inline elements (flush after)
816        Node::Code(x) => {
817            let mut content = None;
818            if let Some(lang) = &x.lang {
819                if lang == "rust" {
820                    let current_prefix_len = line.0.borrow().prefix.chars().count();
821                    let base_prefix_len = line.0.borrow().base_prefix_len.0;
822                    let rel_max_width = line.0.borrow().rel_max_width.map(|v| v.0);
823                    let overall_max_width = line.0.borrow().max_width.0;
824                    let nested_max_width = if let Some(rel) = rel_max_width {
825                        let markdown_indent = current_prefix_len - base_prefix_len;
826                        rel.saturating_sub(markdown_indent)
827                    } else {
828                        overall_max_width.saturating_sub(current_prefix_len)
829                    };
830
831                    // Minimum max width
832                    let nested_max_width = std::cmp::max(nested_max_width, 40);
833                    let mut nested_config = state.config.clone();
834                    nested_config.max_width = nested_max_width;
835                    match crate::format_str(&x.value, &nested_config) {
836                        Ok(res) => {
837                            content = Some(res.rendered);
838                        },
839                        Err(_) => {
840                            // Fallback to original
841                        },
842                    }
843                } else if let Some(fmt_config) = state.config.external_formatters.get(lang.as_str()).cloned() {
844                    match crate::run_external_formatter(&fmt_config.commandline, &x.value) {
845                        Ok(formatted) => {
846                            content = Some(formatted);
847                        },
848                        Err(_) => {
849                            // Fallback to original
850                        },
851                    }
852                }
853            }
854            line.write_unbreakable(state, out, &format!("```{}", match &x.lang {
855                None => "",
856                Some(x) => x,
857            }));
858            line.flush_always(state, out);
859            let content = content.unwrap_or_else(|| x.value.clone());
860            for l in content.as_str().lines() {
861                line.write_unbreakable(state, out, l);
862                line.flush_always(state, out);
863            }
864            line.write_unbreakable(state, out, "```");
865            line.flush_always(state, out);
866        },
867        Node::Heading(x) => {
868            let line = line.clone_unbreakable(Some(format!("{} ", "#".repeat(x.depth as usize))));
869            for child in &x.children {
870                recurse_write(state, out, line.clone_inline(), child, true);
871            }
872            line.flush_always(state, out);
873        },
874        Node::FootnoteDefinition(x) => {
875            let line = line.clone_indent(Some(format!("[^{}]: ", x.identifier)), "   ".into());
876            for child in &x.children {
877                recurse_write(state, out, line.clone_inline(), child, true);
878            }
879            line.flush_always(state, out);
880        },
881        Node::ThematicBreak(_) => {
882            line.write_unbreakable(state, out, "---");
883            line.flush_always(state, out);
884        },
885        Node::Definition(x) => {
886            line.write_unbreakable(state, out, &format!("[{}]: {}", x.identifier.trim(), x.url));
887            if let Some(title) = &x.title {
888                line.write_unbreakable(state, out, " \"");
889                line.write_breakable(state, out, title);
890                line.write_unbreakable(state, out, "\"");
891            }
892            line.flush_always(state, out);
893        },
894        Node::Paragraph(x) => {
895            for child in &x.children {
896                recurse_write(state, out, line.clone_inline(), child, true);
897            }
898            line.flush_always(state, out);
899        },
900        Node::Html(x) if !inline => {
901            line.write_unbreakable(state, out, &format!("`{}`", join_lines(&x.value)));
902            line.flush_always(state, out);
903        },
904        // inline elements
905        Node::Text(x) => {
906            line.write_breakable(state, out, &join_lines(&x.value));
907        },
908        Node::InlineCode(x) => {
909            line.write_unbreakable(state, out, &format!("`{}`", join_lines(&x.value)));
910        },
911        Node::Strong(x) => {
912            line.write_unbreakable(state, out, "**");
913            for child in &x.children {
914                recurse_write(state, out, line.clone_inline(), child, true);
915            }
916            line.write_unbreakable(state, out, "**");
917        },
918        Node::Delete(x) => {
919            line.write_unbreakable(state, out, "~~");
920            for child in &x.children {
921                recurse_write(state, out, line.clone_inline(), child, true);
922            }
923            line.write_unbreakable(state, out, "~~");
924        },
925        Node::Emphasis(x) => {
926            line.write_unbreakable(state, out, "_");
927            for child in &x.children {
928                recurse_write(state, out, line.clone_inline(), child, true);
929            }
930            line.write_unbreakable(state, out, "_");
931        },
932        Node::FootnoteReference(x) => {
933            line.write_unbreakable(state, out, &format!("[^{}]", x.identifier));
934        },
935        Node::Html(x) => {
936            line.write_unbreakable(state, out, &format!("`{}`", join_lines(&x.value)));
937        },
938        Node::Image(x) => {
939            let alt = join_lines(&x.alt);
940            match (get_splits(&join_lines(&alt)).first().is_some(), &x.title) {
941                (false, None) => {
942                    line.write_unbreakable(state, out, &format!("![{}]({})", alt, x.url));
943                },
944                (false, Some(t)) => {
945                    line.write_unbreakable(state, out, &format!("![{}]({}", alt, x.url));
946                    line.write_unbreakable(state, out, " \"");
947                    line.write_breakable(state, out, &join_lines(t));
948                    line.write_unbreakable(state, out, "\")");
949                },
950                (true, None) => {
951                    line.write_unbreakable(state, out, "![");
952                    line.write_breakable(state, out, &alt);
953                    line.write_unbreakable(state, out, &format!("]({})", x.url));
954                },
955                (true, Some(t)) => {
956                    line.write_unbreakable(state, out, "![");
957                    line.write_breakable(state, out, &alt);
958                    line.write_unbreakable(state, out, &format!("]({}", x.url));
959                    line.write_unbreakable(state, out, " \"");
960                    line.write_breakable(state, out, &join_lines(t));
961                    line.write_unbreakable(state, out, "\")");
962                },
963            }
964        },
965        Node::ImageReference(x) => {
966            line.write_unbreakable(state, out, &format!("![][{}]", x.identifier));
967        },
968        Node::Link(x) => {
969            let simple_text = if x.children.len() != 1 {
970                None
971            } else {
972                x.children.get(0)
973            }.and_then(|c| match c {
974                Node::Text(t) => {
975                    let t = join_lines(&t.value);
976                    if get_splits(&t).first().is_some() {
977                        None
978                    } else {
979                        Some(t)
980                    }
981                },
982                Node::InlineCode(t) => {
983                    let t = join_lines(&t.value);
984                    if get_splits(&t).first().is_some() {
985                        None
986                    } else {
987                        Some(format!("`{}`", t))
988                    }
989                },
990                _ => None,
991            });
992            match (simple_text, &x.title) {
993                (Some(unbroken_content), None) => {
994                    if unbroken_content.as_str() == x.url.as_str() {
995                        line.write_unbreakable(state, out, &format!("<{}>", x.url));
996                    } else {
997                        line.write_unbreakable(state, out, &format!("[{}]({})", unbroken_content, x.url));
998                    }
999                },
1000                (Some(c), Some(title)) => {
1001                    line.write_unbreakable(state, out, &format!("[{}]({}", c, x.url));
1002                    line.write_unbreakable(state, out, " \"");
1003                    line.write_breakable(state, out, title);
1004                    line.write_unbreakable(state, out, "\")");
1005                },
1006                (None, None) => {
1007                    line.write_unbreakable(state, out, "[");
1008                    for child in &x.children {
1009                        recurse_write(state, out, line.clone_inline(), child, true);
1010                    }
1011                    line.write_unbreakable(state, out, &format!("]({})", x.url));
1012                },
1013                (None, Some(title)) => {
1014                    line.write_unbreakable(state, out, "[");
1015                    for child in &x.children {
1016                        recurse_write(state, out, line.clone_inline(), child, true);
1017                    }
1018                    line.write_unbreakable(state, out, &format!("]({}", x.url));
1019                    line.write_unbreakable(state, out, " \"");
1020                    line.write_breakable(state, out, title);
1021                    line.write_unbreakable(state, out, "\")");
1022                },
1023            }
1024        },
1025        Node::LinkReference(x) => {
1026            let simple_text = if x.children.len() != 1 {
1027                None
1028            } else {
1029                x.children.get(0)
1030            }.and_then(|c| match c {
1031                Node::Text(t) => if get_splits(&t.value).first().is_some() {
1032                    None
1033                } else {
1034                    Some(t.value.clone())
1035                },
1036                Node::InlineCode(t) => if get_splits(&t.value).first().is_some() {
1037                    None
1038                } else {
1039                    Some(format!("`{}`", t.value))
1040                },
1041                _ => {
1042                    None
1043                },
1044            });
1045            match simple_text {
1046                Some(t) if t == x.identifier => {
1047                    line.write_unbreakable(state, out, &format!("[{}]", t));
1048                },
1049                _ => {
1050                    line.write_unbreakable(state, out, "[");
1051                    for child in &x.children {
1052                        recurse_write(state, out, line.clone_inline(), child, true);
1053                    }
1054                    line.write_unbreakable(state, out, &format!("][{}]", x.identifier));
1055                },
1056            }
1057        },
1058        Node::Break(_) => {
1059            // normalized out
1060        },
1061        Node::Math(_) => unreachable!(),
1062        Node::Table(_) => unreachable!(),
1063        Node::TableRow(_) => unreachable!(),
1064        Node::TableCell(_) => unreachable!(),
1065        Node::MdxJsxTextElement(_) => unreachable!(),
1066        Node::MdxFlowExpression(_) => unreachable!(),
1067        Node::MdxJsxFlowElement(_) => unreachable!(),
1068        Node::MdxjsEsm(_) => unreachable!(),
1069        Node::Toml(_) => unreachable!(),
1070        Node::Yaml(_) => unreachable!(),
1071        Node::InlineMath(_) => unreachable!(),
1072        Node::MdxTextExpression(_) => unreachable!(),
1073    }
1074}
1075
1076struct State {
1077    config: FormatConfig,
1078    line_buffer: String,
1079    need_nl: bool,
1080}
1081
1082fn unicode_len(text: &str) -> VisualLen {
1083    VisualLen(text.chars().count())
1084}
1085
1086#[derive(Debug, derive_more::Add, PartialEq, Eq, PartialOrd, Ord, derive_more::Sub, Clone, Copy)]
1087struct VisualLen(usize);