use std::ops::Range;
use super::{
document::{BlockContent, EditorDocument},
id::BlockId,
inline::InlineText,
kind::BlockKindData,
position::BlockPosition,
};
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct DetachedBlock {
pub(crate) id: BlockId,
pub(crate) kind: BlockKindData,
pub(crate) content: DetachedContent,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum DetachedContent {
Children(Vec<DetachedBlock>),
Leaf(BlockContent),
}
impl DetachedBlock {
pub(crate) fn leaf(kind: BlockKindData, content: BlockContent) -> Self {
Self {
id: BlockId::next(),
kind,
content: DetachedContent::Leaf(content),
}
}
pub(crate) fn container(kind: BlockKindData, children: Vec<DetachedBlock>) -> Self {
Self {
id: BlockId::next(),
kind,
content: DetachedContent::Children(children),
}
}
pub(crate) fn paragraph(inline: InlineText) -> Self {
Self::leaf(BlockKindData::Paragraph, BlockContent::Inline(inline))
}
pub(crate) fn ids(&self, out: &mut Vec<BlockId>) {
out.push(self.id);
if let DetachedContent::Children(children) = &self.content {
for child in children {
child.ids(out);
}
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Step {
ReplaceText {
block: BlockId,
range: Range<usize>,
insert: InlineText,
},
InsertBlocks {
parent: BlockId,
index: usize,
blocks: Vec<DetachedBlock>,
},
RemoveBlocks {
parent: BlockId,
range: Range<usize>,
},
SetKind {
block: BlockId,
kind: BlockKindData,
},
ReplacePlain {
block: BlockId,
range: Range<usize>,
insert: String,
},
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum StepError {
UnknownBlock(BlockId),
NotText(BlockId),
OutOfRange,
KindMismatch,
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum StepMap {
Identity,
Text {
block: BlockId,
range: Range<usize>,
insert_len: usize,
},
Removed(Vec<BlockId>),
}
impl StepMap {
pub(crate) fn map(&self, p: BlockPosition) -> Option<BlockPosition> {
match self {
Self::Identity => Some(p),
Self::Text {
block,
range,
insert_len,
} if *block == p.block => {
let offset = if p.offset <= range.start {
p.offset
} else if p.offset >= range.end {
p.offset - range.len() + insert_len
} else {
range.start + insert_len
};
Some(BlockPosition { offset, ..p })
}
Self::Text { .. } => Some(p),
Self::Removed(ids) => (!ids.contains(&p.block)).then_some(p),
}
}
}
impl Step {
pub(crate) fn apply(&self, doc: &mut EditorDocument) -> Result<(Step, StepMap), StepError> {
let result = self.apply_inner(doc)?;
let touched = match self {
Self::ReplaceText { block, .. }
| Self::SetKind { block, .. }
| Self::ReplacePlain { block, .. } => *block,
Self::InsertBlocks { parent, .. } | Self::RemoveBlocks { parent, .. } => *parent,
};
doc.touch(touched);
Ok(result)
}
fn apply_inner(&self, doc: &mut EditorDocument) -> Result<(Step, StepMap), StepError> {
match self {
Self::ReplaceText {
block,
range,
insert,
} => {
let entry = doc.get_mut(*block).ok_or(StepError::UnknownBlock(*block))?;
let BlockContent::Inline(inline) = &mut entry.content else {
return Err(StepError::NotText(*block));
};
if range.start > range.end
|| range.end > inline.len()
|| !inline.text().is_char_boundary(range.start)
|| !inline.text().is_char_boundary(range.end)
{
return Err(StepError::OutOfRange);
}
let removed = inline.replace(range.clone(), insert.clone());
let inverse = Step::ReplaceText {
block: *block,
range: range.start..range.start + insert.len(),
insert: removed,
};
let map = StepMap::Text {
block: *block,
range: range.clone(),
insert_len: insert.len(),
};
Ok((inverse, map))
}
Self::InsertBlocks {
parent,
index,
blocks,
} => {
if doc.get(*parent).is_none() {
return Err(StepError::UnknownBlock(*parent));
}
if *index > doc.children(*parent).len() {
return Err(StepError::OutOfRange);
}
for (ix, block) in blocks.iter().enumerate() {
if !doc.attach(*parent, index + ix, block.clone()) {
return Err(StepError::OutOfRange);
}
}
let inverse = Step::RemoveBlocks {
parent: *parent,
range: *index..index + blocks.len(),
};
Ok((inverse, StepMap::Identity))
}
Self::RemoveBlocks { parent, range } => {
if doc.get(*parent).is_none() {
return Err(StepError::UnknownBlock(*parent));
}
if range.end > doc.children(*parent).len() || range.start > range.end {
return Err(StepError::OutOfRange);
}
let mut removed = Vec::with_capacity(range.len());
for _ in range.clone() {
removed.push(
doc.detach(*parent, range.start)
.ok_or(StepError::OutOfRange)?,
);
}
let mut ids = Vec::new();
for block in &removed {
block.ids(&mut ids);
}
let inverse = Step::InsertBlocks {
parent: *parent,
index: range.start,
blocks: removed,
};
Ok((inverse, StepMap::Removed(ids)))
}
Self::ReplacePlain {
block,
range,
insert,
} => {
let entry = doc.get_mut(*block).ok_or(StepError::UnknownBlock(*block))?;
let BlockContent::Plain(code) = &mut entry.content else {
return Err(StepError::NotText(*block));
};
if range.start > range.end
|| range.end > code.len()
|| !code.is_char_boundary(range.start)
|| !code.is_char_boundary(range.end)
{
return Err(StepError::OutOfRange);
}
let removed = code[range.clone()].to_string();
code.replace_range(range.clone(), insert);
Ok((
Step::ReplacePlain {
block: *block,
range: range.start..range.start + insert.len(),
insert: removed,
},
StepMap::Text {
block: *block,
range: range.clone(),
insert_len: insert.len(),
},
))
}
Self::SetKind { block, kind } => {
let entry = doc.get_mut(*block).ok_or(StepError::UnknownBlock(*block))?;
if entry.kind.is_container() != kind.is_container()
|| entry.kind.is_textblock() != kind.is_textblock()
{
return Err(StepError::KindMismatch);
}
let prev = std::mem::replace(&mut entry.kind, kind.clone());
doc.structure_dirty = true;
Ok((
Step::SetKind {
block: *block,
kind: prev,
},
StepMap::Identity,
))
}
}
}
}