use crate::cli::*;
use crate::ignore::*;
use crate::indent::*;
use crate::logging::*;
use crate::regexes::{ENV_BEGIN, ENV_END, ITEM, RE_SPLITTING};
use crate::subs::*;
use crate::verbatim::*;
use crate::wrap::*;
use crate::LINE_END;
use log::Level::{Info, Warn};
use std::iter::zip;
pub fn format_file(
old_text: &str,
file: &str,
args: &Cli,
logs: &mut Vec<Log>,
) -> String {
record_file_log(logs, Info, file, "Formatting started.");
let old_text = clean_text(old_text, args);
let mut old_lines = zip(1.., old_text.lines());
let mut state = State::new();
let mut queue: Vec<(usize, String)> = vec![];
let mut new_text = String::with_capacity(2 * old_text.len());
let indent_char = if args.usetabs { "\t" } else { " " };
loop {
if let Some((linum_old, mut line)) = queue.pop() {
let pattern = Pattern::new(&line);
let mut temp_state = state.clone();
temp_state.linum_old = linum_old;
if !set_ignore_and_report(
&line,
&mut temp_state,
logs,
file,
&pattern,
) {
if needs_split(&line, &pattern) {
let (this_line, next_line) =
split_line(&line, &temp_state, file, args, logs);
queue.push((linum_old, next_line.to_string()));
line = this_line.to_string();
}
let indent = calculate_indent(
&line,
&mut temp_state,
logs,
file,
args,
&pattern,
);
#[allow(clippy::cast_possible_wrap)]
let indent_length =
usize::try_from(indent.visual * args.tab as i8)
.expect("Visual indent is non-negative.");
if needs_wrap(line.trim_start(), indent_length, args) {
let wrapped_lines = apply_wrap(
line.trim_start(),
indent_length,
&temp_state,
file,
args,
logs,
);
if let Some([this_line, next_line_start, next_line]) =
wrapped_lines
{
queue.push((
linum_old,
[next_line_start, next_line].concat(),
));
queue.push((linum_old, this_line.to_string()));
continue;
}
}
line = apply_indent(&line, &indent, args, indent_char);
}
state = temp_state;
new_text.push_str(&line);
new_text.push_str(LINE_END);
state.linum_new += 1;
} else if let Some((linum_old, line)) = old_lines.next() {
queue.push((linum_old, line.to_string()));
} else {
break;
}
}
if !indents_return_to_zero(&state) {
record_file_log(logs, Warn, file, "Indent does not return to zero.");
}
new_text = remove_trailing_spaces(&new_text);
record_file_log(logs, Info, file, "Formatting complete.");
new_text
}
fn set_ignore_and_report(
line: &str,
temp_state: &mut State,
logs: &mut Vec<Log>,
file: &str,
pattern: &Pattern,
) -> bool {
temp_state.ignore = get_ignore(line, temp_state, logs, file, true);
temp_state.verbatim =
get_verbatim(line, temp_state, logs, file, true, pattern);
temp_state.verbatim.visual || temp_state.ignore.visual
}
fn clean_text(text: &str, args: &Cli) -> String {
let mut text = remove_extra_newlines(text);
if !args.usetabs {
text = remove_tabs(&text, args);
}
text = remove_trailing_spaces(&text);
text
}
#[derive(Clone, Debug)]
pub struct State {
pub linum_old: usize,
pub linum_new: usize,
pub ignore: Ignore,
pub indent: Indent,
pub verbatim: Verbatim,
}
impl State {
pub const fn new() -> Self {
Self {
linum_old: 1,
linum_new: 1,
ignore: Ignore::new(),
indent: Indent::new(),
verbatim: Verbatim::new(),
}
}
}
pub struct Pattern {
pub contains_env_begin: bool,
pub contains_env_end: bool,
pub contains_item: bool,
pub contains_splitting: bool,
}
impl Pattern {
pub fn new(s: &str) -> Self {
if RE_SPLITTING.is_match(s) {
Self {
contains_env_begin: s.contains(ENV_BEGIN),
contains_env_end: s.contains(ENV_END),
contains_item: s.contains(ITEM),
contains_splitting: true,
}
} else {
Self {
contains_env_begin: false,
contains_env_end: false,
contains_item: false,
contains_splitting: false,
}
}
}
}
const fn indents_return_to_zero(state: &State) -> bool {
state.indent.actual == 0
}