tex-fmt 0.2.1

LaTeX formatter written in Rust
use crate::comments::*;
use crate::ignore::*;
use crate::leave::*;
use crate::logging::*;
use crate::parse::*;
use log::Level::{Info, Warn};

const WRAP: usize = 80;

pub fn needs_wrap(file: &str) -> bool {
    file.lines().any(|l| l.len() > WRAP)
}

fn find_wrap_point(line: &str) -> Option<usize> {
    let mut wrap_point: Option<usize> = None;
    let mut after_char = false;
    let mut prev_char: Option<char> = None;
    for i in 0..WRAP {
        if line.chars().nth(i) == Some(' ') && prev_char != Some('\\') {
            if after_char {
                wrap_point = Some(i);
            }
        } else if line.chars().nth(i) != Some('%') {
            after_char = true;
        }
        prev_char = line.chars().nth(i)
    }
    wrap_point
}

fn wrap_line(
    line: &str,
    linum: usize,
    args: &Cli,
    logs: &mut Vec<Log>,
    pass: Option<usize>,
    filename: &str,
) -> String {
    if args.verbose {
        record_log(
            logs,
            Info,
            pass,
            filename.to_string(),
            Some(linum),
            Some(line.to_string()),
            "Wrapping long line.".to_string(),
        );
    }
    let mut remaining_line = line.to_string();
    let mut new_line = "".to_string();
    let mut can_wrap = true;
    while needs_wrap(&remaining_line) && can_wrap {
        let wrap_point = find_wrap_point(&remaining_line);
        let comment_index = find_comment_index(&remaining_line);
        match wrap_point {
            Some(p) => {
                let line_start = match comment_index {
                    Some(c) => {
                        if p > c {
                            "%"
                        } else {
                            ""
                        }
                    }
                    None => "",
                };
                new_line.push_str(&remaining_line[0..p]);
                new_line.push('\n');
                remaining_line =
                    remaining_line[p..remaining_line.len()].to_string();
                remaining_line.insert_str(0, line_start);
            }
            None => {
                can_wrap = false;
            }
        }
    }
    new_line.push_str(&remaining_line);
    new_line
}

pub fn wrap(
    file: &str,
    filename: &str,
    logs: &mut Vec<Log>,
    pass: Option<usize>,
    args: &Cli,
) -> String {
    if args.verbose {
        record_log(
            logs,
            Info,
            pass,
            filename.to_string(),
            None,
            None,
            format!("Wrap on pass {}.", pass.unwrap_or_default()),
        );
    }
    let mut new_file = "".to_string();
    let mut ignore = Ignore::new();
    let mut leave = Leave::new();
    for (linum, line) in file.lines().enumerate() {
        ignore = get_ignore(line, linum, ignore, filename, logs, pass, false);
        leave = get_leave(line, linum, leave, filename, logs, pass, false);
        if needs_wrap(line) && !leave.visual && !ignore.visual {
            let new_line = wrap_line(line, linum, args, logs, pass, filename);
            new_file.push_str(&new_line);
            if needs_wrap(&new_line) && !ignore.visual {
                record_log(
                    logs,
                    Warn,
                    pass,
                    filename.to_string(),
                    Some(linum),
                    Some(new_line),
                    "Line cannot be wrapped:".to_string(),
                );
            }
        } else {
            new_file.push_str(line);
            if needs_wrap(line) && !ignore.visual {
                record_log(
                    logs,
                    Warn,
                    pass,
                    filename.to_string(),
                    Some(linum),
                    Some(line.to_string()),
                    "Line cannot be wrapped:".to_string(),
                );
            }
        }
        new_file.push('\n');
    }

    new_file
}