use super::{Document, RepairScope, entry_indent_column, walk_tokens};
use crate::cst::builder::{SubtreeContext, parse_subtree, rebuild_with_splice};
use crate::cst::green::{GreenChild, GreenNode};
use crate::cst::syntax::SyntaxKind;
use crate::error::{Error, Result};
use crate::prelude::*;
impl Document {
pub fn replace_span(&mut self, start: usize, end: usize, replacement: &str) -> Result<()> {
#[cfg(test)]
if super::fault::splice_should_fail() {
return Err(Error::Parse("injected splice failure".into()));
}
if start > end || end > self.source.len() {
return Err(Error::Parse(format!(
"replace_span range {start}..{end} out of bounds (source length {})",
self.source.len()
)));
}
if !self.source.is_char_boundary(start) || !self.source.is_char_boundary(end) {
return Err(Error::Parse(format!(
"replace_span range {start}..{end} is not a character boundary"
)));
}
let mut new_source =
String::with_capacity(self.source.len() - (end - start) + replacement.len());
new_source.push_str(&self.source[..start]);
new_source.push_str(replacement);
new_source.push_str(&self.source[end..]);
let new_arc: Arc<str> = Arc::from(new_source.as_str());
if let Some((new_green, scope)) =
self.try_local_repair_green(start, end, replacement, &new_source)
{
let parsed = crate::parser::parse_exactly_one(&new_source, &self.config)?;
self.last_repair_scope.set(Some(scope));
self.source = new_arc;
self.green = new_green;
let _ = self.cache.replace(Some(parsed));
return Ok(());
}
self.commit_source(&new_source, RepairScope::Document)
}
fn try_local_repair_green(
&self,
start: usize,
end: usize,
replacement: &str,
new_source: &str,
) -> Option<(GreenNode, RepairScope)> {
if region_has_anchor_alias_or_tag(&self.green, start, end)
|| replacement_introduces_anchor_alias_or_tag(replacement)
{
return None;
}
let delta = replacement.len() as isize - (end as isize - start as isize);
let candidates = ancestor_candidates(&self.green, start, end);
if candidates
.iter()
.any(|c| matches!(c.kind, SyntaxKind::FlowMapping | SyntaxKind::FlowSequence))
{
return None;
}
for cand in &candidates {
if !is_phase_a_repairable(cand.kind) {
continue;
}
let n_old_start = cand.start;
let n_old_end = cand.end;
let n_new_start = n_old_start; let n_new_end_signed = n_old_end as isize + delta;
if n_new_end_signed < n_new_start as isize {
continue;
}
let n_new_end = n_new_end_signed as usize;
if n_new_end > new_source.len() {
continue;
}
let fragment = &new_source[n_new_start..n_new_end];
let indent = entry_indent_column(&self.source, n_old_start);
let ctx = SubtreeContext::block_at(indent);
match parse_subtree(fragment, ctx, cand.kind) {
Ok(new_sub)
if new_sub.kind() == cand.kind && new_sub.text_len() == fragment.len() =>
{
let new_root =
rebuild_with_splice(&self.green, n_old_start, n_old_end, new_sub);
return Some((new_root, scope_for_kind(cand.kind)));
}
Ok(_) | Err(_) => {
continue;
}
}
}
None
}
}
fn scope_for_kind(kind: SyntaxKind) -> RepairScope {
match kind {
SyntaxKind::MappingEntry | SyntaxKind::SequenceItem => RepairScope::Entry,
SyntaxKind::BlockMapping
| SyntaxKind::BlockSequence
| SyntaxKind::FlowMapping
| SyntaxKind::FlowSequence => RepairScope::Collection,
_ => RepairScope::Document,
}
}
fn is_phase_a_repairable(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::BlockMapping
| SyntaxKind::BlockSequence
| SyntaxKind::MappingEntry
| SyntaxKind::SequenceItem
)
}
struct Candidate {
kind: SyntaxKind,
start: usize,
end: usize,
}
fn ancestor_candidates(root: &GreenNode, start: usize, end: usize) -> Vec<Candidate> {
let mut out = Vec::new();
collect_ancestors(root, start, end, 0, &mut out);
out.reverse();
out
}
fn collect_ancestors(
node: &GreenNode,
start: usize,
end: usize,
base: usize,
out: &mut Vec<Candidate>,
) {
let node_end = base + node.text_len();
if start >= base && end <= node_end {
out.push(Candidate {
kind: node.kind(),
start: base,
end: node_end,
});
let mut pos = base;
for child in node.children() {
let len = child.text_len();
let child_end = pos + len;
if start >= pos && end <= child_end {
if let GreenChild::Node(inner) = child {
collect_ancestors(inner, start, end, pos, out);
}
break;
}
pos += len;
}
}
}
fn region_has_anchor_alias_or_tag(root: &GreenNode, start: usize, end: usize) -> bool {
let mut found = false;
walk_tokens(root, 0, &mut |kind, range| {
if range.start >= end || range.end <= start {
return; }
if matches!(
kind,
SyntaxKind::AnchorMark | SyntaxKind::AliasMark | SyntaxKind::TagMark
) {
found = true;
}
});
found
}
fn replacement_introduces_anchor_alias_or_tag(replacement: &str) -> bool {
replacement.bytes().any(|b| matches!(b, b'&' | b'*' | b'!'))
}