1use crate::error::{EditError, ErrorCode};
2use crate::model::Provenance;
3
4use core::ops::Range;
5use std::sync::OnceLock;
6
7use super::{
8 InsertRun, PreparedEdit, ProvenanceSet,
9 ranges::{prepared_output_size, sort_prepared, verify_ranges},
10 stream::EditChunks,
11 writer::{WriteSummary, write_prepared},
12};
13
14const MAX_COALESCED_INSERT_BYTES: usize = 64 * 1024;
15
16#[derive(Clone, Debug, Eq, PartialEq)]
18pub struct AppliedText {
19 pub text: String,
20 pub edits_applied: usize,
21 pub bytes_before: usize,
22 pub bytes_after: usize,
23}
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
27pub struct ApplySummary {
28 pub edits_applied: usize,
29 pub bytes_before: usize,
30 pub bytes_after: usize,
31}
32
33#[derive(Clone, Copy, Debug, Eq, PartialEq)]
35pub enum OffsetBias {
36 Left,
37 Right,
38}
39
40#[derive(Clone, Debug, Eq, PartialEq)]
42pub struct PreparedChange<'change> {
43 pub source_range: Range<usize>,
44 pub output_range: Range<usize>,
45 pub before: &'change str,
46 pub after: &'change str,
47 pub input_order: usize,
48 provenance: &'change ProvenanceSet,
49}
50
51impl PreparedChange<'_> {
52 #[must_use]
54 pub fn provenance(&self) -> &Provenance {
55 &self.provenance.primary
56 }
57
58 pub fn provenances(&self) -> impl Iterator<Item = &Provenance> {
60 core::iter::once(&self.provenance.primary).chain(self.provenance.additional().iter())
61 }
62
63 #[must_use]
64 pub fn provenance_count(&self) -> usize {
65 1 + self.provenance.additional().len()
66 }
67}
68
69#[derive(Clone, Debug)]
71pub struct PreparedChanges<'change> {
72 source: &'change str,
73 edits: core::slice::Iter<'change, PreparedEdit>,
74 source_cursor: usize,
75 output_cursor: usize,
76}
77
78impl<'change> Iterator for PreparedChanges<'change> {
79 type Item = PreparedChange<'change>;
80
81 fn next(&mut self) -> Option<Self::Item> {
82 let edit = self.edits.next()?;
83 let unchanged = edit.start - self.source_cursor;
84 let output_start = self.output_cursor + unchanged;
85 let output_end = output_start + edit.after.len();
86 self.source_cursor = self.source_cursor.max(edit.end);
87 self.output_cursor = output_end;
88 Some(PreparedChange {
89 source_range: edit.start..edit.end,
90 output_range: output_start..output_end,
91 before: &self.source[edit.start..edit.end],
92 after: &edit.after,
93 input_order: edit.order,
94 provenance: &edit.provenance,
95 })
96 }
97
98 fn size_hint(&self) -> (usize, Option<usize>) {
99 self.edits.size_hint()
100 }
101}
102
103impl ExactSizeIterator for PreparedChanges<'_> {}
104impl core::iter::FusedIterator for PreparedChanges<'_> {}
105
106#[derive(Clone, Copy, Debug, Eq, PartialEq)]
108pub struct ChangeSummary {
109 pub edits: usize,
110 pub bytes_before: usize,
111 pub bytes_after: usize,
112 pub removed_bytes: usize,
113 pub inserted_bytes: usize,
114}
115
116#[derive(Debug)]
124pub struct PreparedEdits<'source> {
125 source: &'source str,
126 edits: Vec<PreparedEdit>,
127 output_size: usize,
128 max_edits: usize,
129 max_output_size: usize,
130 same_size: bool,
131 insert_runs: Vec<InsertRun>,
132 rendered: OnceLock<String>,
133}
134
135impl Clone for PreparedEdits<'_> {
136 fn clone(&self) -> Self {
137 Self {
138 source: self.source,
139 edits: self.edits.clone(),
140 output_size: self.output_size,
141 max_edits: self.max_edits,
142 max_output_size: self.max_output_size,
143 same_size: self.same_size,
144 insert_runs: self.insert_runs.clone(),
145 rendered: OnceLock::new(),
148 }
149 }
150}
151
152impl<'source> PreparedEdits<'source> {
153 pub(super) fn from_validated_parts(
154 source: &'source str,
155 edits: Vec<PreparedEdit>,
156 output_size: usize,
157 max_edits: usize,
158 max_output_size: usize,
159 ) -> Self {
160 let same_size = edits
161 .iter()
162 .all(|edit| edit.end - edit.start == edit.after.len());
163 let insert_runs = coalesce_insert_runs(&edits);
164 Self {
165 source,
166 edits,
167 output_size,
168 max_edits,
169 max_output_size,
170 same_size,
171 insert_runs,
172 rendered: OnceLock::new(),
173 }
174 }
175
176 #[must_use]
181 #[inline]
182 pub fn apply(&self) -> AppliedText {
183 let text = self.rendered.get().map_or_else(
184 || {
185 let mut output = String::with_capacity(self.output_size);
186 self.fill_output(&mut output);
187 output
188 },
189 Clone::clone,
190 );
191 AppliedText {
192 bytes_before: self.source.len(),
193 bytes_after: text.len(),
194 edits_applied: self.edits.len(),
195 text,
196 }
197 }
198
199 #[inline]
209 pub fn apply_into(&self, output: &mut String) -> ApplySummary {
210 if let Some(rendered) = self.rendered.get() {
211 output.clone_from(rendered);
212 } else {
213 output.clear();
214 if output.capacity() < self.output_size {
215 output.reserve(self.output_size);
216 }
217 self.fill_output(output);
218 }
219 ApplySummary {
220 edits_applied: self.edits.len(),
221 bytes_before: self.source.len(),
222 bytes_after: output.len(),
223 }
224 }
225
226 #[inline]
235 pub fn apply_into_bytes(&self, output: &mut Vec<u8>) -> ApplySummary {
236 output.clear();
237 if output.capacity() < self.output_size {
238 output.reserve(self.output_size);
239 }
240 if let Some(rendered) = self.rendered.get() {
241 output.extend_from_slice(rendered.as_bytes());
242 } else if self.same_size {
243 output.extend_from_slice(self.source.as_bytes());
244 for edit in &self.edits {
245 output[edit.start..edit.end].copy_from_slice(edit.after.as_bytes());
246 }
247 } else {
248 self.fill_output_bytes(output);
249 }
250 ApplySummary {
251 edits_applied: self.edits.len(),
252 bytes_before: self.source.len(),
253 bytes_after: output.len(),
254 }
255 }
256
257 #[inline]
258 fn fill_output(&self, output: &mut String) {
259 if self.same_size {
260 output.push_str(self.source);
261 for edit in &self.edits {
262 output.replace_range(edit.start..edit.end, &edit.after);
263 }
264 return;
265 }
266 if !self.insert_runs.is_empty() {
267 self.fill_output_with_insert_runs(output);
268 return;
269 }
270 let mut cursor = 0_usize;
271 for edit in &self.edits {
272 if edit.start > cursor {
273 output.push_str(&self.source[cursor..edit.start]);
274 cursor = edit.start;
275 }
276 output.push_str(&edit.after);
277 if edit.end > edit.start {
278 cursor = edit.end;
279 }
280 }
281 output.push_str(&self.source[cursor..]);
282 }
283
284 fn fill_output_bytes(&self, output: &mut Vec<u8>) {
285 if !self.insert_runs.is_empty() {
286 self.fill_output_bytes_with_insert_runs(output);
287 return;
288 }
289 let mut cursor = 0_usize;
290 let source = self.source.as_bytes();
291 for edit in &self.edits {
292 if edit.start > cursor {
293 output.extend_from_slice(&source[cursor..edit.start]);
294 cursor = edit.start;
295 }
296 output.extend_from_slice(edit.after.as_bytes());
297 if edit.end > edit.start {
298 cursor = edit.end;
299 }
300 }
301 output.extend_from_slice(&source[cursor..]);
302 }
303
304 fn fill_output_with_insert_runs(&self, output: &mut String) {
305 let mut cursor = 0_usize;
306 self.for_each_execution_chunk(|start, end, after| {
307 if start > cursor {
308 output.push_str(&self.source[cursor..start]);
309 cursor = start;
310 }
311 output.push_str(after);
312 if end > start {
313 cursor = end;
314 }
315 });
316 output.push_str(&self.source[cursor..]);
317 }
318
319 fn fill_output_bytes_with_insert_runs(&self, output: &mut Vec<u8>) {
320 let mut cursor = 0_usize;
321 let source = self.source.as_bytes();
322 self.for_each_execution_chunk(|start, end, after| {
323 if start > cursor {
324 output.extend_from_slice(&source[cursor..start]);
325 cursor = start;
326 }
327 output.extend_from_slice(after.as_bytes());
328 if end > start {
329 cursor = end;
330 }
331 });
332 output.extend_from_slice(&source[cursor..]);
333 }
334
335 fn for_each_execution_chunk(&self, mut visit: impl FnMut(usize, usize, &str)) {
336 let mut edit_index = 0_usize;
337 let mut run_index = 0_usize;
338 while edit_index < self.edits.len() {
339 if let Some(run) = self.insert_runs.get(run_index)
340 && run.first_edit == edit_index
341 {
342 visit(run.start, run.start, &run.after);
343 edit_index = run.past_last_edit;
344 run_index += 1;
345 } else {
346 let edit = &self.edits[edit_index];
347 visit(edit.start, edit.end, &edit.after);
348 edit_index += 1;
349 }
350 }
351 }
352
353 #[inline]
354 fn rendered_output(&self) -> &String {
355 self.rendered.get_or_init(|| {
356 let mut output = String::with_capacity(self.output_size);
357 self.fill_output(&mut output);
358 output
359 })
360 }
361
362 #[must_use]
369 #[inline]
370 pub fn rendered_text(&self) -> &str {
371 self.rendered_output()
372 }
373
374 #[must_use]
377 pub fn has_rendered_text(&self) -> bool {
378 self.rendered.get().is_some()
379 }
380
381 pub fn clear_rendered_text(&mut self) {
386 drop(self.rendered.take());
387 }
388
389 #[must_use]
395 pub fn chunks(&self) -> EditChunks<'_> {
396 EditChunks::new(self.source, &self.edits)
397 }
398
399 #[must_use]
406 pub fn changes(&self) -> PreparedChanges<'_> {
407 PreparedChanges {
408 source: self.source,
409 edits: self.edits.iter(),
410 source_cursor: 0,
411 output_cursor: 0,
412 }
413 }
414
415 #[must_use]
417 pub fn change_summary(&self) -> ChangeSummary {
418 let removed_bytes = self.edits.iter().map(|edit| edit.end - edit.start).sum();
419 let inserted_bytes = self.edits.iter().map(|edit| edit.after.len()).sum();
420 ChangeSummary {
421 edits: self.edits.len(),
422 bytes_before: self.source.len(),
423 bytes_after: self.output_size,
424 removed_bytes,
425 inserted_bytes,
426 }
427 }
428
429 pub fn write_to<W: std::io::Write + ?Sized>(
437 &self,
438 writer: &mut W,
439 ) -> std::io::Result<WriteSummary> {
440 write_prepared(
441 writer,
442 self.chunks(),
443 self.source.len(),
444 self.edits.len(),
445 self.output_size,
446 )
447 }
448
449 pub fn apply_with_validator(
451 &self,
452 validator: impl FnOnce(&str) -> bool,
453 ) -> Result<AppliedText, EditError> {
454 let applied = self.apply();
455 if validator(&applied.text) {
456 Ok(applied)
457 } else {
458 Err(EditError::new(
459 ErrorCode::ValidationRejected,
460 "the output validator rejected the complete edit result",
461 ))
462 }
463 }
464
465 pub fn union(mut self, mut other: Self) -> Result<Self, EditError> {
469 if self.source != other.source {
470 return Err(EditError::new(
471 ErrorCode::InvalidEdit,
472 "prepared edits can only be merged over identical source text",
473 ));
474 }
475 self.max_edits = self.max_edits.min(other.max_edits);
476 self.max_output_size = self.max_output_size.min(other.max_output_size);
477 let order_base = self
478 .edits
479 .iter()
480 .map(|edit| edit.order)
481 .max()
482 .map_or(Ok(0), |order| {
483 order.checked_add(1).ok_or_else(|| {
484 EditError::new(ErrorCode::PlanTooLarge, "merged edit order overflow")
485 })
486 })?;
487 for edit in &mut other.edits {
488 edit.order = order_base.checked_add(edit.order).ok_or_else(|| {
489 EditError::new(ErrorCode::PlanTooLarge, "merged edit order overflow")
490 })?;
491 }
492 self.edits.extend(other.edits);
493 sort_prepared(&mut self.edits);
494 self.edits.dedup_by(|right, left| {
495 let identical = left.start != left.end
496 && left.start == right.start
497 && left.end == right.end
498 && left.after == right.after;
499 if identical {
500 let placeholder = ProvenanceSet::new(right.provenance.primary.clone());
501 let other = core::mem::replace(&mut right.provenance, placeholder);
502 left.provenance.extend(other);
503 }
504 identical
505 });
506 if self.edits.len() > self.max_edits {
507 return Err(EditError::new(
508 ErrorCode::PlanTooLarge,
509 "merged edit count exceeds the application limit",
510 ));
511 }
512 verify_ranges(
513 self.edits
514 .iter()
515 .map(|edit| (edit.start, edit.end, edit.order)),
516 )?;
517 self.output_size =
518 prepared_output_size(self.source.len(), &self.edits, self.max_output_size)?;
519 self.same_size = self
520 .edits
521 .iter()
522 .all(|edit| edit.end - edit.start == edit.after.len());
523 self.insert_runs = coalesce_insert_runs(&self.edits);
524 self.rendered = OnceLock::new();
525 Ok(self)
526 }
527
528 #[must_use]
530 pub fn invalidates_offset(&self, offset: usize) -> bool {
531 self.edits
532 .iter()
533 .any(|edit| edit.start < offset && offset < edit.end)
534 }
535
536 #[must_use]
538 pub fn map_offset_forward(&self, offset: usize, bias: OffsetBias) -> Option<usize> {
539 if offset > self.source.len() || !self.source.is_char_boundary(offset) {
540 return None;
541 }
542 let mut delta = 0_i128;
543 for edit in &self.edits {
544 if offset < edit.start {
545 break;
546 }
547 let mapped_start = shifted(edit.start, delta)?;
548 if edit.start < offset && offset < edit.end {
549 return None;
550 }
551 if edit.start == edit.end && offset == edit.start {
552 if bias == OffsetBias::Left {
553 return Some(mapped_start);
554 }
555 delta += i128::try_from(edit.after.len()).ok()?;
556 continue;
557 }
558 if offset == edit.start && edit.end > edit.start {
559 return Some(match bias {
560 OffsetBias::Left => mapped_start,
561 OffsetBias::Right => mapped_start.checked_add(edit.after.len())?,
562 });
563 }
564 if offset >= edit.end {
565 delta += i128::try_from(edit.after.len()).ok()?
566 - i128::try_from(edit.end - edit.start).ok()?;
567 }
568 }
569 shifted(offset, delta)
570 }
571
572 #[must_use]
573 pub fn len(&self) -> usize {
574 self.edits.len()
575 }
576
577 #[must_use]
579 pub fn bytes_before(&self) -> usize {
580 self.source.len()
581 }
582
583 #[must_use]
585 pub fn bytes_after(&self) -> usize {
586 self.output_size
587 }
588
589 #[must_use]
590 pub fn is_empty(&self) -> bool {
591 self.edits.is_empty()
592 }
593}
594
595fn coalesce_insert_runs(edits: &[PreparedEdit]) -> Vec<InsertRun> {
596 let mut runs = Vec::new();
597 let mut retained_bytes = 0_usize;
598 let mut first = 0_usize;
599 while first < edits.len() {
600 let start = edits[first].start;
601 if edits[first].end != start {
602 first += 1;
603 continue;
604 }
605 let mut past_last = first + 1;
606 while past_last < edits.len()
607 && edits[past_last].start == start
608 && edits[past_last].end == start
609 {
610 past_last += 1;
611 }
612 if past_last - first > 1 {
613 let run_bytes = edits[first..past_last]
614 .iter()
615 .map(|edit| edit.after.len())
616 .sum();
617 if run_bytes <= MAX_COALESCED_INSERT_BYTES - retained_bytes {
618 let mut after = String::with_capacity(run_bytes);
619 for edit in &edits[first..past_last] {
620 after.push_str(&edit.after);
621 }
622 runs.push(InsertRun {
623 first_edit: first,
624 past_last_edit: past_last,
625 start,
626 after,
627 });
628 retained_bytes += run_bytes;
629 }
630 }
631 first = past_last;
632 }
633 runs
634}
635
636fn shifted(offset: usize, delta: i128) -> Option<usize> {
637 usize::try_from(i128::try_from(offset).ok()?.checked_add(delta)?).ok()
638}
639
640#[cfg(test)]
641mod tests {
642 use crate::{application::ProvenanceSet, model::Provenance};
643
644 use super::{MAX_COALESCED_INSERT_BYTES, PreparedEdit, coalesce_insert_runs};
645
646 fn prepared(start: usize, end: usize, after: String, order: usize) -> PreparedEdit {
647 PreparedEdit {
648 start,
649 end,
650 order,
651 after,
652 provenance: ProvenanceSet::new(Provenance::new(Provenance::EXACT_LSP)),
653 }
654 }
655
656 #[test]
657 fn coalesced_insert_storage_has_one_global_hard_ceiling() {
658 let half = MAX_COALESCED_INSERT_BYTES / 2;
659 let edits = vec![
660 prepared(0, 0, "a".repeat(half / 2), 0),
661 prepared(0, 0, "b".repeat(half / 2), 1),
662 prepared(1, 2, "X".to_owned(), 2),
663 prepared(2, 2, "c".repeat(half / 2 + 1), 3),
664 prepared(2, 2, "d".repeat(half / 2), 4),
665 ];
666 let runs = coalesce_insert_runs(&edits);
667 assert_eq!(runs.len(), 1);
668 assert_eq!(runs[0].after.len(), half);
669 assert!(
670 runs.iter().map(|run| run.after.len()).sum::<usize>() <= MAX_COALESCED_INSERT_BYTES
671 );
672
673 let oversized = vec![
674 prepared(0, 0, "a".repeat(half + 1), 0),
675 prepared(0, 0, "b".repeat(half), 1),
676 ];
677 assert!(coalesce_insert_runs(&oversized).is_empty());
678 }
679}