Skip to main content

kimun_notes/ropetext/
change.rs

1//! What an edit did, told rather than diffed.
2
3use std::ops::Range;
4
5use crate::ropetext::position::Revision;
6
7/// One replaced region, in the coordinates of the text *after* the change.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct Edit {
10    inserted: Range<usize>,
11    removed_bytes: usize,
12}
13
14impl Edit {
15    pub(crate) fn new(inserted: Range<usize>, removed_bytes: usize) -> Self {
16        Self {
17            inserted,
18            removed_bytes,
19        }
20    }
21
22    /// Byte range the new text occupies, in the new text.
23    pub fn inserted(&self) -> Range<usize> {
24        self.inserted.clone()
25    }
26
27    /// How many bytes stood there before.
28    pub fn removed_bytes(&self) -> usize {
29        self.removed_bytes
30    }
31}
32
33/// The outcome of a committed transaction, an undo, or a redo.
34///
35/// A consumer holding a derived cache — a parse, a layout — is *told* what moved
36/// and re-does that much. Nothing has to compare two buffers to find out, which
37/// is the cost this type exists to remove, and nothing has to guess how far the
38/// damage spread, which is the correctness problem underneath that cost.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct Change {
41    revision: Revision,
42    edits: Option<Vec<Edit>>,
43    rows: Range<usize>,
44    line_delta: isize,
45}
46
47impl Change {
48    pub(crate) fn new(
49        revision: Revision,
50        edits: Option<Vec<Edit>>,
51        rows: Range<usize>,
52        line_delta: isize,
53    ) -> Self {
54        Self {
55            revision,
56            edits,
57            rows,
58            line_delta,
59        }
60    }
61
62    /// Which state of the text this change produced.
63    pub fn revision(&self) -> Revision {
64        self.revision
65    }
66
67    /// The replaced regions, in the new text's coordinates.
68    ///
69    /// `None` when the change cannot be described that precisely: undoing or
70    /// redoing a group that several transactions were folded into means several
71    /// coordinate spaces, and a list of ranges that were each correct at a
72    /// different moment is worse than no list at all. A committed transaction
73    /// always reports its edits; only a coalesced group's undo declines to.
74    /// [`Self::rows`] is always populated.
75    pub fn edits(&self) -> Option<&[Edit]> {
76        self.edits.as_deref()
77    }
78
79    /// Rows of the new text whose content is not what it was, so a consumer
80    /// holding a derived cache re-derives that much. Rows *after* it are
81    /// unchanged in content but have moved by [`Self::line_delta`].
82    ///
83    /// Exact for a single transaction. Widened, never narrowed, for a coalesced
84    /// group — over-reporting costs work, under-reporting costs correctness.
85    pub fn rows(&self) -> Range<usize> {
86        self.rows.clone()
87    }
88
89    /// How the text's row count changed. Positive when rows were added.
90    pub fn line_delta(&self) -> isize {
91        self.line_delta
92    }
93
94    /// Whether the damage reaches past a single row.
95    ///
96    /// The named form of `rows().len() > 1 || line_delta() != 0`, for the caches
97    /// whose cheap path is only valid for a change confined to one row.
98    pub fn is_bulk(&self) -> bool {
99        self.line_delta != 0 || self.rows.end.saturating_sub(self.rows.start) > 1
100    }
101}