use super::{SyntaxNode, SyntaxToken};
use crate::anchor_resolution::document_scope;
use crate::lex::SyntaxKind;
use rowan::ast::AstNode;
use std::collections::{HashMap, HashSet};
pub(crate) fn keeping_aliases(
removed: &SyntaxNode,
kept: Option<SyntaxToken>,
edit: impl FnOnce() -> Option<SyntaxNode>,
) {
if !removed
.descendants_with_tokens()
.any(|c| c.as_token().is_some_and(is_anchor))
{
edit();
return;
}
let scope = document_scope(removed);
let (anchors, aliases) = scan(&scope);
let inside = |n: &SyntaxNode| n.ancestors().any(|a| a == *removed);
let kept_id = kept.and_then(|k| anchors.iter().position(|a| *a == k));
let removed_ids: HashSet<usize> = anchors
.iter()
.enumerate()
.filter(|(i, a)| Some(*i) != kept_id && a.parent().is_some_and(|p| inside(&p)))
.map(|(i, _)| i)
.collect();
let broken: Vec<_> = aliases
.iter()
.filter(|(alias, _)| !inside(alias))
.filter_map(|(alias, target)| Some((alias, target.filter(|t| removed_ids.contains(t))?)))
.collect();
if broken.is_empty() {
edit();
return;
}
for (alias, _) in &broken {
check_copyable(alias);
}
let mut tracker = Tracker::new(
scope,
&anchors,
&aliases,
|i| removed_ids.contains(&i) || Some(i) == kept_id,
|alias| !inside(alias),
);
tracker.removed = removed_ids;
let holder = edit();
if let Some(k) = kept_id {
match holder.as_ref().and_then(super::mapping::held_anchor_token) {
Some(anchor) => {
tracker.live_ids.push((anchor.clone(), k));
tracker.origins[k] = Origin::Live(anchor);
}
None => {
tracker.removed.insert(k);
}
}
}
tracker.repair();
}
pub(crate) fn keeping_aliases_moved(within: &SyntaxNode, edit: impl FnOnce()) {
let scope = document_scope(within);
let (anchors, aliases) = scan(&scope);
if aliases.iter().all(|(_, target)| target.is_none()) {
edit();
return;
}
let mut tracker = Tracker::new(scope, &anchors, &aliases, |_| false, |_| true);
tracker.moved = true;
edit();
tracker.repair();
}
fn scan(scope: &SyntaxNode) -> (Vec<SyntaxToken>, Vec<(SyntaxNode, Option<usize>)>) {
let mut anchors = Vec::new();
let mut aliases = Vec::new();
let mut latest: HashMap<String, usize> = HashMap::new();
for element in scope.descendants_with_tokens() {
match element {
rowan::NodeOrToken::Token(t) if is_anchor(&t) => {
latest.insert(anchor_name(&t).to_string(), anchors.len());
anchors.push(t);
}
rowan::NodeOrToken::Node(n) if n.kind() == SyntaxKind::ALIAS => {
let target = latest.get(&alias_name(&n)).copied();
aliases.push((n, target));
}
_ => {}
}
}
(anchors, aliases)
}
fn is_anchor(token: &SyntaxToken) -> bool {
token.kind() == SyntaxKind::ANCHOR
&& token.parent().map(|p| p.kind()) != Some(SyntaxKind::SCALAR)
}
fn anchor_name(anchor: &SyntaxToken) -> &str {
anchor.text().strip_prefix('&').unwrap_or(anchor.text())
}
fn alias_name(alias: &SyntaxNode) -> String {
crate::yaml::Alias::cast(alias.clone())
.expect("an ALIAS node")
.name()
}
fn anchored_node(anchor: &SyntaxToken) -> SyntaxNode {
let parent = anchor.parent().expect("an anchor has a parent");
if parent.kind() == SyntaxKind::TAGGED_NODE {
return parent;
}
anchor
.siblings_with_tokens(rowan::Direction::Next)
.find_map(|c| c.into_node())
.expect("the parser puts a node after every anchor")
}
fn copied_anchors(anchor: Option<&SyntaxToken>, node: &SyntaxNode) -> Vec<SyntaxToken> {
anchor
.cloned()
.into_iter()
.chain(
node.descendants_with_tokens()
.filter_map(|c| c.into_token())
.filter(is_anchor),
)
.collect()
}
fn descendant_aliases(node: &SyntaxNode) -> Vec<SyntaxNode> {
node.descendants()
.filter(|n| n.kind() == SyntaxKind::ALIAS)
.collect()
}
#[derive(Clone)]
enum Origin {
Live(SyntaxToken),
Snapshot(SyntaxToken),
}
struct Tracker {
scope: SyntaxNode,
origins: Vec<Origin>,
live_ids: Vec<(SyntaxToken, usize)>,
snapshot_ids: HashMap<SyntaxToken, usize>,
live_expected: Vec<(SyntaxNode, Option<usize>)>,
snapshot_expected: HashMap<SyntaxNode, Option<usize>>,
removed: HashSet<usize>,
copied_anchors: Vec<SyntaxToken>,
copied_aliases: Vec<SyntaxNode>,
moved: bool,
snapshot: SyntaxNode,
}
impl Tracker {
fn new(
scope: SyntaxNode,
anchors: &[SyntaxToken],
aliases: &[(SyntaxNode, Option<usize>)],
in_snapshot: impl Fn(usize) -> bool,
tracked: impl Fn(&SyntaxNode) -> bool,
) -> Self {
let snapshot = SyntaxNode::new_root_mut(scope.green().into_owned());
let (snapshot_anchors, snapshot_aliases) = scan(&snapshot);
assert_eq!(snapshot_anchors.len(), anchors.len(), "snapshot differs");
assert_eq!(snapshot_aliases.len(), aliases.len(), "snapshot differs");
let mut tracker = Tracker {
scope,
origins: Vec::with_capacity(anchors.len()),
live_ids: Vec::new(),
snapshot_ids: HashMap::new(),
live_expected: Vec::new(),
snapshot_expected: HashMap::new(),
removed: HashSet::new(),
copied_anchors: Vec::new(),
copied_aliases: Vec::new(),
moved: false,
snapshot,
};
for (i, (live, snapshot)) in anchors.iter().zip(&snapshot_anchors).enumerate() {
if in_snapshot(i) {
tracker.origins.push(Origin::Snapshot(snapshot.clone()));
} else {
tracker.origins.push(Origin::Live(live.clone()));
tracker.live_ids.push((live.clone(), i));
}
tracker.snapshot_ids.insert(snapshot.clone(), i);
}
for ((live, target), (snapshot, _)) in aliases.iter().zip(&snapshot_aliases) {
if tracked(live) {
tracker.live_expected.push((live.clone(), *target));
}
tracker.snapshot_expected.insert(snapshot.clone(), *target);
}
tracker
}
fn live_id(&mut self, anchor: &SyntaxToken) -> usize {
if let Some((_, id)) = self.live_ids.iter().find(|(t, _)| t == anchor) {
return *id;
}
let id = self.origins.len();
self.origins.push(Origin::Live(anchor.clone()));
self.live_ids.push((anchor.clone(), id));
id
}
fn expected(&self, alias: &SyntaxNode) -> Option<Option<usize>> {
self.live_expected
.iter()
.find(|(a, _)| a == alias)
.map(|(_, e)| *e)
}
fn prune(&mut self) {
let scope = self.scope.clone();
let attached = |n: Option<SyntaxNode>| n.is_some_and(|n| n.ancestors().any(|a| a == scope));
self.live_ids.retain(|(t, _)| attached(t.parent()));
self.live_expected
.retain(|(a, _)| attached(Some(a.clone())));
self.copied_anchors.retain(|t| attached(t.parent()));
self.copied_aliases.retain(|a| attached(Some(a.clone())));
}
fn repair(&mut self) {
while let Some((alias, target)) = self.first_broken() {
if copy_obstacle(&alias).is_some() {
self.restore();
check_copyable(&alias);
}
let (origin, in_snapshot) = self.origin(target);
let takes_anchor = self.moved
&& !in_snapshot
&& alias.text_range().start() < origin.text_range().start();
self.copy_for(&alias, target, takes_anchor);
}
}
fn origin(&self, id: usize) -> (SyntaxToken, bool) {
match &self.origins[id] {
Origin::Live(t) => (t.clone(), false),
Origin::Snapshot(t) => (t.clone(), true),
}
}
fn restore(&self) {
let snapshot = SyntaxNode::new_root_mut(self.snapshot.green().into_owned());
let children: Vec<_> = snapshot.children_with_tokens().collect();
for child in &children {
match child {
rowan::NodeOrToken::Node(n) => n.detach(),
rowan::NodeOrToken::Token(t) => t.detach(),
}
}
let count = self.scope.children_with_tokens().count();
super::replace_children(&self.scope, 0..count, children);
}
fn first_broken(&mut self) -> Option<(SyntaxNode, usize)> {
self.prune();
let mut latest: HashMap<String, SyntaxToken> = HashMap::new();
let elements: Vec<_> = self.scope.descendants_with_tokens().collect();
for element in elements {
match element {
rowan::NodeOrToken::Token(t) if is_anchor(&t) => {
latest.insert(anchor_name(&t).to_string(), t);
}
rowan::NodeOrToken::Node(alias) if alias.kind() == SyntaxKind::ALIAS => {
let Some(expected) = self.expected(&alias) else {
continue;
};
let current = latest.get(&alias_name(&alias)).cloned();
let current_id = current.as_ref().map(|t| self.live_id(t));
if current_id == expected {
continue;
}
let broken = self.moved
|| expected.is_some_and(|e| self.removed.contains(&e))
|| current
.as_ref()
.is_some_and(|t| self.copied_anchors.contains(t))
|| self.copied_aliases.contains(&alias);
if let (true, Some(expected)) = (broken, expected) {
return Some((alias, expected));
}
}
_ => {}
}
}
None
}
fn copy_for(&mut self, alias: &SyntaxNode, target: usize, takes_anchor: bool) {
let (origin, in_snapshot) = self.origin(target);
let (tag, own_anchor) = properties(alias);
let node = match anchored_node(&origin) {
n if tag.is_some() && n.kind() == SyntaxKind::TAGGED_NODE => untagged(&n),
n => n,
};
let tag = tag.map(|t| t.text().to_string());
let own_anchor = own_anchor.map(|a| (a.text().to_string(), self.live_id(&a)));
strip_properties(alias);
let outer = (origin.parent().map(|p| p.kind()) != Some(SyntaxKind::TAGGED_NODE)
&& tag.is_none()
&& own_anchor.is_none())
.then_some(&origin);
let anchor = own_anchor
.as_ref()
.map(|(text, _)| text.as_str())
.or(outer.map(|a| a.text()));
let holder = match &tag {
Some(tag) => replace_alias(alias, &tagged(tag, &node), anchor),
None => replace_alias(alias, &node, anchor),
};
let mut originals = copied_anchors(outer, &node);
if own_anchor.is_some() {
originals.retain(|a| *a != origin);
}
let mut copies: Vec<_> = holder
.descendants_with_tokens()
.filter_map(|c| c.into_token())
.filter(is_anchor)
.collect();
if let Some((_, id)) = own_anchor {
let copy = copies.remove(0);
self.live_ids.push((copy.clone(), id));
self.origins[id] = Origin::Live(copy);
}
assert_eq!(
originals.len(),
copies.len(),
"copy of {node} lost or gained anchors: {holder}"
);
for (original, copy) in originals.iter().zip(&copies) {
let id = if in_snapshot {
self.snapshot_ids[original]
} else {
self.live_id(original)
};
self.live_ids.push((copy.clone(), id));
self.copied_anchors.push(copy.clone());
}
let original_aliases = descendant_aliases(&node);
let copied_aliases = descendant_aliases(&holder);
assert_eq!(
original_aliases.len(),
copied_aliases.len(),
"copy of {node} lost or gained aliases: {holder}"
);
for (original, copy) in original_aliases.iter().zip(copied_aliases) {
let expected = if in_snapshot {
self.snapshot_expected[original]
} else {
match self.expected(original) {
Some(expected) => expected,
None => self.current_target(original),
}
};
self.live_expected.push((copy.clone(), expected));
self.copied_aliases.push(copy);
}
let mut kept_outer = None;
let mut unused = Vec::new();
for (original, copy) in originals.iter().zip(&copies).rev() {
let (captures_others, used) = self.captures(copy);
let taken = takes_anchor && *original == origin;
if captures_others {
self.drop_copied_anchor(copy);
} else if taken {
kept_outer = Some(copy.clone());
} else if !in_snapshot && !used {
unused.push(copy.clone());
}
}
let aliased = kept_outer.is_some_and(|copy| self.alias_original(&origin, ©, target));
if !aliased {
for copy in &unused {
self.drop_copied_anchor(copy);
}
}
}
fn drop_copied_anchor(&mut self, copy: &SyntaxToken) {
self.live_ids.retain(|(t, _)| t != copy);
self.copied_anchors.retain(|t| t != copy);
super::detach_anchor(copy);
}
fn alias_original(&mut self, origin: &SyntaxToken, copy: &SyntaxToken, id: usize) -> bool {
let node = anchored_node(origin);
let holder =
if node.kind() == SyntaxKind::TAGGED_NODE && origin.parent() == Some(node.clone()) {
node.parent()
} else {
origin.parent()
};
let Some(holder) = holder.filter(|h| {
matches!(
h.kind(),
SyntaxKind::VALUE | SyntaxKind::SEQUENCE_ENTRY | SyntaxKind::KEY
)
}) else {
return false;
};
let copy_anchors = copied_anchors(None, ©.parent().expect("an anchor has a parent"));
let mut takers = Vec::new();
for anchor in copied_anchors(None, &holder) {
let anchor_id = self.live_id(&anchor);
let Some(taker) = copy_anchors
.iter()
.find(|c| self.live_id(c) == anchor_id)
.cloned()
else {
return false;
};
let name = anchor_name(&anchor);
let redefined = self
.scope
.descendants_with_tokens()
.filter_map(|c| c.into_token())
.filter(|t| is_anchor(t) && anchor_name(t) == name)
.any(|t| {
t.text_range().start() > taker.text_range().start()
&& t.text_range().start() < anchor.text_range().start()
&& !t.parent_ancestors().any(|a| a == holder)
});
if redefined {
return false;
}
takers.push((taker, anchor_id));
}
let name = anchor_name(origin).to_string();
super::detach_anchor(origin);
let holder = replace_in_holder(&holder, crate::yaml::Alias::new(&name), None);
let alias = descendant_aliases(&holder)
.into_iter()
.next()
.expect("the holder was just given an alias");
for (taker, anchor_id) in takers {
self.origins[anchor_id] = Origin::Live(taker);
}
self.live_expected.push((alias.clone(), Some(id)));
self.copied_aliases.push(alias);
true
}
fn current_target(&mut self, alias: &SyntaxNode) -> Option<usize> {
let name = alias_name(alias);
let mut latest = None;
for element in self.scope.descendants_with_tokens() {
match element {
rowan::NodeOrToken::Token(t) if is_anchor(&t) && anchor_name(&t) == name => {
latest = Some(t);
}
rowan::NodeOrToken::Node(n) if n == *alias => break,
_ => {}
}
}
latest.map(|t| self.live_id(&t))
}
fn captures(&mut self, anchor: &SyntaxToken) -> (bool, bool) {
let id = self.live_id(anchor);
let name = anchor_name(anchor).to_string();
let recursive = descendant_aliases(&anchored_node(anchor))
.iter()
.any(|a| self.expected(a) == Some(Some(id)));
let (mut others, mut used) = (false, false);
let mut after = false;
for element in self.scope.descendants_with_tokens() {
match element {
rowan::NodeOrToken::Token(t) if t == *anchor => after = true,
rowan::NodeOrToken::Token(t)
if after && is_anchor(&t) && anchor_name(&t) == name =>
{
break;
}
rowan::NodeOrToken::Node(n)
if after && n.kind() == SyntaxKind::ALIAS && alias_name(&n) == name =>
{
match self.expected(&n) {
Some(e) if e == Some(id) => used = true,
Some(_) => others = true,
None => {}
}
}
_ => {}
}
}
(others && !recursive, used)
}
}
fn replaceable(alias: &SyntaxNode) -> bool {
alias.parent().is_some_and(|p| {
matches!(
p.kind(),
SyntaxKind::VALUE | SyntaxKind::SEQUENCE_ENTRY | SyntaxKind::KEY
) && super::mapping::held_anchor_token(&p).is_none()
})
}
fn copy_obstacle(alias: &SyntaxNode) -> Option<&'static str> {
let parent = alias.parent().expect("an alias in a document has a parent");
let tagged = parent.kind() == SyntaxKind::TAGGED_NODE;
let holder = if tagged {
parent.parent().expect("a tagged alias has a holder")
} else {
parent
};
match holder.kind() {
SyntaxKind::VALUE | SyntaxKind::SEQUENCE_ENTRY | SyntaxKind::KEY => None,
_ => Some("it is not a value, sequence item or key"),
}
}
fn check_copyable(alias: &SyntaxNode) {
if let Some(obstacle) = copy_obstacle(alias) {
panic!("cannot keep alias {alias} resolving: {obstacle}");
}
}
fn properties(alias: &SyntaxNode) -> (Option<SyntaxToken>, Option<SyntaxToken>) {
let parent = alias.parent().expect("an alias in a document has a parent");
let (tagged, holder) = if parent.kind() == SyntaxKind::TAGGED_NODE {
let holder = parent.parent().expect("a tagged alias has a holder");
(Some(parent), holder)
} else {
(None, parent)
};
let tagged_token = |kind: SyntaxKind| {
tagged.as_ref().and_then(|t| {
t.children_with_tokens()
.filter_map(|c| c.into_token())
.find(|t| t.kind() == kind)
})
};
let anchor =
tagged_token(SyntaxKind::ANCHOR).or_else(|| super::mapping::held_anchor_token(&holder));
(tagged_token(SyntaxKind::TAG), anchor)
}
fn strip_properties(alias: &SyntaxNode) {
let parent = alias.parent().expect("an alias in a document has a parent");
if parent.kind() == SyntaxKind::TAGGED_NODE {
let holder = parent.parent().expect("a tagged alias has a holder");
let i = parent.index();
alias.detach();
super::replace_children(&holder, i..i + 1, vec![alias.clone().into()]);
}
let holder = alias.parent().expect("an alias in a document has a parent");
if let Some(anchor) = super::mapping::held_anchor_token(&holder) {
super::detach_anchor(&anchor);
}
}
fn untagged(tagged: &SyntaxNode) -> SyntaxNode {
tagged
.children()
.next()
.expect("the parser puts a node in every TAGGED_NODE")
}
fn tagged(tag: &str, node: &SyntaxNode) -> SyntaxNode {
use rowan::{GreenNode, GreenToken, NodeOrToken};
let token =
|kind: SyntaxKind, text: &str| NodeOrToken::Token(GreenToken::new(kind.into(), text));
let block_collection = matches!(node.kind(), SyntaxKind::MAPPING | SyntaxKind::SEQUENCE)
&& !super::starts_a_flow_collection(node);
let mut children = vec![token(SyntaxKind::TAG, tag)];
if block_collection {
children.push(token(SyntaxKind::NEWLINE, "\n"));
let column = crate::column::node_column(node);
if column > 0 {
children.push(token(SyntaxKind::INDENT, &" ".repeat(column)));
}
} else {
children.push(token(SyntaxKind::WHITESPACE, " "));
}
children.push(NodeOrToken::Node(node.green().into_owned()));
SyntaxNode::new_root_mut(GreenNode::new(SyntaxKind::TAGGED_NODE.into(), children))
}
pub(crate) fn replace_alias_with_copy(alias: &SyntaxNode, node: &SyntaxNode) -> SyntaxNode {
check_copyable(alias);
let (tag, _) = properties(alias);
let node = match node {
n if tag.is_some() && n.kind() == SyntaxKind::TAGGED_NODE => untagged(n),
n => n.clone(),
};
let tag = tag.map(|t| t.text().to_string());
strip_properties(alias);
match &tag {
Some(tag) => replace_alias(alias, &tagged(tag, &node), None),
None => replace_alias(alias, &node, None),
}
}
fn replace_alias(alias: &SyntaxNode, node: &SyntaxNode, anchor: Option<&str>) -> SyntaxNode {
assert!(
replaceable(alias),
"cannot replace alias {alias} with a copy"
);
let value = crate::as_yaml::YamlNode::from_syntax(node.clone())
.expect("an anchored node is a YAML node");
let parent = alias.parent().expect("an alias in a document has a parent");
replace_in_holder(&parent, value, anchor)
}
fn replace_in_holder(
parent: &SyntaxNode,
value: impl crate::AsYaml,
anchor: Option<&str>,
) -> SyntaxNode {
let container = parent.parent();
match parent.kind() {
SyntaxKind::VALUE => {
let entry = container
.and_then(super::MappingEntry::cast)
.expect("a VALUE is in a MAPPING_ENTRY");
let flow = entry
.syntax()
.parent()
.filter(|m| m.kind() == SyntaxKind::MAPPING)
.and_then(super::Mapping::cast)
.filter(|m| m.is_flow_style())
.map(|m| super::flow_quoting(m.syntax()));
match flow.and_then(|q| super::mapping::flow_form(&value, 2, q)) {
Some(flow_value) => entry.replace_value_under(flow_value, true, anchor),
None => entry.replace_value_under(value, flow.is_some(), anchor),
}
entry.value()
}
SyntaxKind::SEQUENCE_ENTRY => {
let sequence = container
.and_then(super::Sequence::cast)
.expect("a SEQUENCE_ENTRY is in a SEQUENCE");
let index = sequence
.syntax()
.children()
.filter(|n| n.kind() == SyntaxKind::SEQUENCE_ENTRY)
.position(|n| n == *parent)
.expect("the entry is in its sequence");
sequence.replace_item_under(index, value, anchor)
}
SyntaxKind::KEY => {
let entry = container.expect("a KEY is in a MAPPING_ENTRY");
let mapping = entry
.parent()
.and_then(super::Mapping::cast)
.expect("a MAPPING_ENTRY is in a MAPPING");
mapping.replace_key(&entry, value, anchor)
}
kind => unreachable!("cannot replace the content of a {kind:?}"),
}
}