Skip to content

Commit

Permalink
Merge pull request WGUNDERWOOD#42 from cdesaintguilhem/pr-refactor-logic
Browse files Browse the repository at this point in the history
Alternative formatting processing
  • Loading branch information
WGUNDERWOOD authored Oct 15, 2024
2 parents 3f658fd + db634ec commit f73426c
Show file tree
Hide file tree
Showing 11 changed files with 287 additions and 175 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ lazy_static = "1.5.0"
log = "0.4.22"
regex = "1.11.0"
similar = "2.6.0"
unicode-width = "0.2.0"

[profile.release]
codegen-units = 1
Expand Down
5 changes: 0 additions & 5 deletions src/comments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,3 @@ pub fn find_comment_index(line: &str) -> Option<usize> {
pub fn remove_comment(line: &str, comment: Option<usize>) -> &str {
comment.map_or_else(|| line, |c| &line[0..c])
}

/// Extract a comment from the end of a line
pub fn get_comment(line: &str, comment: Option<usize>) -> &str {
comment.map_or_else(|| "", |c| &line[c..])
}
155 changes: 107 additions & 48 deletions src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,77 +14,106 @@ use std::iter::zip;

/// Central function to format a file
pub fn format_file(
text: &str,
old_text: &str,
file: &str,
args: &Cli,
logs: &mut Vec<Log>,
) -> String {
record_file_log(logs, Info, file, "Formatting started.");
let mut old_text = remove_extra_newlines(text);
if !args.usetabs {
old_text = remove_tabs(&old_text, args);
}
old_text = remove_trailing_spaces(&old_text);

// Clean the source file and zip its lines with line numbers
let old_text = clean_text(old_text, args);
let mut old_lines = zip(1.., old_text.lines());

// Initialise
let mut state = State::new();
let old_lines = old_text.lines();
let mut old_lines = zip(1.., old_lines);
let mut queue: Vec<(usize, String)> = vec![];
let mut new_text = String::with_capacity(2 * text.len());
let mut new_text = String::with_capacity(2 * old_text.len());

// Select the character used for indentation.
let indent_char = if args.usetabs { "\t" } else { " " };

loop {
if let Some((linum_old, mut line)) = queue.pop() {
// Read the patterns present on this line.
let pattern = Pattern::new(&line);
let temp_state: State;
(line, temp_state) = apply_indent(

// Temporary state for working on this line.
let mut temp_state = state.clone();

// Update the state with the line number from the queue.
temp_state.linum_old = linum_old;

// If the line should not be ignored ...
if !set_ignore_and_report(
&line,
linum_old,
&state,
&mut temp_state,
logs,
file,
args,
&pattern,
indent_char,
);
if needs_env_new_line(&line, &temp_state, &pattern) {
let env_lines =
put_env_new_line(&line, &temp_state, file, args, logs);
if env_lines.is_some() {
queue.push((linum_old, env_lines.clone().unwrap().1));
queue.push((linum_old, env_lines.clone().unwrap().0));
} else {
state = temp_state;
new_text.push_str(&line);
new_text.push_str(LINE_END);
state.linum_new += 1;
};
} else if needs_wrap(&line, &temp_state, args) {
let wrapped_lines =
apply_wrap(&line, &temp_state, file, args, logs);
if wrapped_lines.is_some() {
queue.push((linum_old, wrapped_lines.clone().unwrap().1));
queue.push((linum_old, wrapped_lines.clone().unwrap().0));
} else {
state = temp_state;
new_text.push_str(&line);
new_text.push_str(LINE_END);
state.linum_new += 1;
};
} else {
state = temp_state;
new_text.push_str(&line);
new_text.push_str(LINE_END);
state.linum_new += 1;
) {
// Check if the line should be split because of a pattern that should begin on a new line.
if needs_env_new_line(&line, &temp_state, &pattern) {
// Split the line into two ...
let (this_line, next_line) =
put_env_new_line(&line, &temp_state, file, args, logs);
// ... and queue the second part for formatting.
queue.push((linum_old, next_line.to_string()));
line = this_line.to_string();
}

// Calculate the indent based on the current state and the patterns in the line.
let indent = calculate_indent(
&line,
&mut temp_state,
logs,
file,
args,
&pattern,
);

let indent_length = usize::try_from(indent.visual * args.tab)
.expect("Visual indent is non-negative.");

// Wrap the line before applying the indent, and loop back if the line needed wrapping.
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;
}
}

// Lastly, apply the indent if the line didn't need wrapping.
line = apply_indent(&line, &indent, args, indent_char);
}

// Add line to new text
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(&new_text) {
if !indents_return_to_zero(&state) {
record_file_log(logs, Warn, file, "Indent does not return to zero.");
}

Expand All @@ -93,6 +122,35 @@ pub fn format_file(
new_text
}

/// Sets the `ignore` and `verbatim` flags in the given [State] based on `line` and returns whether `line` should be
/// ignored by formatting.
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
}

/// Cleans the given text by removing extra line breaks and trailing spaces, and tabs if they shouldn't be used.
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
}

/// Information on the current state during formatting
#[derive(Clone, Debug)]
pub struct State {
Expand Down Expand Up @@ -143,6 +201,7 @@ impl Pattern {
}

/// Ensure that the indentation returns to zero at the end of the file
fn indents_return_to_zero(text: &str) -> bool {
!text.lines().last().unwrap_or_default().starts_with(' ')
fn indents_return_to_zero(state: &State) -> bool {
#![allow(clippy::missing_const_for_fn)]
state.indent.actual == 0
}
136 changes: 69 additions & 67 deletions src/indent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
use crate::cli::*;
use crate::comments::*;
use crate::format::*;
use crate::ignore::*;
use crate::logging::*;
use crate::regexes::*;
use crate::verbatim::*;
use core::cmp::max;
use log::Level::{Trace, Warn};

Expand Down Expand Up @@ -109,78 +107,82 @@ fn get_indent(line: &str, prev_indent: &Indent, pattern: &Pattern) -> Indent {
Indent { actual, visual }
}

/// Apply the correct indentation to a line
pub fn apply_indent(
/// Calculates the indent for `line` based on its contents. This functions saves the calculated [Indent], which might be
/// negative, to the given [State], and then ensures that the returned [Indent] is non-negative.
pub fn calculate_indent(
line: &str,
linum_old: usize,
state: &State,
state: &mut State,
logs: &mut Vec<Log>,
file: &str,
args: &Cli,
pattern: &Pattern,
indent_char: &str,
) -> (String, State) {
#![allow(clippy::too_many_arguments)]
let mut new_state = state.clone();
new_state.linum_old = linum_old;

new_state.ignore = get_ignore(line, &new_state, logs, file, true);
new_state.verbatim =
get_verbatim(line, &new_state, logs, file, true, pattern);
) -> Indent {
// Calculate the new indent by first removing the comment from the line (if there is one) to ignore diffs from
// characters in there.
let comment_index = find_comment_index(line);
let line_strip = remove_comment(line, comment_index);
let mut indent = get_indent(line_strip, &state.indent, pattern);

// Record the indent to the logs.
if args.trace {
record_line_log(
logs,
Trace,
file,
state.linum_new,
state.linum_old,
line,
&format!(
"Indent: actual = {}, visual = {}:",
indent.actual, indent.visual
),
);
}

let new_line = if new_state.verbatim.visual || new_state.ignore.visual {
line.to_string()
} else {
// calculate indent
let comment_index = find_comment_index(line);
let line_strip = &remove_comment(line, comment_index);
let mut indent = get_indent(line_strip, &state.indent, pattern);
new_state.indent = indent.clone();
if args.trace {
record_line_log(
logs,
Trace,
file,
state.linum_new,
new_state.linum_old,
line,
&format!(
"Indent: actual = {}, visual = {}:",
indent.actual, indent.visual
),
);
}
// Save the indent to the state. Note, this indent might be negative; it is saved without correction so that this is
// not forgotten for the next iterations.
state.indent = indent.clone();

// However, we can't negatively indent a line. So we log the negative indent and reset the values to 0.
if (indent.visual < 0) || (indent.actual < 0) {
record_line_log(
logs,
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 (indent.visual < 0) || (indent.actual < 0) {
record_line_log(
logs,
Warn,
file,
new_state.linum_new,
new_state.linum_old,
line,
"Indent is negative.",
);
indent.actual = indent.actual.max(0);
indent.visual = indent.visual.max(0);
}
indent
}

// apply indent
let trimmed_line = line.trim_start();
if trimmed_line.is_empty() {
String::new()
} else {
let n_indent_chars =
usize::try_from(indent.visual * args.tab).unwrap();
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
/// Apply the given indentation to a line
pub fn apply_indent(
line: &str,
indent: &Indent,
args: &Cli,
indent_char: &str,
) -> String {
// Remove white space from the start of the line
let trimmed_line = line.trim_start();

// If the line is now empty, return a new empty String
if trimmed_line.is_empty() {
String::new()
// Otherwise, allocate enough memory to fit line with the added indentation and insert the appropriate string slices
} else {
let n_indent_chars = usize::try_from(indent.visual * args.tab).unwrap();
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, new_state)
new_line.insert_str(n_indent_chars, trimmed_line);
new_line
}
}
Loading

0 comments on commit f73426c

Please sign in to comment.