use crate::pos::PosRange;
use crate::pos_edit::PosEdit;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Assoc {
#[default]
Left,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
struct Step {
start: usize,
old_len: usize,
new_len: usize,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct PosMap {
steps: Vec<Step>,
}
impl PosMap {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.steps.is_empty()
}
pub fn push(&mut self, start: usize, old_len: usize, new_len: usize) {
if old_len == 0 && new_len == 0 {
return;
}
let step = Step {
start,
old_len,
new_len,
};
let i = self.steps.partition_point(|s| s.start < start);
self.steps.insert(i, step);
}
pub fn map(&self, pos: usize, assoc: Assoc) -> usize {
let mut diff: i64 = 0;
for s in &self.steps {
if pos < s.start {
break; }
let old_end = s.start + s.old_len;
if pos > old_end {
diff += s.new_len as i64 - s.old_len as i64;
continue;
}
let into = match assoc {
Assoc::Left => 0,
Assoc::Right => s.new_len as i64,
};
return (s.start as i64 + diff + into) as usize;
}
(pos as i64 + diff) as usize
}
pub fn map_range(&self, range: PosRange, assoc: Assoc) -> PosRange {
PosRange::new(self.map(range.from, assoc), self.map(range.to, assoc))
}
pub fn from_pos_edits(edits: &[PosEdit]) -> Self {
let mut map = PosMap::new();
for e in edits {
match e {
PosEdit::Insert { pos, content } => map.push(*pos, 0, content.flat_len()),
PosEdit::Delete { from, to } => map.push(*from, to.saturating_sub(*from), 0),
PosEdit::Replace { from, to, content } => {
map.push(*from, to.saturating_sub(*from), content.flat_len())
}
PosEdit::AddMark { .. }
| PosEdit::RemoveMark { .. }
| PosEdit::SetBlockAttrs { .. } => {}
}
}
map
}
}