use std::ops::Range;
use markdown::{Annotation, Cursor, Part, Selection, Splice};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct AnchorId(pub u64);
pub type SourceRanges = Vec<(AnchorId, Option<Range<usize>>)>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Anchor {
pub id: AnchorId,
pub range: Selection,
pub state: Annotation,
pub actionable: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceAnchor {
pub id: AnchorId,
pub range: Range<usize>,
pub state: Annotation,
pub actionable: bool,
}
impl Anchor {
pub fn new(id: AnchorId, range: Selection) -> Self {
Self {
id,
range,
state: Annotation::default(),
actionable: false,
}
}
pub fn detached(&self) -> bool {
self.range.is_collapsed()
}
pub(crate) fn map(&mut self, delta: &Delta) {
let (start, end) = self.range.ordered();
self.range = match (delta.cursor(start), delta.cursor(end)) {
(Some(start), Some(end)) => Selection::new(start.min(end), start.max(end)),
(surviving, other) => Selection::at(surviving.or(other).unwrap_or_default()),
};
}
}
pub(crate) enum Delta {
CellsMoved {
block: usize,
from: crate::editor::Line,
to: usize,
},
Spliced(Splice),
Moved { at: Range<usize>, to: Option<usize> },
Opened { at: usize, count: usize },
Cells {
block: usize,
row: Option<usize>,
column: Option<usize>,
},
CellsRemoved {
block: usize,
row: Option<usize>,
column: Option<usize>,
},
}
impl Delta {
pub(crate) fn cursor(&self, at: Cursor) -> Option<Cursor> {
match self {
Self::CellsMoved { block, from, to } => {
let mut at = at;
if at.block == *block
&& let Part::Cell { row, column } = &mut at.part
{
let (index, from) = match from {
crate::editor::Line::Row(from) => (row, *from),
crate::editor::Line::Column(from) => (column, *from),
};
*index = if *index == from {
*to
} else if from < *index && *index <= *to {
*index - 1
} else if *to <= *index && *index < from {
*index + 1
} else {
*index
};
}
Some(at)
}
Self::Spliced(splice) => {
let (start, end) = splice.removed.ordered();
if at < start {
return Some(at);
}
if at <= end {
return Some(splice.caret);
}
if at.block == end.block && at.part == end.part {
return Some(Cursor {
offset: splice.caret.offset + (at.offset - end.offset),
..splice.caret
});
}
Some(Cursor {
block: at.block.checked_add_signed(splice.blocks)?,
..at
})
}
Self::Moved { at: span, to } => {
if span.contains(&at.block) {
return Some(Cursor {
block: (*to)? + (at.block - span.start),
..at
});
}
let pulled = if at.block >= span.end {
at.block - span.len()
} else {
at.block
};
let block = match to {
Some(to) if pulled >= *to => pulled + span.len(),
_ => pulled,
};
Some(Cursor { block, ..at })
}
Self::Cells { block, row, column } => Some(match at.part {
Part::Cell { row: r, column: c } if at.block == *block => {
let past = |line: usize, opened: Option<usize>| {
line + usize::from(opened.is_some_and(|opened| line >= opened))
};
Cursor {
part: Part::Cell {
row: past(r, *row),
column: past(c, *column),
},
..at
}
}
_ => at,
}),
Self::CellsRemoved { block, row, column } => match at.part {
Part::Cell { row: r, column: c } if at.block == *block => {
if Some(r) == *row || Some(c) == *column {
return None;
}
let past = |line: usize, removed: Option<usize>| {
line - usize::from(removed.is_some_and(|removed| line > removed))
};
Some(Cursor {
part: Part::Cell {
row: past(r, *row),
column: past(c, *column),
},
..at
})
}
_ => Some(at),
},
Self::Opened { at: opened, count } => Some(Cursor {
block: if at.block >= *opened {
at.block + count
} else {
at.block
},
..at
}),
}
}
}