|
1 | 1 | use std::cmp;
|
2 |
| -use std::string::String; |
3 | 2 |
|
4 |
| -use crate::clean::{self, DocFragment, Item}; |
| 3 | +use crate::clean::{self, DocFragment, DocFragmentKind, Item}; |
5 | 4 | use crate::core::DocContext;
|
6 | 5 | use crate::fold::{self, DocFolder};
|
7 | 6 | use crate::passes::Pass;
|
@@ -35,65 +34,81 @@ impl clean::Attributes {
|
35 | 34 | }
|
36 | 35 |
|
37 | 36 | fn unindent_fragments(docs: &mut Vec<DocFragment>) {
|
38 |
| - for fragment in docs { |
39 |
| - fragment.doc = unindent(&fragment.doc); |
40 |
| - } |
41 |
| -} |
42 |
| - |
43 |
| -fn unindent(s: &str) -> String { |
44 |
| - let lines = s.lines().collect::<Vec<&str>>(); |
45 |
| - let mut saw_first_line = false; |
46 |
| - let mut saw_second_line = false; |
47 |
| - let min_indent = lines.iter().fold(usize::MAX, |min_indent, line| { |
48 |
| - // After we see the first non-whitespace line, look at |
49 |
| - // the line we have. If it is not whitespace, and therefore |
50 |
| - // part of the first paragraph, then ignore the indentation |
51 |
| - // level of the first line |
52 |
| - let ignore_previous_indents = |
53 |
| - saw_first_line && !saw_second_line && !line.chars().all(|c| c.is_whitespace()); |
| 37 | + // `add` is used in case the most common sugared doc syntax is used ("/// "). The other |
| 38 | + // fragments kind's lines are never starting with a whitespace unless they are using some |
| 39 | + // markdown formatting requiring it. Therefore, if the doc block have a mix between the two, |
| 40 | + // we need to take into account the fact that the minimum indent minus one (to take this |
| 41 | + // whitespace into account). |
| 42 | + // |
| 43 | + // For example: |
| 44 | + // |
| 45 | + // /// hello! |
| 46 | + // #[doc = "another"] |
| 47 | + // |
| 48 | + // In this case, you want "hello! another" and not "hello! another". |
| 49 | + let add = if docs.windows(2).any(|arr| arr[0].kind != arr[1].kind) |
| 50 | + && docs.iter().any(|d| d.kind == DocFragmentKind::SugaredDoc) |
| 51 | + { |
| 52 | + // In case we have a mix of sugared doc comments and "raw" ones, we want the sugared one to |
| 53 | + // "decide" how much the minimum indent will be. |
| 54 | + 1 |
| 55 | + } else { |
| 56 | + 0 |
| 57 | + }; |
54 | 58 |
|
55 |
| - let min_indent = if ignore_previous_indents { usize::MAX } else { min_indent }; |
| 59 | + // `min_indent` is used to know how much whitespaces from the start of each lines must be |
| 60 | + // removed. Example: |
| 61 | + // |
| 62 | + // /// hello! |
| 63 | + // #[doc = "another"] |
| 64 | + // |
| 65 | + // In here, the `min_indent` is 1 (because non-sugared fragment are always counted with minimum |
| 66 | + // 1 whitespace), meaning that "hello!" will be considered a codeblock because it starts with 4 |
| 67 | + // (5 - 1) whitespaces. |
| 68 | + let min_indent = match docs |
| 69 | + .iter() |
| 70 | + .map(|fragment| { |
| 71 | + fragment.doc.lines().fold(usize::MAX, |min_indent, line| { |
| 72 | + if line.chars().all(|c| c.is_whitespace()) { |
| 73 | + min_indent |
| 74 | + } else { |
| 75 | + // Compare against either space or tab, ignoring whether they are |
| 76 | + // mixed or not. |
| 77 | + let whitespace = line.chars().take_while(|c| *c == ' ' || *c == '\t').count(); |
| 78 | + cmp::min(min_indent, whitespace) |
| 79 | + + if fragment.kind == DocFragmentKind::SugaredDoc { 0 } else { add } |
| 80 | + } |
| 81 | + }) |
| 82 | + }) |
| 83 | + .min() |
| 84 | + { |
| 85 | + Some(x) => x, |
| 86 | + None => return, |
| 87 | + }; |
56 | 88 |
|
57 |
| - if saw_first_line { |
58 |
| - saw_second_line = true; |
| 89 | + for fragment in docs { |
| 90 | + if fragment.doc.lines().count() == 0 { |
| 91 | + continue; |
59 | 92 | }
|
60 | 93 |
|
61 |
| - if line.chars().all(|c| c.is_whitespace()) { |
62 |
| - min_indent |
| 94 | + let min_indent = if fragment.kind != DocFragmentKind::SugaredDoc && min_indent > 0 { |
| 95 | + min_indent - add |
63 | 96 | } else {
|
64 |
| - saw_first_line = true; |
65 |
| - let mut whitespace = 0; |
66 |
| - line.chars().all(|char| { |
67 |
| - // Compare against either space or tab, ignoring whether they |
68 |
| - // are mixed or not |
69 |
| - if char == ' ' || char == '\t' { |
70 |
| - whitespace += 1; |
71 |
| - true |
| 97 | + min_indent |
| 98 | + }; |
| 99 | + |
| 100 | + fragment.doc = fragment |
| 101 | + .doc |
| 102 | + .lines() |
| 103 | + .map(|line| { |
| 104 | + if line.chars().all(|c| c.is_whitespace()) { |
| 105 | + line.to_string() |
72 | 106 | } else {
|
73 |
| - false |
| 107 | + assert!(line.len() >= min_indent); |
| 108 | + line[min_indent..].to_string() |
74 | 109 | }
|
75 |
| - }); |
76 |
| - cmp::min(min_indent, whitespace) |
77 |
| - } |
78 |
| - }); |
79 |
| - |
80 |
| - if !lines.is_empty() { |
81 |
| - let mut unindented = vec![lines[0].trim_start().to_string()]; |
82 |
| - unindented.extend_from_slice( |
83 |
| - &lines[1..] |
84 |
| - .iter() |
85 |
| - .map(|&line| { |
86 |
| - if line.chars().all(|c| c.is_whitespace()) { |
87 |
| - line.to_string() |
88 |
| - } else { |
89 |
| - assert!(line.len() >= min_indent); |
90 |
| - line[min_indent..].to_string() |
91 |
| - } |
92 |
| - }) |
93 |
| - .collect::<Vec<_>>(), |
94 |
| - ); |
95 |
| - unindented.join("\n") |
96 |
| - } else { |
97 |
| - s.to_string() |
| 110 | + }) |
| 111 | + .collect::<Vec<_>>() |
| 112 | + .join("\n"); |
98 | 113 | }
|
99 | 114 | }
|
0 commit comments