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 explicit_wrap: bool,
430 max_width: VisualLen,
431 rel_max_width: Option<VisualLen>,
432 backward_break: Option<(usize, bool)>,
433}
434
435impl LineState_ {
436 fn flush_always(&mut self, state: &mut State, out: &mut String, explicit_wrap: bool, wrapping: bool) {
437 out.push_str(format!("{}{}{}{}", if state.need_nl {
438 "\n"
439 } else {
440 ""
441 }, match &self.first_prefix.take() {
442 Some(t) => t,
443 None => &*self.prefix,
444 }, &state.line_buffer, if wrapping && explicit_wrap {
445 " \\"
446 } else {
447 ""
448 }).trim_end());
449 state.line_buffer.clear();
450 state.need_nl = true;
451 self.backward_break = None;
452 }
453
454 fn flush(&mut self, state: &mut State, out: &mut String, explicit_wrap: bool, wrapping: bool) {
455 if !state.line_buffer.trim().is_empty() {
456 self.flush_always(state, out, explicit_wrap, wrapping);
457 }
458 }
459
460 fn calc_max_width(&self) -> VisualLen {
461 match self.rel_max_width {
462 Some(w) => unicode_len(&self.prefix) + w,
463 None => self.max_width - if self.explicit_wrap {
464 VisualLen(2)
465 } else {
466 VisualLen(0)
467 },
468 }
469 }
470
471 fn calc_current_len(&self, state: &State) -> VisualLen {
472 self.base_prefix_len + unicode_len(&state.line_buffer)
473 }
474}
475
476fn get_splits(text: &str) -> Vec<usize> {
478 text.char_indices().filter(|i| i.1 == ' ').map(|i| i.0 + 1).collect()
482}
483
484struct LineState(Rc<RefCell<LineState_>>);
485
486impl LineState {
487 fn new(
488 base_prefix_len: VisualLen,
489 first_prefix: Option<String>,
490 prefix: String,
491 max_width: VisualLen,
492 rel_max_width: Option<VisualLen>,
493 explicit_wrap: bool,
494 ) -> LineState {
495 LineState(Rc::new(RefCell::new(LineState_ {
496 base_prefix_len: base_prefix_len,
497 first_prefix,
498 prefix,
499 explicit_wrap,
500 max_width,
501 rel_max_width,
502 backward_break: None,
503 })))
504 }
505
506 fn clone_inline(&self) -> LineState {
507 LineState(self.0.clone())
508 }
509
510 fn clone_zero_indent(&self) -> LineState {
511 let mut s = self.0.as_ref().borrow_mut();
512 LineState(Rc::new(RefCell::new(LineState_ {
513 base_prefix_len: s.base_prefix_len,
514 first_prefix: s.first_prefix.take(),
515 prefix: s.prefix.clone(),
516 explicit_wrap: s.explicit_wrap,
517 max_width: s.max_width,
518 rel_max_width: s.rel_max_width,
519 backward_break: None,
520 })))
521 }
522
523 fn clone_indent(&self, first_prefix: Option<String>, prefix: String, explicit_wrap: bool) -> LineState {
524 let mut s = self.0.as_ref().borrow_mut();
525 LineState(Rc::new(RefCell::new(LineState_ {
526 base_prefix_len: s.base_prefix_len,
527 first_prefix: match (s.first_prefix.take(), first_prefix) {
528 (None, None) => None,
529 (None, Some(p)) => Some(format!("{}{}", s.prefix, p)),
530 (Some(p), None) => Some(p),
531 (Some(p1), Some(p2)) => Some(format!("{}{}", p1, p2)),
532 },
533 prefix: format!("{}{}", s.prefix, prefix),
534 explicit_wrap: s.explicit_wrap || explicit_wrap,
535 max_width: s.max_width,
536 rel_max_width: s.rel_max_width,
537 backward_break: None,
538 })))
539 }
540
541 fn write(&self, state: &mut State, out: &mut String, text: &str, breaks: &[usize]) {
542 let mut s = self.0.as_ref().borrow_mut();
543 let max_len = s.calc_max_width();
544
545 struct FoundWritableLen<'a> {
546 writable: usize,
549 previous_break: Option<(usize, &'a [usize])>,
551 next_break: Option<(usize, &'a [usize])>,
554 }
555
556 fn find_writable_len<
557 'a,
558 >(
559 width: VisualLen,
560 max_len: VisualLen,
561 text: &str,
562 breaks_offset: usize,
563 breaks: &'a [usize],
564 ) -> FoundWritableLen<'a> {
565 let mut previous_break = None;
566 let mut writable = 0;
567 for (i, b) in breaks.iter().enumerate() {
568 let b = *b - breaks_offset;
569 let next_break = Some((b, &breaks[i + 1..]));
570 if width + unicode_len(&text[..b]) > max_len {
571 return FoundWritableLen {
572 writable: writable,
573 previous_break: previous_break,
574 next_break: next_break,
575 };
576 }
577 previous_break = next_break;
578 writable = b;
579 }
580 return FoundWritableLen {
581 writable: if width + unicode_len(&text) > max_len {
582 writable
583 } else {
584 text.len()
585 },
586 previous_break: previous_break,
587 next_break: None,
588 };
589 }
590
591 fn write_forward(state: &mut State, s: &mut LineState_, text: &str, b: Option<usize>) {
593 if let Some(b) = b {
594 s.backward_break = Some((state.line_buffer.len() + b, s.explicit_wrap));
595 }
596 state.line_buffer.push_str(&text);
597 }
598
599 fn write_forward_breaks(
600 state: &mut State,
601 s: &mut LineState_,
602 out: &mut String,
603 max_len: VisualLen,
604 mut first: bool,
605 mut text: String,
606 mut breaks_offset: usize,
607 breaks: &[usize],
608 ) {
609 let mut breaks = breaks;
610 while !text.is_empty() {
611 if first {
612 first = false;
613 } else {
614 s.flush(state, out, s.explicit_wrap, true);
615 }
616 let found = find_writable_len(s.calc_current_len(state), max_len, &text, breaks_offset, breaks);
617 if found.writable > 0 {
618 write_forward(state, s, &text[..found.writable], found.previous_break.map(|b| b.0));
619 breaks = found.previous_break.map(|b| b.1).unwrap_or(breaks);
620 text = text.split_off(found.writable);
621 breaks_offset += found.writable;
622 } else if let Some((b, breaks0)) = found.next_break {
623 write_forward(state, s, &text[..b], Some(b));
624 breaks = breaks0;
625 text = text.split_off(b);
626 breaks_offset += b;
627 } else {
628 state.line_buffer.push_str(&text);
629 return;
630 }
631 }
632 }
633
634 let found = find_writable_len(s.calc_current_len(state), max_len, text, 0, breaks);
635 if found.writable > 0 {
636 write_forward(state, &mut s, &text[..found.writable], found.previous_break.map(|b| b.0));
637 write_forward_breaks(
638 state,
639 &mut s,
640 out,
641 max_len,
642 false,
643 (&text[found.writable..]).to_string(),
644 found.writable,
645 found.previous_break.map(|b| b.1).unwrap_or(breaks),
646 );
647 } else if let Some((at, explicit_wrap)) = s.backward_break.take() {
648 let prefix = state.line_buffer.split_off(at);
651 s.flush(state, out, explicit_wrap, true);
652 state.line_buffer.push_str(&prefix);
653 write_forward_breaks(state, &mut s, out, max_len, true, text.to_string(), 0, breaks);
654 } else if let Some((b, breaks)) = found.next_break {
655 write_forward(state, &mut s, &text[..b], Some(b));
658 write_forward_breaks(state, &mut s, out, max_len, false, (&text[b..]).to_string(), b, breaks);
659 } else {
660 state.line_buffer.push_str(text);
661 return;
662 }
663 }
664
665 fn write_breakable(&self, state: &mut State, out: &mut String, text: &str) {
666 self.write(state, out, text, &get_splits(text));
667 }
668
669 fn write_unbreakable(&self, state: &mut State, out: &mut String, text: &str) {
670 self.write(state, out, text, &[]);
671 }
672
673 fn flush_always(&self, state: &mut State, out: &mut String) {
674 self.0.as_ref().borrow_mut().flush_always(state, out, false, false);
675 }
676
677 fn write_newline(&self, state: &mut State, out: &mut String) {
678 let mut s = self.0.as_ref().borrow_mut();
679 if !state.line_buffer.is_empty() {
680 panic!();
681 }
682 s.flush_always(state, out, false, false);
683 }
684}
685
686fn recurse_write(state: &mut State, out: &mut String, line: LineState, node: &Node, inline: bool) {
687 fn join_lines(text: &str) -> String {
688 let lines = Regex::new("\r?\n").unwrap().split(text).collect::<Vec<&str>>();
689 let mut joined = String::new();
690 for (i, line) in lines.iter().enumerate() {
691 let mut line = *line;
692 if i > 0 {
693 line = line.trim_start();
694 joined.push(' ');
695 }
696 if i < lines.len() - 1 {
697 line = line.trim_end();
698 }
699 joined.push_str(line);
700 }
701 joined
702 }
703
704 match node {
705 Node::Root(x) => {
707 for (i, child) in x.children.iter().enumerate() {
708 if i > 0 {
709 line.write_newline(state, out);
710 }
711 recurse_write(state, out, line.clone_zero_indent(), child, false);
712 }
713 },
714 Node::Blockquote(x) => {
715 let line = line.clone_indent(None, "> ".into(), false);
716 for (i, child) in x.children.iter().enumerate() {
717 if i > 0 {
718 line.write_newline(state, out);
719 }
720 recurse_write(state, out, line.clone_inline(), child, false);
721 }
722 },
723 Node::List(x) => {
724 match &x.start {
725 Some(i) => {
726 for (j, child) in x.children.iter().enumerate() {
729 if j > 0 {
730 line.write_newline(state, out);
731 }
732 recurse_write(
733 state,
734 out,
735 line.clone_indent(Some(format!("{}. ", *i as usize + j)), " ".into(), false),
736 child,
737 false,
738 );
739 }
740 },
741 None => {
742 for (i, child) in x.children.iter().enumerate() {
743 if i > 0 {
744 line.write_newline(state, out);
745 }
746 recurse_write(
747 state,
748 out,
749 line.clone_indent(Some("* ".into()), " ".into(), false),
750 child,
751 false,
752 );
753 }
754 },
755 };
756 },
757 Node::ListItem(x) => {
758 for (i, child) in x.children.iter().enumerate() {
759 if i > 0 {
760 line.write_newline(state, out);
761 }
762 recurse_write(state, out, line.clone_zero_indent(), child, false);
763 }
764 },
765 Node::Code(x) => {
767 line.write_unbreakable(state, out, &format!("```{}", match &x.lang {
768 None => "",
769 Some(x) => x,
770 }));
771 line.flush_always(state, out);
772 for l in x.value.as_str().lines() {
773 line.write_unbreakable(state, out, l);
774 line.flush_always(state, out);
775 }
776 line.write_unbreakable(state, out, "```");
777 line.flush_always(state, out);
778 },
779 Node::Heading(x) => {
780 let line = line.clone_indent(Some(format!("{} ", "#".repeat(x.depth as usize))), " ".into(), true);
781 for child in &x.children {
782 recurse_write(state, out, line.clone_inline(), child, true);
783 }
784 line.flush_always(state, out);
785 },
786 Node::FootnoteDefinition(x) => {
787 let line = line.clone_indent(Some(format!("[^{}]: ", x.identifier)), " ".into(), false);
788 for child in &x.children {
789 recurse_write(state, out, line.clone_inline(), child, true);
790 }
791 line.flush_always(state, out);
792 },
793 Node::ThematicBreak(_) => {
794 line.write_unbreakable(state, out, "---");
795 line.flush_always(state, out);
796 },
797 Node::Definition(x) => {
798 line.write_unbreakable(state, out, &format!("[{}]: {}", x.identifier.trim(), x.url));
799 if let Some(title) = &x.title {
800 line.write_unbreakable(state, out, " \"");
801 line.write_breakable(state, out, title);
802 line.write_unbreakable(state, out, "\"");
803 }
804 line.flush_always(state, out);
805 },
806 Node::Paragraph(x) => {
807 for child in &x.children {
808 recurse_write(state, out, line.clone_inline(), child, true);
809 }
810 line.flush_always(state, out);
811 },
812 Node::Html(x) if !inline => {
813 line.write_unbreakable(state, out, &format!("`{}`", join_lines(&x.value)));
814 line.flush_always(state, out);
815 },
816 Node::Text(x) => {
818 line.write_breakable(state, out, &join_lines(&x.value));
819 },
820 Node::InlineCode(x) => {
821 line.write_unbreakable(state, out, &format!("`{}`", join_lines(&x.value)));
822 },
823 Node::Strong(x) => {
824 line.write_unbreakable(state, out, "**");
825 for child in &x.children {
826 recurse_write(state, out, line.clone_inline(), child, true);
827 }
828 line.write_unbreakable(state, out, "**");
829 },
830 Node::Delete(x) => {
831 line.write_unbreakable(state, out, "~~");
832 for child in &x.children {
833 recurse_write(state, out, line.clone_inline(), child, true);
834 }
835 line.write_unbreakable(state, out, "~~");
836 },
837 Node::Emphasis(x) => {
838 line.write_unbreakable(state, out, "_");
839 for child in &x.children {
840 recurse_write(state, out, line.clone_inline(), child, true);
841 }
842 line.write_unbreakable(state, out, "_");
843 },
844 Node::FootnoteReference(x) => {
845 line.write_unbreakable(state, out, &format!("[^{}]", x.identifier));
846 },
847 Node::Html(x) => {
848 line.write_unbreakable(state, out, &format!("`{}`", join_lines(&x.value)));
849 },
850 Node::Image(x) => {
851 let alt = join_lines(&x.alt);
852 match (get_splits(&join_lines(&alt)).first().is_some(), &x.title) {
853 (false, None) => {
854 line.write_unbreakable(state, out, &format!("", alt, x.url));
855 },
856 (false, Some(t)) => {
857 line.write_unbreakable(state, out, &format!(");
858 line.write_unbreakable(state, out, " \"");
859 line.write_breakable(state, out, &join_lines(t));
860 line.write_unbreakable(state, out, "\")");
861 },
862 (true, None) => {
863 line.write_unbreakable(state, out, "", x.url));
866 },
867 (true, Some(t)) => {
868 line.write_unbreakable(state, out, ");
871 line.write_unbreakable(state, out, " \"");
872 line.write_breakable(state, out, &join_lines(t));
873 line.write_unbreakable(state, out, "\")");
874 },
875 }
876 },
877 Node::ImageReference(x) => {
878 line.write_unbreakable(state, out, &format!("![][{}]", x.identifier));
879 },
880 Node::Link(x) => {
881 let simple_text = if x.children.len() != 1 {
882 None
883 } else {
884 x.children.get(0)
885 }.and_then(|c| match c {
886 Node::Text(t) => {
887 let t = join_lines(&t.value);
888 if get_splits(&t).first().is_some() {
889 None
890 } else {
891 Some(t)
892 }
893 },
894 Node::InlineCode(t) => {
895 let t = join_lines(&t.value);
896 if get_splits(&t).first().is_some() {
897 None
898 } else {
899 Some(format!("`{}`", t))
900 }
901 },
902 _ => None,
903 });
904 match (simple_text, &x.title) {
905 (Some(unbroken_content), None) => {
906 if unbroken_content.as_str() == x.url.as_str() {
907 line.write_unbreakable(state, out, &format!("<{}>", x.url));
908 } else {
909 line.write_unbreakable(state, out, &format!("[{}]({})", unbroken_content, x.url));
910 }
911 },
912 (Some(c), Some(title)) => {
913 line.write_unbreakable(state, out, &format!("[{}]({}", c, x.url));
914 line.write_unbreakable(state, out, " \"");
915 line.write_breakable(state, out, title);
916 line.write_unbreakable(state, out, "\")");
917 },
918 (None, None) => {
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, &format!("]({})", x.url));
924 },
925 (None, Some(title)) => {
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, &format!("]({}", x.url));
931 line.write_unbreakable(state, out, " \"");
932 line.write_breakable(state, out, title);
933 line.write_unbreakable(state, out, "\")");
934 },
935 }
936 },
937 Node::LinkReference(x) => {
938 let simple_text = if x.children.len() != 1 {
939 None
940 } else {
941 x.children.get(0)
942 }.and_then(|c| match c {
943 Node::Text(t) => if get_splits(&t.value).first().is_some() {
944 None
945 } else {
946 Some(t.value.clone())
947 },
948 Node::InlineCode(t) => if get_splits(&t.value).first().is_some() {
949 None
950 } else {
951 Some(format!("`{}`", t.value))
952 },
953 _ => {
954 None
955 },
956 });
957 match simple_text {
958 Some(t) if t == x.identifier => {
959 line.write_unbreakable(state, out, &format!("[{}]", t));
960 },
961 _ => {
962 line.write_unbreakable(state, out, "[");
963 for child in &x.children {
964 recurse_write(state, out, line.clone_inline(), child, true);
965 }
966 line.write_unbreakable(state, out, &format!("][{}]", x.identifier));
967 },
968 }
969 },
970 Node::Break(_) => {
971 },
973 Node::Math(_) => unreachable!(),
974 Node::Table(_) => unreachable!(),
975 Node::TableRow(_) => unreachable!(),
976 Node::TableCell(_) => unreachable!(),
977 Node::MdxJsxTextElement(_) => unreachable!(),
978 Node::MdxFlowExpression(_) => unreachable!(),
979 Node::MdxJsxFlowElement(_) => unreachable!(),
980 Node::MdxjsEsm(_) => unreachable!(),
981 Node::Toml(_) => unreachable!(),
982 Node::Yaml(_) => unreachable!(),
983 Node::InlineMath(_) => unreachable!(),
984 Node::MdxTextExpression(_) => unreachable!(),
985 }
986}
987
988pub fn format_md(
989 true_out: &mut String,
990 max_width: usize,
991 rel_max_width: Option<usize>,
992 prefix: &str,
993 source: &str,
994) -> Result<(), loga::Error> {
995 match || -> Result<String, loga::Error> {
999 let mut out = String::new();
1000 let mut state = State {
1001 line_buffer: String::new(),
1002 need_nl: false,
1003 };
1004 let ast = markdown::to_mdast(source, &markdown::ParseOptions {
1005 constructs: markdown::Constructs { ..Default::default() },
1006 ..Default::default()
1007 }).map_err(|e| loga::err_with("Error parsing markdown", ea!(err = e)))?;
1008 recurse_write(
1009 &mut state,
1010 &mut out,
1011 LineState::new(
1012 unicode_len(&prefix),
1013 None,
1014 prefix.to_string(),
1015 VisualLen(max_width),
1016 rel_max_width.map(VisualLen),
1017 false,
1018 ),
1019 &ast,
1020 false,
1021 );
1022 Ok(out)
1023 }() {
1024 Ok(o) => {
1025 true_out.push_str(&o);
1026 Ok(())
1027 },
1028 Err(e) => {
1029 Err(e)
1030 },
1031 }
1032}