use crate::format::{Pattern, State};
use crate::regexes::{TABLES_BEGIN, TABLES_END};
use crate::LINE_END;
use itertools::Itertools;
use regex::Regex;
fn remove_double_spaces(text: &str) -> String {
let re = Regex::new(r"(\S) {2,}").unwrap();
re.replace_all(text, "$1 ").to_string()
}
fn add_line_breaks(text: &str) -> (String, bool) {
let re_break = Regex::new(r"\\\\ .*\S").unwrap();
let re_indent = Regex::new(r"^\s*\S").unwrap();
let re_first_non_white = Regex::new(r"\S.*").unwrap();
let re_to_break = Regex::new(r"^[^\\]*\\\\").unwrap();
let mut new_text = String::new();
let mut finished: bool = true;
for line in text.lines() {
if re_break.is_match(line) {
finished = false;
let indent = re_indent.find(line).map_or("", |m| {
let s = m.as_str();
&s[..s.len() - 1]
});
let next_line_long = re_break.find(line).map_or("", |m| {
let s = m.as_str();
&s[2..]
});
let next_line = re_first_non_white
.find(next_line_long)
.map_or("", |m| m.as_str());
let this_line = re_to_break.find(line).map_or("", |m| m.as_str());
new_text.push_str(this_line);
new_text.push_str(LINE_END);
new_text.push_str(indent);
new_text.push_str(next_line);
new_text.push_str(LINE_END);
} else {
new_text.push_str(line);
new_text.push_str(LINE_END);
}
}
(new_text, finished)
}
fn get_positions(text: &str) -> Vec<Vec<usize>> {
text.lines()
.map(|l| {
let mut prev = None;
l.char_indices()
.filter_map(|(i, c)| {
let is_match = c == '&' && prev != Some('\\');
prev = Some(c);
if is_match {
Some(i)
} else {
None
}
})
.collect()
})
.collect()
}
fn get_new_positions(positions: &[Vec<usize>]) -> Vec<Vec<usize>> {
let n_delims = positions.iter().map(std::vec::Vec::len).max().unwrap();
let mut new_positions = positions.to_owned();
for j in 0..n_delims {
let positions: Vec<Option<usize>> =
new_positions.iter().map(|l| l.get(j).copied()).collect();
let new_position: usize = positions.iter().max().unwrap().unwrap();
for line in &mut new_positions {
let n_delims_line = line.len();
if n_delims_line > j {
let offset = new_position - line[j];
for pos in line.iter_mut().skip(j) {
*pos += offset;
}
}
}
}
new_positions
}
fn get_offsets(
positions: &[Vec<usize>],
new_positions: &[Vec<usize>],
) -> Vec<Vec<usize>> {
let mut offsets = vec![];
for l in 0..positions.len() {
let mut prev_offset = 0;
let mut offset = vec![];
for j in 0..positions[l].len() {
offset.push(new_positions[l][j] - positions[l][j] - prev_offset);
prev_offset = new_positions[l][j] - positions[l][j];
}
offsets.push(offset);
}
offsets
}
fn format_table_line(line: &str, offsets_delims_row: &[usize]) -> String {
let mut new_line = String::new();
let mut j = 0;
for c in line.chars() {
if c == '&' {
let offset = offsets_delims_row.get(j).copied().unwrap_or(0);
new_line.extend(std::iter::repeat_n(' ', offset));
j += 1;
}
new_line.push(c);
}
new_line
}
fn format_table(text: &str) -> String {
let mut clean_text = remove_double_spaces(text);
let mut finished: bool;
(clean_text, finished) = add_line_breaks(&clean_text);
let max_line_break_attempts = 10;
for _attempt in 0..max_line_break_attempts {
if finished {
break;
}
(clean_text, finished) = add_line_breaks(&clean_text);
}
let positions = get_positions(&clean_text);
let new_positions = get_new_positions(&positions);
let offsets = get_offsets(&positions, &new_positions);
let mut new_text = String::new();
for (linum, line) in clean_text.lines().enumerate() {
let new_line = format_table_line(line, &offsets[linum]);
new_text.push_str(&new_line);
new_text.push_str(LINE_END);
}
new_text
}
fn contains_table_begin(line: &str) -> bool {
TABLES_BEGIN.iter().any(|r| line.contains(r))
}
fn contains_table_end(line: &str) -> bool {
TABLES_END.iter().any(|r| line.contains(r))
}
fn find_table_positions(text: &str) -> Vec<(usize, usize)> {
let mut table_positions = vec![];
let mut begin: usize = 0;
let mut end: usize;
for (linum, line) in text.lines().enumerate() {
if contains_table_begin(line) {
begin = linum;
} else if contains_table_end(line) {
end = linum;
table_positions.push((begin, end));
}
}
table_positions
}
#[must_use]
pub fn format_tables(text: &str) -> String {
let table_positions = find_table_positions(text);
if table_positions.is_empty() {
return text.to_string();
}
let first_table_begin = table_positions[0].0;
let mut new_text: String =
text.lines().take(first_table_begin).join(LINE_END);
new_text.push_str(LINE_END);
for (t, table_position) in table_positions.iter().enumerate() {
let begin = table_position.0;
let end = table_position.1;
let table_text: String = text
.lines()
.skip(begin)
.take(end - begin + 1)
.join(LINE_END);
let new_table_text = format_table(&table_text);
new_text.push_str(&new_table_text);
let next_table_begin = if t + 1 < table_positions.len() {
table_positions[t + 1].0
} else {
text.lines().count()
};
let next_text: String = text
.lines()
.skip(end + 1)
.take(next_table_begin - end - 1)
.join(LINE_END);
new_text.push_str(&next_text);
new_text.push_str(LINE_END);
}
new_text
}
#[derive(Clone, Debug)]
pub struct Table {
pub actual: bool,
pub visual: bool,
}
impl Table {
#[must_use]
pub const fn new() -> Self {
Self {
actual: false,
visual: false,
}
}
}
impl Default for Table {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn is_inside_table(line: &str, state: &State, pattern: &Pattern) -> Table {
let begin = pattern.contains_env_begin && contains_table_begin(line);
let end = pattern.contains_env_end && contains_table_end(line);
let actual: bool;
let visual: bool;
if begin {
actual = true;
visual = true;
} else if end {
actual = false;
visual = true;
} else {
actual = state.table.actual;
visual = state.table.actual;
}
Table { actual, visual }
}