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},
parse::parse_file,
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 = parse_file(formatted_input, path)?.value;
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))
{
if let Some(newline_sequence) = get_newline_sequence_between_spans(
previous_unformatted_newline_span,
unformatted_newline_span,
&newline_map,
) {
offset += insert_after_span(
previous_formatted_newline_span,
newline_sequence,
offset,
formatted_code,
newline_threshold,
)?;
}
previous_unformatted_newline_span = unformatted_newline_span;
previous_formatted_newline_span = formatted_newline_span;
}
Ok(())
}
fn format_newline_sequence(newline_sequence: &NewlineSequence, threshold: usize) -> String {
if newline_sequence.sequence_length > threshold {
(0..threshold).map(|_| "\n").collect::<String>()
} else {
newline_sequence.to_string()
}
}
fn insert_after_span(
from: &ByteSpan,
newline_sequence: NewlineSequence,
offset: usize,
formatted_code: &mut FormattedCode,
threshold: usize,
) -> Result<usize, FormatterError> {
let mut sequence_string = String::new();
write!(
sequence_string,
"{}",
format_newline_sequence(&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_sequence_between_spans(
from: &ByteSpan,
to: &ByteSpan,
newline_map: &NewlineMap,
) -> Option<NewlineSequence> {
if from < to {
if let Some((_, newline_sequence)) =
newline_map.range((Included(from), Excluded(to))).next()
{
return Some(newline_sequence.clone());
}
}
None
}
#[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);
}
}