use crate::args::Args;
use crate::comments::{find_comment_index, remove_comment};
use crate::format::{Pattern, State};
use crate::logging::{record_line_log, Log};
use crate::regexes::{ENV_BEGIN, ENV_END, ITEM, VERBS};
use core::cmp::max;
use log::Level;
use log::LevelFilter;
use std::path::Path;
const OPENS: [char; 3] = ['{', '(', '['];
const CLOSES: [char; 3] = ['}', ')', ']'];
#[derive(Debug, Clone)]
pub struct Indent {
pub actual: i8,
pub visual: i8,
}
impl Indent {
#[must_use]
pub const fn new() -> Self {
Self {
actual: 0,
visual: 0,
}
}
}
impl Default for Indent {
fn default() -> Self {
Self::new()
}
}
fn get_diff(
line: &str,
pattern: &Pattern,
lists_begin: &[String],
lists_end: &[String],
no_indent_envs_begin: &[String],
no_indent_envs_end: &[String],
) -> i8 {
if pattern.contains_verb && VERBS.iter().any(|x| line.contains(x)) {
return 0;
}
let mut diff: i8 = 0;
if pattern.contains_env_begin && line.contains(ENV_BEGIN) {
if no_indent_envs_begin.iter().any(|r| line.contains(r)) {
return 0;
}
diff += 1;
diff += i8::from(lists_begin.iter().any(|r| line.contains(r)));
} else if pattern.contains_env_end && line.contains(ENV_END) {
if no_indent_envs_end.iter().any(|r| line.contains(r)) {
return 0;
}
diff -= 1;
diff -= i8::from(lists_end.iter().any(|r| line.contains(r)));
}
diff
}
fn get_back(
line: &str,
pattern: &Pattern,
state: &State,
lists_end: &[String],
no_indent_envs_end: &[String],
) -> i8 {
if state.indent.actual == 0 {
return 0;
}
let mut back: i8 = 0;
if pattern.contains_verb && VERBS.iter().any(|x| line.contains(x)) {
return 0;
}
if pattern.contains_env_end && line.contains(ENV_END) {
if no_indent_envs_end.iter().any(|r| line.contains(r)) {
return 0;
}
for r in lists_end {
if line.contains(r) {
return 2;
}
}
back = 1;
} else if pattern.contains_item && line.contains(ITEM) {
back += 1;
}
back
}
fn get_diff_back_delim(line: &str) -> (i8, i8) {
let mut diff: i8 = 0;
let mut back: i8 = 0;
for c in line.chars() {
diff -= i8::from(OPENS.contains(&c));
diff += i8::from(CLOSES.contains(&c));
back = max(diff, back);
}
(-diff, back)
}
#[allow(clippy::too_many_arguments)]
fn get_indent(
line: &str,
prev_indent: &Indent,
pattern: &Pattern,
state: &State,
lists_begin: &[String],
lists_end: &[String],
no_indent_envs_begin: &[String],
no_indent_envs_end: &[String],
) -> Indent {
let mut diff = get_diff(
line,
pattern,
lists_begin,
lists_end,
no_indent_envs_begin,
no_indent_envs_end,
);
let mut back =
get_back(line, pattern, state, lists_end, no_indent_envs_end);
let diff_back_delim = get_diff_back_delim(line);
diff += diff_back_delim.0;
back += diff_back_delim.1;
let actual = prev_indent.actual + diff;
let visual = prev_indent.actual - back;
Indent { actual, visual }
}
#[allow(clippy::too_many_arguments)]
pub fn calculate_indent(
line: &str,
state: &mut State,
logs: &mut Vec<Log>,
file: &Path,
args: &Args,
pattern: &Pattern,
lists_begin: &[String],
lists_end: &[String],
no_indent_envs_begin: &[String],
no_indent_envs_end: &[String],
) -> Indent {
let comment_index = find_comment_index(line, pattern);
let line_strip = remove_comment(line, comment_index);
let mut indent = get_indent(
line_strip,
&state.indent,
pattern,
state,
lists_begin,
lists_end,
no_indent_envs_begin,
no_indent_envs_end,
);
if args.verbosity == LevelFilter::Trace {
record_line_log(
logs,
Level::Trace,
file,
state.linum_new,
state.linum_old,
line,
&format!(
"Indent: actual = {}, visual = {}:",
indent.actual, indent.visual
),
);
}
state.indent = indent.clone();
if indent.visual == 0 && state.linum_new > state.linum_last_zero_indent {
state.linum_last_zero_indent = state.linum_new;
}
if (indent.visual < 0) || (indent.actual < 0) {
record_line_log(
logs,
Level::Warn,
file,
state.linum_new,
state.linum_old,
line,
"Indent is negative.",
);
indent.actual = indent.actual.max(0);
indent.visual = indent.visual.max(0);
if state.linum_first_negative_indent.is_none() {
state.linum_first_negative_indent = Some(state.linum_new);
}
}
indent
}
#[must_use]
pub fn apply_indent(
line: &str,
indent: &Indent,
args: &Args,
indent_char: &str,
) -> String {
let first_non_whitespace = line.chars().position(|c| !c.is_whitespace());
if first_non_whitespace.is_none() {
return String::new();
}
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
let n_indent_chars = (indent.visual * args.tabsize as i8) as usize;
if first_non_whitespace == Some(n_indent_chars) {
return line.into();
}
let trimmed_line = line.trim_start();
let mut new_line =
String::with_capacity(trimmed_line.len() + n_indent_chars);
for idx in 0..n_indent_chars {
new_line.insert_str(idx, indent_char);
}
new_line.insert_str(n_indent_chars, trimmed_line);
new_line
}