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