use std::{
borrow::Cow,
collections::{HashMap, HashSet},
mem,
ops::Deref,
};
use topiary_tree_sitter_facade::Node;
use crate::{
Atom, Capitalisation, FormatterError, FormatterResult, ScopeCondition, ScopeInformation,
tree_sitter::NodeExt,
};
struct NodesWithLinebreaks {
before: HashSet<usize>,
after: HashSet<usize>,
}
#[derive(Debug)]
pub struct AtomCollection {
atoms: Vec<Atom>,
prepend: HashMap<usize, Vec<Atom>>,
append: HashMap<usize, Vec<Atom>>,
specified_leaf_nodes: HashSet<usize>,
parent_leaf_nodes: HashMap<usize, usize>,
multi_line_nodes: HashSet<usize>,
blank_lines_before: HashSet<usize>,
line_break_before: HashSet<usize>,
line_break_after: HashSet<usize>,
counter: usize,
}
impl AtomCollection {
#[cfg(test)]
pub fn new(atoms: Vec<Atom>) -> Self {
Self {
atoms,
prepend: HashMap::new(),
append: HashMap::new(),
specified_leaf_nodes: HashSet::new(),
parent_leaf_nodes: HashMap::new(),
multi_line_nodes: HashSet::new(),
blank_lines_before: HashSet::new(),
line_break_before: HashSet::new(),
line_break_after: HashSet::new(),
counter: 0,
}
}
pub fn collect_leaves(
root: &Node,
source: &[u8],
specified_leaf_nodes: HashSet<usize>,
) -> FormatterResult<Self> {
let dfs_nodes = dfs_flatten(root);
let multi_line_nodes = detect_multi_line_nodes(&dfs_nodes);
let blank_line_nodes = detect_line_breaks(&dfs_nodes, 2);
let line_break_nodes = detect_line_breaks(&dfs_nodes, 1);
let mut atoms = Self {
atoms: Vec::new(),
prepend: HashMap::new(),
append: HashMap::new(),
specified_leaf_nodes,
parent_leaf_nodes: HashMap::new(),
multi_line_nodes,
blank_lines_before: blank_line_nodes.before,
line_break_before: line_break_nodes.before,
line_break_after: line_break_nodes.after,
counter: 0,
};
atoms.collect_leaves_inner(root, source, &Vec::new(), 0)?;
Ok(atoms)
}
fn wrap(&mut self, atom: Atom, predicates: &QueryPredicates) -> Atom {
if let Some(scope_id) = &predicates.single_line_scope_only {
let id = self.next_id();
Atom::ScopedConditional {
id,
scope_id: scope_id.to_string(),
condition: ScopeCondition::SingleLineOnly,
atom: Box::new(atom),
}
} else if let Some(scope_id) = &predicates.multi_line_scope_only {
let id = self.next_id();
Atom::ScopedConditional {
id,
scope_id: scope_id.to_string(),
condition: ScopeCondition::MultiLineOnly,
atom: Box::new(atom),
}
} else {
atom
}
}
pub fn resolve_capture(
&mut self,
name: &str,
node: &Node,
predicates: &QueryPredicates,
) -> FormatterResult<()> {
log::debug!("Resolving {name}");
let requires_delimiter = || {
predicates.delimiter.as_deref().ok_or_else(|| {
FormatterError::Query(format!("@{name} requires a #delimiter! predicate"), None)
})
};
let requires_scope_id = || {
predicates.scope_id.as_deref().ok_or_else(|| {
FormatterError::Query(format!("@{name} requires a #scope_id! predicate"), None)
})
};
let scope_information_prepend = || -> FormatterResult<ScopeInformation> {
Ok(ScopeInformation {
line_number: node.start_position().row(),
scope_id: requires_scope_id()?.to_owned(),
})
};
let scope_information_append = || -> FormatterResult<ScopeInformation> {
Ok(ScopeInformation {
line_number: node.end_position().row(),
scope_id: requires_scope_id()?.to_owned(),
})
};
let mut is_multi_line = false;
if let Some(parent) = node.parent() {
let parent_id = parent.id();
if self.multi_line_nodes.contains(&parent_id) {
is_multi_line = true;
}
}
if is_multi_line && predicates.single_line_only {
log::debug!("Skipping because context is multi-line and #single_line_only! is set");
return Ok(());
}
if !is_multi_line && predicates.multi_line_only {
log::debug!("Skipping because context is single-line and #multi_line_only! is set");
return Ok(());
}
if let Some(parent_id) = self.parent_leaf_nodes.get(&node.id())
&& *parent_id != node.id()
{
log::debug!(
"Skipping because the match occurred below a leaf node: {}",
node.display_one_based()
);
return Ok(());
}
match name {
"allow_blank_line_before" => {
if self.blank_lines_before.contains(&node.id()) {
self.prepend(Atom::Blankline, node, predicates);
}
}
"append_delimiter" => self.append(
Atom::Literal(requires_delimiter()?.to_string()),
node,
predicates,
),
"append_empty_softline" => {
self.append(Atom::Softline { spaced: false }, node, predicates);
}
"append_hardline" => self.append(Atom::Hardline, node, predicates),
"append_indent_start" => self.append(Atom::IndentStart, node, predicates),
"append_indent_end" => self.append(Atom::IndentEnd, node, predicates),
"append_input_softline" => {
let space = if self.line_break_after.contains(&node.id()) {
Atom::Hardline
} else {
Atom::Space
};
self.append(space, node, predicates);
}
"append_space" => self.append(Atom::Space, node, predicates),
"append_antispace" => self.append(Atom::Antispace, node, predicates),
"append_spaced_softline" => {
self.append(Atom::Softline { spaced: true }, node, predicates);
}
"prepend_delimiter" => self.prepend(
Atom::Literal(requires_delimiter()?.to_string()),
node,
predicates,
),
"prepend_empty_softline" => {
self.prepend(Atom::Softline { spaced: false }, node, predicates);
}
"prepend_hardline" => self.prepend(Atom::Hardline, node, predicates),
"prepend_indent_start" => self.prepend(Atom::IndentStart, node, predicates),
"prepend_indent_end" => self.prepend(Atom::IndentEnd, node, predicates),
"prepend_input_softline" => {
let space = if self.line_break_before.contains(&node.id()) {
Atom::Hardline
} else {
Atom::Space
};
self.prepend(space, node, predicates);
}
"prepend_space" => self.prepend(Atom::Space, node, predicates),
"prepend_antispace" => self.prepend(Atom::Antispace, node, predicates),
"prepend_spaced_softline" => {
self.prepend(Atom::Softline { spaced: true }, node, predicates);
}
"leaf" => {
self.prepend(Atom::CaseBegin(Capitalisation::Pass), node, predicates);
self.append(Atom::CaseEnd, node, predicates);
}
"delete" => {
self.prepend(Atom::DeleteBegin, node, predicates);
self.append(Atom::DeleteEnd, node, predicates);
}
"upper_case" => {
self.prepend(Atom::CaseBegin(Capitalisation::UpperCase), node, predicates);
self.append(Atom::CaseEnd, node, predicates);
}
"lower_case" => {
self.prepend(Atom::CaseBegin(Capitalisation::LowerCase), node, predicates);
self.append(Atom::CaseEnd, node, predicates);
}
"prepend_begin_scope" => {
self.prepend(
Atom::ScopeBegin(scope_information_prepend()?),
node,
predicates,
);
}
"append_begin_scope" => {
self.append(
Atom::ScopeBegin(scope_information_append()?),
node,
predicates,
);
}
"prepend_end_scope" => {
self.prepend(
Atom::ScopeEnd(scope_information_prepend()?),
node,
predicates,
);
}
"append_end_scope" => {
self.append(
Atom::ScopeEnd(scope_information_append()?),
node,
predicates,
);
}
"prepend_begin_measuring_scope" => {
self.prepend(
Atom::MeasuringScopeBegin(scope_information_prepend()?),
node,
predicates,
);
}
"append_begin_measuring_scope" => {
self.append(
Atom::MeasuringScopeBegin(scope_information_append()?),
node,
predicates,
);
}
"prepend_end_measuring_scope" => {
self.prepend(
Atom::MeasuringScopeEnd(scope_information_prepend()?),
node,
predicates,
);
}
"append_end_measuring_scope" => {
self.append(
Atom::MeasuringScopeEnd(scope_information_append()?),
node,
predicates,
);
}
"append_empty_scoped_softline" => {
let id = self.next_id();
self.append(
Atom::ScopedSoftline {
id,
scope_id: requires_scope_id()?.to_string(),
spaced: false,
},
node,
predicates,
);
}
"append_spaced_scoped_softline" => {
let id = self.next_id();
self.append(
Atom::ScopedSoftline {
id,
scope_id: requires_scope_id()?.to_string(),
spaced: true,
},
node,
predicates,
);
}
"prepend_empty_scoped_softline" => {
let id = self.next_id();
self.prepend(
Atom::ScopedSoftline {
id,
scope_id: requires_scope_id()?.to_string(),
spaced: false,
},
node,
predicates,
);
}
"prepend_spaced_scoped_softline" => {
let id = self.next_id();
self.prepend(
Atom::ScopedSoftline {
id,
scope_id: requires_scope_id()?.to_string(),
spaced: true,
},
node,
predicates,
);
}
"single_line_no_indent" => {
for a in &mut self.atoms {
if let Atom::Leaf {
id,
single_line_no_indent,
..
} = a
&& *id == node.id()
{
*single_line_no_indent = true;
}
}
self.append(Atom::Hardline, node, predicates);
}
"multi_line_indent_all" => {
for a in &mut self.atoms {
if let Atom::Leaf {
id,
multi_line_indent_all,
..
} = a
&& *id == node.id()
{
*multi_line_indent_all = true;
}
}
}
"keep_whitespace" => {
for a in &mut self.atoms {
if let Atom::Leaf {
id,
keep_whitespace,
..
} = a
&& *id == node.id()
{
*keep_whitespace = true;
}
}
}
unknown => {
return Err(FormatterError::Query(
format!("@{unknown} is not a valid capture name"),
None,
));
}
}
Ok(())
}
pub fn apply_prepends_and_appends(&mut self) {
let mut expanded: Vec<Atom> = Vec::new();
fn atom_key(atom: &Atom) -> i8 {
match atom {
Atom::ScopeBegin(_) => -2,
Atom::MeasuringScopeBegin(_) => -1,
Atom::MeasuringScopeEnd(_) => 1,
Atom::ScopeEnd(_) => 2,
_ => 0,
}
}
for atom in &mut self.atoms {
if let Atom::Leaf { id, .. } = atom {
let prepends = self.prepend.entry(*id).or_default();
prepends.sort_by_key(atom_key);
let appends = self.append.entry(*id).or_default();
appends.sort_by_key(atom_key);
let swapped_atom = mem::take(atom);
if !prepends.is_empty() {
log::debug!("Applying prepend of {prepends:?} to {:?}.", &swapped_atom);
}
if !appends.is_empty() {
log::debug!("Applying append of {appends:?} to {:?}.", &swapped_atom);
}
expanded.append(prepends);
expanded.push(swapped_atom);
expanded.append(appends);
} else {
log::debug!("Not a leaf: {atom:?}");
expanded.push(mem::take(atom));
}
}
self.atoms = expanded;
}
fn mark_leaf_parent(&mut self, node: &Node, parent_id: usize) {
self.parent_leaf_nodes.insert(node.id(), parent_id);
for child in node.children(&mut node.walk()) {
self.mark_leaf_parent(&child, parent_id);
}
}
fn collect_leaves_inner(
&mut self,
node: &Node,
source: &[u8],
parent_ids: &[usize],
level: usize,
) -> FormatterResult<()> {
let id = node.id();
let parent_ids = [parent_ids, &[id]].concat();
log::debug!(
"CST node: {}{} - Named: {}",
" ".repeat(level),
node.display_one_based(),
node.is_named()
);
if node.end_byte() == node.start_byte() {
log::debug!("Skipping zero-byte node: {}", node.display_one_based());
} else if node.child_count() == 0
|| self.specified_leaf_nodes.contains(&node.id())
|| node.kind() == "ERROR"
{
self.atoms.push(Atom::Leaf {
content: String::from(node.utf8_text(source)?),
id,
original_position: node.start_position().into(),
single_line_no_indent: false,
multi_line_indent_all: false,
keep_whitespace: false,
capitalisation: Capitalisation::Pass,
});
self.mark_leaf_parent(node, node.id());
} else {
for child in node.children(&mut node.walk()) {
self.collect_leaves_inner(&child, source, &parent_ids, level + 1)?;
}
}
Ok(())
}
fn prepend(&mut self, atom: Atom, node: &Node, predicates: &QueryPredicates) {
let atom = self.expand_multiline(atom, node);
let atom = self.wrap(atom, predicates);
let target_node = self.first_leaf(node);
log::debug!(
"Prepending {atom:?} to node {}",
target_node.display_one_based()
);
self.prepend.entry(target_node.id()).or_default().push(atom);
}
fn append(&mut self, atom: Atom, node: &Node, predicates: &QueryPredicates) {
let atom = self.expand_multiline(atom, node);
let atom = self.wrap(atom, predicates);
let target_node = self.last_leaf(node);
log::debug!(
"Appending {atom:?} to node {}",
target_node.display_one_based()
);
self.append.entry(target_node.id()).or_default().push(atom);
}
fn expand_multiline(&self, atom: Atom, node: &Node) -> Atom {
if let Atom::Softline { spaced } = atom {
if let Some(parent) = node.parent() {
let parent_id = parent.id();
if self.multi_line_nodes.contains(&parent_id) {
log::debug!(
"Expanding softline to hardline in node {} with parent {}: {}",
node.display_one_based(),
parent_id,
parent.display_one_based()
);
Atom::Hardline
} else if spaced {
log::debug!(
"Expanding softline to space in node {} with parent {}: {}",
node.display_one_based(),
parent_id,
parent.display_one_based()
);
Atom::Space
} else {
Atom::Empty
}
} else {
Atom::Empty
}
} else {
atom
}
}
fn post_process_scopes(&mut self) {
type ScopeId = String;
type LineIndex = u32;
type ScopedNodeId = usize;
type OpenedScopeInfo<'a> = (LineIndex, Vec<&'a Atom>, Option<bool>);
let mut opened_scopes: HashMap<&ScopeId, Vec<OpenedScopeInfo>> = HashMap::new();
let mut opened_measuring_scopes: HashMap<&ScopeId, Vec<LineIndex>> = HashMap::new();
let mut modifications: HashMap<ScopedNodeId, Atom> = HashMap::new();
let mut force_apply_modifications = false;
for atom in &self.atoms {
if let Atom::ScopeBegin(ScopeInformation {
line_number: line_start,
scope_id,
}) = atom
{
opened_scopes
.entry(scope_id)
.or_default()
.push((*line_start, Vec::new(), None));
} else if let Atom::ScopeEnd(ScopeInformation {
line_number: line_end,
scope_id,
}) = atom
{
if let Some((line_start, atoms, measuring_scope)) =
opened_scopes.get_mut(scope_id).and_then(Vec::pop)
{
let multiline = if let Some(mult) = measuring_scope {
mult
} else {
line_start != *line_end
};
for atom in atoms {
if let Atom::ScopedSoftline { id, spaced, .. } = atom {
let new_atom = if multiline {
Atom::Hardline
} else if *spaced {
Atom::Space
} else {
Atom::Empty
};
modifications.insert(*id, new_atom);
} else if let Atom::ScopedConditional {
id,
atom,
condition,
..
} = atom
{
let multiline_only = *condition == ScopeCondition::MultiLineOnly;
let new_atom = if multiline == multiline_only {
atom.deref().clone()
} else {
Atom::Empty
};
modifications.insert(*id, new_atom);
}
}
} else {
log::warn!("Closing unopened scope {scope_id:?}");
force_apply_modifications = true;
}
} else if let Atom::MeasuringScopeBegin(ScopeInformation {
line_number: line_start,
scope_id,
}) = atom
{
if opened_scopes.entry(scope_id).or_default().is_empty() {
log::warn!(
"Opening measuring scope with no associated regular scope {scope_id:?}"
);
force_apply_modifications = true;
} else {
opened_measuring_scopes
.entry(scope_id)
.or_default()
.push(*line_start)
}
} else if let Atom::MeasuringScopeEnd(ScopeInformation {
line_number: line_end,
scope_id,
}) = atom
{
if let Some(line_start) =
opened_measuring_scopes.get_mut(scope_id).and_then(Vec::pop)
{
let multi_line = line_start != *line_end;
if let Some((regular_line_start, vec, measuring_scope)) =
opened_scopes.get_mut(scope_id).and_then(Vec::pop)
{
if measuring_scope.is_none() {
opened_scopes.entry(scope_id).or_default().push((
regular_line_start,
vec,
Some(multi_line),
));
} else {
log::warn!(
"Found several measuring scopes in a single regular scope {scope_id:?}"
);
force_apply_modifications = true;
}
} else {
log::warn!("Found measuring scope outside of regular scope {scope_id:?}");
force_apply_modifications = true;
}
} else {
log::warn!("Closing unopened measuring scope {scope_id:?}");
force_apply_modifications = true;
}
} else if let Atom::ScopedSoftline { scope_id, .. } = atom {
if let Some((_, vec, _)) =
opened_scopes.get_mut(&scope_id).and_then(|v| v.last_mut())
{
vec.push(atom);
} else {
log::warn!("Found scoped softline {atom:?} outside of its scope");
force_apply_modifications = true;
}
} else if let Atom::ScopedConditional { scope_id, .. } = atom {
if let Some((_, vec, _)) =
opened_scopes.get_mut(&scope_id).and_then(|v| v.last_mut())
{
vec.push(atom);
} else {
log::warn!("Found scoped conditional {atom:?} outside of its scope");
force_apply_modifications = true;
}
}
}
let mut still_opened: Vec<&String> = opened_scopes
.into_iter()
.filter_map(|(scope_id, vec)| if vec.is_empty() { None } else { Some(scope_id) })
.collect();
if !still_opened.is_empty() {
log::warn!("Some scopes have been left opened: {still_opened:?}");
force_apply_modifications = true;
}
still_opened = opened_measuring_scopes
.into_iter()
.filter_map(|(scope_id, vec)| if vec.is_empty() { None } else { Some(scope_id) })
.collect();
if !still_opened.is_empty() {
log::warn!("Some measuring scopes have been left opened: {still_opened:?}");
force_apply_modifications = true;
}
for atom in &mut self.atoms {
match atom {
Atom::ScopeBegin(_)
| Atom::ScopeEnd(_)
| Atom::MeasuringScopeBegin(_)
| Atom::MeasuringScopeEnd(_) => *atom = Atom::Empty,
_ => {}
}
}
if !modifications.is_empty() || force_apply_modifications {
for atom in &mut self.atoms {
if let Atom::ScopedSoftline { id, .. } = atom {
if let Some(replacement) = modifications.remove(id) {
*atom = replacement;
} else {
log::warn!("Found scoped softline {atom:?}, but was unable to replace it.");
*atom = Atom::Empty;
}
} else if let Atom::ScopedConditional { id, .. } = atom {
if let Some(replacement) = modifications.remove(id) {
*atom = replacement;
} else {
log::warn!(
"Found scoped conditional {atom:?}, but was unable to replace it."
);
*atom = Atom::Empty;
}
}
}
}
}
fn post_process_deletes(&mut self) {
let mut delete_level = 0;
for atom in &mut self.atoms {
match atom {
Atom::DeleteBegin => {
delete_level += 1;
*atom = Atom::Empty;
}
Atom::DeleteEnd => {
delete_level -= 1;
*atom = Atom::Empty;
}
_ => {
if delete_level > 0 {
*atom = Atom::Empty;
}
}
}
}
if delete_level != 0 {
log::warn!("The number of DeleteBegin is different from the number of DeleteEnd.");
}
}
fn post_process_capitalization(&mut self) {
let mut case_context: Vec<Capitalisation> = Vec::new();
for atom in &mut self.atoms {
match atom {
Atom::CaseBegin(case) => {
case_context.push(case.clone());
*atom = Atom::Empty;
}
Atom::CaseEnd => {
case_context.pop();
*atom = Atom::Empty;
}
Atom::Leaf { capitalisation, .. } => {
*capitalisation = case_context.last().unwrap_or(&Capitalisation::Pass).clone()
}
_ => {}
}
}
}
pub fn post_process(&mut self) {
self.post_process_scopes();
self.post_process_deletes();
self.post_process_capitalization();
self.post_process_inner();
collapse_spaces_before_antispace(&mut self.atoms);
self.post_process_inner();
log::debug!("List of atoms after post-processing: {:?}", self.atoms);
}
fn post_process_inner(&mut self) {
let mut remaining = &mut self.atoms[..];
let mut prev: &mut Atom = if let [head, tail @ ..] = remaining {
remaining = tail;
head
} else {
return;
};
while let Atom::Space | Atom::Antispace | Atom::Hardline | Atom::Blankline = *prev {
*prev = Atom::Empty;
if let [head, tail @ ..] = remaining {
prev = head;
remaining = tail;
} else {
return;
}
}
while !remaining.is_empty() {
match (prev, remaining) {
(
moved_prev @ Atom::Antispace,
[head @ (Atom::Space | Atom::Antispace), tail @ ..],
) => {
*head = Atom::Empty;
prev = moved_prev;
remaining = tail;
}
(
moved_prev @ (Atom::Space | Atom::Hardline | Atom::Blankline),
[
head @ (Atom::Space | Atom::Hardline | Atom::Blankline),
tail @ ..,
],
) => {
if head.dominates(moved_prev) {
*moved_prev = Atom::Empty;
} else {
*head = Atom::Empty;
}
prev = moved_prev;
remaining = tail;
}
(
moved_prev @ (Atom::Antispace | Atom::Space | Atom::Hardline | Atom::Blankline),
moved_remaining @ [Atom::IndentStart | Atom::IndentEnd, ..],
) => {
let old_prev = moved_prev.clone();
let indent = moved_remaining.first_mut().unwrap();
*moved_prev = indent.clone();
*indent = old_prev;
prev = moved_prev;
remaining = moved_remaining;
}
(moved_prev, [head, tail @ ..]) => {
prev = if matches!(head, Atom::Empty) {
moved_prev
} else {
head
};
remaining = tail;
}
(_, []) => unreachable!("remaining cannot be empty"),
}
}
}
fn next_id(&mut self) -> usize {
self.counter += 1;
self.counter
}
fn first_leaf<'tree, 'node: 'tree>(&self, node: &'node Node<'tree>) -> Cow<'node, Node<'tree>> {
let mut node = Cow::Borrowed(node);
while node.child_count() != 0 && !self.specified_leaf_nodes.contains(&node.id()) {
node = Cow::Owned(node.child(0).unwrap());
}
node
}
fn last_leaf<'tree, 'node: 'tree>(&self, node: &'node Node<'tree>) -> Cow<'node, Node<'tree>> {
let mut node = Cow::Borrowed(node);
while node.child_count() != 0 && !self.specified_leaf_nodes.contains(&node.id()) {
node = Cow::Owned(node.child(node.child_count() - 1).unwrap());
}
node
}
}
#[derive(Clone, Debug, Default)]
pub struct QueryPredicates {
pub delimiter: Option<String>,
pub scope_id: Option<String>,
pub single_line_only: bool,
pub multi_line_only: bool,
pub single_line_scope_only: Option<String>,
pub multi_line_scope_only: Option<String>,
pub query_name: Option<String>,
}
fn collapse_spaces_before_antispace(v: &mut [Atom]) {
let mut antispace_mode = false;
for a in v.iter_mut().rev() {
if *a == Atom::Antispace {
*a = Atom::Empty;
antispace_mode = true;
} else if *a == Atom::Space && antispace_mode {
*a = Atom::Empty;
} else if *a != Atom::Empty && *a != Atom::IndentStart && *a != Atom::IndentEnd {
antispace_mode = false;
}
}
}
fn dfs_flatten<'tree>(node: &Node<'tree>) -> Vec<Node<'tree>> {
let mut walker = node.walk();
let mut dfs_nodes = Vec::new();
'walk: loop {
dfs_nodes.push(walker.node());
if !walker.goto_first_child() {
while !walker.goto_next_sibling() {
if !walker.goto_parent() {
break 'walk;
}
}
}
}
dfs_nodes
}
fn detect_multi_line_nodes(dfs_nodes: &[Node]) -> HashSet<usize> {
dfs_nodes
.iter()
.filter_map(|node| {
let start_line = node.start_position().row();
let end_line = node.end_position().row();
if end_line > start_line {
log::debug!(
"Multi-line node {}: {}",
node.id(),
node.display_one_based()
);
return Some(node.id());
}
None
})
.collect()
}
fn detect_line_breaks(dfs_nodes: &[Node], minimum_line_breaks: u32) -> NodesWithLinebreaks {
let (before, after) = dfs_nodes
.iter()
.zip(dfs_nodes[1..].iter())
.filter_map(|(left, right)| {
let last = left.end_position().row();
let next = right.start_position().row();
if next >= last + minimum_line_breaks {
log::debug!(
"There are at least {} line breaks between {:?} and {:?}",
minimum_line_breaks,
left.id(),
right.id()
);
return Some((right.id(), left.id()));
}
None
})
.unzip();
NodesWithLinebreaks { before, after }
}
impl<Idx> std::ops::Index<Idx> for AtomCollection
where
Idx: std::slice::SliceIndex<[Atom]>,
{
type Output = Idx::Output;
fn index(&self, index: Idx) -> &Self::Output {
&self.atoms[index]
}
}
#[cfg(test)]
mod test {
use crate::{Atom, atom_collection::AtomCollection};
use test_log::test;
#[test]
fn post_process_indent_before_hardline() {
let mut atom_collection = AtomCollection::new(vec![
Atom::Literal("foo".into()),
Atom::Hardline,
Atom::IndentEnd,
Atom::Literal("foo".into()),
]);
atom_collection.post_process();
assert_eq!(
atom_collection.atoms,
vec![
Atom::Literal("foo".into()),
Atom::IndentEnd,
Atom::Hardline,
Atom::Literal("foo".into()),
]
);
}
#[test]
fn post_process_hardline_before_hardline() {
let mut atom_collection = AtomCollection::new(vec![
Atom::Literal("foo".into()),
Atom::Hardline,
Atom::Hardline,
Atom::Literal("foo".into()),
]);
atom_collection.post_process();
assert_eq!(
atom_collection.atoms,
vec![
Atom::Literal("foo".into()),
Atom::Hardline,
Atom::Empty,
Atom::Literal("foo".into()),
]
);
}
#[test]
fn post_process_empty_blank_hard() {
let mut atom_collection = AtomCollection::new(vec![
Atom::Empty,
Atom::Blankline,
Atom::Hardline,
Atom::Literal("foo".into()),
]);
atom_collection.post_process();
assert_eq!(
atom_collection.atoms,
vec![
Atom::Empty,
Atom::Blankline,
Atom::Empty,
Atom::Literal("foo".into()),
]
);
}
#[test]
fn issue_549_post_process_indent_before_hardline_with_antispace_in_between() {
let mut atom_collection = AtomCollection::new(vec![
Atom::Literal("foo".into()),
Atom::Hardline,
Atom::Antispace,
Atom::IndentEnd,
Atom::Literal("foo".into()),
]);
atom_collection.post_process();
assert_eq!(
atom_collection.atoms,
vec![
Atom::Literal("foo".into()),
Atom::IndentEnd,
Atom::Hardline,
Atom::Empty,
Atom::Literal("foo".into()),
]
);
}
}