use crate::block::BlockError;
use crate::diff::{ApplyError, Change};
use crate::node::{Mark, Node};
use crate::normalize::{normalize_children, NormalizeOptions};
use crate::pos::PosError;
use crate::range::{ensure_boundary, resolve_range, Position, Range, RangeError};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(
tag = "type",
rename_all = "camelCase",
rename_all_fields = "camelCase"
)]
pub enum PosContent {
Text {
text: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
marks: Option<Vec<Mark>>,
},
Nodes {
nodes: Vec<Node>,
},
}
impl PosContent {
pub(crate) fn flat_len(&self) -> usize {
match self {
PosContent::Text { text, .. } => text.chars().count(),
PosContent::Nodes { nodes } => nodes.iter().map(Node::node_size).sum(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(
tag = "type",
rename_all = "camelCase",
rename_all_fields = "camelCase"
)]
pub enum PosEdit {
Insert {
pos: usize,
content: PosContent,
},
Delete {
from: usize,
to: usize,
},
Replace {
from: usize,
to: usize,
content: PosContent,
},
AddMark {
from: usize,
to: usize,
mark: Mark,
},
RemoveMark {
from: usize,
to: usize,
mark_type: String,
},
SetBlockAttrs {
pos: usize,
attrs: Map<String, Value>,
},
}
impl PosEdit {
fn span(&self) -> (usize, usize) {
match self {
PosEdit::Insert { pos, .. } | PosEdit::SetBlockAttrs { pos, .. } => (*pos, *pos),
PosEdit::Delete { from, to }
| PosEdit::Replace { from, to, .. }
| PosEdit::AddMark { from, to, .. }
| PosEdit::RemoveMark { from, to, .. } => (*from, *to),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PosEditError {
Pos(PosError),
Range(RangeError),
Block(BlockError),
Apply(ApplyError),
UnsupportedSpan {
from: usize,
to: usize,
},
OverlappingEdits {
from: usize,
to: usize,
},
}
impl From<PosError> for PosEditError {
fn from(e: PosError) -> Self {
PosEditError::Pos(e)
}
}
impl From<RangeError> for PosEditError {
fn from(e: RangeError) -> Self {
PosEditError::Range(e)
}
}
impl From<BlockError> for PosEditError {
fn from(e: BlockError) -> Self {
PosEditError::Block(e)
}
}
impl From<ApplyError> for PosEditError {
fn from(e: ApplyError) -> Self {
PosEditError::Apply(e)
}
}
impl fmt::Display for PosEditError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PosEditError::Pos(e) => write!(f, "pos-edit: {e}"),
PosEditError::Range(e) => write!(f, "pos-edit: {e}"),
PosEditError::Block(e) => write!(f, "pos-edit: {e}"),
PosEditError::Apply(e) => write!(f, "pos-edit: {e}"),
PosEditError::UnsupportedSpan { from, to } => {
write!(f, "pos-edit: unsupported cross-block span [{from},{to})")
}
PosEditError::OverlappingEdits { from, to } => {
write!(f, "pos-edit: overlapping edit at [{from},{to})")
}
}
}
}
impl std::error::Error for PosEditError {}
impl Node {
pub fn apply_pos_edits(&mut self, edits: &[PosEdit]) -> Result<Vec<Change>, PosEditError> {
for e in edits {
let (lo, hi) = e.span();
if lo > hi {
return Err(PosEditError::Range(RangeError::InvertedRange));
}
}
let mut order: Vec<usize> = (0..edits.len()).collect();
order.sort_by(|&a, &b| edits[b].span().0.cmp(&edits[a].span().0));
for k in 1..order.len() {
let higher = edits[order[k - 1]].span();
let lower = edits[order[k]].span();
if lower.1 > higher.0 {
return Err(PosEditError::OverlappingEdits {
from: lower.0,
to: lower.1,
});
}
}
let mut work = self.clone();
for &i in &order {
apply_one(&mut work, &edits[i])?;
}
let patch = self.diff(&work);
*self = work;
Ok(patch)
}
pub fn apply_pos_edits_mapped(
&mut self,
edits: &[PosEdit],
) -> Result<(Vec<Change>, crate::PosMap), PosEditError> {
let patch = self.apply_pos_edits(edits)?;
Ok((patch, crate::PosMap::from_pos_edits(edits)))
}
}
fn block_mut<'a>(root: &'a mut Node, path: &[usize]) -> Result<&'a mut Node, PosEditError> {
root.node_at_mut(path).ok_or_else(|| {
PosEditError::Pos(PosError::PathNotFound {
path: path.to_vec(),
})
})
}
fn apply_one(work: &mut Node, edit: &PosEdit) -> Result<(), PosEditError> {
match edit {
PosEdit::Insert { pos, content } => insert_at(work, *pos, content),
PosEdit::Delete { from, to } => splice(work, *from, *to, None),
PosEdit::Replace { from, to, content } => splice(work, *from, *to, Some(content)),
PosEdit::AddMark { from, to, mark } => {
mark_span(work, *from, *to, &MarkOp::Add(mark.clone()))
}
PosEdit::RemoveMark {
from,
to,
mark_type,
} => mark_span(work, *from, *to, &MarkOp::Remove(mark_type.clone())),
PosEdit::SetBlockAttrs { pos, attrs } => set_block_attrs(work, *pos, attrs.clone()),
}
}
fn insert_at(work: &mut Node, pos: usize, content: &PosContent) -> Result<(), PosEditError> {
match content {
PosContent::Text { text, marks } => {
let (block, inline) = work.pos_to_inline(pos)?;
block_mut(work, &block)?.insert_text(inline, text, marks.as_deref())?;
Ok(())
}
PosContent::Nodes { nodes } => {
let r = work.resolve(pos)?;
let (block_path, idx) = match &r.text_offset {
Some(tp) => {
let bp = r.path.clone();
let i = ensure_boundary(
block_mut(work, &bp)?.children_mut(),
Position::new(r.index, tp.offset),
)?;
(bp, i)
}
None => (r.path.clone(), r.index),
};
let parent = block_mut(work, &block_path)?;
for (k, n) in nodes.iter().enumerate() {
parent.insert_child(idx + k, n.clone());
}
Ok(())
}
}
}
fn splice(
work: &mut Node,
from: usize,
to: usize,
content: Option<&PosContent>,
) -> Result<(), PosEditError> {
let (fb, fi) = work.pos_to_inline(from)?;
let (tb, ti) = work.pos_to_inline(to)?;
if fb == tb {
return splice_same_block(block_mut(work, &fb)?, fi, ti, content);
}
let (parent, a, b) =
sibling_blocks(&fb, &tb).ok_or(PosEditError::UnsupportedSpan { from, to })?;
{
let block_a = block_mut(work, &fb)?;
let end = Position::new(block_a.children().len(), 0);
block_a.delete_range(Range::new(fi, end))?;
append_content(block_a, content)?;
}
block_mut(work, &tb)?.delete_range(Range::new(Position::new(0, 0), ti))?;
block_mut(work, &parent)?.children_mut().drain(a + 1..b);
work.join_blocks(&parent, a + 1)?;
Ok(())
}
fn splice_same_block(
block: &mut Node,
from: Position,
to: Position,
content: Option<&PosContent>,
) -> Result<(), PosEditError> {
match content {
None => block.delete_range(Range::new(from, to))?,
Some(PosContent::Text { text, marks }) => {
block.replace_range(Range::new(from, to), text, marks.as_deref())?
}
Some(PosContent::Nodes { nodes }) => {
let children = block.children_mut();
let (s, e) = resolve_range(children, Range::new(from, to))?;
children.drain(s..e);
for (k, n) in nodes.iter().enumerate() {
children.insert(s + k, n.clone());
}
normalize_children(children, &NormalizeOptions::default());
}
}
Ok(())
}
fn append_content(block: &mut Node, content: Option<&PosContent>) -> Result<(), PosEditError> {
match content {
None => {}
Some(PosContent::Text { text, marks }) => {
let at = Position::new(block.children().len(), 0);
block.insert_text(at, text, marks.as_deref())?;
}
Some(PosContent::Nodes { nodes }) => {
let at = block.children().len();
for (k, n) in nodes.iter().enumerate() {
block.insert_child(at + k, n.clone());
}
}
}
Ok(())
}
enum MarkOp {
Add(Mark),
Remove(String),
}
fn apply_mark(block: &mut Node, range: Range, op: &MarkOp) -> Result<(), RangeError> {
match op {
MarkOp::Add(m) => block.add_mark_range(range, m.clone()),
MarkOp::Remove(t) => block.remove_mark_range(range, t),
}
}
fn mark_span(work: &mut Node, from: usize, to: usize, op: &MarkOp) -> Result<(), PosEditError> {
let (fb, fi) = work.pos_to_inline(from)?;
let (tb, ti) = work.pos_to_inline(to)?;
if fb == tb {
apply_mark(block_mut(work, &fb)?, Range::new(fi, ti), op)?;
return Ok(());
}
let (parent, a, b) =
sibling_blocks(&fb, &tb).ok_or(PosEditError::UnsupportedSpan { from, to })?;
{
let block_a = block_mut(work, &fb)?;
let end = Position::new(block_a.children().len(), 0);
apply_mark(block_a, Range::new(fi, end), op)?;
}
for k in (a + 1)..b {
let mut p = parent.clone();
p.push(k);
let blk = block_mut(work, &p)?;
let end = Position::new(blk.children().len(), 0);
apply_mark(blk, Range::new(Position::new(0, 0), end), op)?;
}
apply_mark(
block_mut(work, &tb)?,
Range::new(Position::new(0, 0), ti),
op,
)?;
Ok(())
}
fn set_block_attrs(
work: &mut Node,
pos: usize,
attrs: Map<String, Value>,
) -> Result<(), PosEditError> {
let r = work.resolve(pos)?;
let mut target = r.path.clone();
if r.text_offset.is_none() {
let parent = block_mut(work, &r.path)?;
let descend = parent
.children()
.get(r.index)
.is_some_and(|c| c.node_type.as_deref() != Some("text"));
if descend {
target.push(r.index);
}
}
let node = block_mut(work, &target)?;
node.attrs = if attrs.is_empty() { None } else { Some(attrs) };
Ok(())
}
fn sibling_blocks(fb: &[usize], tb: &[usize]) -> Option<(Vec<usize>, usize, usize)> {
let ((&a, fp), (&b, tp)) = (fb.split_last()?, tb.split_last()?);
if fp == tp && a < b {
Some((fp.to_vec(), a, b))
} else {
None
}
}