use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::sync::Arc;
use crate::{
ByteRange, Link, LinkId, LinkMetadata, LinkNetwork, LinkType, ParseConfiguration,
tree_sitter_adapter,
};
impl LinkNetwork {
pub fn apply_edit(&mut self, range: ByteRange, replacement: &str) -> bool {
self.apply_edit_with_configuration(range, replacement, ParseConfiguration::default())
}
pub fn apply_edit_with_configuration(
&mut self,
range: ByteRange,
replacement: &str,
configuration: ParseConfiguration,
) -> bool {
let old_text = self.reconstruct_text();
let Some(edited_text) = apply_text_edit(&old_text, range, replacement) else {
return false;
};
let Some(language) = document_language(self).map(ToOwned::to_owned) else {
return false;
};
let reparsed = tree_sitter_adapter::parse_incremental(
&old_text,
range,
replacement,
&language,
configuration,
)
.unwrap_or_else(|| Self::parse(&edited_text, &language, configuration));
let edit = AppliedEdit::new(range, replacement.len());
*self = remap_reparsed_network(self, &reparsed, edit);
true
}
}
fn document_language(network: &LinkNetwork) -> Option<&str> {
network
.links()
.find(|link| link.metadata().link_type() == Some(LinkType::Document))
.and_then(|link| link.metadata().language())
}
fn remap_reparsed_network(
old: &LinkNetwork,
reparsed: &LinkNetwork,
edit: AppliedEdit,
) -> LinkNetwork {
let mut id_map = stable_id_map(old, reparsed, edit);
let mut used_targets = id_map.values().copied().collect::<BTreeSet<_>>();
let mut next_id = old.next_id.max(reparsed.next_id);
for link in reparsed.links() {
if id_map.contains_key(&link.id()) {
continue;
}
let target = if used_targets.contains(&link.id()) {
let fresh = next_unused_id(&mut next_id, &used_targets);
used_targets.insert(fresh);
fresh
} else {
used_targets.insert(link.id());
link.id()
};
id_map.insert(link.id(), target);
}
keep_tied_sibling_order(reparsed, &mut id_map, &mut used_targets, &mut next_id);
let mut links = BTreeMap::new();
for link in reparsed.links() {
let id = id_map[&link.id()];
let references = link
.references()
.iter()
.map(|reference| id_map[reference])
.collect::<Vec<_>>();
let candidate = Link {
id,
references: Arc::from(references),
metadata: link.metadata().clone(),
};
if let Some(shared) = old.links.get(&id)
&& shared.as_ref() == &candidate
{
links.insert(id, Arc::clone(shared));
continue;
}
links.insert(id, Arc::new(candidate));
}
let terms = reparsed
.terms
.iter()
.filter_map(|(term, id)| id_map.get(id).map(|mapped| (Arc::clone(term), *mapped)))
.collect();
let next_id = used_targets
.iter()
.map(|id| id.as_u64())
.max()
.map_or(1, |id| id + 1);
LinkNetwork {
next_id,
links,
terms,
concept_syntax: reparsed.concept_syntax.clone(),
strings: reparsed.strings.clone(),
}
}
fn stable_id_map(
old: &LinkNetwork,
reparsed: &LinkNetwork,
edit: AppliedEdit,
) -> BTreeMap<LinkId, LinkId> {
let mut old_links = OldLinkIndex::new(old, edit);
let mut mapped = BTreeMap::new();
for new_link in reparsed.links() {
let key = MatchKey::of(new_link.metadata());
let matched = match new_link.metadata().span() {
Some(span) => old_links.take_spanned(key, span.byte_range()),
None => old_links.take_unspanned(key, new_link),
};
if let Some(old_id) = matched {
mapped.insert(new_link.id(), old_id);
}
}
let mut pending = reparsed
.links()
.filter(|link| link.metadata().span().is_none() && !mapped.contains_key(&link.id()))
.collect::<Vec<_>>();
while !pending.is_empty() {
let remaining = pending.len();
let mut unmatched = Vec::with_capacity(remaining);
for new_link in pending {
let matched = new_link
.references()
.iter()
.map(|reference| mapped.get(reference).copied())
.collect::<Option<Vec<_>>>()
.and_then(|references| {
old_links.take_referencing(MatchKey::of(new_link.metadata()), references)
});
match matched {
Some(old_id) => {
mapped.insert(new_link.id(), old_id);
}
None => unmatched.push(new_link),
}
}
if unmatched.len() == remaining {
break;
}
pending = unmatched;
}
mapped
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct MatchKey<'a> {
link_type: Option<LinkType>,
named: bool,
term: Option<&'a str>,
definition: Option<&'a str>,
language: Option<&'a str>,
flags: (bool, bool, bool, bool),
}
impl<'a> MatchKey<'a> {
fn of(metadata: &'a LinkMetadata) -> Self {
let flags = metadata.flags();
Self {
link_type: metadata.link_type(),
named: metadata.is_named(),
term: metadata.term(),
definition: metadata.definition(),
language: metadata.language(),
flags: (
flags.is_error(),
flags.has_error(),
flags.is_missing(),
flags.is_extra(),
),
}
}
}
struct OldLinkIndex<'a> {
spanned: BTreeMap<(MatchKey<'a>, usize, usize), VecDeque<LinkId>>,
referencing: BTreeMap<(MatchKey<'a>, Vec<LinkId>), VecDeque<LinkId>>,
self_referencing: BTreeMap<MatchKey<'a>, VecDeque<LinkId>>,
used: BTreeSet<LinkId>,
}
impl<'a> OldLinkIndex<'a> {
fn new(old: &'a LinkNetwork, edit: AppliedEdit) -> Self {
let mut index = Self {
spanned: BTreeMap::new(),
referencing: BTreeMap::new(),
self_referencing: BTreeMap::new(),
used: BTreeSet::new(),
};
for link in old.links() {
let key = MatchKey::of(link.metadata());
if let Some(span) = link.metadata().span() {
if let Some(range) = edit.adjusted_range(span.byte_range()) {
index
.spanned
.entry((key, range.start(), range.end()))
.or_default()
.push_back(link.id());
}
continue;
}
index
.referencing
.entry((key, link.references().to_vec()))
.or_default()
.push_back(link.id());
if is_self_reference(link) {
index
.self_referencing
.entry(key)
.or_default()
.push_back(link.id());
}
}
index
}
fn take_spanned(&mut self, key: MatchKey<'a>, range: ByteRange) -> Option<LinkId> {
let matched = Self::front(
self.spanned.get_mut(&(key, range.start(), range.end())),
&self.used,
)?;
self.used.insert(matched);
Some(matched)
}
fn take_unspanned(&mut self, key: MatchKey<'a>, new_link: &Link) -> Option<LinkId> {
let by_references = Self::front(
self.referencing
.get_mut(&(key, new_link.references().to_vec())),
&self.used,
);
let by_self_reference = if is_self_reference(new_link) {
Self::front(self.self_referencing.get_mut(&key), &self.used)
} else {
None
};
let matched = by_references.into_iter().chain(by_self_reference).min()?;
self.used.insert(matched);
Some(matched)
}
fn take_referencing(&mut self, key: MatchKey<'a>, references: Vec<LinkId>) -> Option<LinkId> {
let matched = Self::front(self.referencing.get_mut(&(key, references)), &self.used)?;
self.used.insert(matched);
Some(matched)
}
fn front(group: Option<&mut VecDeque<LinkId>>, used: &BTreeSet<LinkId>) -> Option<LinkId> {
let group = group?;
while group.front().is_some_and(|id| used.contains(id)) {
group.pop_front();
}
group.front().copied()
}
}
fn apply_text_edit(old_text: &str, range: ByteRange, replacement: &str) -> Option<String> {
if range.end() > old_text.len()
|| !old_text.is_char_boundary(range.start())
|| !old_text.is_char_boundary(range.end())
{
return None;
}
let mut edited =
String::with_capacity(old_text.len() - (range.end() - range.start()) + replacement.len());
edited.push_str(&old_text[..range.start()]);
edited.push_str(replacement);
edited.push_str(&old_text[range.end()..]);
Some(edited)
}
fn keep_tied_sibling_order(
reparsed: &LinkNetwork,
id_map: &mut BTreeMap<LinkId, LinkId>,
used_targets: &mut BTreeSet<LinkId>,
next_id: &mut u64,
) {
let mut ties: BTreeMap<(Vec<LinkId>, usize, usize), Vec<LinkId>> = BTreeMap::new();
for link in reparsed.links() {
if let Some(span) = link.metadata().span() {
let range = span.byte_range();
ties.entry((link.references().to_vec(), range.start(), range.end()))
.or_default()
.push(link.id());
}
}
for group in ties.values().filter(|group| group.len() > 1) {
let mut last: Option<LinkId> = None;
for id in group {
let target = id_map[id];
if last.is_some_and(|last| target <= last) {
let fresh = next_unused_id(next_id, used_targets);
used_targets.insert(fresh);
id_map.insert(*id, fresh);
last = Some(fresh);
} else {
last = Some(target);
}
}
}
}
fn next_unused_id(next_id: &mut u64, used_targets: &BTreeSet<LinkId>) -> LinkId {
loop {
let candidate = LinkId(*next_id);
*next_id += 1;
if !used_targets.contains(&candidate) {
return candidate;
}
}
}
fn is_self_reference(link: &Link) -> bool {
link.references() == [link.id()]
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct AppliedEdit {
old_start: usize,
old_end: usize,
new_end: usize,
}
impl AppliedEdit {
const fn new(range: ByteRange, replacement_len: usize) -> Self {
Self {
old_start: range.start(),
old_end: range.end(),
new_end: range.start() + replacement_len,
}
}
const fn adjusted_range(self, range: ByteRange) -> Option<ByteRange> {
if range.end() <= self.old_start {
return Some(range);
}
if range.start() < self.old_end {
return None;
}
let start = shift_byte(range.start(), self.old_end, self.new_end);
let end = shift_byte(range.end(), self.old_end, self.new_end);
Some(ByteRange::new(start, end))
}
}
const fn shift_byte(byte: usize, old_end: usize, new_end: usize) -> usize {
if new_end >= old_end {
byte + (new_end - old_end)
} else {
byte - (old_end - new_end)
}
}