use super::blocks::Marks;
use super::source::START;
use super::{Converter, Labels, ParseOptions, Parsed, Source, frontmatter, parse};
use crate::diagnostic::Diagnostic;
use crate::lines::{LineStarts, Place};
use crate::tree::{Move, Moved, Node, Point, Root, Shift};
use std::collections::BTreeSet;
use std::ops;
use std::rc::Rc;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TextEdit {
pub start: usize,
pub end: usize,
pub text: String,
}
#[derive(Clone, Debug, PartialEq)]
pub(super) struct Unit {
start: Point,
end: Point,
nodes: usize,
diagnostics: usize,
labels: Labels,
}
impl Unit {
pub(super) fn from_marks(marks: &Marks, children: &[Node], diagnostics: usize) -> Vec<Self> {
let starts = &marks.starts;
starts
.iter()
.enumerate()
.map(|(index, mark)| {
let next = starts.get(index + 1);
let nodes = next.map_or(children.len(), |next| next.nodes);
Self {
start: mark.start,
end: next.map_or(marks.end, |next| next.after),
nodes: nodes - mark.nodes,
diagnostics: next.map_or(diagnostics, |next| next.diagnostics)
- mark.diagnostics,
labels: Labels::of(&children[mark.nodes..nodes]),
}
})
.collect()
}
fn shift(&mut self, by: Move) {
self.start.shift(by);
self.end.shift(by);
}
}
impl Labels {
fn of(nodes: &[Node]) -> Self {
let mut labels = Self::default();
labels.collect_tree(nodes);
labels
}
fn collect_tree(&mut self, nodes: &[Node]) {
for node in nodes {
match node {
Node::Definition(definition) => self.links.push(definition.identifier.clone()),
Node::FootnoteDefinition(definition) => {
self.footnotes.push(definition.identifier.clone());
}
Node::ComponentContainer(container) => {
for slot in &container.slots {
self.collect_tree(&slot.children);
}
}
_ => {}
}
if !matches!(node, Node::ComponentDefinition(_)) {
self.collect_tree(node.children());
}
}
}
fn extend(&mut self, other: &Self) {
self.links.extend_from_slice(&other.links);
self.footnotes.extend_from_slice(&other.footnotes);
}
fn of_units(units: &[Unit]) -> Self {
let mut labels = Self::default();
for unit in units {
labels.extend(&unit.labels);
}
labels
}
fn same(&self, other: &Self) -> bool {
same_set(&self.links, &other.links) && same_set(&self.footnotes, &other.footnotes)
}
}
fn same_set(one: &[String], other: &[String]) -> bool {
one.iter().collect::<BTreeSet<_>>() == other.iter().collect::<BTreeSet<_>>()
}
enum Attempt {
Done(Box<Slice>),
Grow,
Whole,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Change {
Whole,
Blocks(Spliced),
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct Spliced {
pub(crate) blocks: ops::Range<usize>,
pub(crate) inserted: usize,
pub(crate) moved: Moved,
}
pub(crate) fn reparse(
previous: Parsed,
text: &str,
lines: &mut LineStarts,
edit: &TextEdit,
options: &ParseOptions,
) -> (String, Parsed, Change) {
let (new_text, bom, edit) = apply(text, lines, edit);
let (old, new) = (&text[bom..], &new_text[bom..]);
let slice = Slicer {
previous: &previous,
old,
new,
lines,
edit: edit.clone(),
}
.parse(None);
lines.edit(
edit.from,
edit.to,
&new[edit.from.byte..edit.from.byte + edit.written],
);
if let Some(slice) = slice {
let change = slice.change(&previous, &edit);
return (new_text, slice.splice_into(previous), change);
}
let parsed = parse(&new_text, options);
let change = whole_change(&previous, &parsed, old, new, &edit);
(new_text, parsed, change)
}
fn whole_change(previous: &Parsed, parsed: &Parsed, old: &str, new: &str, edit: &Edit) -> Change {
let frontmatter = |parsed: &Parsed| {
parsed
.root
.children
.first()
.filter(|node| matches!(node, Node::Yaml(_)))
.cloned()
};
if previous.settings != parsed.settings
|| previous.syntax != parsed.syntax
|| frontmatter(previous) != frontmatter(parsed)
{
return Change::Whole;
}
let by = Move {
lines: line_endings(new).cast_signed() - line_endings(old).cast_signed(),
offset: edit.offset,
};
let old = Run::new(&previous.units, &previous.root.children);
let new = Run::new(&parsed.units, &parsed.root.children);
change(0, &old, &new, None, edit, by)
}
struct Run<'p> {
units: &'p [Unit],
nodes: &'p [Node],
starts: Vec<usize>,
}
impl<'p> Run<'p> {
fn new(units: &'p [Unit], nodes: &'p [Node]) -> Self {
let starts = std::iter::once(0)
.chain(units.iter().scan(0, |start, unit| {
*start += unit.nodes;
Some(*start)
}))
.collect();
Self {
units,
nodes,
starts,
}
}
fn blocks(&self, units: ops::Range<usize>) -> &'p [Node] {
&self.nodes[self.starts[units.start]..self.starts[units.end]]
}
fn moved_is(&self, index: usize, by: Move, new: &Self, other: usize) -> bool {
let mut unit = self.units[index].clone();
unit.shift(by);
let mut blocks = self.blocks(index..index + 1).to_vec();
blocks.shift(by);
unit == new.units[other] && blocks == new.blocks(other..other + 1)
}
}
fn change(
first: usize,
old: &Run,
new: &Run,
after: Option<Point>,
edit: &Edit,
by: Move,
) -> Change {
let shortest = old.units.len().min(new.units.len());
let head = (0..shortest)
.take_while(|&index| {
old.units[index].end.offset <= edit.start
&& old.units[index] == new.units[index]
&& old.blocks(index..index + 1) == new.blocks(index..index + 1)
})
.count();
let tail = (0..shortest - head)
.take_while(|&back| {
let (index, other) = (old.units.len() - 1 - back, new.units.len() - 1 - back);
old.units[index].start.offset >= edit.end && old.moved_is(index, by, new, other)
})
.count();
let removed = head..old.units.len() - tail;
let inserted = head..new.units.len() - tail;
let defines = |nodes: &[Node]| {
nodes.iter().any(|node| {
matches!(
node,
Node::ComponentDefinition(_) | Node::ComponentSignature(_)
)
})
};
if defines(old.blocks(removed.clone())) || defines(new.blocks(inserted.clone())) {
return Change::Whole;
}
let from = old.units.get(removed.end).map(|unit| unit.start).or(after);
Change::Blocks(Spliced {
blocks: first + old.starts[removed.start]..first + old.starts[removed.end],
inserted: new.starts[inserted.end] - new.starts[inserted.start],
moved: from.map_or(Moved::NOTHING, |from| Moved { from, by }),
})
}
fn apply(text: &str, lines: &LineStarts, edit: &TextEdit) -> (String, usize, Edit) {
let bom = if text.starts_with('\u{feff}') {
"\u{feff}"
} else {
""
};
let body = &text[bom.len()..];
let to = lines.place(body, edit.end);
let from = lines.place(body, edit.start.min(to.offset));
let new_text = [bom, &body[..from.byte], &edit.text, &body[to.byte..]].concat();
let applied = Edit {
start: from.offset,
end: to.offset,
offset: edit.text.chars().count().cast_signed() - (to.offset - from.offset).cast_signed(),
from,
to,
written: edit.text.len(),
};
(new_text, bom.len(), applied)
}
#[derive(Clone)]
struct Edit {
start: usize,
end: usize,
offset: isize,
from: Place,
to: Place,
written: usize,
}
impl Edit {
fn byte_after(&self, place: Place) -> usize {
place.byte + self.written - (self.to.byte - self.from.byte)
}
}
#[derive(Debug)]
#[cfg_attr(
not(test),
allow(dead_code, reason = "the tests read it to explain a parse")
)]
struct Tried {
first: usize,
last: usize,
outcome: &'static str,
}
struct Slicer<'a> {
previous: &'a Parsed,
old: &'a str,
new: &'a str,
lines: &'a LineStarts,
edit: Edit,
}
impl Slicer<'_> {
fn parse(&self, mut tried: Option<&mut Vec<Tried>>) -> Option<Slice> {
let units = &self.previous.units;
let last = units.len().checked_sub(1)?;
let frontmatter = matches!(self.previous.root.children.first(), Some(Node::Yaml(_)));
if !frontmatter && frontmatter::find(self.new).is_some() {
return None;
}
let (start, end) = (self.edit.start, self.edit.end);
let touched_first = units
.partition_point(|unit| unit.end.offset < start)
.min(last);
let touched_last = units
.partition_point(|unit| unit.start.offset <= end)
.saturating_sub(1);
let low = touched_first.min(touched_last);
let high = touched_first.max(touched_last);
let first = low.saturating_sub(1);
if frontmatter && first == 0 {
return None;
}
let mut after = 1;
loop {
let last_unit = (high + after).min(last);
let attempt = self.attempt(first, last_unit);
if let Some(tried) = tried.as_deref_mut() {
tried.push(Tried {
first,
last: last_unit,
outcome: match attempt {
Attempt::Done(_) => "done",
Attempt::Grow => "grow",
Attempt::Whole => "whole",
},
});
}
match attempt {
Attempt::Done(slice) => return Some(*slice),
Attempt::Whole => return None,
Attempt::Grow => after *= 2,
}
}
}
fn attempt(&self, first: usize, last: usize) -> Attempt {
let previous = self.previous;
let units = &previous.units;
let to_end = last + 1 == units.len();
let from = if first == 0 {
START
} else {
units[first - 1].end
};
let start = self.lines.place(self.old, from.offset).byte;
let old_to = if to_end {
self.lines.place(self.old, usize::MAX)
} else {
self.lines.place(self.old, units[last + 1].start.offset)
};
let old_slice = &self.old[start..old_to.byte];
let new_slice = &self.new[start..self.edit.byte_after(old_to)];
let by = Move {
lines: line_endings(new_slice).cast_signed() - line_endings(old_slice).cast_signed(),
offset: self.edit.offset,
};
let mut outside = Labels::of_units(&units[..first]);
outside.extend(&Labels::of_units(&units[last + 1..]));
let mut diagnostics = Vec::new();
let (root, new_units) = Converter {
source: Source::new(new_slice, from),
syntax: previous.syntax,
labels: &outside,
frontmatter: None,
diagnostics: &mut diagnostics,
}
.units(first == 0);
if !Labels::of_units(&new_units).same(&Labels::of_units(&units[first..=last])) {
return Attempt::Whole;
}
let slice = Slice {
first,
last,
by,
to_end,
root,
diagnostics,
units: new_units,
};
if !to_end && !slice.ends_as(previous) {
return Attempt::Grow;
}
Attempt::Done(Box::new(slice))
}
}
struct Slice {
first: usize,
last: usize,
by: Move,
to_end: bool,
root: Root,
diagnostics: Vec<Diagnostic>,
units: Vec<Unit>,
}
impl Slice {
fn change(&self, previous: &Parsed, edit: &Edit) -> Change {
let (nodes, _) = Self::kept(previous, self.first, self.last + 1);
let old = Run::new(
&previous.units[self.first..=self.last],
&previous.root.children[nodes.clone()],
);
let new = Run::new(&self.units, &self.root.children);
let after = previous.units.get(self.last + 1).map(|unit| unit.start);
change(nodes.start, &old, &new, after, edit, self.by)
}
fn kept(
previous: &Parsed,
first: usize,
last: usize,
) -> (ops::Range<usize>, ops::Range<usize>) {
let units = &previous.units;
let nodes = |units: &[Unit]| units.iter().map(|unit| unit.nodes).sum::<usize>();
let found = |units: &[Unit]| units.iter().map(|unit| unit.diagnostics).sum::<usize>();
let head = previous.diagnostics.len() - found(units);
(
nodes(&units[..first])..nodes(&units[..last]),
head + found(&units[..first])..head + found(&units[..last]),
)
}
fn ends_as(&self, previous: &Parsed) -> bool {
let (nodes, diagnostics) = Self::kept(previous, self.last, self.last + 1);
let mut unit = previous.units[self.last].clone();
unit.shift(self.by);
let mut old_nodes = previous.root.children[nodes].to_vec();
old_nodes.shift(self.by);
let mut old_diagnostics = previous.diagnostics[diagnostics].to_vec();
old_diagnostics.shift(self.by);
self.units.last().is_some_and(|new| {
*new == unit
&& self.root.children[self.root.children.len() - new.nodes..] == old_nodes[..]
&& self.diagnostics[self.diagnostics.len() - new.diagnostics..]
== old_diagnostics[..]
})
}
fn splice_into(self, mut previous: Parsed) -> Parsed {
let (nodes, diagnostics) = Self::kept(&previous, self.first, self.last + 1);
let root = Rc::make_mut(&mut previous.root);
if !self.to_end {
root.children[nodes.end..].shift(self.by);
previous.diagnostics[diagnostics.end..].shift(self.by);
for unit in &mut previous.units[self.last + 1..] {
unit.shift(self.by);
}
}
if let Some(position) = &mut root.position {
match self.root.position {
Some(slice) if self.to_end => position.end = slice.end,
_ => position.end.shift(self.by),
}
}
root.children.splice(nodes, self.root.children);
previous.diagnostics.splice(diagnostics, self.diagnostics);
previous.units.splice(self.first..=self.last, self.units);
previous
}
}
fn line_endings(text: &str) -> usize {
text.matches('\n').count() + text.matches('\r').count() - text.matches("\r\n").count()
}
#[cfg(test)]
mod tests;