use crate::lex::SyntaxKind;
use crate::nodes::{has_child_token, is_block_scalar, Scalar, SyntaxNode};
use rowan::ast::AstNode;
use rowan::GreenNodeBuilder;
pub(super) fn value_is_block(value: &SyntaxNode) -> bool {
let is_line_break = |k| k == SyntaxKind::NEWLINE;
has_child_token(value, is_line_break)
|| value
.children()
.find(|c| c.kind() == SyntaxKind::TAGGED_NODE)
.is_some_and(|tagged| has_child_token(&tagged, is_line_break))
}
pub(super) fn find_key_line_comment(entry: &SyntaxNode) -> Option<(String, String)> {
let mut seen_colon = false;
let mut last_ws: Option<String> = None;
for tok in entry
.descendants_with_tokens()
.filter_map(|c| c.into_token())
{
match tok.kind() {
SyntaxKind::COLON if !seen_colon => {
seen_colon = true;
last_ws = None;
}
_ if !seen_colon => {}
SyntaxKind::WHITESPACE => last_ws = Some(tok.text().to_string()),
SyntaxKind::COMMENT => {
return Some((
last_ws.unwrap_or_else(|| " ".to_string()),
tok.text().to_string(),
));
}
SyntaxKind::NEWLINE => return None,
_ => last_ws = None,
}
}
None
}
pub(crate) struct BlockValue {
pub(crate) node: SyntaxNode,
pub(crate) on_key_line: bool,
pub(crate) ends_with_newline: bool,
}
pub(crate) fn build_block_value_node(
source: &SyntaxNode,
target: usize,
anchor: Option<&str>,
) -> BlockValue {
if source.kind() == SyntaxKind::TAGGED_NODE {
return build_tagged_value_node(source, target, anchor);
}
let is_scalar = is_block_scalar(source);
let delta = if is_scalar {
block_scalar_body_shift(source, target)
} else {
target as isize - crate::column::node_column(source) as isize
};
let anchor = anchor
.map(str::to_string)
.or_else(|| source_anchor_text(source));
let mut value_builder = GreenNodeBuilder::new();
value_builder.start_node(SyntaxKind::VALUE.into());
if let Some(a) = anchor.as_deref() {
value_builder.token(SyntaxKind::ANCHOR.into(), a);
}
if !is_scalar {
value_builder.token(SyntaxKind::NEWLINE.into(), "\n");
value_builder.token(SyntaxKind::INDENT.into(), &" ".repeat(target));
} else if anchor.is_some() {
value_builder.token(SyntaxKind::WHITESPACE.into(), " ");
}
value_builder.start_node(source.kind().into());
let ends_with_newline =
crate::as_yaml::copy_node_content_reindent(&mut value_builder, source, delta);
value_builder.finish_node();
value_builder.finish_node();
BlockValue {
node: SyntaxNode::new_root_mut(value_builder.finish()),
on_key_line: is_scalar || anchor.is_some(),
ends_with_newline,
}
}
pub(super) fn built_block_node(value: &impl crate::AsYaml) -> Option<SyntaxNode> {
if value.value_node().is_some() {
return None;
}
let mut builder = GreenNodeBuilder::new();
builder.start_node(SyntaxKind::ROOT.into());
let ends_with_newline = value.build_content(&mut builder, 0, false);
builder.finish_node();
if value.is_inline() && !ends_with_newline {
return None;
}
let node = SyntaxNode::new_root(builder.finish()).first_child()?;
let scalar = match node.kind() {
SyntaxKind::TAGGED_NODE => node.children().last(),
_ => Some(node.clone()),
};
if scalar.is_some_and(|s| is_block_scalar(&s)) {
return None;
}
Some(node)
}
pub(super) fn build_tagged_value_node(
source: &SyntaxNode,
target: usize,
replacement_anchor: Option<&str>,
) -> BlockValue {
let anchor = replacement_anchor
.map(str::to_string)
.or_else(|| source_anchor_text(source));
let delta = source.first_child().map_or(0, |inner| {
let content_on_tag_line = source
.children_with_tokens()
.take_while(|c| c.as_node().is_none())
.all(|c| c.kind() != SyntaxKind::NEWLINE);
if !content_on_tag_line {
target as isize - crate::column::node_column(&inner) as isize
} else if is_block_scalar(&inner) {
block_scalar_body_shift(&inner, target)
} else {
0
}
});
let mut value_builder = GreenNodeBuilder::new();
value_builder.start_node(SyntaxKind::VALUE.into());
if let Some(a) = anchor.as_deref() {
value_builder.token(SyntaxKind::ANCHOR.into(), a);
value_builder.token(SyntaxKind::WHITESPACE.into(), " ");
}
value_builder.start_node(SyntaxKind::TAGGED_NODE.into());
let ends_with_newline =
crate::as_yaml::copy_node_content_reindent(&mut value_builder, source, delta);
value_builder.finish_node(); value_builder.finish_node(); let node = SyntaxNode::new_root_mut(value_builder.finish());
if replacement_anchor.is_some() {
drop_tagged_anchor(&node);
}
BlockValue {
node,
on_key_line: true,
ends_with_newline,
}
}
pub(crate) fn source_anchor_text(source: &SyntaxNode) -> Option<String> {
let mut cursor = source.prev_sibling_or_token();
while let Some(item) = cursor {
let tok = item.as_token()?;
match tok.kind() {
SyntaxKind::ANCHOR => return Some(tok.text().to_string()),
SyntaxKind::WHITESPACE | SyntaxKind::INDENT | SyntaxKind::NEWLINE => {}
_ => return None,
}
cursor = item.prev_sibling_or_token();
}
None
}
pub(crate) fn held_anchor_token(holder: &SyntaxNode) -> Option<crate::nodes::SyntaxToken> {
let tagged = holder
.children()
.next()
.filter(|n| n.kind() == SyntaxKind::TAGGED_NODE);
own_anchor(holder).or_else(|| tagged.and_then(|n| own_anchor(&n)))
}
pub(crate) fn kept_anchor_token(
holder: &SyntaxNode,
new_value: &impl crate::AsYaml,
) -> Option<crate::nodes::SyntaxToken> {
held_anchor_token(holder).filter(|_| new_value.kind() != crate::as_yaml::YamlKind::Alias)
}
fn own_anchor(node: &SyntaxNode) -> Option<crate::nodes::SyntaxToken> {
node.children_with_tokens()
.map_while(|c| c.into_token())
.find(|t| t.kind() == SyntaxKind::ANCHOR)
}
pub(crate) fn drop_tagged_anchor(holder: &SyntaxNode) {
let anchor = holder
.children()
.find(|n| n.kind() == SyntaxKind::TAGGED_NODE)
.and_then(|n| own_anchor(&n));
if let Some(anchor) = anchor {
crate::nodes::detach_anchor(&anchor);
}
}
fn block_scalar_content_indent(scalar: &SyntaxNode) -> Option<usize> {
let mut after_newline = false;
for child in scalar.children_with_tokens() {
if let Some(tok) = child.as_token() {
match tok.kind() {
SyntaxKind::NEWLINE => after_newline = true,
SyntaxKind::WHITESPACE | SyntaxKind::INDENT if after_newline => {
return Some(tok.text().len());
}
_ => after_newline = false,
}
}
}
None
}
pub(crate) fn block_scalar_body_shift(scalar: &SyntaxNode, target: usize) -> isize {
if crate::nodes::scalar_node::block_scalar_indicators(scalar)
.1
.is_some()
{
let parent = crate::nodes::scalar_node::block_parent_column(scalar);
return target.saturating_sub(2) as isize - parent as isize;
}
let body = block_scalar_content_indent(scalar).unwrap_or(target);
target as isize - body as isize
}
pub(crate) fn flow_form(
value: &impl crate::AsYaml,
indent: usize,
quoting: crate::as_yaml::FlowQuoting,
) -> Option<crate::as_yaml::YamlNode> {
if value.is_inline() {
return None;
}
let Some(node) = value.value_node() else {
let mut builder = GreenNodeBuilder::new();
value.build_flow_content(&mut builder, 0, quoting);
return crate::as_yaml::YamlNode::from_syntax(SyntaxNode::new_root_mut(builder.finish()));
};
let mut writer = FlowWriter {
builder: GreenNodeBuilder::new(),
pending: Vec::new(),
};
writer.node(&node, indent);
assert!(writer.pending.is_empty(), "unplaced comments in flow form");
crate::as_yaml::YamlNode::from_syntax(SyntaxNode::new_root_mut(writer.builder.finish()))
}
pub(super) fn has_comment(node: &SyntaxNode) -> bool {
node.descendants_with_tokens()
.any(|c| c.kind() == SyntaxKind::COMMENT)
}
struct FlowWriter {
builder: GreenNodeBuilder<'static>,
pending: Vec<(String, String)>,
}
impl FlowWriter {
fn node(&mut self, node: &SyntaxNode, indent: usize) {
match node.kind() {
SyntaxKind::MAPPING | SyntaxKind::SEQUENCE => self.collection(node, indent),
SyntaxKind::SCALAR => self.scalar(node),
SyntaxKind::TAGGED_NODE => {
self.builder.start_node(SyntaxKind::TAGGED_NODE.into());
self.decorated(node, indent, true);
self.builder.finish_node();
}
_ => {
self.builder.start_node(node.kind().into());
crate::as_yaml::copy_node_content(&mut self.builder, node);
self.builder.finish_node();
}
}
}
fn decorated(&mut self, wrapper: &SyntaxNode, indent: usize, nested: bool) {
let mut decorated = false;
for child in wrapper.children_with_tokens() {
match child {
rowan::NodeOrToken::Token(t) => match t.kind() {
SyntaxKind::ANCHOR | SyntaxKind::TAG => {
if decorated {
self.builder.token(SyntaxKind::WHITESPACE.into(), " ");
}
self.builder.token(t.kind().into(), t.text());
decorated = true;
}
SyntaxKind::COMMENT => self.push_comment(&t),
_ => {}
},
rowan::NodeOrToken::Node(n) => {
let empty = n.text_range().is_empty();
if empty && !decorated && !nested {
self.builder.start_node(SyntaxKind::SCALAR.into());
self.builder.token(SyntaxKind::NULL.into(), "null");
self.builder.finish_node();
continue;
}
if decorated && !empty {
self.builder.token(SyntaxKind::WHITESPACE.into(), " ");
}
let inner = indent + if nested { 0 } else { 2 };
self.node(&n, inner);
}
}
}
}
fn collection(&mut self, node: &SyntaxNode, indent: usize) {
let (entry_kind, open, close) = if node.kind() == SyntaxKind::MAPPING {
(
SyntaxKind::MAPPING_ENTRY,
("{", SyntaxKind::LEFT_BRACE),
("}", SyntaxKind::RIGHT_BRACE),
)
} else {
(
SyntaxKind::SEQUENCE_ENTRY,
("[", SyntaxKind::LEFT_BRACKET),
("]", SyntaxKind::RIGHT_BRACKET),
)
};
self.builder.start_node(node.kind().into());
self.builder.token(open.1.into(), open.0);
let children: Vec<_> = node.children_with_tokens().collect();
let entries: Vec<usize> = (0..children.len())
.filter(|&i| children[i].kind() == entry_kind)
.collect();
self.push_comments(&children[..entries.first().copied().unwrap_or(children.len())]);
let lines = self.end_line();
self.comment_lines(lines, indent);
for (n, &i) in entries.iter().enumerate() {
let entry = children[i].as_node().expect("entry is a node");
self.builder.start_node(entry_kind.into());
if entry_kind == SyntaxKind::MAPPING_ENTRY {
self.mapping_entry(entry, indent);
} else {
self.decorated(entry, indent, false);
}
let next = entries.get(n + 1).copied();
self.push_comments(&children[i + 1..next.unwrap_or(children.len())]);
if next.is_some() {
self.builder.token(SyntaxKind::COMMA.into(), ",");
if self.pending.is_empty() {
self.builder.token(SyntaxKind::WHITESPACE.into(), " ");
}
}
let lines = self.end_line();
self.builder.finish_node();
self.comment_lines(lines, indent);
}
self.builder.token(close.1.into(), close.0);
self.builder.finish_node();
}
fn mapping_entry(&mut self, entry: &SyntaxNode, indent: usize) {
let mut has_value = false;
for child in entry.children_with_tokens() {
match child {
rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::COMMENT => {
self.push_comment(&t)
}
rowan::NodeOrToken::Node(n) if n.kind() == SyntaxKind::KEY => self.key(&n, indent),
rowan::NodeOrToken::Node(n) if n.kind() == SyntaxKind::VALUE => {
self.colon();
self.builder.start_node(SyntaxKind::VALUE.into());
self.decorated(&n, indent, false);
self.builder.finish_node();
has_value = true;
}
_ => {}
}
}
assert!(has_value, "MAPPING_ENTRY without a VALUE");
}
fn key(&mut self, key: &SyntaxNode, indent: usize) {
let mut sub = FlowWriter {
builder: GreenNodeBuilder::new(),
pending: std::mem::take(&mut self.pending),
};
sub.builder.start_node(SyntaxKind::KEY.into());
sub.decorated(key, indent, false);
sub.builder.finish_node();
self.pending = sub.pending;
let written = SyntaxNode::new_root(sub.builder.finish());
self.builder.start_node(SyntaxKind::KEY.into());
let multiline = written
.descendants_with_tokens()
.any(|c| c.kind() == SyntaxKind::NEWLINE);
if multiline {
self.builder.token(SyntaxKind::QUESTION.into(), "?");
self.builder.token(SyntaxKind::WHITESPACE.into(), " ");
}
crate::as_yaml::copy_node_content(&mut self.builder, &written);
self.builder.finish_node();
if crate::nodes::ends_in_property_name(&written) {
self.builder.token(SyntaxKind::WHITESPACE.into(), " ");
}
}
fn colon(&mut self) {
self.builder.token(SyntaxKind::COLON.into(), ":");
self.builder.token(SyntaxKind::WHITESPACE.into(), " ");
}
fn scalar(&mut self, node: &SyntaxNode) {
let tokens: Vec<_> = node
.descendants_with_tokens()
.filter_map(|c| c.into_token())
.collect();
for t in tokens.iter().filter(|t| t.kind() == SyntaxKind::COMMENT) {
self.push_comment(t);
}
let text: String = tokens
.iter()
.filter(|t| t.kind() != SyntaxKind::COMMENT)
.map(|t| t.text())
.collect();
let text = text.trim();
let quoted = text.starts_with(['"', '\'']);
let breaks_flow = is_block_scalar(node)
|| text.contains('\n')
|| (!quoted && text.contains([',', '[', ']', '{', '}']));
if breaks_flow {
let value = Scalar::cast(node.clone()).expect("SCALAR node").as_string();
crate::AsYaml::build_content(
&crate::scalar::ScalarValue::double_quoted(value),
&mut self.builder,
0,
true,
);
return;
}
self.builder.start_node(SyntaxKind::SCALAR.into());
let content = tokens.iter().filter(|t| {
!matches!(
t.kind(),
SyntaxKind::COMMENT
| SyntaxKind::WHITESPACE
| SyntaxKind::NEWLINE
| SyntaxKind::INDENT
)
});
for t in content {
self.builder.token(t.kind().into(), t.text());
}
self.builder.finish_node();
}
fn push_comments(&mut self, elements: &[rowan::SyntaxElement<crate::nodes::Lang>]) {
for element in elements {
match element {
rowan::NodeOrToken::Token(t) if t.kind() == SyntaxKind::COMMENT => {
self.push_comment(t)
}
rowan::NodeOrToken::Node(n) => {
for t in n.descendants_with_tokens().filter_map(|c| c.into_token()) {
if t.kind() == SyntaxKind::COMMENT {
self.push_comment(&t);
}
}
}
_ => {}
}
}
}
fn push_comment(&mut self, comment: &crate::nodes::SyntaxToken) {
let ws = comment
.prev_token()
.filter(|t| t.kind() == SyntaxKind::WHITESPACE)
.filter(|t| {
t.prev_token()
.is_some_and(|p| p.kind() != SyntaxKind::NEWLINE)
})
.map_or_else(|| " ".to_string(), |t| t.text().to_string());
self.pending.push((ws, comment.text().to_string()));
}
fn end_line(&mut self) -> Option<Vec<String>> {
let mut pending = std::mem::take(&mut self.pending).into_iter();
let (ws, comment) = pending.next()?;
self.builder.token(SyntaxKind::WHITESPACE.into(), &ws);
self.builder.token(SyntaxKind::COMMENT.into(), &comment);
self.builder.token(SyntaxKind::NEWLINE.into(), "\n");
Some(pending.map(|(_, comment)| comment).collect())
}
fn comment_lines(&mut self, comments: Option<Vec<String>>, indent: usize) {
let Some(comments) = comments else {
return;
};
let indentation = " ".repeat(indent);
for comment in comments {
self.builder.token(SyntaxKind::INDENT.into(), &indentation);
self.builder.token(SyntaxKind::COMMENT.into(), &comment);
self.builder.token(SyntaxKind::NEWLINE.into(), "\n");
}
self.builder.token(SyntaxKind::INDENT.into(), &indentation);
}
}