1use crate::block::BlockError;
54use crate::diff::{ApplyError, Change};
55use crate::node::{Mark, Node};
56use crate::normalize::{normalize_children, NormalizeOptions};
57use crate::pos::PosError;
58use crate::range::{ensure_boundary, resolve_range, Position, Range, RangeError};
59use serde::{Deserialize, Serialize};
60use serde_json::{Map, Value};
61use std::fmt;
62
63#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65#[serde(
66 tag = "type",
67 rename_all = "camelCase",
68 rename_all_fields = "camelCase"
69)]
70pub enum PosContent {
71 Text {
73 text: String,
75 #[serde(skip_serializing_if = "Option::is_none", default)]
77 marks: Option<Vec<Mark>>,
78 },
79 Nodes {
81 nodes: Vec<Node>,
83 },
84}
85
86impl PosContent {
87 pub(crate) fn flat_len(&self) -> usize {
90 match self {
91 PosContent::Text { text, .. } => text.chars().count(),
92 PosContent::Nodes { nodes } => nodes.iter().map(Node::node_size).sum(),
93 }
94 }
95}
96
97#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
99#[serde(
100 tag = "type",
101 rename_all = "camelCase",
102 rename_all_fields = "camelCase"
103)]
104pub enum PosEdit {
105 Insert {
107 pos: usize,
109 content: PosContent,
111 },
112 Delete {
114 from: usize,
116 to: usize,
118 },
119 Replace {
121 from: usize,
123 to: usize,
125 content: PosContent,
127 },
128 AddMark {
130 from: usize,
132 to: usize,
134 mark: Mark,
136 },
137 RemoveMark {
139 from: usize,
141 to: usize,
143 mark_type: String,
145 },
146 SetBlockAttrs {
148 pos: usize,
150 attrs: Map<String, Value>,
152 },
153}
154
155impl PosEdit {
156 fn span(&self) -> (usize, usize) {
158 match self {
159 PosEdit::Insert { pos, .. } | PosEdit::SetBlockAttrs { pos, .. } => (*pos, *pos),
160 PosEdit::Delete { from, to }
161 | PosEdit::Replace { from, to, .. }
162 | PosEdit::AddMark { from, to, .. }
163 | PosEdit::RemoveMark { from, to, .. } => (*from, *to),
164 }
165 }
166}
167
168#[derive(Debug, Clone, PartialEq, Eq)]
170pub enum PosEditError {
171 Pos(PosError),
173 Range(RangeError),
175 Block(BlockError),
177 Apply(ApplyError),
179 UnsupportedSpan {
182 from: usize,
184 to: usize,
186 },
187 OverlappingEdits {
189 from: usize,
191 to: usize,
193 },
194}
195
196impl From<PosError> for PosEditError {
197 fn from(e: PosError) -> Self {
198 PosEditError::Pos(e)
199 }
200}
201impl From<RangeError> for PosEditError {
202 fn from(e: RangeError) -> Self {
203 PosEditError::Range(e)
204 }
205}
206impl From<BlockError> for PosEditError {
207 fn from(e: BlockError) -> Self {
208 PosEditError::Block(e)
209 }
210}
211impl From<ApplyError> for PosEditError {
212 fn from(e: ApplyError) -> Self {
213 PosEditError::Apply(e)
214 }
215}
216
217impl fmt::Display for PosEditError {
218 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219 match self {
220 PosEditError::Pos(e) => write!(f, "pos-edit: {e}"),
221 PosEditError::Range(e) => write!(f, "pos-edit: {e}"),
222 PosEditError::Block(e) => write!(f, "pos-edit: {e}"),
223 PosEditError::Apply(e) => write!(f, "pos-edit: {e}"),
224 PosEditError::UnsupportedSpan { from, to } => {
225 write!(f, "pos-edit: unsupported cross-block span [{from},{to})")
226 }
227 PosEditError::OverlappingEdits { from, to } => {
228 write!(f, "pos-edit: overlapping edit at [{from},{to})")
229 }
230 }
231 }
232}
233
234impl std::error::Error for PosEditError {}
235
236impl Node {
237 pub fn apply_pos_edits(&mut self, edits: &[PosEdit]) -> Result<Vec<Change>, PosEditError> {
245 for e in edits {
249 let (lo, hi) = e.span();
250 if lo > hi {
251 return Err(PosEditError::Range(RangeError::InvertedRange));
252 }
253 }
254
255 let mut order: Vec<usize> = (0..edits.len()).collect();
257 order.sort_by(|&a, &b| edits[b].span().0.cmp(&edits[a].span().0));
258
259 for k in 1..order.len() {
262 let higher = edits[order[k - 1]].span();
263 let lower = edits[order[k]].span();
264 if lower.1 > higher.0 {
265 return Err(PosEditError::OverlappingEdits {
266 from: lower.0,
267 to: lower.1,
268 });
269 }
270 }
271
272 let mut work = self.clone();
273 for &i in &order {
274 apply_one(&mut work, &edits[i])?;
275 }
276 let patch = self.diff(&work);
277 *self = work;
278 Ok(patch)
279 }
280
281 pub fn apply_pos_edits_mapped(
286 &mut self,
287 edits: &[PosEdit],
288 ) -> Result<(Vec<Change>, crate::PosMap), PosEditError> {
289 let patch = self.apply_pos_edits(edits)?;
290 Ok((patch, crate::PosMap::from_pos_edits(edits)))
291 }
292}
293
294fn block_mut<'a>(root: &'a mut Node, path: &[usize]) -> Result<&'a mut Node, PosEditError> {
297 root.node_at_mut(path).ok_or_else(|| {
298 PosEditError::Pos(PosError::PathNotFound {
299 path: path.to_vec(),
300 })
301 })
302}
303
304fn apply_one(work: &mut Node, edit: &PosEdit) -> Result<(), PosEditError> {
305 match edit {
306 PosEdit::Insert { pos, content } => insert_at(work, *pos, content),
307 PosEdit::Delete { from, to } => splice(work, *from, *to, None),
308 PosEdit::Replace { from, to, content } => splice(work, *from, *to, Some(content)),
309 PosEdit::AddMark { from, to, mark } => {
310 mark_span(work, *from, *to, &MarkOp::Add(mark.clone()))
311 }
312 PosEdit::RemoveMark {
313 from,
314 to,
315 mark_type,
316 } => mark_span(work, *from, *to, &MarkOp::Remove(mark_type.clone())),
317 PosEdit::SetBlockAttrs { pos, attrs } => set_block_attrs(work, *pos, attrs.clone()),
318 }
319}
320
321fn insert_at(work: &mut Node, pos: usize, content: &PosContent) -> Result<(), PosEditError> {
322 match content {
323 PosContent::Text { text, marks } => {
324 let (block, inline) = work.pos_to_inline(pos)?;
325 block_mut(work, &block)?.insert_text(inline, text, marks.as_deref())?;
326 Ok(())
327 }
328 PosContent::Nodes { nodes } => {
329 let r = work.resolve(pos)?;
330 let (block_path, idx) = match &r.text_offset {
331 Some(tp) => {
333 let bp = r.path.clone();
334 let i = ensure_boundary(
335 block_mut(work, &bp)?.children_mut(),
336 Position::new(r.index, tp.offset),
337 )?;
338 (bp, i)
339 }
340 None => (r.path.clone(), r.index),
341 };
342 let parent = block_mut(work, &block_path)?;
343 for (k, n) in nodes.iter().enumerate() {
344 parent.insert_child(idx + k, n.clone());
345 }
346 Ok(())
347 }
348 }
349}
350
351fn splice(
352 work: &mut Node,
353 from: usize,
354 to: usize,
355 content: Option<&PosContent>,
356) -> Result<(), PosEditError> {
357 let (fb, fi) = work.pos_to_inline(from)?;
358 let (tb, ti) = work.pos_to_inline(to)?;
359
360 if fb == tb {
361 return splice_same_block(block_mut(work, &fb)?, fi, ti, content);
362 }
363
364 let (parent, a, b) =
366 sibling_blocks(&fb, &tb).ok_or(PosEditError::UnsupportedSpan { from, to })?;
367
368 {
370 let block_a = block_mut(work, &fb)?;
371 let end = Position::new(block_a.children().len(), 0);
372 block_a.delete_range(Range::new(fi, end))?;
373 append_content(block_a, content)?;
374 }
375 block_mut(work, &tb)?.delete_range(Range::new(Position::new(0, 0), ti))?;
377 block_mut(work, &parent)?.children_mut().drain(a + 1..b);
379 work.join_blocks(&parent, a + 1)?;
380 Ok(())
381}
382
383fn splice_same_block(
384 block: &mut Node,
385 from: Position,
386 to: Position,
387 content: Option<&PosContent>,
388) -> Result<(), PosEditError> {
389 match content {
390 None => block.delete_range(Range::new(from, to))?,
391 Some(PosContent::Text { text, marks }) => {
392 block.replace_range(Range::new(from, to), text, marks.as_deref())?
393 }
394 Some(PosContent::Nodes { nodes }) => {
395 let children = block.children_mut();
396 let (s, e) = resolve_range(children, Range::new(from, to))?;
397 children.drain(s..e);
398 for (k, n) in nodes.iter().enumerate() {
399 children.insert(s + k, n.clone());
400 }
401 normalize_children(children, &NormalizeOptions::default());
402 }
403 }
404 Ok(())
405}
406
407fn append_content(block: &mut Node, content: Option<&PosContent>) -> Result<(), PosEditError> {
410 match content {
411 None => {}
412 Some(PosContent::Text { text, marks }) => {
413 let at = Position::new(block.children().len(), 0);
414 block.insert_text(at, text, marks.as_deref())?;
415 }
416 Some(PosContent::Nodes { nodes }) => {
417 let at = block.children().len();
418 for (k, n) in nodes.iter().enumerate() {
419 block.insert_child(at + k, n.clone());
420 }
421 }
422 }
423 Ok(())
424}
425
426enum MarkOp {
427 Add(Mark),
428 Remove(String),
429}
430
431fn apply_mark(block: &mut Node, range: Range, op: &MarkOp) -> Result<(), RangeError> {
432 match op {
433 MarkOp::Add(m) => block.add_mark_range(range, m.clone()),
434 MarkOp::Remove(t) => block.remove_mark_range(range, t),
435 }
436}
437
438fn mark_span(work: &mut Node, from: usize, to: usize, op: &MarkOp) -> Result<(), PosEditError> {
439 let (fb, fi) = work.pos_to_inline(from)?;
440 let (tb, ti) = work.pos_to_inline(to)?;
441
442 if fb == tb {
443 apply_mark(block_mut(work, &fb)?, Range::new(fi, ti), op)?;
444 return Ok(());
445 }
446
447 let (parent, a, b) =
448 sibling_blocks(&fb, &tb).ok_or(PosEditError::UnsupportedSpan { from, to })?;
449
450 {
452 let block_a = block_mut(work, &fb)?;
453 let end = Position::new(block_a.children().len(), 0);
454 apply_mark(block_a, Range::new(fi, end), op)?;
455 }
456 for k in (a + 1)..b {
458 let mut p = parent.clone();
459 p.push(k);
460 let blk = block_mut(work, &p)?;
461 let end = Position::new(blk.children().len(), 0);
462 apply_mark(blk, Range::new(Position::new(0, 0), end), op)?;
463 }
464 apply_mark(
466 block_mut(work, &tb)?,
467 Range::new(Position::new(0, 0), ti),
468 op,
469 )?;
470 Ok(())
471}
472
473fn set_block_attrs(
474 work: &mut Node,
475 pos: usize,
476 attrs: Map<String, Value>,
477) -> Result<(), PosEditError> {
478 let r = work.resolve(pos)?;
479 let mut target = r.path.clone();
485 if r.text_offset.is_none() {
486 let parent = block_mut(work, &r.path)?;
487 let descend = parent
488 .children()
489 .get(r.index)
490 .is_some_and(|c| c.node_type.as_deref() != Some("text"));
491 if descend {
492 target.push(r.index);
493 }
494 }
495 let node = block_mut(work, &target)?;
496 node.attrs = if attrs.is_empty() { None } else { Some(attrs) };
497 Ok(())
498}
499
500fn sibling_blocks(fb: &[usize], tb: &[usize]) -> Option<(Vec<usize>, usize, usize)> {
503 let ((&a, fp), (&b, tp)) = (fb.split_last()?, tb.split_last()?);
504 if fp == tp && a < b {
505 Some((fp.to_vec(), a, b))
506 } else {
507 None
508 }
509}