1use std::fmt;
10
11use crate::error::DocError;
12use crate::fragment::Fragment;
13use crate::node::Node;
14use crate::pos::ResolvedPos;
15use crate::schema::Schema;
16use crate::slice::Slice;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum ReplaceError {
21 OpenTooDeep,
23 InconsistentOpenDepths,
25 CannotJoin {
27 onto: String,
29 joined: String,
31 },
32 InvalidContent {
34 parent: String,
36 },
37 Position {
39 pos: usize,
41 max: usize,
43 },
44}
45
46impl fmt::Display for ReplaceError {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match self {
49 ReplaceError::OpenTooDeep => {
50 write!(f, "inserted content deeper than the insertion position")
51 }
52 ReplaceError::InconsistentOpenDepths => write!(f, "inconsistent open depths"),
53 ReplaceError::CannotJoin { onto, joined } => {
54 write!(f, "cannot join `{joined}` onto `{onto}`")
55 }
56 ReplaceError::InvalidContent { parent } => {
57 write!(f, "replacement violates the schema for `{parent}`")
58 }
59 ReplaceError::Position { pos, max } => {
60 write!(f, "position {pos} out of range (max {max})")
61 }
62 }
63 }
64}
65
66impl std::error::Error for ReplaceError {}
67
68fn map_pos(e: DocError) -> ReplaceError {
69 match e {
70 DocError::PositionOutOfRange { pos, max } => ReplaceError::Position { pos, max },
71 _ => ReplaceError::Position { pos: 0, max: 0 },
72 }
73}
74
75impl Node {
76 pub fn replace(
82 &self,
83 from: usize,
84 to: usize,
85 slice: &Slice,
86 schema: &Schema,
87 ) -> Result<Node, ReplaceError> {
88 let rf = ResolvedPos::resolve(self, from).map_err(map_pos)?;
89 let rt = ResolvedPos::resolve(self, to).map_err(map_pos)?;
90 replace(&rf, &rt, slice, schema)
91 }
92
93 pub fn slice(&self, from: usize, to: usize) -> Result<Slice, DocError> {
96 if from == to {
97 return Ok(Slice::empty());
98 }
99 let rf = ResolvedPos::resolve(self, from)?;
100 let rt = ResolvedPos::resolve(self, to)?;
101 let depth = rf.shared_depth(to);
102 let start = rf.start(depth);
103 let node = rf.node(depth);
104 let content = node.content().cut(rf.pos() - start, rt.pos() - start);
105 Ok(Slice::new(content, rf.depth() - depth, rt.depth() - depth))
106 }
107}
108
109fn replace(
110 from: &ResolvedPos,
111 to: &ResolvedPos,
112 slice: &Slice,
113 schema: &Schema,
114) -> Result<Node, ReplaceError> {
115 if slice.open_start() > from.depth() {
116 return Err(ReplaceError::OpenTooDeep);
117 }
118 if from.depth() as isize - slice.open_start() as isize
119 != to.depth() as isize - slice.open_end() as isize
120 {
121 return Err(ReplaceError::InconsistentOpenDepths);
122 }
123 replace_outer(from, to, slice, 0, schema)
124}
125
126fn close(schema: &Schema, template: &Node, content: Fragment) -> Result<Node, ReplaceError> {
127 if !schema.fragment_valid(template.node_type(), &content) {
128 return Err(ReplaceError::InvalidContent {
129 parent: template.node_type().name().to_string(),
130 });
131 }
132 Ok(template.copy_content(content))
133}
134
135fn check_join(schema: &Schema, onto: &Node, joined: &Node) -> Result<(), ReplaceError> {
137 if !schema.types_compatible(joined.node_type(), onto.node_type()) {
138 return Err(ReplaceError::CannotJoin {
139 onto: onto.node_type().name().to_string(),
140 joined: joined.node_type().name().to_string(),
141 });
142 }
143 Ok(())
144}
145
146fn joinable(
147 schema: &Schema,
148 before: &ResolvedPos,
149 after: &ResolvedPos,
150 depth: usize,
151) -> Result<Node, ReplaceError> {
152 let node = before.node(depth).clone();
153 check_join(schema, &node, after.node(depth))?;
154 Ok(node)
155}
156
157fn add_node(child: Node, target: &mut Vec<Node>) {
158 if let Some(last) = target.last() {
159 if child.is_text() && last.is_text() && child.same_markup(last) {
160 let merged = child.with_text(format!(
161 "{}{}",
162 last.text().unwrap_or(""),
163 child.text().unwrap_or("")
164 ));
165 let n = target.len() - 1;
166 target[n] = merged;
167 return;
168 }
169 }
170 target.push(child);
171}
172
173fn add_range(
174 start: Option<&ResolvedPos>,
175 end: Option<&ResolvedPos>,
176 depth: usize,
177 target: &mut Vec<Node>,
178) {
179 let anchor = end.or(start).expect("add_range needs a bound");
180 let node = anchor.node(depth);
181 let mut start_index = 0;
182 let end_index = match end {
183 Some(e) => e.index(depth),
184 None => node.child_count(),
185 };
186 if let Some(s) = start {
187 start_index = s.index(depth);
188 if s.depth() > depth {
189 start_index += 1;
190 } else if s.text_offset() > 0 {
191 add_node(s.node_after().expect("node after start"), target);
192 start_index += 1;
193 }
194 }
195 for i in start_index..end_index {
196 add_node(node.child(i).clone(), target);
197 }
198 if let Some(e) = end {
199 if e.depth() == depth && e.text_offset() > 0 {
200 add_node(e.node_before().expect("node before end"), target);
201 }
202 }
203}
204
205fn replace_two_way(
206 from: &ResolvedPos,
207 to: &ResolvedPos,
208 depth: usize,
209 schema: &Schema,
210) -> Result<Fragment, ReplaceError> {
211 let mut content = Vec::new();
212 add_range(None, Some(from), depth, &mut content);
213 if from.depth() > depth {
214 let template = joinable(schema, from, to, depth + 1)?;
215 let inner = replace_two_way(from, to, depth + 1, schema)?;
216 add_node(close(schema, &template, inner)?, &mut content);
217 }
218 add_range(Some(to), None, depth, &mut content);
219 Ok(Fragment::from_vec(content))
220}
221
222#[allow(clippy::too_many_arguments)]
223fn replace_three_way(
224 from: &ResolvedPos,
225 start: &ResolvedPos,
226 end: &ResolvedPos,
227 to: &ResolvedPos,
228 depth: usize,
229 schema: &Schema,
230) -> Result<Fragment, ReplaceError> {
231 let open_start = if from.depth() > depth {
232 Some(joinable(schema, from, start, depth + 1)?)
233 } else {
234 None
235 };
236 let open_end = if to.depth() > depth {
237 Some(joinable(schema, end, to, depth + 1)?)
238 } else {
239 None
240 };
241
242 let mut content = Vec::new();
243 add_range(None, Some(from), depth, &mut content);
244
245 match (&open_start, &open_end) {
246 (Some(os), Some(oe)) if start.index(depth) == end.index(depth) => {
247 check_join(schema, os, oe)?;
248 let inner = replace_three_way(from, start, end, to, depth + 1, schema)?;
249 add_node(close(schema, os, inner)?, &mut content);
250 }
251 _ => {
252 if let Some(os) = &open_start {
253 let inner = replace_two_way(from, start, depth + 1, schema)?;
254 add_node(close(schema, os, inner)?, &mut content);
255 }
256 add_range(Some(start), Some(end), depth, &mut content);
257 if let Some(oe) = &open_end {
258 let inner = replace_two_way(end, to, depth + 1, schema)?;
259 add_node(close(schema, oe, inner)?, &mut content);
260 }
261 }
262 }
263
264 add_range(Some(to), None, depth, &mut content);
265 Ok(Fragment::from_vec(content))
266}
267
268fn prepare_slice_for_replace(
269 slice: &Slice,
270 along: &ResolvedPos,
271) -> Result<(Node, ResolvedPos, ResolvedPos), DocError> {
272 let extra = along.depth() - slice.open_start();
273 let parent = along.node(extra);
274 let mut node = parent.copy_content(slice.content().clone());
275 for i in (0..extra).rev() {
276 node = along.node(i).copy_content(Fragment::from_vec(vec![node]));
277 }
278 let start = ResolvedPos::resolve(&node, slice.open_start() + extra)?;
279 let end_pos = node.content().size() - slice.open_end() - extra;
280 let end = ResolvedPos::resolve(&node, end_pos)?;
281 Ok((node, start, end))
282}
283
284fn replace_outer(
285 from: &ResolvedPos,
286 to: &ResolvedPos,
287 slice: &Slice,
288 depth: usize,
289 schema: &Schema,
290) -> Result<Node, ReplaceError> {
291 let index = from.index(depth);
292 let node = from.node(depth).clone();
293 if index == to.index(depth) && depth < from.depth() - slice.open_start() {
294 let inner = replace_outer(from, to, slice, depth + 1, schema)?;
295 Ok(node.copy_content(node.content().replace_child(index, inner)))
296 } else if slice.content().size() == 0 {
297 let frag = replace_two_way(from, to, depth, schema)?;
298 close(schema, &node, frag)
299 } else if slice.open_start() == 0
300 && slice.open_end() == 0
301 && from.depth() == depth
302 && to.depth() == depth
303 {
304 let parent = from.parent().clone();
305 let content = parent.content();
306 let merged = content
307 .cut(0, from.parent_offset())
308 .append(slice.content())
309 .append(&content.cut(to.parent_offset(), content.size()));
310 close(schema, &parent, merged)
311 } else {
312 let (_root, start, end) = prepare_slice_for_replace(slice, from).map_err(map_pos)?;
313 let frag = replace_three_way(from, &start, &end, to, depth, schema)?;
314 close(schema, &node, frag)
315 }
316}