use super::{
append_children, fresh_token, has_child_token, insert_children, replace_children,
starts_a_flow_collection, trivia, SyntaxNode, SyntaxToken,
};
use crate::lex::SyntaxKind;
use rowan::ast::AstNode;
type Element = rowan::NodeOrToken<SyntaxNode, SyntaxToken>;
pub(crate) fn render_empty(collection: &SyntaxNode, line_break: Option<&str>) {
if brackets(collection.kind()).is_none()
|| starts_a_flow_collection(collection)
|| collection.children().any(|c| {
matches!(
c.kind(),
SyntaxKind::MAPPING_ENTRY | SyntaxKind::SEQUENCE_ENTRY
)
})
{
return;
}
let parent = outer_node(collection).parent();
assert!(
!in_collection(collection) || !has_child_token(collection, |k| k == SyntaxKind::COMMENT),
"comment lines left in a drained nested {:?}",
collection.kind()
);
let on_key_line = parent
.as_ref()
.is_some_and(|p| p.children().count() == 1 && p.parent().is_some());
match parent.map(|p| p.kind()) {
Some(SyntaxKind::VALUE) if on_key_line => render_as_value(collection, line_break),
Some(
SyntaxKind::VALUE | SyntaxKind::SEQUENCE_ENTRY | SyntaxKind::KEY | SyntaxKind::DOCUMENT,
)
| None => render_in_place(collection, line_break),
Some(kind) => panic!("unexpected {kind:?} holding a block collection"),
}
}
fn in_collection(node: &SyntaxNode) -> bool {
node.ancestors()
.skip(1)
.any(|n| matches!(n.kind(), SyntaxKind::MAPPING | SyntaxKind::SEQUENCE))
}
fn brackets(kind: SyntaxKind) -> Option<((SyntaxKind, &'static str), (SyntaxKind, &'static str))> {
match kind {
SyntaxKind::MAPPING => Some((
(SyntaxKind::LEFT_BRACE, "{"),
(SyntaxKind::RIGHT_BRACE, "}"),
)),
SyntaxKind::SEQUENCE => Some((
(SyntaxKind::LEFT_BRACKET, "["),
(SyntaxKind::RIGHT_BRACKET, "]"),
)),
_ => None,
}
}
fn replace_with_brackets(collection: &SyntaxNode) -> Vec<SyntaxToken> {
let leftovers: Vec<_> = collection
.children_with_tokens()
.map(|el| match el {
rowan::NodeOrToken::Token(t) => t,
rowan::NodeOrToken::Node(n) => {
panic!("unexpected {:?} left in an empty collection", n.kind())
}
})
.collect();
if let Some(t) = leftovers.iter().find(|t| {
!matches!(
t.kind(),
SyntaxKind::COMMENT | SyntaxKind::INDENT | SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE
)
}) {
panic!("unexpected {:?} left in an empty collection", t.kind());
}
let (open, close) = brackets(collection.kind()).expect("a collection");
replace_children(
collection,
0..leftovers.len(),
vec![
fresh_token(open.0, open.1).into(),
pending_block_marker().into(),
fresh_token(close.0, close.1).into(),
],
);
leftovers
}
fn copies(tokens: &[SyntaxToken]) -> Vec<Element> {
tokens
.iter()
.map(|t| fresh_token(t.kind(), t.text()).into())
.collect()
}
fn render_in_place(collection: &SyntaxNode, line_break: Option<&str>) {
let leftovers = replace_with_brackets(collection);
let tail_start = leftovers
.iter()
.position(|t| t.kind() == SyntaxKind::COMMENT)
.map(|i| {
if i > 0 && leftovers[i - 1].kind() != SyntaxKind::NEWLINE {
i - 1
} else {
i
}
})
.unwrap_or(leftovers.len());
let tail = copies(&leftovers[tail_start..]);
indent_past_parent(collection);
if line_break.is_none() && tail.is_empty() {
return;
}
let newline = || fresh_token(SyntaxKind::NEWLINE, line_break.unwrap_or("\n"));
let mut lines = vec![newline().into()];
lines.extend(tail);
let node = line_node(collection);
if node.kind() == SyntaxKind::MAPPING_ENTRY {
append_children(&node, lines);
return;
}
let Some(parent) = node.parent() else {
assert!(lines.len() == 1, "comment lines left in a built collection");
return;
};
insert_children(&parent, node.index() + 1, lines);
}
fn render_as_value(collection: &SyntaxNode, line_break: Option<&str>) {
let host = collection.parent().expect("a mapping value has a parent");
let outer = outer_node(collection);
let value_node = outer.parent().expect("a mapping value has a VALUE");
let entry_node = value_node
.parent()
.filter(|p| p.kind() == SyntaxKind::MAPPING_ENTRY)
.expect("a VALUE is in a MAPPING_ENTRY");
let mut properties = Vec::new();
let mut comment = None;
let mut comment_lines = false;
let mut seen_newline = false;
for el in host.children_with_tokens() {
let token = match el {
rowan::NodeOrToken::Node(n) if &n == collection => continue,
rowan::NodeOrToken::Node(n) => {
panic!("unexpected {:?} next to a block collection", n.kind())
}
rowan::NodeOrToken::Token(t) => t,
};
match token.kind() {
SyntaxKind::TAG | SyntaxKind::ANCHOR => properties.push(token),
SyntaxKind::COMMENT if seen_newline => comment_lines = true,
SyntaxKind::COMMENT => comment = Some(token),
SyntaxKind::NEWLINE => seen_newline = true,
SyntaxKind::INDENT | SyntaxKind::WHITESPACE => {}
kind => panic!("unexpected {kind:?} next to a block collection"),
}
}
replace_with_brackets(collection);
if comment_lines {
indent_past_parent(collection);
if let Some(line_break) = line_break {
trivia::terminate_with(&entry_node, line_break);
}
return;
}
let start = properties.last().map_or(0, |p| p.index() + 1);
let end = host.children_with_tokens().count();
let ws = || fresh_token(SyntaxKind::WHITESPACE, " ");
let has_properties = !properties.is_empty();
let mut replacement: Vec<Element> = Vec::new();
if has_properties {
replacement.push(ws().into());
}
replacement.push(collection.clone().into());
let mut comment: Vec<Element> = comment
.into_iter()
.flat_map(|c| {
[
ws().into(),
fresh_token(SyntaxKind::COMMENT, c.text()).into(),
]
})
.collect();
if host.kind() != SyntaxKind::TAGGED_NODE {
replacement.append(&mut comment);
}
replace_children(&host, start..end, replacement);
if !comment.is_empty() {
let after_host = outer.index() + 1;
insert_children(&value_node, after_host, comment);
}
if !has_properties {
let index = value_node.index();
let separated = value_node
.prev_sibling_or_token()
.is_some_and(|el| el.kind() == SyntaxKind::WHITESPACE);
if !separated {
insert_children(&entry_node, index, vec![ws().into()]);
}
}
if let Some(line_break) = line_break {
trivia::terminate_with(&entry_node, line_break);
}
}
fn indent_past_parent(collection: &SyntaxNode) {
let Some(host) = collection.parent() else {
return;
};
let Some(slot) = host
.ancestors()
.find(|n| n.kind() != SyntaxKind::TAGGED_NODE)
else {
return;
};
let Some(entry) = slot.parent() else {
return;
};
let indicator = match slot.kind() {
SyntaxKind::KEY => entry
.children_with_tokens()
.filter_map(|el| el.into_token())
.find(|t| t.kind() == SyntaxKind::QUESTION),
SyntaxKind::VALUE => entry.first_token(),
_ => None,
};
let Some(indicator) = indicator else {
return;
};
let Some(prev) = collection
.prev_sibling_or_token()
.and_then(|el| el.into_token())
else {
return;
};
let indent = match prev.kind() {
SyntaxKind::NEWLINE => None,
SyntaxKind::INDENT => Some(prev),
_ => return,
};
let column = crate::column::token_column(&indicator);
if indent
.as_ref()
.is_some_and(|t| t.text().chars().count() > column)
{
return;
}
let new_indent = fresh_token(SyntaxKind::INDENT, &" ".repeat(column + 2));
let at = collection.index();
match indent {
Some(t) => replace_children(&host, t.index()..at, vec![new_indent.into()]),
None => insert_children(&host, at, vec![new_indent.into()]),
}
}
fn outer_node(collection: &SyntaxNode) -> SyntaxNode {
collection
.ancestors()
.take_while(|n| n == collection || n.kind() == SyntaxKind::TAGGED_NODE)
.last()
.expect("ancestors has self")
}
fn line_node(collection: &SyntaxNode) -> SyntaxNode {
let node = outer_node(collection);
let Some(key) = node.parent().filter(|p| p.kind() == SyntaxKind::KEY) else {
return node;
};
let entry = key.parent().expect("a KEY is in an entry");
if has_child_token(&entry, |k| k == SyntaxKind::COLON) {
key
} else {
entry
}
}
pub(crate) fn detach_empty_collection_placeholder_newline(
empty_collection: &SyntaxNode,
new_entry: &SyntaxNode,
) {
if !trivia::ends_with_line_break(new_entry) {
return;
}
let node = line_node(empty_collection);
let parent = node.parent();
let entry = parent
.as_ref()
.filter(|p| p.kind() == SyntaxKind::VALUE)
.and_then(|p| p.parent());
let line_break = match entry {
Some(entry) => entry.last_child_or_token(),
None if node.kind() == SyntaxKind::MAPPING_ENTRY => node.last_child_or_token(),
None => node.next_sibling_or_token(),
};
if let Some(line_break) = line_break.filter(|t| t.kind() == SyntaxKind::NEWLINE) {
line_break.detach();
}
if parent.is_some_and(|p| p.kind() == SyntaxKind::DOCUMENT) {
let lines: Vec<_> =
std::iter::successors(node.next_sibling_or_token(), |c| c.next_sibling_or_token())
.take_while(|c| c.as_token().is_some() && c.kind() != SyntaxKind::DOC_END)
.collect();
for c in &lines {
c.detach();
}
super::append_children(empty_collection, lines);
}
}
fn pending_block_marker() -> SyntaxToken {
fresh_token(SyntaxKind::WHITESPACE, "")
}
pub(crate) fn is_block_placeholder(collection: &SyntaxNode) -> bool {
!collection.children().any(|c| {
matches!(
c.kind(),
SyntaxKind::MAPPING_ENTRY | SyntaxKind::SEQUENCE_ENTRY
)
}) && collection
.children_with_tokens()
.filter_map(|c| c.into_token())
.any(|t| t.kind() == SyntaxKind::WHITESPACE && t.text().is_empty())
}
pub(crate) fn convert_placeholder_to_block(collection: &SyntaxNode) {
if !is_block_placeholder(collection) || super::sequence::must_render_flow(collection) {
return;
}
let children: Vec<_> = collection.children_with_tokens().collect();
for child in children {
child.detach();
}
let Some(host) = collection.parent() else {
return;
};
let node = outer_node(collection);
let Some(value) = node.parent().filter(|p| p.kind() == SyntaxKind::VALUE) else {
return;
};
let pos = collection.index();
let already_scaffolded = pos >= 1
&& host
.children_with_tokens()
.nth(pos - 1)
.is_some_and(|c| matches!(c.kind(), SyntaxKind::INDENT | SyntaxKind::NEWLINE));
if already_scaffolded {
return;
}
let entry = value.parent();
let indent_width = entry
.as_ref()
.and_then(|e| e.parent())
.filter(|m| m.kind() == SyntaxKind::MAPPING)
.and_then(crate::nodes::Mapping::cast)
.map_or(2, |m| m.detect_indentation_level() + 2);
let after: Vec<_> = value
.children_with_tokens()
.skip(node.index() + 1)
.collect();
let comment = after
.iter()
.filter_map(|el| el.as_token())
.find(|t| t.kind() == SyntaxKind::COMMENT)
.map(|t| fresh_token(SyntaxKind::COMMENT, t.text()));
for el in after {
el.detach();
}
let space_before = collection
.prev_sibling_or_token()
.filter(|el| el.kind() == SyntaxKind::WHITESPACE);
let has_properties = host
.children_with_tokens()
.any(|el| matches!(el.kind(), SyntaxKind::TAG | SyntaxKind::ANCHOR));
if let Some(space) = space_before {
space.detach();
}
let mut scaffold: Vec<Element> = Vec::new();
if let Some(comment) = comment {
if has_properties {
scaffold.push(fresh_token(SyntaxKind::WHITESPACE, " ").into());
}
scaffold.push(comment.into());
} else if !has_properties {
let index = value.index();
let separator = entry
.and_then(|e| e.children_with_tokens().nth(index.wrapping_sub(1)))
.filter(|el| index > 0 && el.kind() == SyntaxKind::WHITESPACE);
if let Some(separator) = separator {
separator.detach();
}
}
scaffold.push(fresh_token(SyntaxKind::NEWLINE, "\n").into());
scaffold.push(fresh_token(SyntaxKind::INDENT, &" ".repeat(indent_width)).into());
let pos = collection.index();
insert_children(&host, pos, scaffold);
}