use anyhow::Result;
use ropey::Rope;
use std::{
collections::BTreeMap,
fmt::Write,
ops::Bound::{Excluded, Included},
path::PathBuf,
sync::Arc,
};
use sway_ast::Module;
use crate::{
formatter::{FormattedCode, Formatter},
utils::map::byte_span::{ByteSpan, LeafSpans},
FormatterError,
};
#[derive(Debug, Clone)]
struct NewlineSequence {
sequence_length: usize,
}
impl ToString for NewlineSequence {
fn to_string(&self) -> String {
(0..self.sequence_length - 1)
.map(|_| "\n")
.collect::<String>()
}
}
type NewlineMap = BTreeMap<ByteSpan, NewlineSequence>;
fn newline_map_from_src(unformatted_input: &str) -> Result<NewlineMap, FormatterError> {
let mut newline_map = BTreeMap::new();
let mut input_iter = unformatted_input.chars().enumerate().peekable();
let mut current_sequence_length = 0;
let mut in_sequence = false;
let mut sequence_start = 0;
while let Some((char_index, char)) = input_iter.next() {
let next_char = input_iter.peek().map(|input| input.1);
if (char == '}' || char == ';') && next_char == Some('\n') {
if !in_sequence {
sequence_start = char_index + 1;
in_sequence = true;
}
} else if char == '\n' && in_sequence {
current_sequence_length += 1;
}
if (Some('}') == next_char || Some('(') == next_char) && in_sequence {
current_sequence_length = 0;
in_sequence = false;
}
if next_char == Some(' ') || next_char == Some('\t') {
continue;
}
if Some('\n') != next_char && current_sequence_length > 0 && in_sequence {
let byte_span = ByteSpan {
start: sequence_start,
end: char_index,
};
let newline_sequence = NewlineSequence {
sequence_length: current_sequence_length,
};
newline_map.insert(byte_span, newline_sequence);
current_sequence_length = 0;
in_sequence = false;
}
}
Ok(newline_map)
}
pub fn handle_newlines(
unformatted_input: Arc<str>,
unformatted_module: &Module,
formatted_input: Arc<str>,
path: Option<Arc<PathBuf>>,
formatted_code: &mut FormattedCode,
formatter: &Formatter,
) -> Result<(), FormatterError> {
let newline_threshold = formatter.config.whitespace.newline_threshold;
let newline_map = newline_map_from_src(&unformatted_input)?;
let formatted_module = sway_parse::parse_file_standalone(formatted_input, path)?;
add_newlines(
newline_map,
unformatted_module,
&formatted_module,
formatted_code,
unformatted_input,
newline_threshold,
)?;
Ok(())
}
fn add_newlines(
newline_map: NewlineMap,
unformatted_module: &Module,
formatted_module: &Module,
formatted_code: &mut FormattedCode,
unformatted_code: Arc<str>,
newline_threshold: usize,
) -> Result<(), FormatterError> {
let mut unformatted_newline_spans = unformatted_module.leaf_spans();
let mut formatted_newline_spans = formatted_module.leaf_spans();
unformatted_newline_spans.push(ByteSpan {
start: unformatted_code.len(),
end: unformatted_code.len(),
});
formatted_newline_spans.push(ByteSpan {
start: formatted_code.len(),
end: formatted_code.len(),
});
let mut offset = 0;
let mut previous_unformatted_newline_span = unformatted_newline_spans
.first()
.ok_or(FormatterError::NewlineSequenceError)?;
let mut previous_formatted_newline_span = formatted_newline_spans
.first()
.ok_or(FormatterError::NewlineSequenceError)?;
for (unformatted_newline_span, formatted_newline_span) in unformatted_newline_spans
.iter()
.skip(1)
.zip(formatted_newline_spans.iter().skip(1))
{
let newline_sequences = get_newline_sequences_between_spans(
previous_unformatted_newline_span,
unformatted_newline_span,
&newline_map,
);
if !newline_sequences.is_empty() {
offset += insert_after_span(
previous_formatted_newline_span,
newline_sequences,
offset,
formatted_code,
newline_threshold,
)?;
}
previous_unformatted_newline_span = unformatted_newline_span;
previous_formatted_newline_span = formatted_newline_span;
}
Ok(())
}
fn format_newline_sequnce(newline_sequence: &NewlineSequence, threshold: usize) -> String {
if newline_sequence.sequence_length > threshold {
(0..threshold).map(|_| "\n").collect::<String>()
} else {
newline_sequence.to_string()
}
}
fn find_already_present_extra_newlines(from: usize, src: String) -> usize {
let mut number_of_newlines_present = 0;
for char in src.chars().skip(from) {
if char == '\n' {
number_of_newlines_present += 1;
} else {
break;
}
}
if number_of_newlines_present == 0 {
0
} else {
number_of_newlines_present - 1
}
}
fn insert_after_span(
from: &ByteSpan,
newline_sequences_to_insert: Vec<NewlineSequence>,
offset: usize,
formatted_code: &mut FormattedCode,
threshold: usize,
) -> Result<usize, FormatterError> {
let iter = newline_sequences_to_insert.iter();
let mut sequence_string = String::new();
let newlines_to_skip =
find_already_present_extra_newlines(from.end, formatted_code.to_string());
for newline_sequence in iter.skip(newlines_to_skip) {
write!(
sequence_string,
"{}",
format_newline_sequnce(newline_sequence, threshold)
)?;
}
let mut src_rope = Rope::from_str(formatted_code);
src_rope.insert(from.end + offset, &sequence_string);
formatted_code.clear();
formatted_code.push_str(&src_rope.to_string());
Ok(sequence_string.len())
}
fn get_newline_sequences_between_spans(
from: &ByteSpan,
to: &ByteSpan,
newline_map: &NewlineMap,
) -> Vec<NewlineSequence> {
let mut newline_sequences: Vec<NewlineSequence> = Vec::new();
if from < to {
for (_, newline_sequence) in newline_map.range((Included(from), Excluded(to))) {
newline_sequences.push(newline_sequence.clone());
}
}
newline_sequences
}
#[cfg(test)]
mod tests {
use super::newline_map_from_src;
#[test]
fn test_newline_map() {
let raw_src = r#"script;
fn main() {
let number: u64 = 10;
let number2: u64 = 20;
let number3: u64 = 30;
}"#;
let newline_map = newline_map_from_src(raw_src.trim_start()).unwrap();
let newline_sequence_lengths = Vec::from_iter(
newline_map
.iter()
.map(|map_item| map_item.1.sequence_length),
);
let correct_newline_sequence_lengths = vec![2, 2, 3];
assert_eq!(newline_sequence_lengths, correct_newline_sequence_lengths);
}
#[test]
fn test_newline_map_with_whitespaces() {
let raw_src = r#"script;
fuel_coin.mint {
gas: default_gas
}
(11);"#;
let newline_map = newline_map_from_src(raw_src.trim_start()).unwrap();
let newline_sequence_lengths = Vec::from_iter(
newline_map
.iter()
.map(|map_item| map_item.1.sequence_length),
);
let correct_newline_sequence_lengths = vec![1];
assert_eq!(newline_sequence_lengths, correct_newline_sequence_lengths);
}
}