Skip to main content

merman_core/preprocess/
source_edit_map.rs

1use crate::{
2    EditorExpectedSyntax, EditorExpectedSyntaxKind, OperationControl, OperationControlResult,
3    SourceSpan,
4};
5use std::ops::Range;
6
7const CONTROLLED_EDIT_REBUILD_CHECKPOINT_BYTES: usize = 4 * 1024;
8const CONTROLLED_EDIT_REBUILD_CHECKPOINT_ITEMS: usize = 128;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub(super) enum ReplacementMapping {
12    ExactBytes,
13    Boundaries,
14}
15
16#[derive(Debug, Clone)]
17pub(super) struct SourceEdit {
18    range: Range<usize>,
19    replacement: String,
20    mapping: ReplacementMapping,
21}
22
23impl SourceEdit {
24    pub(super) fn delete(range: Range<usize>) -> Self {
25        Self {
26            range,
27            replacement: String::new(),
28            mapping: ReplacementMapping::Boundaries,
29        }
30    }
31
32    pub(super) fn replace(
33        range: Range<usize>,
34        replacement: impl Into<String>,
35        mapping: ReplacementMapping,
36    ) -> Self {
37        Self {
38            range,
39            replacement: replacement.into(),
40            mapping,
41        }
42    }
43}
44
45/// Parser text together with an exact, composable map back to the caller's source.
46#[derive(Debug, Clone)]
47pub struct PreprocessedSource {
48    text: String,
49    edit_map: SourceEditMap,
50    global_expected_syntax: Vec<EditorExpectedSyntax>,
51    global_directive_prefixes: Vec<String>,
52    recovered_incomplete_directive: bool,
53    frontmatter_line_offset: usize,
54    first_line_column_offset: usize,
55}
56
57impl PreprocessedSource {
58    pub fn new(source: &str) -> Self {
59        Self::new_controlled(source, &OperationControl::new())
60            .expect("a private parse control cannot be cancelled")
61    }
62
63    pub(super) fn new_controlled(
64        source: &str,
65        control: &OperationControl,
66    ) -> OperationControlResult<Self> {
67        control.checkpoint()?;
68        let mut text = String::with_capacity(source.len());
69        let mut chunk_start = 0usize;
70        while source.len().saturating_sub(chunk_start) >= CONTROLLED_EDIT_REBUILD_CHECKPOINT_BYTES {
71            let mut chunk_end = chunk_start + CONTROLLED_EDIT_REBUILD_CHECKPOINT_BYTES;
72            while !source.is_char_boundary(chunk_end) {
73                chunk_end += 1;
74            }
75            text.push_str(&source[chunk_start..chunk_end]);
76            control.checkpoint()?;
77            chunk_start = chunk_end;
78        }
79        text.push_str(&source[chunk_start..]);
80        control.checkpoint()?;
81        Ok(Self {
82            text,
83            edit_map: SourceEditMap::identity(source.len()),
84            global_expected_syntax: Vec::new(),
85            global_directive_prefixes: Vec::new(),
86            recovered_incomplete_directive: false,
87            frontmatter_line_offset: 0,
88            first_line_column_offset: 0,
89        })
90    }
91
92    pub fn text(&self) -> &str {
93        &self.text
94    }
95
96    pub(super) fn copy_global_editor_evidence_from(&mut self, source: &Self) {
97        self.global_expected_syntax
98            .clone_from(&source.global_expected_syntax);
99        self.global_directive_prefixes
100            .clone_from(&source.global_directive_prefixes);
101    }
102
103    pub(crate) fn parser_position_offset(&self) -> (usize, usize) {
104        (self.frontmatter_line_offset, self.first_line_column_offset)
105    }
106
107    pub(super) fn record_frontmatter_removal(&mut self, end: usize) {
108        self.frontmatter_line_offset += self.text[..end]
109            .bytes()
110            .filter(|byte| *byte == b'\n')
111            .count();
112        self.first_line_column_offset = 0;
113    }
114
115    pub(super) fn set_first_line_column_offset(&mut self, columns: usize) {
116        self.first_line_column_offset = columns;
117    }
118
119    pub fn try_map_span(&self, span: SourceSpan) -> Option<SourceSpan> {
120        if !self.text.is_char_boundary(span.start) || !self.text.is_char_boundary(span.end) {
121            return None;
122        }
123        self.edit_map.try_map_span(span)
124    }
125
126    pub(super) fn try_map_enclosing_span(&self, span: SourceSpan) -> Option<SourceSpan> {
127        if !self.text.is_char_boundary(span.start) || !self.text.is_char_boundary(span.end) {
128            return None;
129        }
130        self.edit_map.try_map_enclosing_span(span)
131    }
132
133    pub(crate) fn global_expected_syntax(&self) -> &[EditorExpectedSyntax] {
134        &self.global_expected_syntax
135    }
136
137    pub(crate) fn global_directive_prefixes(&self) -> &[String] {
138        &self.global_directive_prefixes
139    }
140
141    pub(crate) const fn recovered_incomplete_directive(&self) -> bool {
142        self.recovered_incomplete_directive
143    }
144
145    pub(super) fn mark_recovered_incomplete_directive(&mut self) {
146        self.recovered_incomplete_directive = true;
147    }
148
149    pub(super) fn record_global_directive_prefix(&mut self, prefix: String) {
150        if !self.global_directive_prefixes.contains(&prefix) {
151            self.global_directive_prefixes.push(prefix);
152        }
153    }
154
155    pub(super) fn record_global_expected_syntax(
156        &mut self,
157        kind: EditorExpectedSyntaxKind,
158        span: SourceSpan,
159    ) {
160        let Some(span) = self.try_map_span(span) else {
161            return;
162        };
163        self.global_expected_syntax
164            .push(EditorExpectedSyntax::new(kind, span));
165    }
166
167    pub fn into_text(self) -> String {
168        self.text
169    }
170
171    pub(super) fn apply_edits(
172        &mut self,
173        edits: Vec<SourceEdit>,
174        control: &OperationControl,
175    ) -> OperationControlResult<()> {
176        let mut checkpoints = ControlledEditRebuildCheckpoints::new(control)?;
177        if edits.is_empty() {
178            return Ok(());
179        }
180        // Preprocessors emit edits in source order. Keeping that as an invariant avoids an
181        // uninterruptible sort inside the controlled reconstruction path.
182        assert!(
183            edits_are_sorted_controlled(&edits, &mut checkpoints)?,
184            "source edits must be sorted by range"
185        );
186        let (replacement_bytes, removed_bytes) =
187            assert_valid_edits_controlled(&self.text, &edits, &mut checkpoints)?;
188
189        // Borrow the published state until every checkpoint and invariant check succeeds. A
190        // cancellation drops only these local builders and cannot expose a partial rewrite.
191        let old_text = &self.text;
192        let old_map = &self.edit_map;
193        let mut text = String::with_capacity(
194            old_text
195                .len()
196                .saturating_sub(removed_bytes)
197                .saturating_add(replacement_bytes),
198        );
199        let mut builder = EditMapBuilder::new(old_map.original_len);
200        let mut cursor = 0usize;
201
202        for edit in edits {
203            checkpoints.processed_item()?;
204            checkpoints.push_str(&mut text, &old_text[cursor..edit.range.start])?;
205            builder.copy_from(old_map, cursor..edit.range.start, &mut checkpoints)?;
206
207            if edit.replacement.is_empty() {
208                builder.delete_from(old_map, edit.range.clone(), &mut checkpoints)?;
209            } else {
210                checkpoints.push_str(&mut text, &edit.replacement)?;
211                builder.replace_from(
212                    old_map,
213                    edit.range.clone(),
214                    edit.replacement.len(),
215                    edit.mapping,
216                    &mut checkpoints,
217                )?;
218            }
219            cursor = edit.range.end;
220        }
221
222        checkpoints.push_str(&mut text, &old_text[cursor..])?;
223        builder.copy_from(old_map, cursor..old_text.len(), &mut checkpoints)?;
224        let edit_map = builder.finish(text.len(), &mut checkpoints)?;
225        #[cfg(debug_assertions)]
226        {
227            let well_formed = edit_map.is_well_formed_controlled(&mut checkpoints)?;
228            debug_assert!(well_formed);
229        }
230        checkpoints.finish()?;
231
232        self.text = text;
233        self.edit_map = edit_map;
234        Ok(())
235    }
236
237    #[cfg(test)]
238    fn apply_edits_uncontrolled(&mut self, edits: Vec<SourceEdit>) {
239        self.apply_edits(edits, &OperationControl::new())
240            .expect("a private parse control cannot be cancelled");
241    }
242}
243
244struct ControlledEditRebuildCheckpoints<'a> {
245    control: &'a OperationControl,
246    bytes_since_checkpoint: usize,
247    items_since_checkpoint: usize,
248}
249
250impl<'a> ControlledEditRebuildCheckpoints<'a> {
251    fn new(control: &'a OperationControl) -> OperationControlResult<Self> {
252        control.checkpoint()?;
253        Ok(Self {
254            control,
255            bytes_since_checkpoint: 0,
256            items_since_checkpoint: 0,
257        })
258    }
259
260    fn push_str(&mut self, output: &mut String, value: &str) -> OperationControlResult<()> {
261        let mut start = 0usize;
262        while start < value.len() {
263            let remaining_budget = CONTROLLED_EDIT_REBUILD_CHECKPOINT_BYTES
264                .saturating_sub(self.bytes_since_checkpoint);
265            if remaining_budget == 0 {
266                self.checkpoint()?;
267                continue;
268            }
269
270            let mut end = start.saturating_add(remaining_budget).min(value.len());
271            while end > start && !value.is_char_boundary(end) {
272                end -= 1;
273            }
274            if end == start {
275                self.checkpoint()?;
276                continue;
277            }
278
279            output.push_str(&value[start..end]);
280            self.bytes_since_checkpoint += end - start;
281            start = end;
282            if self.bytes_since_checkpoint == CONTROLLED_EDIT_REBUILD_CHECKPOINT_BYTES {
283                self.checkpoint()?;
284            }
285        }
286        Ok(())
287    }
288
289    fn processed_item(&mut self) -> OperationControlResult<()> {
290        self.items_since_checkpoint += 1;
291        if self.items_since_checkpoint == CONTROLLED_EDIT_REBUILD_CHECKPOINT_ITEMS {
292            self.checkpoint()?;
293        }
294        Ok(())
295    }
296
297    fn checkpoint(&mut self) -> OperationControlResult<()> {
298        self.control.checkpoint()?;
299        self.bytes_since_checkpoint = 0;
300        self.items_since_checkpoint = 0;
301        Ok(())
302    }
303
304    fn finish(&self) -> OperationControlResult<()> {
305        self.control.checkpoint()
306    }
307}
308
309#[derive(Debug, Clone)]
310struct SourceEditMap {
311    original_len: usize,
312    output_len: usize,
313    segments: Vec<EditMapSegment>,
314    gaps: Vec<EditMapGap>,
315    // Boundary mappings are frequent (notably CRLF normalization) but do not make a span
316    // unmappable. Keep only the relevant regions in a searchable index for fact lookups.
317    unmapped_output_ranges: Vec<Range<usize>>,
318    #[cfg(test)]
319    scan_stats: EditMapScanStats,
320}
321
322impl SourceEditMap {
323    fn identity(source_len: usize) -> Self {
324        let segments = (source_len > 0)
325            .then_some(EditMapSegment {
326                output: 0..source_len,
327                original: 0..source_len,
328                mapping: SegmentMapping::ExactBytes,
329            })
330            .into_iter()
331            .collect();
332        Self {
333            original_len: source_len,
334            output_len: source_len,
335            segments,
336            gaps: Vec::new(),
337            unmapped_output_ranges: Vec::new(),
338            #[cfg(test)]
339            scan_stats: EditMapScanStats::default(),
340        }
341    }
342
343    /// Maps a parser-input span to the smallest exact span in the original source.
344    ///
345    /// A span that crosses deleted text or ends inside a length-changing replacement is not exact
346    /// and returns `None`. Adjacent facts remain independently mappable.
347    fn try_map_span(&self, span: SourceSpan) -> Option<SourceSpan> {
348        if span.start > span.end || span.end > self.output_len {
349            return None;
350        }
351        if self.has_unmapped_overlap(span.start, span.end)
352            || (span.start < span.end && self.has_gap_inside(span.start, span.end))
353        {
354            return None;
355        }
356
357        let start = self.original_at_start(span.start)?;
358        let end = if span.start == span.end {
359            start
360        } else {
361            self.original_at_end(span.end)?
362        };
363        (start <= end).then(|| SourceSpan::new(start, end))
364    }
365
366    /// Maps span boundaries while allowing deleted source bytes inside the enclosing range.
367    ///
368    /// This is provenance-only: callers must not treat the returned range as an exact rewrite
369    /// target. It is used for parser facts such as multiline keys that cross dedented indentation
370    /// or normalized line endings.
371    fn try_map_enclosing_span(&self, span: SourceSpan) -> Option<SourceSpan> {
372        if span.start > span.end
373            || span.end > self.output_len
374            || self.has_unmapped_overlap(span.start, span.end)
375        {
376            return None;
377        }
378
379        let start = self.original_at_start(span.start)?;
380        let end = if span.start == span.end {
381            start
382        } else {
383            self.original_at_end(span.end)?
384        };
385        (start <= end).then(|| SourceSpan::new(start, end))
386    }
387
388    fn original_at_start(&self, offset: usize) -> Option<usize> {
389        if offset > self.output_len {
390            return None;
391        }
392        if let Some(gap) = self.gap_at(offset) {
393            return Some(gap.original_right);
394        }
395        if offset == self.output_len {
396            return match self.segments.last() {
397                Some(segment) => segment.original_at_end(offset),
398                None => Some(self.original_len),
399            };
400        }
401        self.segment_to_right(offset)?.original_at_start(offset)
402    }
403
404    fn original_at_end(&self, offset: usize) -> Option<usize> {
405        if offset > self.output_len {
406            return None;
407        }
408        if let Some(gap) = self.gap_at(offset) {
409            return Some(gap.original_left);
410        }
411        if offset == 0 {
412            return match self.segments.first() {
413                Some(segment) => segment.original_at_start(offset),
414                None => Some(0),
415            };
416        }
417        self.segment_to_left(offset)?.original_at_end(offset)
418    }
419
420    fn segment_to_right(&self, offset: usize) -> Option<&EditMapSegment> {
421        let index = self
422            .segments
423            .partition_point(|segment| segment.output.end <= offset);
424        self.segments
425            .get(index)
426            .filter(|segment| segment.output.start <= offset && offset < segment.output.end)
427    }
428
429    fn segment_to_left(&self, offset: usize) -> Option<&EditMapSegment> {
430        let index = self
431            .segments
432            .partition_point(|segment| segment.output.start < offset)
433            .checked_sub(1)?;
434        self.segments
435            .get(index)
436            .filter(|segment| segment.output.start < offset && offset <= segment.output.end)
437    }
438
439    fn gap_at(&self, offset: usize) -> Option<&EditMapGap> {
440        self.gaps
441            .binary_search_by_key(&offset, |gap| gap.output_offset)
442            .ok()
443            .and_then(|index| self.gaps.get(index))
444    }
445
446    fn has_gap_inside(&self, start: usize, end: usize) -> bool {
447        let first = self.gaps.partition_point(|gap| gap.output_offset <= start);
448        self.gaps
449            .get(first)
450            .is_some_and(|gap| gap.output_offset < end)
451    }
452
453    fn has_unmapped_overlap(&self, start: usize, end: usize) -> bool {
454        let first = self
455            .unmapped_output_ranges
456            .partition_point(|range| range.end <= start);
457        self.unmapped_output_ranges.get(first).is_some_and(|range| {
458            if start == end {
459                range.start < start && start < range.end
460            } else {
461                range.start < end && start < range.end
462            }
463        })
464    }
465
466    #[cfg(test)]
467    fn unmapped_ranges_match_segments(&self) -> bool {
468        self.unmapped_output_ranges.iter().eq(self
469            .segments
470            .iter()
471            .filter(|segment| segment.mapping == SegmentMapping::Unmapped)
472            .map(|segment| &segment.output))
473    }
474
475    #[cfg(test)]
476    fn is_well_formed(&self) -> bool {
477        let control = OperationControl::new();
478        let mut checkpoints = ControlledEditRebuildCheckpoints::new(&control)
479            .expect("a private parse control cannot be cancelled");
480        self.is_well_formed_controlled(&mut checkpoints)
481            .expect("a private parse control cannot be cancelled")
482    }
483
484    #[cfg(any(test, debug_assertions))]
485    fn is_well_formed_controlled(
486        &self,
487        checkpoints: &mut ControlledEditRebuildCheckpoints<'_>,
488    ) -> OperationControlResult<bool> {
489        let mut previous_segment: Option<&EditMapSegment> = None;
490        let mut unmapped_index = 0usize;
491        for segment in &self.segments {
492            checkpoints.processed_item()?;
493            if segment.output.start >= segment.output.end {
494                return Ok(false);
495            }
496            if let Some(previous) = previous_segment
497                && (previous.output.end > segment.output.start
498                    || previous.original.end > segment.original.start)
499            {
500                return Ok(false);
501            }
502            if segment.mapping == SegmentMapping::Unmapped {
503                if self.unmapped_output_ranges.get(unmapped_index) != Some(&segment.output) {
504                    return Ok(false);
505                }
506                unmapped_index += 1;
507            }
508            previous_segment = Some(segment);
509        }
510        if unmapped_index != self.unmapped_output_ranges.len()
511            || self
512                .segments
513                .last()
514                .is_some_and(|segment| segment.output.end > self.output_len)
515        {
516            return Ok(false);
517        }
518
519        let mut previous_gap_offset: Option<usize> = None;
520        for gap in &self.gaps {
521            checkpoints.processed_item()?;
522            if previous_gap_offset.is_some_and(|offset| offset >= gap.output_offset) {
523                return Ok(false);
524            }
525            previous_gap_offset = Some(gap.output_offset);
526        }
527
528        let mut previous_unmapped_end: Option<usize> = None;
529        for range in &self.unmapped_output_ranges {
530            checkpoints.processed_item()?;
531            if previous_unmapped_end.is_some_and(|end| end > range.start) {
532                return Ok(false);
533            }
534            previous_unmapped_end = Some(range.end);
535        }
536        Ok(true)
537    }
538}
539
540#[derive(Debug, Clone, PartialEq, Eq)]
541struct EditMapSegment {
542    output: Range<usize>,
543    original: Range<usize>,
544    mapping: SegmentMapping,
545}
546
547impl EditMapSegment {
548    fn original_at_start(&self, offset: usize) -> Option<usize> {
549        match self.mapping {
550            SegmentMapping::ExactBytes => self
551                .original
552                .start
553                .checked_add(offset.checked_sub(self.output.start)?),
554            SegmentMapping::Boundaries if offset == self.output.start => Some(self.original.start),
555            SegmentMapping::Boundaries if offset == self.output.end => Some(self.original.end),
556            SegmentMapping::Boundaries => None,
557            SegmentMapping::Unmapped => None,
558        }
559    }
560
561    fn original_at_end(&self, offset: usize) -> Option<usize> {
562        self.original_at_start(offset)
563    }
564}
565
566#[derive(Debug, Clone, Copy, PartialEq, Eq)]
567enum SegmentMapping {
568    ExactBytes,
569    Boundaries,
570    Unmapped,
571}
572
573#[derive(Debug, Clone, Copy, PartialEq, Eq)]
574struct EditMapGap {
575    output_offset: usize,
576    original_left: usize,
577    original_right: usize,
578}
579
580#[cfg(test)]
581#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
582struct EditMapScanStats {
583    copy_ranges: usize,
584    copy_segment_steps: usize,
585    copy_gap_steps: usize,
586    lookup_segment_advances: usize,
587    lookup_gap_advances: usize,
588    exact_segment_steps: usize,
589    exact_gap_steps: usize,
590}
591
592/// Monotonic read positions into the map consumed by one sorted edit batch.
593///
594/// Copying, endpoint lookup, and exactness checks advance independently because replacements read
595/// the same range for all three purposes. Every index only increases; copying can revisit at most
596/// the one segment or gap that straddles the next range boundary.
597#[derive(Default)]
598struct EditMapCursor {
599    copy_segment_index: usize,
600    copy_gap_index: usize,
601    lookup_segment_index: usize,
602    lookup_gap_index: usize,
603    exact_segment_index: usize,
604    exact_gap_index: usize,
605    #[cfg(debug_assertions)]
606    last_copy_end: usize,
607    #[cfg(debug_assertions)]
608    last_lookup_offset: usize,
609    #[cfg(debug_assertions)]
610    last_exact_end: usize,
611    #[cfg(test)]
612    scan_stats: EditMapScanStats,
613}
614
615impl EditMapCursor {
616    fn advance_lookup_to(
617        &mut self,
618        old: &SourceEditMap,
619        offset: usize,
620        checkpoints: &mut ControlledEditRebuildCheckpoints<'_>,
621    ) -> OperationControlResult<()> {
622        #[cfg(debug_assertions)]
623        {
624            debug_assert!(self.last_lookup_offset <= offset);
625            self.last_lookup_offset = offset;
626        }
627
628        while old
629            .segments
630            .get(self.lookup_segment_index)
631            .is_some_and(|segment| segment.output.end <= offset)
632        {
633            checkpoints.processed_item()?;
634            self.lookup_segment_index += 1;
635            #[cfg(test)]
636            {
637                self.scan_stats.lookup_segment_advances += 1;
638            }
639        }
640        while old
641            .gaps
642            .get(self.lookup_gap_index)
643            .is_some_and(|gap| gap.output_offset < offset)
644        {
645            checkpoints.processed_item()?;
646            self.lookup_gap_index += 1;
647            #[cfg(test)]
648            {
649                self.scan_stats.lookup_gap_advances += 1;
650            }
651        }
652        Ok(())
653    }
654
655    fn gap_at_lookup_offset<'a>(
656        &self,
657        old: &'a SourceEditMap,
658        offset: usize,
659    ) -> Option<&'a EditMapGap> {
660        old.gaps
661            .get(self.lookup_gap_index)
662            .filter(|gap| gap.output_offset == offset)
663    }
664
665    fn original_at_start(
666        &mut self,
667        old: &SourceEditMap,
668        offset: usize,
669        checkpoints: &mut ControlledEditRebuildCheckpoints<'_>,
670    ) -> OperationControlResult<Option<usize>> {
671        if offset > old.output_len {
672            return Ok(None);
673        }
674        self.advance_lookup_to(old, offset, checkpoints)?;
675        if let Some(gap) = self.gap_at_lookup_offset(old, offset) {
676            return Ok(Some(gap.original_right));
677        }
678        if offset == old.output_len {
679            return Ok(match old.segments.last() {
680                Some(segment) => segment.original_at_end(offset),
681                None => Some(old.original_len),
682            });
683        }
684        Ok(old
685            .segments
686            .get(self.lookup_segment_index)
687            .filter(|segment| segment.output.start <= offset && offset < segment.output.end)
688            .and_then(|segment| segment.original_at_start(offset)))
689    }
690
691    fn original_at_end(
692        &mut self,
693        old: &SourceEditMap,
694        offset: usize,
695        checkpoints: &mut ControlledEditRebuildCheckpoints<'_>,
696    ) -> OperationControlResult<Option<usize>> {
697        if offset > old.output_len {
698            return Ok(None);
699        }
700        self.advance_lookup_to(old, offset, checkpoints)?;
701        if let Some(gap) = self.gap_at_lookup_offset(old, offset) {
702            return Ok(Some(gap.original_left));
703        }
704        if offset == 0 {
705            return Ok(match old.segments.first() {
706                Some(segment) => segment.original_at_start(offset),
707                None => Some(0),
708            });
709        }
710        if let Some(segment) = old.segments.get(self.lookup_segment_index)
711            && segment.output.start < offset
712            && offset < segment.output.end
713        {
714            return Ok(segment.original_at_end(offset));
715        }
716        Ok(self
717            .lookup_segment_index
718            .checked_sub(1)
719            .and_then(|index| old.segments.get(index))
720            .filter(|segment| segment.output.start < offset && offset <= segment.output.end)
721            .and_then(|segment| segment.original_at_end(offset)))
722    }
723
724    fn original_anchor(
725        &mut self,
726        old: &SourceEditMap,
727        offset: usize,
728        checkpoints: &mut ControlledEditRebuildCheckpoints<'_>,
729    ) -> OperationControlResult<usize> {
730        self.advance_lookup_to(old, offset, checkpoints)?;
731        if let Some(gap) = self.gap_at_lookup_offset(old, offset) {
732            return Ok(gap.original_right);
733        }
734        if offset == old.output_len {
735            return Ok(old.original_len);
736        }
737        Ok(old
738            .segments
739            .get(self.lookup_segment_index)
740            .filter(|segment| segment.output.start <= offset && offset < segment.output.end)
741            .map_or(old.original_len, |segment| segment.original.start))
742    }
743
744    fn range_is_exact_bytes(
745        &mut self,
746        old: &SourceEditMap,
747        range: &Range<usize>,
748        checkpoints: &mut ControlledEditRebuildCheckpoints<'_>,
749    ) -> OperationControlResult<bool> {
750        #[cfg(debug_assertions)]
751        {
752            debug_assert!(self.last_exact_end <= range.start);
753            self.last_exact_end = range.end;
754        }
755        if range.start > range.end || range.end > old.output_len {
756            return Ok(false);
757        }
758        if range.start == range.end {
759            return Ok(true);
760        }
761
762        while old
763            .segments
764            .get(self.exact_segment_index)
765            .is_some_and(|segment| segment.output.end <= range.start)
766        {
767            checkpoints.processed_item()?;
768            self.exact_segment_index += 1;
769            #[cfg(test)]
770            {
771                self.scan_stats.exact_segment_steps += 1;
772            }
773        }
774        while old
775            .gaps
776            .get(self.exact_gap_index)
777            .is_some_and(|gap| gap.output_offset <= range.start)
778        {
779            checkpoints.processed_item()?;
780            self.exact_gap_index += 1;
781            #[cfg(test)]
782            {
783                self.scan_stats.exact_gap_steps += 1;
784            }
785        }
786        if old
787            .gaps
788            .get(self.exact_gap_index)
789            .is_some_and(|gap| gap.output_offset < range.end)
790        {
791            checkpoints.processed_item()?;
792            #[cfg(test)]
793            {
794                self.scan_stats.exact_gap_steps += 1;
795            }
796            return Ok(false);
797        }
798
799        let mut output_offset = range.start;
800        while output_offset < range.end {
801            let Some(segment) = old.segments.get(self.exact_segment_index) else {
802                return Ok(false);
803            };
804            checkpoints.processed_item()?;
805            #[cfg(test)]
806            {
807                self.scan_stats.exact_segment_steps += 1;
808            }
809            if segment.output.start > output_offset || segment.mapping != SegmentMapping::ExactBytes
810            {
811                return Ok(false);
812            }
813            output_offset = segment.output.end.min(range.end);
814            if segment.output.end <= range.end {
815                self.exact_segment_index += 1;
816            }
817        }
818        Ok(true)
819    }
820}
821
822struct EditMapBuilder {
823    original_len: usize,
824    output_len: usize,
825    segments: Vec<EditMapSegment>,
826    gaps: Vec<EditMapGap>,
827    cursor: EditMapCursor,
828}
829
830impl EditMapBuilder {
831    fn new(original_len: usize) -> Self {
832        Self {
833            original_len,
834            output_len: 0,
835            segments: Vec::new(),
836            gaps: Vec::new(),
837            cursor: EditMapCursor::default(),
838        }
839    }
840
841    fn copy_from(
842        &mut self,
843        old: &SourceEditMap,
844        range: Range<usize>,
845        checkpoints: &mut ControlledEditRebuildCheckpoints<'_>,
846    ) -> OperationControlResult<()> {
847        if range.start >= range.end {
848            return Ok(());
849        }
850        #[cfg(debug_assertions)]
851        {
852            debug_assert!(self.cursor.last_copy_end <= range.start);
853            self.cursor.last_copy_end = range.end;
854        }
855        #[cfg(test)]
856        {
857            self.cursor.scan_stats.copy_ranges += 1;
858        }
859        let output_base = self.output_len;
860
861        while old
862            .segments
863            .get(self.cursor.copy_segment_index)
864            .is_some_and(|segment| segment.output.end <= range.start)
865        {
866            checkpoints.processed_item()?;
867            #[cfg(test)]
868            {
869                self.cursor.scan_stats.copy_segment_steps += 1;
870            }
871            self.cursor.copy_segment_index += 1;
872        }
873        while let Some(segment) = old.segments.get(self.cursor.copy_segment_index) {
874            if segment.output.start >= range.end {
875                break;
876            }
877            checkpoints.processed_item()?;
878            #[cfg(test)]
879            {
880                self.cursor.scan_stats.copy_segment_steps += 1;
881            }
882            let start = segment.output.start.max(range.start);
883            let end = segment.output.end.min(range.end);
884            debug_assert!(start < end);
885
886            let (original, mapping) = match segment.mapping {
887                SegmentMapping::ExactBytes => {
888                    let original_start = segment.original.start + (start - segment.output.start);
889                    (
890                        original_start..original_start + (end - start),
891                        SegmentMapping::ExactBytes,
892                    )
893                }
894                SegmentMapping::Boundaries
895                    if start == segment.output.start && end == segment.output.end =>
896                {
897                    (segment.original.clone(), SegmentMapping::Boundaries)
898                }
899                SegmentMapping::Boundaries | SegmentMapping::Unmapped => {
900                    let anchor = segment.original.start;
901                    (anchor..anchor, SegmentMapping::Unmapped)
902                }
903            };
904            self.push_segment(EditMapSegment {
905                output: output_base + (start - range.start)..output_base + (end - range.start),
906                original,
907                mapping,
908            });
909            if segment.output.end <= range.end {
910                self.cursor.copy_segment_index += 1;
911            } else {
912                break;
913            }
914        }
915
916        while old
917            .gaps
918            .get(self.cursor.copy_gap_index)
919            .is_some_and(|gap| gap.output_offset < range.start)
920        {
921            checkpoints.processed_item()?;
922            #[cfg(test)]
923            {
924                self.cursor.scan_stats.copy_gap_steps += 1;
925            }
926            self.cursor.copy_gap_index += 1;
927        }
928        while let Some(gap) = old.gaps.get(self.cursor.copy_gap_index) {
929            if gap.output_offset > range.end {
930                break;
931            }
932            checkpoints.processed_item()?;
933            #[cfg(test)]
934            {
935                self.cursor.scan_stats.copy_gap_steps += 1;
936            }
937            self.push_gap(EditMapGap {
938                output_offset: output_base + (gap.output_offset - range.start),
939                original_left: gap.original_left,
940                original_right: gap.original_right,
941            });
942            if gap.output_offset == range.end {
943                break;
944            }
945            self.cursor.copy_gap_index += 1;
946        }
947        self.output_len += range.end - range.start;
948        Ok(())
949    }
950
951    fn delete_from(
952        &mut self,
953        old: &SourceEditMap,
954        range: Range<usize>,
955        checkpoints: &mut ControlledEditRebuildCheckpoints<'_>,
956    ) -> OperationControlResult<()> {
957        let left = match self.cursor.original_at_end(old, range.start, checkpoints)? {
958            Some(offset) => offset,
959            None => self.cursor.original_anchor(old, range.start, checkpoints)?,
960        };
961        let right = match self.cursor.original_at_start(old, range.end, checkpoints)? {
962            Some(offset) => offset,
963            None => self.cursor.original_anchor(old, range.end, checkpoints)?,
964        };
965        self.push_gap(EditMapGap {
966            output_offset: self.output_len,
967            original_left: left,
968            original_right: right,
969        });
970        Ok(())
971    }
972
973    fn replace_from(
974        &mut self,
975        old: &SourceEditMap,
976        range: Range<usize>,
977        replacement_len: usize,
978        mapping: ReplacementMapping,
979        checkpoints: &mut ControlledEditRebuildCheckpoints<'_>,
980    ) -> OperationControlResult<()> {
981        let original_start = self
982            .cursor
983            .original_at_start(old, range.start, checkpoints)?;
984        let start_is_mappable = original_start.is_some();
985        let original_start = match original_start {
986            Some(offset) => offset,
987            None => self.cursor.original_anchor(old, range.start, checkpoints)?,
988        };
989        let original_end = self.cursor.original_at_end(old, range.end, checkpoints)?;
990        let endpoints_are_mappable = start_is_mappable && original_end.is_some();
991        let original_end = original_end.unwrap_or(original_start);
992        let exact = mapping == ReplacementMapping::ExactBytes
993            && endpoints_are_mappable
994            && replacement_len == range.end - range.start
995            && self.cursor.range_is_exact_bytes(old, &range, checkpoints)?
996            && original_end.checked_sub(original_start) == Some(replacement_len);
997        self.push_segment(EditMapSegment {
998            output: self.output_len..self.output_len + replacement_len,
999            original: original_start..original_end,
1000            mapping: if !endpoints_are_mappable {
1001                SegmentMapping::Unmapped
1002            } else if exact {
1003                SegmentMapping::ExactBytes
1004            } else {
1005                SegmentMapping::Boundaries
1006            },
1007        });
1008        self.output_len += replacement_len;
1009        Ok(())
1010    }
1011
1012    fn push_segment(&mut self, segment: EditMapSegment) {
1013        if let Some(last) = self.segments.last_mut()
1014            && last.mapping == SegmentMapping::ExactBytes
1015            && segment.mapping == SegmentMapping::ExactBytes
1016            && last.output.end == segment.output.start
1017            && last.original.end == segment.original.start
1018        {
1019            last.output.end = segment.output.end;
1020            last.original.end = segment.original.end;
1021            return;
1022        }
1023        self.segments.push(segment);
1024    }
1025
1026    fn push_gap(&mut self, gap: EditMapGap) {
1027        if let Some(last) = self.gaps.last_mut() {
1028            assert!(
1029                last.output_offset <= gap.output_offset,
1030                "source edit map gaps must be emitted in output order"
1031            );
1032            if last.output_offset == gap.output_offset {
1033                last.original_left = last.original_left.min(gap.original_left);
1034                last.original_right = last.original_right.max(gap.original_right);
1035                return;
1036            }
1037        }
1038        self.gaps.push(gap);
1039    }
1040
1041    fn finish(
1042        self,
1043        output_len: usize,
1044        checkpoints: &mut ControlledEditRebuildCheckpoints<'_>,
1045    ) -> OperationControlResult<SourceEditMap> {
1046        debug_assert_eq!(self.output_len, output_len);
1047        let segments = self.segments;
1048        let mut unmapped_output_ranges = Vec::new();
1049        for segment in &segments {
1050            checkpoints.processed_item()?;
1051            if segment.mapping == SegmentMapping::Unmapped {
1052                unmapped_output_ranges.push(segment.output.clone());
1053            }
1054        }
1055        Ok(SourceEditMap {
1056            original_len: self.original_len,
1057            output_len,
1058            segments,
1059            gaps: self.gaps,
1060            unmapped_output_ranges,
1061            #[cfg(test)]
1062            scan_stats: self.cursor.scan_stats,
1063        })
1064    }
1065}
1066
1067fn edits_are_sorted_controlled(
1068    edits: &[SourceEdit],
1069    checkpoints: &mut ControlledEditRebuildCheckpoints<'_>,
1070) -> OperationControlResult<bool> {
1071    for pair in edits.windows(2) {
1072        checkpoints.processed_item()?;
1073        if (pair[0].range.start, pair[0].range.end) > (pair[1].range.start, pair[1].range.end) {
1074            return Ok(false);
1075        }
1076    }
1077    Ok(true)
1078}
1079
1080fn assert_valid_edits_controlled(
1081    source: &str,
1082    edits: &[SourceEdit],
1083    checkpoints: &mut ControlledEditRebuildCheckpoints<'_>,
1084) -> OperationControlResult<(usize, usize)> {
1085    let mut cursor = 0usize;
1086    let mut replacement_bytes = 0usize;
1087    let mut removed_bytes = 0usize;
1088    for edit in edits {
1089        checkpoints.processed_item()?;
1090        assert!(edit.range.start <= edit.range.end, "reversed source edit");
1091        assert!(edit.range.start >= cursor, "overlapping source edits");
1092        assert!(edit.range.end <= source.len(), "source edit out of bounds");
1093        assert!(source.is_char_boundary(edit.range.start));
1094        assert!(source.is_char_boundary(edit.range.end));
1095        replacement_bytes = replacement_bytes.saturating_add(edit.replacement.len());
1096        removed_bytes = removed_bytes.saturating_add(edit.range.end - edit.range.start);
1097        cursor = edit.range.end;
1098    }
1099    Ok((replacement_bytes, removed_bytes))
1100}
1101
1102#[cfg(test)]
1103mod tests {
1104    use super::*;
1105    use std::fmt::Write as _;
1106
1107    #[test]
1108    fn composes_deletions_replacements_and_unicode_without_global_degradation() {
1109        let original = "前A\r\nREMOVE中#quot;后";
1110        let mut source = PreprocessedSource::new(original);
1111        let cr = source.text().find('\r').unwrap();
1112        source.apply_edits_uncontrolled(vec![SourceEdit::replace(
1113            cr..cr + 2,
1114            "\n",
1115            ReplacementMapping::Boundaries,
1116        )]);
1117        let remove = source.text().find("REMOVE").unwrap();
1118        source.apply_edits_uncontrolled(vec![SourceEdit::delete(remove..remove + "REMOVE".len())]);
1119        let hash = source.text().find('#').unwrap();
1120        source.apply_edits_uncontrolled(vec![
1121            SourceEdit::replace(hash..hash + 1, "fl°", ReplacementMapping::Boundaries),
1122            SourceEdit::replace(
1123                hash + "#quot".len()..hash + "#quot;".len(),
1124                "¶ß",
1125                ReplacementMapping::Boundaries,
1126            ),
1127        ]);
1128
1129        assert_eq!(source.text(), "前A\n中fl°quot¶ß后");
1130        let middle = source.text().find('中').unwrap();
1131        let mapped = source
1132            .try_map_span(SourceSpan::new(middle, middle + '中'.len_utf8()))
1133            .unwrap();
1134        assert_eq!(&original[mapped.start..mapped.end], "中");
1135
1136        let entity = source.text().find("fl°quot¶ß").unwrap();
1137        let mapped = source
1138            .try_map_span(SourceSpan::new(entity, entity + "fl°quot¶ß".len()))
1139            .unwrap();
1140        assert_eq!(&original[mapped.start..mapped.end], "#quot;");
1141
1142        let crossing_deletion = source.text().find('A').unwrap();
1143        let after_deletion = source.text().find('中').unwrap() + '中'.len_utf8();
1144        assert_eq!(
1145            source.try_map_span(SourceSpan::new(crossing_deletion, after_deletion)),
1146            None
1147        );
1148    }
1149
1150    #[test]
1151    fn enclosing_mapping_preserves_provenance_across_deleted_indentation() {
1152        let original = "  first\n  second\n";
1153        let mut source = PreprocessedSource::new(original);
1154        let second_indent = original.find("  second").expect("second-line indentation");
1155        source.apply_edits_uncontrolled(vec![
1156            SourceEdit::delete(0..2),
1157            SourceEdit::delete(second_indent..second_indent + 2),
1158        ]);
1159
1160        let span = SourceSpan::new(0, "first\nsecond".len());
1161        assert_eq!(source.try_map_span(span), None);
1162        let enclosing = source
1163            .try_map_enclosing_span(span)
1164            .expect("provenance boundaries remain mappable");
1165        assert_eq!(&original[enclosing.start..enclosing.end], "first\n  second");
1166    }
1167
1168    #[test]
1169    fn boundary_replacements_only_reject_locally_ambiguous_offsets() {
1170        let mut source = PreprocessedSource::new("A#x;B");
1171        source.apply_edits_uncontrolled(vec![
1172            SourceEdit::replace(1..2, "fl°", ReplacementMapping::Boundaries),
1173            SourceEdit::replace(3..4, "¶ß", ReplacementMapping::Boundaries),
1174        ]);
1175        assert_eq!(source.text(), "Afl°x¶ßB");
1176
1177        let b = source.text().find('B').unwrap();
1178        assert_eq!(
1179            source.try_map_span(SourceSpan::new(b, b + 1)),
1180            Some(SourceSpan::new(4, 5))
1181        );
1182        assert_eq!(source.try_map_span(SourceSpan::new(2, 2)), None);
1183
1184        let identity = PreprocessedSource::new("A😀B");
1185        assert_eq!(identity.try_map_span(SourceSpan::new(2, 2)), None);
1186    }
1187
1188    #[test]
1189    fn controlled_text_rebuild_cancellation_preserves_the_previous_source_atomically() {
1190        let original = format!(
1191            "{}TAIL",
1192            "a".repeat(CONTROLLED_EDIT_REBUILD_CHECKPOINT_BYTES * 2)
1193        );
1194        let mut source = PreprocessedSource::new(&original);
1195        let before = source.clone();
1196        let tail = source.text().len() - "TAIL".len();
1197        let control = OperationControl::new();
1198        control.cancel_after_checkpoints(1);
1199
1200        let result = source.apply_edits(
1201            vec![SourceEdit::replace(
1202                tail..tail + "TAIL".len(),
1203                "tail",
1204                ReplacementMapping::ExactBytes,
1205            )],
1206            &control,
1207        );
1208
1209        assert!(result.is_err());
1210        assert!(control.is_cancelled());
1211        assert_source_state_eq(&source, &before);
1212    }
1213
1214    #[test]
1215    fn controlled_mapping_rebuild_cancellation_preserves_the_previous_source_atomically() {
1216        let original = "x".repeat(CONTROLLED_EDIT_REBUILD_CHECKPOINT_ITEMS * 2);
1217        let mut source = PreprocessedSource::new(&original);
1218        source.apply_edits_uncontrolled(
1219            (0..original.len())
1220                .map(|offset| {
1221                    SourceEdit::replace(offset..offset + 1, "x", ReplacementMapping::Boundaries)
1222                })
1223                .collect(),
1224        );
1225        assert!(source.edit_map.segments.len() > CONTROLLED_EDIT_REBUILD_CHECKPOINT_ITEMS);
1226        assert!(source.text().len() < CONTROLLED_EDIT_REBUILD_CHECKPOINT_BYTES);
1227
1228        let before = source.clone();
1229        let control = OperationControl::new();
1230        control.cancel_after_checkpoints(1);
1231        let last_byte = source.text().len() - 1;
1232        let result =
1233            source.apply_edits(vec![SourceEdit::delete(last_byte..last_byte + 1)], &control);
1234
1235        assert!(result.is_err());
1236        assert!(control.is_cancelled());
1237        assert_source_state_eq(&source, &before);
1238    }
1239
1240    #[test]
1241    fn same_offset_insertions_and_adjacent_deletions_coalesce_gaps_in_order() {
1242        let mut source = PreprocessedSource::new("aXbYc");
1243        source.apply_edits_uncontrolled(vec![SourceEdit::delete(1..2)]);
1244
1245        source.apply_edits_uncontrolled(vec![
1246            SourceEdit::replace(1..1, "<", ReplacementMapping::Boundaries),
1247            SourceEdit::replace(1..1, ">", ReplacementMapping::Boundaries),
1248            SourceEdit::delete(1..2),
1249            SourceEdit::delete(2..3),
1250        ]);
1251
1252        assert_eq!(source.text(), "a<>c");
1253        assert!(source.edit_map.is_well_formed());
1254        assert_eq!(source.edit_map.gaps.len(), 2);
1255
1256        let c = source.text().find('c').unwrap();
1257        assert_eq!(
1258            source.try_map_span(SourceSpan::new(c, c + 1)),
1259            Some(SourceSpan::new(4, 5))
1260        );
1261        assert_eq!(source.try_map_span(SourceSpan::new(0, c + 1)), None);
1262    }
1263
1264    #[test]
1265    fn composing_an_edit_inside_generated_text_marks_only_that_region_unmappable() {
1266        let mut source = PreprocessedSource::new("A#x;B");
1267        source.apply_edits_uncontrolled(vec![SourceEdit::replace(
1268            1..2,
1269            "fl°",
1270            ReplacementMapping::Boundaries,
1271        )]);
1272        let degree = source.text().find('°').unwrap();
1273        source.apply_edits_uncontrolled(vec![SourceEdit::replace(
1274            degree..degree + '°'.len_utf8(),
1275            "!",
1276            ReplacementMapping::ExactBytes,
1277        )]);
1278        assert!(source.edit_map.unmapped_ranges_match_segments());
1279        assert!(!source.edit_map.unmapped_output_ranges.is_empty());
1280
1281        let generated = source.text().find('fl').unwrap();
1282        assert_eq!(
1283            source.try_map_span(SourceSpan::new(generated, generated + 'fl'.len_utf8())),
1284            None
1285        );
1286        let b = source.text().find('B').unwrap();
1287        assert_eq!(
1288            source.try_map_span(SourceSpan::new(b, b + 1)),
1289            Some(SourceSpan::new(4, 5))
1290        );
1291    }
1292
1293    #[test]
1294    fn crlf_comment_batches_scan_old_map_once_and_preserve_offsets() {
1295        const MIN_INPUT_BYTES: usize = 1024 * 1024;
1296
1297        let mut original = String::with_capacity(MIN_INPUT_BYTES + 256);
1298        let mut pair_count = 0usize;
1299        while original.len() < MIN_INPUT_BYTES {
1300            let group = if pair_count.is_multiple_of(2) {
1301                "even"
1302            } else {
1303                "odd"
1304            };
1305            write!(
1306                original,
1307                "KEEP-{pair_count:05} payload\r\n%% {group}-comment-{pair_count:05} payload\r\n"
1308            )
1309            .unwrap();
1310            pair_count += 1;
1311        }
1312        original.push_str("TAIL\r\n");
1313        assert!(original.len() >= MIN_INPUT_BYTES);
1314
1315        let mut source = PreprocessedSource::new(&original);
1316        source.apply_edits_uncontrolled(crlf_normalization_edits(source.text()));
1317        assert!(!source.text().contains('\r'));
1318        assert!(
1319            source.edit_map.unmapped_output_ranges.is_empty(),
1320            "CRLF boundary mappings must not make every fact lookup scan every line"
1321        );
1322
1323        let even_comments = comment_line_deletions(source.text(), "%% even-comment-");
1324        source.apply_edits_uncontrolled(even_comments);
1325
1326        let old_segment_count = source.edit_map.segments.len();
1327        let old_gap_count = source.edit_map.gaps.len();
1328        let odd_comments = comment_line_deletions(source.text(), "%% odd-comment-");
1329        let odd_comment_count = odd_comments.len();
1330        assert!(odd_comment_count > 8_000);
1331        assert!(old_gap_count > 8_000);
1332
1333        source.apply_edits_uncontrolled(odd_comments);
1334
1335        let scans = source.edit_map.scan_stats;
1336        assert_eq!(scans.copy_ranges, odd_comment_count + 1);
1337        assert!(
1338            scans.copy_segment_steps <= old_segment_count + scans.copy_ranges,
1339            "copying revisited old segments: {scans:?} for {old_segment_count} segments"
1340        );
1341        assert!(
1342            scans.copy_gap_steps <= old_gap_count + scans.copy_ranges,
1343            "copying revisited old gaps: {scans:?} for {old_gap_count} gaps"
1344        );
1345        assert!(
1346            scans.lookup_segment_advances <= old_segment_count,
1347            "endpoint lookup revisited old segments: {scans:?}"
1348        );
1349        assert!(
1350            scans.lookup_gap_advances <= old_gap_count,
1351            "endpoint lookup revisited old gaps: {scans:?}"
1352        );
1353
1354        assert!(!source.text().contains("%%"));
1355        for pair_index in [0, pair_count / 2, pair_count - 1] {
1356            let literal = format!("KEEP-{pair_index:05}");
1357            assert_literal_mapping(&source, &original, &literal, &literal);
1358        }
1359
1360        let first_keep = "KEEP-00000 payload";
1361        let output_newline = source.text().find(first_keep).unwrap() + first_keep.len();
1362        let original_newline = original.find(first_keep).unwrap() + first_keep.len();
1363        assert_eq!(
1364            source.try_map_span(SourceSpan::new(output_newline, output_newline + 1)),
1365            Some(SourceSpan::new(original_newline, original_newline + 2))
1366        );
1367
1368        let second_keep = source.text().find("KEEP-00001").unwrap();
1369        assert_eq!(
1370            source.try_map_span(SourceSpan::new(output_newline, second_keep + 1)),
1371            None,
1372            "a span crossing a removed comment must remain unmappable"
1373        );
1374    }
1375
1376    #[test]
1377    fn seeded_edit_compositions_preserve_exact_monotonic_source_ranges() {
1378        const SEED: u64 = 0x4d45_524d_414e_5538;
1379        const CASES: usize = 64;
1380        let original = "前|KEEP_A|COPY|DELETE|\r\n|#quot;|<span title=\"值😀\">|WIDE|KEEP_B|后";
1381        let mut rng = DeterministicRng::new(SEED);
1382
1383        for case in 0..CASES {
1384            let mut source = PreprocessedSource::new(original);
1385            for transformation in shuffled_transformations(&mut rng) {
1386                apply_transformation(&mut source, transformation);
1387                assert_mapping_invariants(&source, original, case, transformation);
1388            }
1389
1390            assert_literal_mapping(&source, original, "KEEP_A", "KEEP_A");
1391            assert_literal_mapping(&source, original, "copy", "COPY");
1392            assert_literal_mapping(&source, original, "\n", "\r\n");
1393            assert_literal_mapping(
1394                &source,
1395                original,
1396                "title=&quot;值😀&quot;",
1397                "title=\"值😀\"",
1398            );
1399            assert_literal_mapping(&source, original, "长值😀", "WIDE");
1400            assert_literal_mapping(&source, original, "KEEP_B", "KEEP_B");
1401
1402            let crossing_start = source.text().find("copy").unwrap();
1403            let crossing_end = source.text().find('\n').unwrap() + 1;
1404            assert_eq!(
1405                source.try_map_span(SourceSpan::new(crossing_start, crossing_end)),
1406                None,
1407                "case {case}: a span crossing deleted source must stay locally unmappable"
1408            );
1409
1410            if case % 2 == 0 {
1411                assert_literal_mapping(&source, original, "fl°quot¶ß", "#quot;");
1412            } else {
1413                let degree = source.text().find('°').unwrap();
1414                source.apply_edits_uncontrolled(vec![SourceEdit::delete(
1415                    degree..degree + '°'.len_utf8(),
1416                )]);
1417                assert_mapping_invariants(
1418                    &source,
1419                    original,
1420                    case,
1421                    SeededTransformation::GeneratedInteriorDeletion,
1422                );
1423                let generated = source.text().find("flquot¶ß").unwrap();
1424                assert_eq!(
1425                    source.try_map_span(SourceSpan::new(generated, generated + "flquot¶ß".len(),)),
1426                    None,
1427                    "case {case}: editing generated bytes must not invent an original range"
1428                );
1429                assert_literal_mapping(&source, original, "KEEP_B", "KEEP_B");
1430            }
1431        }
1432    }
1433
1434    #[derive(Debug, Clone, Copy)]
1435    enum SeededTransformation {
1436        ExactByteReplacement,
1437        Deletion,
1438        CrLfNormalization,
1439        EntityEncoding,
1440        QuoteExpansion,
1441        BoundaryReplacement,
1442        GeneratedInteriorDeletion,
1443    }
1444
1445    struct DeterministicRng(u64);
1446
1447    impl DeterministicRng {
1448        fn new(seed: u64) -> Self {
1449            Self(seed)
1450        }
1451
1452        fn index(&mut self, upper: usize) -> usize {
1453            self.0 ^= self.0 << 13;
1454            self.0 ^= self.0 >> 7;
1455            self.0 ^= self.0 << 17;
1456            (self.0 as usize) % upper
1457        }
1458    }
1459
1460    fn shuffled_transformations(rng: &mut DeterministicRng) -> [SeededTransformation; 6] {
1461        let mut transformations = [
1462            SeededTransformation::ExactByteReplacement,
1463            SeededTransformation::Deletion,
1464            SeededTransformation::CrLfNormalization,
1465            SeededTransformation::EntityEncoding,
1466            SeededTransformation::QuoteExpansion,
1467            SeededTransformation::BoundaryReplacement,
1468        ];
1469        for index in (1..transformations.len()).rev() {
1470            transformations.swap(index, rng.index(index + 1));
1471        }
1472        transformations
1473    }
1474
1475    fn apply_transformation(source: &mut PreprocessedSource, transformation: SeededTransformation) {
1476        match transformation {
1477            SeededTransformation::ExactByteReplacement => {
1478                replace_once(source, "COPY", "copy", ReplacementMapping::ExactBytes);
1479            }
1480            SeededTransformation::Deletion => {
1481                let start = source.text().find("DELETE|").unwrap();
1482                source.apply_edits_uncontrolled(vec![SourceEdit::delete(
1483                    start..start + "DELETE|".len(),
1484                )]);
1485            }
1486            SeededTransformation::CrLfNormalization => {
1487                replace_once(source, "\r\n", "\n", ReplacementMapping::Boundaries);
1488            }
1489            SeededTransformation::EntityEncoding => {
1490                let start = source.text().find("#quot;").unwrap();
1491                source.apply_edits_uncontrolled(vec![
1492                    SourceEdit::replace(start..start + 1, "fl°", ReplacementMapping::Boundaries),
1493                    SourceEdit::replace(
1494                        start + "#quot".len()..start + "#quot;".len(),
1495                        "¶ß",
1496                        ReplacementMapping::Boundaries,
1497                    ),
1498                ]);
1499            }
1500            SeededTransformation::QuoteExpansion => {
1501                let edits = source
1502                    .text()
1503                    .match_indices('"')
1504                    .map(|(start, _)| {
1505                        SourceEdit::replace(
1506                            start..start + 1,
1507                            "&quot;",
1508                            ReplacementMapping::Boundaries,
1509                        )
1510                    })
1511                    .collect();
1512                source.apply_edits_uncontrolled(edits);
1513            }
1514            SeededTransformation::BoundaryReplacement => {
1515                replace_once(source, "WIDE", "长值😀", ReplacementMapping::Boundaries);
1516            }
1517            SeededTransformation::GeneratedInteriorDeletion => unreachable!(),
1518        }
1519    }
1520
1521    fn replace_once(
1522        source: &mut PreprocessedSource,
1523        needle: &str,
1524        replacement: &str,
1525        mapping: ReplacementMapping,
1526    ) {
1527        let start = source.text().find(needle).unwrap();
1528        source.apply_edits_uncontrolled(vec![SourceEdit::replace(
1529            start..start + needle.len(),
1530            replacement,
1531            mapping,
1532        )]);
1533    }
1534
1535    fn crlf_normalization_edits(text: &str) -> Vec<SourceEdit> {
1536        text.match_indices("\r\n")
1537            .map(|(start, _)| {
1538                SourceEdit::replace(start..start + 2, "\n", ReplacementMapping::Boundaries)
1539            })
1540            .collect()
1541    }
1542
1543    fn comment_line_deletions(text: &str, prefix: &str) -> Vec<SourceEdit> {
1544        let mut line_start = 0usize;
1545        text.split_inclusive('\n')
1546            .filter_map(|line| {
1547                let range = line_start..line_start + line.len();
1548                line_start = range.end;
1549                line.starts_with(prefix).then(|| SourceEdit::delete(range))
1550            })
1551            .collect()
1552    }
1553
1554    fn assert_literal_mapping(
1555        source: &PreprocessedSource,
1556        original: &str,
1557        output_literal: &str,
1558        original_literal: &str,
1559    ) {
1560        let output_start = source.text().find(output_literal).unwrap();
1561        let original_start = original.find(original_literal).unwrap();
1562        assert_eq!(
1563            source.try_map_span(SourceSpan::new(
1564                output_start,
1565                output_start + output_literal.len(),
1566            )),
1567            Some(SourceSpan::new(
1568                original_start,
1569                original_start + original_literal.len(),
1570            )),
1571            "{output_literal:?} must map exactly to {original_literal:?}"
1572        );
1573    }
1574
1575    fn assert_source_state_eq(actual: &PreprocessedSource, expected: &PreprocessedSource) {
1576        assert_eq!(actual.text, expected.text);
1577        assert_eq!(actual.edit_map.original_len, expected.edit_map.original_len);
1578        assert_eq!(actual.edit_map.output_len, expected.edit_map.output_len);
1579        assert_eq!(actual.edit_map.segments, expected.edit_map.segments);
1580        assert_eq!(actual.edit_map.gaps, expected.edit_map.gaps);
1581        assert_eq!(
1582            actual.edit_map.unmapped_output_ranges,
1583            expected.edit_map.unmapped_output_ranges
1584        );
1585        assert_eq!(actual.edit_map.scan_stats, expected.edit_map.scan_stats);
1586        assert_eq!(
1587            actual.global_expected_syntax,
1588            expected.global_expected_syntax
1589        );
1590        assert_eq!(
1591            actual.global_directive_prefixes,
1592            expected.global_directive_prefixes
1593        );
1594        assert_eq!(
1595            actual.recovered_incomplete_directive,
1596            expected.recovered_incomplete_directive
1597        );
1598    }
1599
1600    fn assert_mapping_invariants(
1601        source: &PreprocessedSource,
1602        original: &str,
1603        case: usize,
1604        transformation: SeededTransformation,
1605    ) {
1606        assert!(
1607            source.edit_map.is_well_formed(),
1608            "case {case} after {transformation:?}: malformed composed edit map"
1609        );
1610        assert_eq!(source.edit_map.original_len, original.len());
1611        assert_eq!(source.edit_map.output_len, source.text().len());
1612
1613        let mut boundaries = source
1614            .text()
1615            .char_indices()
1616            .map(|(offset, _)| offset)
1617            .collect::<Vec<_>>();
1618        boundaries.push(source.text().len());
1619
1620        let mut previous_original_offset = None;
1621        for &offset in &boundaries {
1622            if let Some(mapped) = source.try_map_span(SourceSpan::new(offset, offset)) {
1623                assert_eq!(mapped.start, mapped.end);
1624                assert!(mapped.end <= original.len());
1625                assert!(original.is_char_boundary(mapped.start));
1626                if let Some(previous) = previous_original_offset {
1627                    assert!(
1628                        previous <= mapped.start,
1629                        "case {case} after {transformation:?}: output boundary mapping regressed"
1630                    );
1631                }
1632                previous_original_offset = Some(mapped.start);
1633            }
1634        }
1635
1636        for (start_index, &start) in boundaries.iter().enumerate() {
1637            for &end in &boundaries[start_index..] {
1638                let Some(mapped) = source.try_map_span(SourceSpan::new(start, end)) else {
1639                    continue;
1640                };
1641                assert!(mapped.start <= mapped.end);
1642                assert!(mapped.end <= original.len());
1643                assert!(original.is_char_boundary(mapped.start));
1644                assert!(original.is_char_boundary(mapped.end));
1645            }
1646        }
1647
1648        assert_eq!(
1649            source.try_map_span(SourceSpan::new(0, source.text().len() + 1)),
1650            None
1651        );
1652        let emoji = source.text().find('😀').unwrap();
1653        assert_eq!(
1654            source.try_map_span(SourceSpan::new(emoji + 1, emoji + 1)),
1655            None
1656        );
1657    }
1658}