use super::Mapping;
use crate::lex::SyntaxKind;
use crate::nodes::{append_children, fresh_token, has_child_token, Scalar, Sequence, SyntaxNode};
use crate::yaml::ValueNode;
use rowan::ast::AstNode;
use rowan::GreenNodeBuilder;
pub(in crate::nodes) enum FlowInsertPos {
End,
After(SyntaxNode),
Before(SyntaxNode),
}
pub(super) fn value_is_block(value: &SyntaxNode) -> bool {
let carrier = value
.children()
.find(|c| c.kind() == SyntaxKind::TAGGED_NODE)
.unwrap_or_else(|| value.clone());
has_child_token(&carrier, |k| k == SyntaxKind::NEWLINE)
}
pub(super) fn ensure_top_level_empty_renders_as_flow(mapping: &SyntaxNode) {
if mapping
.children()
.any(|c| c.kind() == SyntaxKind::MAPPING_ENTRY)
{
return;
}
let has_map_entry_parent = mapping
.parent()
.filter(|p| p.kind() == SyntaxKind::VALUE)
.and_then(|v| v.parent())
.is_some_and(|e| e.kind() == SyntaxKind::MAPPING_ENTRY);
if has_map_entry_parent {
return;
}
let leftovers: Vec<_> = mapping.children_with_tokens().collect();
if !leftovers
.iter()
.all(|c| matches!(c.kind(), SyntaxKind::NEWLINE | SyntaxKind::INDENT))
{
return;
}
if !leftovers.is_empty() {
mapping.splice_children(0..leftovers.len(), vec![]);
}
let lbrace = fresh_token(SyntaxKind::LEFT_BRACE, "{");
let rbrace = fresh_token(SyntaxKind::RIGHT_BRACE, "}");
mapping.splice_children(0..0, vec![lbrace.into(), rbrace.into()]);
}
pub(super) fn index_after_entry_line(parent: &SyntaxNode, idx: usize) -> usize {
let follows_newline = parent
.children_with_tokens()
.nth(idx + 1)
.is_some_and(|c| c.kind() == SyntaxKind::NEWLINE);
if follows_newline {
idx + 2
} else {
idx + 1
}
}
pub(super) fn append_comma_space_to_entry(entry: &SyntaxNode) {
let ends_with_comma = entry
.children_with_tokens()
.filter_map(|c| c.into_token())
.filter(|t| {
!matches!(
t.kind(),
SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE | SyntaxKind::INDENT
)
})
.last()
.is_some_and(|t| t.kind() == SyntaxKind::COMMA);
if ends_with_comma {
return;
}
append_children(
entry,
vec![
fresh_token(SyntaxKind::COMMA, ",").into(),
fresh_token(SyntaxKind::WHITESPACE, " ").into(),
],
);
}
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(super) fn is_block_scalar(source: &SyntaxNode) -> bool {
source.kind() == SyntaxKind::SCALAR
&& source
.first_token()
.is_some_and(|t| matches!(t.kind(), SyntaxKind::PIPE | SyntaxKind::GREATER))
}
pub(super) fn build_block_value_node(source: &SyntaxNode, target: usize) -> (SyntaxNode, bool) {
if source.kind() == SyntaxKind::TAGGED_NODE {
return build_tagged_value_node(source, target);
}
let is_scalar = is_block_scalar(source);
let source_base = if is_scalar {
block_scalar_content_indent(source).unwrap_or(0)
} else {
crate::as_yaml::source_base_indent(source)
};
let delta = target as isize - source_base as isize;
let anchor = 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::WHITESPACE.into(), " ");
value_builder.token(SyntaxKind::ANCHOR.into(), a);
}
if is_scalar {
value_builder.token(SyntaxKind::WHITESPACE.into(), " ");
} else {
value_builder.token(SyntaxKind::NEWLINE.into(), "\n");
value_builder.token(SyntaxKind::INDENT.into(), &" ".repeat(target));
}
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();
(
SyntaxNode::new_root_mut(value_builder.finish()),
ends_with_newline,
)
}
pub(super) fn node_is_inline(node: &SyntaxNode) -> bool {
match node.kind() {
SyntaxKind::MAPPING => {
Mapping::cast(node.clone()).is_some_and(|m| ValueNode::is_inline(&m))
}
SyntaxKind::SEQUENCE => {
Sequence::cast(node.clone()).is_some_and(|s| ValueNode::is_inline(&s))
}
SyntaxKind::SCALAR => Scalar::cast(node.clone()).is_some_and(|s| ValueNode::is_inline(&s)),
_ => true,
}
}
pub(super) fn build_tagged_value_node(source: &SyntaxNode, target: usize) -> (SyntaxNode, bool) {
let tag_text = source
.children_with_tokens()
.filter_map(|c| c.into_token())
.find(|t| t.kind() == SyntaxKind::TAG)
.map(|t| t.text().to_string());
let inner = source.first_child();
let anchor = source_anchor_text(source);
let mut value_builder = GreenNodeBuilder::new();
value_builder.start_node(SyntaxKind::VALUE.into());
value_builder.token(SyntaxKind::WHITESPACE.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());
if let Some(tag) = tag_text.as_deref() {
value_builder.token(SyntaxKind::TAG.into(), tag);
}
let ends_with_newline = match inner {
None => false,
Some(inner) if node_is_inline(&inner) => {
value_builder.token(SyntaxKind::WHITESPACE.into(), " ");
value_builder.start_node(inner.kind().into());
let n = crate::as_yaml::copy_node_content_reindent(&mut value_builder, &inner, 0);
value_builder.finish_node();
n
}
Some(inner) => {
value_builder.token(SyntaxKind::NEWLINE.into(), "\n");
value_builder.token(SyntaxKind::INDENT.into(), &" ".repeat(target));
let delta = target as isize - crate::as_yaml::source_base_indent(&inner) as isize;
value_builder.start_node(inner.kind().into());
let n = crate::as_yaml::copy_node_content_reindent(&mut value_builder, &inner, delta);
value_builder.finish_node();
n
}
};
value_builder.finish_node(); value_builder.finish_node(); (
SyntaxNode::new_root_mut(value_builder.finish()),
ends_with_newline,
)
}
pub(super) 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(super) 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
}