1use std::collections::{BTreeMap, BTreeSet, VecDeque};
2use std::sync::Arc;
3
4use crate::{
5 ByteRange, Link, LinkId, LinkMetadata, LinkNetwork, LinkType, ParseConfiguration,
6 tree_sitter_adapter,
7};
8
9impl LinkNetwork {
10 pub fn apply_edit(&mut self, range: ByteRange, replacement: &str) -> bool {
16 self.apply_edit_with_configuration(range, replacement, ParseConfiguration::default())
17 }
18
19 pub fn apply_edit_with_configuration(
24 &mut self,
25 range: ByteRange,
26 replacement: &str,
27 configuration: ParseConfiguration,
28 ) -> bool {
29 let old_text = self.reconstruct_text();
30 let Some(edited_text) = apply_text_edit(&old_text, range, replacement) else {
31 return false;
32 };
33 let Some(language) = document_language(self).map(ToOwned::to_owned) else {
34 return false;
35 };
36
37 let reparsed = tree_sitter_adapter::parse_incremental(
38 &old_text,
39 range,
40 replacement,
41 &language,
42 configuration,
43 )
44 .unwrap_or_else(|| Self::parse(&edited_text, &language, configuration));
45
46 let edit = AppliedEdit::new(range, replacement.len());
47 *self = remap_reparsed_network(self, &reparsed, edit);
48 true
49 }
50}
51
52fn document_language(network: &LinkNetwork) -> Option<&str> {
53 network
54 .links()
55 .find(|link| link.metadata().link_type() == Some(LinkType::Document))
56 .and_then(|link| link.metadata().language())
57}
58
59fn remap_reparsed_network(
60 old: &LinkNetwork,
61 reparsed: &LinkNetwork,
62 edit: AppliedEdit,
63) -> LinkNetwork {
64 let mut id_map = stable_id_map(old, reparsed, edit);
65 let mut used_targets = id_map.values().copied().collect::<BTreeSet<_>>();
66 let mut next_id = old.next_id.max(reparsed.next_id);
67
68 for link in reparsed.links() {
69 if id_map.contains_key(&link.id()) {
70 continue;
71 }
72
73 let target = if used_targets.contains(&link.id()) {
74 let fresh = next_unused_id(&mut next_id, &used_targets);
75 used_targets.insert(fresh);
76 fresh
77 } else {
78 used_targets.insert(link.id());
79 link.id()
80 };
81 id_map.insert(link.id(), target);
82 }
83 keep_tied_sibling_order(reparsed, &mut id_map, &mut used_targets, &mut next_id);
84
85 let mut links = BTreeMap::new();
86 for link in reparsed.links() {
87 let id = id_map[&link.id()];
88 let references = link
89 .references()
90 .iter()
91 .map(|reference| id_map[reference])
92 .collect::<Vec<_>>();
93 let candidate = Link {
94 id,
95 references: Arc::from(references),
96 metadata: link.metadata().clone(),
97 };
98
99 if let Some(shared) = old.links.get(&id)
100 && shared.as_ref() == &candidate
101 {
102 links.insert(id, Arc::clone(shared));
103 continue;
104 }
105
106 links.insert(id, Arc::new(candidate));
107 }
108
109 let terms = reparsed
110 .terms
111 .iter()
112 .filter_map(|(term, id)| id_map.get(id).map(|mapped| (Arc::clone(term), *mapped)))
113 .collect();
114 let next_id = used_targets
115 .iter()
116 .map(|id| id.as_u64())
117 .max()
118 .map_or(1, |id| id + 1);
119
120 LinkNetwork {
121 next_id,
122 links,
123 terms,
124 concept_syntax: reparsed.concept_syntax.clone(),
125 strings: reparsed.strings.clone(),
126 }
127}
128
129fn stable_id_map(
130 old: &LinkNetwork,
131 reparsed: &LinkNetwork,
132 edit: AppliedEdit,
133) -> BTreeMap<LinkId, LinkId> {
134 let mut old_links = OldLinkIndex::new(old, edit);
135 let mut mapped = BTreeMap::new();
136
137 for new_link in reparsed.links() {
138 let key = MatchKey::of(new_link.metadata());
139 let matched = match new_link.metadata().span() {
140 Some(span) => old_links.take_spanned(key, span.byte_range()),
141 None => old_links.take_unspanned(key, new_link),
142 };
143 if let Some(old_id) = matched {
144 mapped.insert(new_link.id(), old_id);
145 }
146 }
147
148 let mut pending = reparsed
152 .links()
153 .filter(|link| link.metadata().span().is_none() && !mapped.contains_key(&link.id()))
154 .collect::<Vec<_>>();
155 while !pending.is_empty() {
156 let remaining = pending.len();
157 let mut unmatched = Vec::with_capacity(remaining);
158 for new_link in pending {
159 let matched = new_link
160 .references()
161 .iter()
162 .map(|reference| mapped.get(reference).copied())
163 .collect::<Option<Vec<_>>>()
164 .and_then(|references| {
165 old_links.take_referencing(MatchKey::of(new_link.metadata()), references)
166 });
167 match matched {
168 Some(old_id) => {
169 mapped.insert(new_link.id(), old_id);
170 }
171 None => unmatched.push(new_link),
172 }
173 }
174 if unmatched.len() == remaining {
175 break;
176 }
177 pending = unmatched;
178 }
179
180 mapped
181}
182
183#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
186struct MatchKey<'a> {
187 link_type: Option<LinkType>,
188 named: bool,
189 term: Option<&'a str>,
190 definition: Option<&'a str>,
191 language: Option<&'a str>,
192 flags: (bool, bool, bool, bool),
195}
196
197impl<'a> MatchKey<'a> {
198 fn of(metadata: &'a LinkMetadata) -> Self {
199 let flags = metadata.flags();
200 Self {
201 link_type: metadata.link_type(),
202 named: metadata.is_named(),
203 term: metadata.term(),
204 definition: metadata.definition(),
205 language: metadata.language(),
206 flags: (
207 flags.is_error(),
208 flags.has_error(),
209 flags.is_missing(),
210 flags.is_extra(),
211 ),
212 }
213 }
214}
215
216struct OldLinkIndex<'a> {
225 spanned: BTreeMap<(MatchKey<'a>, usize, usize), VecDeque<LinkId>>,
227 referencing: BTreeMap<(MatchKey<'a>, Vec<LinkId>), VecDeque<LinkId>>,
229 self_referencing: BTreeMap<MatchKey<'a>, VecDeque<LinkId>>,
231 used: BTreeSet<LinkId>,
233}
234
235impl<'a> OldLinkIndex<'a> {
236 fn new(old: &'a LinkNetwork, edit: AppliedEdit) -> Self {
237 let mut index = Self {
238 spanned: BTreeMap::new(),
239 referencing: BTreeMap::new(),
240 self_referencing: BTreeMap::new(),
241 used: BTreeSet::new(),
242 };
243
244 for link in old.links() {
245 let key = MatchKey::of(link.metadata());
246 if let Some(span) = link.metadata().span() {
247 if let Some(range) = edit.adjusted_range(span.byte_range()) {
249 index
250 .spanned
251 .entry((key, range.start(), range.end()))
252 .or_default()
253 .push_back(link.id());
254 }
255 continue;
256 }
257
258 index
259 .referencing
260 .entry((key, link.references().to_vec()))
261 .or_default()
262 .push_back(link.id());
263 if is_self_reference(link) {
264 index
265 .self_referencing
266 .entry(key)
267 .or_default()
268 .push_back(link.id());
269 }
270 }
271
272 index
273 }
274
275 fn take_spanned(&mut self, key: MatchKey<'a>, range: ByteRange) -> Option<LinkId> {
277 let matched = Self::front(
278 self.spanned.get_mut(&(key, range.start(), range.end())),
279 &self.used,
280 )?;
281 self.used.insert(matched);
282 Some(matched)
283 }
284
285 fn take_unspanned(&mut self, key: MatchKey<'a>, new_link: &Link) -> Option<LinkId> {
289 let by_references = Self::front(
290 self.referencing
291 .get_mut(&(key, new_link.references().to_vec())),
292 &self.used,
293 );
294 let by_self_reference = if is_self_reference(new_link) {
295 Self::front(self.self_referencing.get_mut(&key), &self.used)
296 } else {
297 None
298 };
299
300 let matched = by_references.into_iter().chain(by_self_reference).min()?;
303 self.used.insert(matched);
304 Some(matched)
305 }
306
307 fn take_referencing(&mut self, key: MatchKey<'a>, references: Vec<LinkId>) -> Option<LinkId> {
310 let matched = Self::front(self.referencing.get_mut(&(key, references)), &self.used)?;
311 self.used.insert(matched);
312 Some(matched)
313 }
314
315 fn front(group: Option<&mut VecDeque<LinkId>>, used: &BTreeSet<LinkId>) -> Option<LinkId> {
317 let group = group?;
318 while group.front().is_some_and(|id| used.contains(id)) {
319 group.pop_front();
320 }
321 group.front().copied()
322 }
323}
324
325fn apply_text_edit(old_text: &str, range: ByteRange, replacement: &str) -> Option<String> {
326 if range.end() > old_text.len()
327 || !old_text.is_char_boundary(range.start())
328 || !old_text.is_char_boundary(range.end())
329 {
330 return None;
331 }
332
333 let mut edited =
334 String::with_capacity(old_text.len() - (range.end() - range.start()) + replacement.len());
335 edited.push_str(&old_text[..range.start()]);
336 edited.push_str(replacement);
337 edited.push_str(&old_text[range.end()..]);
338 Some(edited)
339}
340
341fn keep_tied_sibling_order(
346 reparsed: &LinkNetwork,
347 id_map: &mut BTreeMap<LinkId, LinkId>,
348 used_targets: &mut BTreeSet<LinkId>,
349 next_id: &mut u64,
350) {
351 let mut ties: BTreeMap<(Vec<LinkId>, usize, usize), Vec<LinkId>> = BTreeMap::new();
352 for link in reparsed.links() {
353 if let Some(span) = link.metadata().span() {
354 let range = span.byte_range();
355 ties.entry((link.references().to_vec(), range.start(), range.end()))
356 .or_default()
357 .push(link.id());
358 }
359 }
360 for group in ties.values().filter(|group| group.len() > 1) {
361 let mut last: Option<LinkId> = None;
362 for id in group {
363 let target = id_map[id];
364 if last.is_some_and(|last| target <= last) {
365 let fresh = next_unused_id(next_id, used_targets);
366 used_targets.insert(fresh);
367 id_map.insert(*id, fresh);
368 last = Some(fresh);
369 } else {
370 last = Some(target);
371 }
372 }
373 }
374}
375
376fn next_unused_id(next_id: &mut u64, used_targets: &BTreeSet<LinkId>) -> LinkId {
377 loop {
378 let candidate = LinkId(*next_id);
379 *next_id += 1;
380 if !used_targets.contains(&candidate) {
381 return candidate;
382 }
383 }
384}
385
386fn is_self_reference(link: &Link) -> bool {
387 link.references() == [link.id()]
388}
389
390#[derive(Clone, Copy, Debug, PartialEq, Eq)]
391struct AppliedEdit {
392 old_start: usize,
393 old_end: usize,
394 new_end: usize,
395}
396
397impl AppliedEdit {
398 const fn new(range: ByteRange, replacement_len: usize) -> Self {
399 Self {
400 old_start: range.start(),
401 old_end: range.end(),
402 new_end: range.start() + replacement_len,
403 }
404 }
405
406 const fn adjusted_range(self, range: ByteRange) -> Option<ByteRange> {
407 if range.end() <= self.old_start {
408 return Some(range);
409 }
410 if range.start() < self.old_end {
411 return None;
412 }
413
414 let start = shift_byte(range.start(), self.old_end, self.new_end);
415 let end = shift_byte(range.end(), self.old_end, self.new_end);
416 Some(ByteRange::new(start, end))
417 }
418}
419
420const fn shift_byte(byte: usize, old_end: usize, new_end: usize) -> usize {
421 if new_end >= old_end {
422 byte + (new_end - old_end)
423 } else {
424 byte - (old_end - new_end)
425 }
426}