pub(crate) fn tabbed(text: &str) -> String {
let escaped = text.replace('\x1b', "^[").replace('\r', "\\r");
let mut output = String::new();
let mut sections = escaped.split('\x0c').peekable();
while let Some(section) = sections.next() {
let mut rows: Vec<Vec<String>> = section
.split_terminator('\n')
.map(|line| line.split(['\t', '\x0b']).map(String::from).collect())
.collect();
align(&mut rows, 0);
for row in rows {
output.push_str(&row.concat());
output.push('\n');
}
if sections.peek().is_some() && (section.is_empty() || section.ends_with('\n')) {
output.push('\n');
}
}
output
}
fn align(rows: &mut [Vec<String>], col: usize) {
let mut start = 0;
while start < rows.len() {
if rows[start].len() <= col + 1 {
start += 1;
continue;
}
let end = (start..rows.len())
.find(|&i| rows[i].len() <= col + 1)
.unwrap_or(rows.len());
let width = rows[start..end]
.iter()
.map(|r| r[col].chars().count() + 2)
.max()
.unwrap();
for row in &mut rows[start..end] {
let padding = width - row[col].chars().count();
row[col].push_str(&" ".repeat(padding));
}
align(&mut rows[start..end], col + 1);
start = end;
}
}