noyalib/cst/document/
edit.rs1use super::{Document, RepairScope, entry_indent_column, walk_tokens};
7use crate::cst::builder::{
8 SubtreeContext, check_document_length, parse_subtree, rebuild_with_splice,
9};
10use crate::cst::green::{GreenChild, GreenNode};
11use crate::cst::syntax::SyntaxKind;
12use crate::error::{Error, Result};
13use crate::prelude::*;
14
15impl Document {
16 pub fn replace_span(&mut self, start: usize, end: usize, replacement: &str) -> Result<()> {
39 #[cfg(test)]
40 if super::fault::splice_should_fail() {
41 return Err(Error::Parse("injected splice failure".into()));
42 }
43 if start > end || end > self.source.len() {
44 return Err(Error::Parse(format!(
45 "replace_span range {start}..{end} out of bounds (source length {})",
46 self.source.len()
47 )));
48 }
49 if !self.source.is_char_boundary(start) || !self.source.is_char_boundary(end) {
50 return Err(Error::Parse(format!(
51 "replace_span range {start}..{end} is not a character boundary"
52 )));
53 }
54 let mut new_source =
55 String::with_capacity(self.source.len() - (end - start) + replacement.len());
56 new_source.push_str(&self.source[..start]);
57 new_source.push_str(replacement);
58 new_source.push_str(&self.source[end..]);
59 check_document_length(&new_source, &self.config)?;
60
61 let new_arc: Arc<str> = Arc::from(new_source.as_str());
66 if let Some((new_green, scope)) =
67 self.try_local_repair_green(start, end, replacement, &new_source)
68 {
69 let parsed = crate::parser::parse_exactly_one(&new_source, &self.config)?;
70 crate::policy::check_document(&self.config.policies, &parsed.0)?;
71 self.last_repair_scope.set(Some(scope));
72 self.source = new_arc;
73 self.green = new_green;
74 let _ = self.cache.replace(Some(parsed));
75 return Ok(());
76 }
77
78 self.commit_source(&new_source, RepairScope::Document)
81 }
82
83 fn try_local_repair_green(
89 &self,
90 start: usize,
91 end: usize,
92 replacement: &str,
93 new_source: &str,
94 ) -> Option<(GreenNode, RepairScope)> {
95 if region_has_anchor_alias_or_tag(&self.green, start, end)
99 || replacement_introduces_anchor_alias_or_tag(replacement)
100 {
101 return None;
102 }
103
104 let delta = replacement.len() as isize - (end as isize - start as isize);
105 let candidates = ancestor_candidates(&self.green, start, end);
106
107 if candidates
113 .iter()
114 .any(|c| matches!(c.kind, SyntaxKind::FlowMapping | SyntaxKind::FlowSequence))
115 {
116 return None;
117 }
118
119 for cand in &candidates {
120 if !is_phase_a_repairable(cand.kind) {
125 continue;
126 }
127
128 let n_old_start = cand.start;
129 let n_old_end = cand.end;
130 let n_new_start = n_old_start; let n_new_end_signed = n_old_end as isize + delta;
132 if n_new_end_signed < n_new_start as isize {
133 continue;
134 }
135 let n_new_end = n_new_end_signed as usize;
136 if n_new_end > new_source.len() {
138 continue;
139 }
140 let fragment = &new_source[n_new_start..n_new_end];
141 let indent = entry_indent_column(&self.source, n_old_start);
142 let ctx = SubtreeContext::block_at(indent);
143
144 match parse_subtree(fragment, ctx, cand.kind, self.config.max_depth) {
145 Ok(new_sub)
146 if new_sub.kind() == cand.kind && new_sub.text_len() == fragment.len() =>
147 {
148 let new_root =
149 rebuild_with_splice(&self.green, n_old_start, n_old_end, new_sub)?;
150 return Some((new_root, scope_for_kind(cand.kind)));
151 }
152 Ok(_) | Err(_) => {
153 continue;
158 }
159 }
160 }
161 None
162 }
163}
164
165fn scope_for_kind(kind: SyntaxKind) -> RepairScope {
166 match kind {
167 SyntaxKind::MappingEntry | SyntaxKind::SequenceItem => RepairScope::Entry,
168 SyntaxKind::BlockMapping
169 | SyntaxKind::BlockSequence
170 | SyntaxKind::FlowMapping
171 | SyntaxKind::FlowSequence => RepairScope::Collection,
172 _ => RepairScope::Document,
173 }
174}
175
176fn is_phase_a_repairable(kind: SyntaxKind) -> bool {
177 matches!(
178 kind,
179 SyntaxKind::BlockMapping
180 | SyntaxKind::BlockSequence
181 | SyntaxKind::MappingEntry
182 | SyntaxKind::SequenceItem
183 )
184}
185
186struct Candidate {
188 kind: SyntaxKind,
189 start: usize,
190 end: usize,
191}
192
193fn ancestor_candidates(root: &GreenNode, start: usize, end: usize) -> Vec<Candidate> {
198 let mut out = Vec::new();
199 collect_ancestors(root, start, end, 0, &mut out);
200 out.reverse();
203 out
204}
205
206fn collect_ancestors(
207 node: &GreenNode,
208 start: usize,
209 end: usize,
210 base: usize,
211 out: &mut Vec<Candidate>,
212) {
213 let node_end = base + node.text_len();
214 if start >= base && end <= node_end {
215 out.push(Candidate {
217 kind: node.kind(),
218 start: base,
219 end: node_end,
220 });
221 let mut pos = base;
223 for child in node.children() {
224 let len = child.text_len();
225 let child_end = pos + len;
226 if start >= pos && end <= child_end {
227 if let GreenChild::Node(inner) = child {
228 collect_ancestors(inner, start, end, pos, out);
229 }
230 break;
231 }
232 pos += len;
233 }
234 }
235}
236
237fn region_has_anchor_alias_or_tag(root: &GreenNode, start: usize, end: usize) -> bool {
242 let mut found = false;
243 walk_tokens(root, 0, &mut |kind, range| {
244 if range.start >= end || range.end <= start {
245 return; }
247 if matches!(
248 kind,
249 SyntaxKind::AnchorMark | SyntaxKind::AliasMark | SyntaxKind::TagMark
250 ) {
251 found = true;
252 }
253 });
254 found
255}
256
257fn replacement_introduces_anchor_alias_or_tag(replacement: &str) -> bool {
261 replacement.bytes().any(|b| matches!(b, b'&' | b'*' | b'!'))
262}