1use super::Buffer;
3use crate::history::{Edit, EditKind};
4use crate::id::{BufferRevision, ByteOffset};
5use crate::{InputEdit, Range};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
8pub enum ChangeOrigin {
9 User,
10 History,
11 System,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct Change {
16 pub revision: BufferRevision,
17 pub origin: ChangeOrigin,
18 pub edit: InputEdit,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
22pub enum EditError {
23 #[error("readonly buffer")]
24 ReadOnly,
25 #[error("invalid edit range")]
26 InvalidRange,
27 #[error("overlapping replacements in pre-edit coordinates")]
28 Overlap,
29 #[error("prepared replacement belongs to another buffer")]
30 WrongBuffer,
31 #[error("stale revision: expected {expected}, found {found}")]
32 StaleRevision {
33 expected: BufferRevision,
34 found: BufferRevision,
35 },
36 #[error("buffer revision exhausted")]
37 RevisionExhausted,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct Replacement {
42 pub range: Range,
43 pub text: String,
44}
45impl Replacement {
46 pub fn new(range: Range, text: impl Into<String>) -> Self {
47 Self {
48 range,
49 text: text.into(),
50 }
51 }
52}
53
54pub struct PreparedReplacements {
57 buffer: crate::diagnostics::BufferTraceId,
58 revision: BufferRevision,
59 edits: Vec<Replacement>,
60}
61impl PreparedReplacements {
62 pub fn is_empty(&self) -> bool {
63 self.edits.is_empty()
64 }
65}
66
67pub struct UserEdit<'a> {
68 buffer: &'a mut Buffer,
69}
70pub struct SystemEdit<'a> {
71 buffer: &'a mut Buffer,
72}
73
74impl Buffer {
75 pub fn edit(&mut self) -> UserEdit<'_> {
76 UserEdit { buffer: self }
77 }
78 pub fn system_edit(&mut self) -> SystemEdit<'_> {
79 SystemEdit { buffer: self }
80 }
81 pub fn changes(&self) -> &[Change] {
82 &self.changes
83 }
84 pub fn clear_changes(&mut self) {
85 self.changes.clear();
86 }
87 pub fn begin_undo_group(&mut self) {
88 self.history.begin();
89 }
90 pub fn commit_undo_group(&mut self) {
91 self.history.commit();
92 }
93
94 fn validate_range(&self, range: Range) -> Result<(), EditError> {
95 if range.start > range.end || !self.is_boundary(range.start) || !self.is_boundary(range.end)
96 {
97 return Err(EditError::InvalidRange);
98 }
99 Ok(())
100 }
101
102 pub fn prepare_replacements(
105 &self,
106 base: BufferRevision,
107 mut replacements: Vec<Replacement>,
108 ) -> Result<PreparedReplacements, EditError> {
109 if self.readonly {
110 return Err(EditError::ReadOnly);
111 }
112 if base != self.revision() {
113 return Err(EditError::StaleRevision {
114 expected: base,
115 found: self.revision(),
116 });
117 }
118 replacements.retain(|edit| !edit.range.is_empty() || !edit.text.is_empty());
119 for edit in &replacements {
120 self.validate_range(edit.range)?;
121 }
122 let pairs: Vec<(usize, usize)> = replacements
126 .iter()
127 .map(|edit| (edit.range.start.get(), edit.range.end.get()))
128 .collect();
129 let sorted = crate::editmap::check_batch(self.len_bytes(), pairs)
130 .map_err(|()| EditError::Overlap)?;
131 let mut rest = replacements;
134 let mut ordered = Vec::with_capacity(sorted.len());
135 for pair in sorted {
136 let at = rest
137 .iter()
138 .position(|edit| edit.range.start.get() == pair.0 && edit.range.end.get() == pair.1)
139 .expect("a validated batch contains only input ranges");
140 ordered.push(rest.remove(at));
141 }
142 replacements = ordered;
143 self.epoch
144 .checked_add(replacements.len() as u64)
145 .ok_or(EditError::RevisionExhausted)?;
146 Ok(PreparedReplacements {
147 buffer: self.trace_id(),
148 revision: base,
149 edits: replacements,
150 })
151 }
152
153 pub fn apply_prepared(
154 &mut self,
155 prepared: PreparedReplacements,
156 undo_open: bool,
157 ) -> Result<BufferRevision, EditError> {
158 if prepared.buffer != self.trace_id() {
159 return Err(EditError::WrongBuffer);
160 }
161 if self.readonly {
162 return Err(EditError::ReadOnly);
163 }
164 if prepared.revision != self.revision() {
165 return Err(EditError::StaleRevision {
166 expected: prepared.revision,
167 found: self.revision(),
168 });
169 }
170 if prepared.is_empty() {
171 return Ok(self.revision());
172 }
173 self.history.begin();
174 for replacement in prepared.edits.into_iter().rev() {
175 self.replace_validated(replacement.range, &replacement.text, ChangeOrigin::User);
176 }
177 if !undo_open {
178 self.history.commit();
179 }
180 Ok(self.revision())
181 }
182
183 fn replace_validated(&mut self, range: Range, text: &str, origin: ChangeOrigin) {
184 debug_assert!(self.validate_range(range).is_ok());
185 debug_assert!(self.epoch < u64::MAX);
186 let (start_byte, end_byte) = (range.start.get(), range.end.get());
187 let start_point = self.point_of(start_byte);
188 let old_end_point = self.point_of(end_byte);
189 let extent = Self::point_extent(text);
190 let new_end_point = if extent.0 == 0 {
191 (start_point.0, start_point.1 + extent.1)
192 } else {
193 (start_point.0 + extent.0, extent.1)
194 };
195 let edit = InputEdit {
196 start_byte,
197 old_end_byte: end_byte,
198 new_end_byte: start_byte + text.len(),
199 start_point,
200 old_end_point,
201 new_end_point,
202 };
203 if origin == ChangeOrigin::User {
204 if !range.is_empty() {
205 let removed = self.rope.byte_slice(start_byte..end_byte).to_string();
206 self.history.record(
207 Edit {
208 at: start_byte,
209 text: removed.clone(),
210 kind: EditKind::Insert,
211 },
212 Edit {
213 at: start_byte,
214 text: removed,
215 kind: EditKind::Delete,
216 },
217 );
218 }
219 if !text.is_empty() {
220 self.history.record(
221 Edit {
222 at: start_byte,
223 text: text.into(),
224 kind: EditKind::Delete,
225 },
226 Edit {
227 at: start_byte,
228 text: text.into(),
229 kind: EditKind::Insert,
230 },
231 );
232 }
233 }
234 let start = self.rope.byte_to_char(start_byte);
235 let end = self.rope.byte_to_char(end_byte);
236 if start != end {
237 self.rope.remove(start..end);
238 }
239 if !text.is_empty() {
240 self.rope.insert(start, text);
241 }
242 self.publish_change(edit, origin);
243 self.trace_edit(origin, start_byte, range.len(), text);
244 }
245
246 fn publish_change(&mut self, edit: InputEdit, origin: ChangeOrigin) {
247 debug_assert!(self.epoch < u64::MAX);
248 self.epoch += 1;
249 self.invalidate_line_layouts(&edit);
250 self.dirty |= origin != ChangeOrigin::System;
251 self.changes.push(Change {
252 revision: self.revision(),
253 origin,
254 edit,
255 });
256 }
257}
258
259impl UserEdit<'_> {
260 pub fn insert(&mut self, at: impl Into<ByteOffset>, text: &str) -> Result<(), EditError> {
261 let at = at.into();
262 self.replace(Range::charwise(at, at), text)
263 }
264 pub fn delete(&mut self, range: Range) -> Result<String, EditError> {
265 if self.buffer.readonly {
266 return Err(EditError::ReadOnly);
267 }
268 self.buffer.validate_range(range)?;
269 let removed = self.buffer.slice_string(range);
270 self.replace(range, "")?;
271 Ok(removed)
272 }
273 pub fn replace(&mut self, range: Range, text: &str) -> Result<(), EditError> {
274 if self.buffer.readonly {
275 return Err(EditError::ReadOnly);
276 }
277 self.buffer.validate_range(range)?;
278 if range.is_empty() && text.is_empty() {
279 return Ok(());
280 }
281 self.buffer
282 .epoch
283 .checked_add(1)
284 .ok_or(EditError::RevisionExhausted)?;
285 self.buffer
286 .replace_validated(range, text, ChangeOrigin::User);
287 Ok(())
288 }
289}
290
291impl SystemEdit<'_> {
292 pub fn replace_all(&mut self, text: &str) -> Result<(), EditError> {
294 self.buffer
295 .epoch
296 .checked_add(1)
297 .ok_or(EditError::RevisionExhausted)?;
298 self.buffer.replace_validated(
299 Range::charwise(0, self.buffer.len_bytes()),
300 text,
301 ChangeOrigin::System,
302 );
303 self.buffer.history = Default::default();
304 Ok(())
305 }
306
307 pub fn replace_rope(&mut self, rope: ropey::Rope) -> Result<(), EditError> {
310 self.buffer
311 .epoch
312 .checked_add(1)
313 .ok_or(EditError::RevisionExhausted)?;
314 let old_end_byte = self.buffer.len_bytes();
315 let old_end_point = self.buffer.point_of(old_end_byte);
316 let new_end_byte = rope.len_bytes();
317 let line = rope.byte_to_line(new_end_byte);
318 let new_end_point = (line, new_end_byte - rope.line_to_byte(line));
319 self.buffer.rope = rope;
320 self.buffer.publish_change(
321 InputEdit {
322 start_byte: 0,
323 old_end_byte,
324 new_end_byte,
325 start_point: (0, 0),
326 old_end_point,
327 new_end_point,
328 },
329 ChangeOrigin::System,
330 );
331 self.buffer.history = Default::default();
332 self.buffer.trace_snapshot(old_end_byte);
333 Ok(())
334 }
335}
336
337#[derive(Debug, Clone, Copy)]
338pub struct HistoryMove {
339 pub start: ByteOffset,
340 pub end: ByteOffset,
341}
342
343impl Buffer {
344 pub fn undo(&mut self) -> Result<Option<HistoryMove>, EditError> {
345 self.move_history(crate::history::HistoryAction::Undo)
346 }
347 pub fn redo(&mut self) -> Result<Option<HistoryMove>, EditError> {
348 self.move_history(crate::history::HistoryAction::Redo)
349 }
350 pub fn restore_revision(&mut self, revision: usize) -> Result<Option<HistoryMove>, EditError> {
351 self.move_history(crate::history::HistoryAction::Jump(revision))
352 }
353 fn move_history(
354 &mut self,
355 action: crate::history::HistoryAction,
356 ) -> Result<Option<HistoryMove>, EditError> {
357 if self.readonly {
358 return Err(EditError::ReadOnly);
359 }
360 let Some(cost) = self.history.movement_cost(action) else {
361 return Ok(None);
362 };
363 self.epoch
364 .checked_add(cost as u64)
365 .ok_or(EditError::RevisionExhausted)?;
366 let Some(ops) = self.history.navigate(action) else {
368 return Ok(None);
369 };
370 debug_assert_eq!(cost, ops.len());
371 let start = ops.iter().map(|edit| edit.at).min().unwrap_or(0);
372 let end = ops
373 .last()
374 .map(|edit| {
375 edit.at
376 + if edit.kind == EditKind::Insert {
377 edit.text.len()
378 } else {
379 0
380 }
381 })
382 .unwrap_or(start);
383 for op in &ops {
384 let (range, text) = match op.kind {
385 EditKind::Insert => (Range::charwise(op.at, op.at), op.text.as_str()),
386 EditKind::Delete => (Range::charwise(op.at, op.at + op.text.len()), ""),
387 };
388 debug_assert!(self.validate_range(range).is_ok(), "sealed history range");
389 debug_assert!(
390 op.kind != EditKind::Delete
391 || self.rope.byte_slice(range.start.get()..range.end.get()) == op.text,
392 "sealed history content"
393 );
394 self.replace_validated(range, text, ChangeOrigin::History);
395 }
396 self.trace_history(&ops);
397 Ok(Some(HistoryMove {
398 start: start.into(),
399 end: end.into(),
400 }))
401 }
402}