use super::{append_children, fresh_token, insert_children, SyntaxNode, SyntaxToken};
use crate::lex::SyntaxKind;
fn is_block_collection(node: &SyntaxNode) -> bool {
matches!(node.kind(), SyntaxKind::MAPPING | SyntaxKind::SEQUENCE)
&& !super::starts_a_flow_collection(node)
}
pub(crate) fn remove_comment_after_item(item: &SyntaxNode) {
let after_dash_line = item
.siblings_with_tokens(rowan::Direction::Prev)
.any(|c| c.kind() == SyntaxKind::NEWLINE);
if !after_dash_line
&& !item
.descendants_with_tokens()
.any(|c| c.kind() == SyntaxKind::NEWLINE)
{
return;
}
let after: Vec<_> = item
.siblings_with_tokens(rowan::Direction::Next)
.skip(1)
.take_while(|c| matches!(c.kind(), SyntaxKind::WHITESPACE | SyntaxKind::COMMENT))
.collect();
if after.iter().any(|c| c.kind() == SyntaxKind::COMMENT) {
for c in after {
c.detach();
}
}
}
pub(crate) fn settle_lines_after_block_scalar(entry: &SyntaxNode) {
let Some(scalar) = super::ending_block_scalar(entry) else {
return;
};
absorb_blank_lines_after(&scalar);
dedent_comment_line_after(entry, &scalar);
}
fn block_scalar_body_column(scalar: &SyntaxNode) -> Option<usize> {
if let Some(indent) = crate::nodes::scalar_node::block_scalar_indicators(scalar).1 {
return Some(crate::nodes::scalar_node::block_parent_column(scalar) + indent);
}
let text = scalar.text().to_string();
text.split(['\n', '\r'])
.skip(1)
.find(|line| !line.trim().is_empty())
.map(crate::column::space_indent)
}
fn block_scalar_line_end(scalar: &SyntaxNode) -> Option<SyntaxToken> {
scalar
.last_token()
.filter(|t| t.kind() == SyntaxKind::NEWLINE)
}
fn absorb_blank_lines_after(scalar: &SyntaxNode) {
let Some(end) = block_scalar_line_end(scalar) else {
return;
};
let mut lines = Vec::new();
let mut next = end.next_token();
loop {
let space = next
.clone()
.filter(|t| matches!(t.kind(), SyntaxKind::INDENT | SyntaxKind::WHITESPACE));
let line_break = match space.as_ref().map_or(next.clone(), |t| t.next_token()) {
Some(t) if t.kind() == SyntaxKind::NEWLINE => Some(t),
None if space.is_some() => None,
_ => break,
};
next = line_break.as_ref().and_then(|t| t.next_token());
let last = line_break.is_none();
lines.push((space, line_break));
if last {
break;
}
}
let keep = crate::nodes::scalar_node::block_scalar_indicators(scalar).0
== crate::nodes::scalar_node::Chomping::Keep;
let body = block_scalar_body_column(scalar);
let fits =
|text: &str| text.bytes().all(|b| b == b' ') && body.map_or(true, |b| text.len() <= b);
let mut elements = Vec::new();
for (space, line_break) in lines {
space.iter().for_each(SyntaxToken::detach);
line_break.iter().for_each(SyntaxToken::detach);
if keep {
continue;
}
let fitting_space = space.filter(|t| fits(t.text()));
elements.extend(fitting_space.map(|t| fresh_token(SyntaxKind::INDENT, t.text()).into()));
elements.extend(line_break.map(|t| fresh_token(SyntaxKind::NEWLINE, t.text()).into()));
}
append_children(scalar, elements);
}
fn dedent_comment_line_after(entry: &SyntaxNode, scalar: &SyntaxNode) {
let column = crate::column::node_column(entry);
let body = block_scalar_body_column(scalar).unwrap_or(column + 1);
let is_space =
|t: &SyntaxToken| matches!(t.kind(), SyntaxKind::INDENT | SyntaxKind::WHITESPACE);
let next = block_scalar_line_end(scalar).and_then(|t| t.next_token());
let Some(indent) = next.filter(|t| {
is_space(t)
&& t.text().len() >= body
&& t.next_token()
.is_some_and(|c| c.kind() == SyntaxKind::COMMENT)
}) else {
return;
};
let parent = indent.parent().expect("a token has a parent");
let at = indent.index();
indent.detach();
if column > 0 {
insert_children(
&parent,
at,
vec![fresh_token(SyntaxKind::INDENT, &" ".repeat(column)).into()],
);
}
}
type LineComment = (String, String);
pub(crate) fn take_dash_line_comment(entry: &SyntaxNode) -> Option<LineComment> {
let dash = entry
.children_with_tokens()
.filter_map(|c| c.into_token())
.find(|t| t.kind() == SyntaxKind::DASH)?;
let mut spaces: Vec<SyntaxToken> = Vec::new();
let mut next = dash.next_token();
let comment = loop {
let t = next?;
match t.kind() {
SyntaxKind::COMMENT => break t,
SyntaxKind::NEWLINE => return None,
SyntaxKind::WHITESPACE => spaces.push(t.clone()),
_ if t.text().is_empty() => {}
_ => spaces.clear(),
}
next = t.next_token();
};
let ws = spaces
.last()
.map_or_else(|| " ".to_string(), |t| t.text().to_string());
let line_break = comment.next_token().filter(|t| {
t.kind() == SyntaxKind::NEWLINE
&& t.parent().as_ref() == Some(entry)
&& t.next_sibling_or_token().is_some()
});
comment.detach();
if let Some(last) = spaces.last() {
last.detach();
}
if let Some(line_break) = line_break {
let indent = line_break
.next_sibling_or_token()
.and_then(|c| c.into_token())
.filter(|t| t.kind() == SyntaxKind::INDENT);
line_break.detach();
if let Some(indent) = indent {
indent.detach();
}
}
Some((ws, comment.text().to_string()))
}
pub(crate) fn add_dash_line_comment(entry: &SyntaxNode, comment: LineComment) {
let (ws, text) = comment;
let first_line: Vec<SyntaxToken> = entry
.descendants_with_tokens()
.filter_map(|c| c.into_token())
.take_while(|t| t.kind() != SyntaxKind::NEWLINE)
.collect();
if first_line.iter().any(|t| t.kind() == SyntaxKind::COMMENT) {
insert_comment_line_above(entry, &text);
return;
}
let item_on_dash_line = entry.children().find(is_block_collection).filter(|item| {
first_line
.iter()
.any(|t| item.text_range().contains_range(t.text_range()))
});
if let Some(item) = item_on_dash_line {
let column = crate::column::node_column(&item);
if let Some(space) = item
.prev_sibling_or_token()
.and_then(|c| c.into_token())
.filter(|t| t.kind() == SyntaxKind::WHITESPACE)
{
space.detach();
}
let at = item.index();
insert_children(
entry,
at,
vec![
fresh_token(SyntaxKind::WHITESPACE, &ws).into(),
fresh_token(SyntaxKind::COMMENT, &text).into(),
fresh_token(SyntaxKind::NEWLINE, "\n").into(),
fresh_token(SyntaxKind::INDENT, &" ".repeat(column)).into(),
],
);
return;
}
let last = first_line.last().expect("an entry starts with its dash");
let line_break = match last.next_token() {
Some(nl) if nl.parent().as_ref() == Some(entry) && nl.next_sibling_or_token().is_none() => {
Some(nl)
}
Some(t) if entry.text_range().contains_range(t.text_range()) => {
let parent = last.parent().expect("a token has a parent");
let at = last.index() + 1;
insert_children(
&parent,
at,
vec![
fresh_token(SyntaxKind::WHITESPACE, &ws).into(),
fresh_token(SyntaxKind::COMMENT, &text).into(),
],
);
return;
}
_ => None,
};
let at = line_break.map_or_else(|| entry.children_with_tokens().count(), |nl| nl.index());
insert_children(
entry,
at,
vec![
fresh_token(SyntaxKind::WHITESPACE, &ws).into(),
fresh_token(SyntaxKind::COMMENT, &text).into(),
],
);
}
pub(crate) fn insert_comment_line_above(entry: &SyntaxNode, comment: &str) {
let column = crate::column::node_column(entry);
let mut node = entry.clone();
while node.prev_sibling_or_token().is_none() {
match node.parent().filter(|p| p.kind() != SyntaxKind::DOCUMENT) {
Some(parent) => node = parent,
None => break,
}
}
let mut elements = vec![
fresh_token(SyntaxKind::COMMENT, comment).into(),
fresh_token(SyntaxKind::NEWLINE, "\n").into(),
];
if column > 0 {
elements.push(fresh_token(SyntaxKind::INDENT, &" ".repeat(column)).into());
}
match node.parent() {
Some(parent) => {
let at = node.index();
insert_children(&parent, at, elements);
}
None => insert_children(&node, 0, elements),
}
}
pub(crate) fn add_to_first_line(value: &SyntaxNode, comment: LineComment) -> bool {
let first_line: Vec<_> = value
.descendants_with_tokens()
.filter_map(|c| c.into_token())
.take_while(|t| t.kind() != SyntaxKind::NEWLINE)
.collect();
if first_line.iter().any(|t| t.kind() == SyntaxKind::COMMENT) {
return false;
}
let last = first_line
.last()
.expect("a value on the key line starts with a token");
let parent = last.parent().expect("a token has a parent");
let at = last.index() + 1;
insert_children(
&parent,
at,
vec![
fresh_token(SyntaxKind::WHITESPACE, &comment.0).into(),
fresh_token(SyntaxKind::COMMENT, &comment.1).into(),
],
);
true
}