1use crate::format::{Pattern, State};
4use crate::regexes::{TABLES_BEGIN, TABLES_END};
5use crate::LINE_END;
6use itertools::Itertools;
7use regex::Regex;
8
9fn remove_double_spaces(text: &str) -> String {
11 let re = Regex::new(r"(\S) {2,}").unwrap();
12 re.replace_all(text, "$1 ").to_string()
13}
14
15fn add_line_breaks(text: &str) -> (String, bool) {
17 let re_break = Regex::new(r"\\\\ .*\S").unwrap();
18 let re_indent = Regex::new(r"^\s*\S").unwrap();
19 let re_first_non_white = Regex::new(r"\S.*").unwrap();
20 let re_to_break = Regex::new(r"^[^\\]*\\\\").unwrap();
21 let mut new_text = String::new();
22 let mut finished: bool = true;
23 for line in text.lines() {
24 if re_break.is_match(line) {
25 finished = false;
26 let indent = re_indent.find(line).map_or("", |m| {
27 let s = m.as_str();
28 &s[..s.len() - 1]
29 });
30 let next_line_long = re_break.find(line).map_or("", |m| {
31 let s = m.as_str();
32 &s[2..]
33 });
34 let next_line = re_first_non_white
35 .find(next_line_long)
36 .map_or("", |m| m.as_str());
37 let this_line = re_to_break.find(line).map_or("", |m| m.as_str());
38 new_text.push_str(this_line);
39 new_text.push_str(LINE_END);
40 new_text.push_str(indent);
41 new_text.push_str(next_line);
42 new_text.push_str(LINE_END);
43 } else {
44 new_text.push_str(line);
45 new_text.push_str(LINE_END);
46 }
47 }
48 (new_text, finished)
49}
50
51fn get_positions(text: &str) -> Vec<Vec<usize>> {
53 text.lines()
54 .map(|l| {
55 let mut prev = None;
56 l.char_indices()
57 .filter_map(|(i, c)| {
58 let is_match = c == '&' && prev != Some('\\');
59 prev = Some(c);
60 if is_match {
61 Some(i)
62 } else {
63 None
64 }
65 })
66 .collect()
67 })
68 .collect()
69}
70
71fn get_new_positions(positions: &[Vec<usize>]) -> Vec<Vec<usize>> {
73 let n_delims = positions.iter().map(std::vec::Vec::len).max().unwrap();
74 let mut new_positions = positions.to_owned();
75 for j in 0..n_delims {
76 let positions: Vec<Option<usize>> =
77 new_positions.iter().map(|l| l.get(j).copied()).collect();
78 let new_position: usize = positions.iter().max().unwrap().unwrap();
79 for line in &mut new_positions {
80 let n_delims_line = line.len();
81 if n_delims_line > j {
82 let offset = new_position - line[j];
83 for pos in line.iter_mut().skip(j) {
84 *pos += offset;
85 }
86 }
87 }
88 }
89 new_positions
90}
91
92fn get_offsets(
94 positions: &[Vec<usize>],
95 new_positions: &[Vec<usize>],
96) -> Vec<Vec<usize>> {
97 let mut offsets = vec![];
98 for l in 0..positions.len() {
99 let mut prev_offset = 0;
100 let mut offset = vec![];
101 for j in 0..positions[l].len() {
102 offset.push(new_positions[l][j] - positions[l][j] - prev_offset);
103 prev_offset = new_positions[l][j] - positions[l][j];
104 }
105 offsets.push(offset);
106 }
107 offsets
108}
109
110fn format_table_line(line: &str, offsets_delims_row: &[usize]) -> String {
112 let mut new_line = String::new();
113 let mut j = 0;
114 for c in line.chars() {
115 if c == '&' {
116 let offset = offsets_delims_row.get(j).copied().unwrap_or(0);
117 new_line.extend(std::iter::repeat_n(' ', offset));
118 j += 1;
119 }
120 new_line.push(c);
121 }
122 new_line
123}
124
125fn format_table(text: &str) -> String {
127 let mut clean_text = remove_double_spaces(text);
128
129 let mut finished: bool;
131 (clean_text, finished) = add_line_breaks(&clean_text);
132 let max_line_break_attempts = 10;
133 for _attempt in 0..max_line_break_attempts {
134 if finished {
135 break;
136 }
137 (clean_text, finished) = add_line_breaks(&clean_text);
138 }
139
140 let positions = get_positions(&clean_text);
141 let new_positions = get_new_positions(&positions);
142 let offsets = get_offsets(&positions, &new_positions);
143 let mut new_text = String::new();
144 for (linum, line) in clean_text.lines().enumerate() {
145 let new_line = format_table_line(line, &offsets[linum]);
146 new_text.push_str(&new_line);
147 new_text.push_str(LINE_END);
148 }
149 new_text
150}
151
152fn contains_table_begin(line: &str) -> bool {
153 TABLES_BEGIN.iter().any(|r| line.contains(r))
154}
155
156fn contains_table_end(line: &str) -> bool {
157 TABLES_END.iter().any(|r| line.contains(r))
158}
159
160fn find_table_positions(text: &str) -> Vec<(usize, usize)> {
162 let mut table_positions = vec![];
163 let mut begin: usize = 0;
164 let mut end: usize;
165 for (linum, line) in text.lines().enumerate() {
166 if contains_table_begin(line) {
167 begin = linum;
168 } else if contains_table_end(line) {
169 end = linum;
170 table_positions.push((begin, end));
171 }
172 }
173 table_positions
174}
175
176#[must_use]
178pub fn format_tables(text: &str) -> String {
179 let table_positions = find_table_positions(text);
180 if table_positions.is_empty() {
181 return text.to_string();
182 }
183
184 let first_table_begin = table_positions[0].0;
185 let mut new_text: String =
186 text.lines().take(first_table_begin).join(LINE_END);
187 new_text.push_str(LINE_END);
188
189 for (t, table_position) in table_positions.iter().enumerate() {
190 let begin = table_position.0;
192 let end = table_position.1;
193 let table_text: String = text
194 .lines()
195 .skip(begin)
196 .take(end - begin + 1)
197 .join(LINE_END);
198 let new_table_text = format_table(&table_text);
199 new_text.push_str(&new_table_text);
200
201 let next_table_begin = if t + 1 < table_positions.len() {
203 table_positions[t + 1].0
204 } else {
205 text.lines().count()
206 };
207 let next_text: String = text
208 .lines()
209 .skip(end + 1)
210 .take(next_table_begin - end - 1)
211 .join(LINE_END);
212 new_text.push_str(&next_text);
213 new_text.push_str(LINE_END);
214 }
215
216 new_text
217}
218
219#[derive(Clone, Debug)]
221pub struct Table {
222 pub actual: bool,
224 pub visual: bool,
226}
227
228impl Table {
229 #[must_use]
231 pub const fn new() -> Self {
232 Self {
233 actual: false,
234 visual: false,
235 }
236 }
237}
238
239impl Default for Table {
240 fn default() -> Self {
241 Self::new()
242 }
243}
244
245#[must_use]
247pub fn is_inside_table(line: &str, state: &State, pattern: &Pattern) -> Table {
248 let begin = pattern.contains_env_begin && contains_table_begin(line);
249 let end = pattern.contains_env_end && contains_table_end(line);
250 let actual: bool;
251 let visual: bool;
252 if begin {
253 actual = true;
254 visual = true;
255 } else if end {
256 actual = false;
257 visual = true;
258 } else {
259 actual = state.table.actual;
260 visual = state.table.actual;
261 }
262
263 Table { actual, visual }
264}