compose_syntax/
patch.rs

1use crate::span::HasSpan;
2use ecow::{EcoString, EcoVec};
3use std::ops::Range;
4use std::sync::Arc;
5
6#[derive(Debug, Clone, PartialEq, Hash, Eq)]
7pub enum Patch {
8    Insert {
9        // byte index to insert at within the source
10        at: usize,
11        text: EcoString,
12    },
13    Replace {
14        // byte range to replace within the source
15        range: Range<usize>,
16        text: EcoString,
17    },
18    Delete {
19        // byte range to delete within the source
20        range: Range<usize>,
21    },
22}
23
24impl Patch {
25    pub fn apply(&self, source: &mut String) {
26        match self {
27            Patch::Insert { at, text } => source.insert_str(*at, text),
28            Patch::Replace { range, text } => source.replace_range(range.clone(), text),
29            Patch::Delete { range } => source.replace_range(range.clone(), ""),
30        }
31    }
32
33    /// Return a new Patch shifted by `delta` bytes (positive or negative),
34    /// or `None` if the shift would result in invalid (negative) positions.
35    pub fn shifted(self, delta: isize) -> Result<Patch, PatchError> {
36        fn shift_pos(pos: usize, delta: isize) -> Result<usize, PatchError> {
37            let new = pos as isize + delta;
38            if new < 0 { Err(PatchError::OutOfBounds) } else { Ok(new as usize) }
39        }
40
41        Ok(match self {
42            Patch::Insert { at, text } => Patch::Insert {
43                at: shift_pos(at, delta)?,
44                text,
45            },
46            Patch::Replace { range, text } => Patch::Replace {
47                range: shift_pos(range.start, delta)?..shift_pos(range.end, delta)?,
48                text,
49            },
50            Patch::Delete { range } => Patch::Delete {
51                range: shift_pos(range.start, delta)?..range.end,
52            },
53        })
54    }
55
56    fn range(&self) -> Range<usize> {
57        match self {
58            Patch::Insert { at, .. } => *at..*at,
59            Patch::Replace { range, .. } => range.clone(),
60            Patch::Delete { range } => range.clone(),
61        }
62    }
63
64    fn start_pos(&self) -> usize {
65        match self {
66            Patch::Insert { at, .. } => *at,
67            Patch::Replace { range, .. } => range.start,
68            Patch::Delete { range } => range.start,
69        }
70    }
71
72    fn conflicts_with(&self, other: &Patch) -> bool {
73        let r1 = self.range();
74        let r2 = other.range();
75
76        if r1.end > r2.start && r2.end > r1.start {
77            return true;
78        }
79
80        if let Patch::Insert { at, .. } = self {
81            if *at > r2.start && *at < r2.end {
82                return true;
83            }
84        }
85        if let Patch::Insert { at, .. } = other {
86            if *at > r1.start && *at < r1.end {
87                return true;
88            }
89        }
90
91        false
92    }
93}
94
95pub struct PatchEngine {
96    patches: Vec<Patch>,
97}
98
99impl PatchEngine {
100}
101
102#[derive(Debug, Clone)]
103pub enum PatchError {
104    SpanWithoutRange,
105    OutOfBounds,
106    Conflict {
107        a: Arc<Patch>,
108        b: Arc<Patch>,
109    }
110}
111
112impl PatchEngine {
113    pub fn new() -> Self {
114        Self { patches: vec![] }
115    }
116
117    pub fn add_patch(&mut self, patch: Patch) {
118        self.patches.push(patch);
119    }
120
121    pub fn add_patches(&mut self, patches: impl IntoIterator<Item = Patch>) {
122        self.patches.extend(patches);
123    }
124
125    pub fn insert_before(
126        &mut self,
127        node: &impl HasSpan,
128        text: impl Into<EcoString>,
129    ) -> Result<(), PatchError> {
130        let span = node.span();
131        self.add_patch(Patch::Insert {
132            at: span.range().ok_or(PatchError::SpanWithoutRange)?.start,
133            text: text.into(),
134        });
135
136        Ok(())
137    }
138
139    pub fn insert_after(
140        &mut self,
141        node: &impl HasSpan,
142        text: impl Into<EcoString>,
143    ) -> Result<(), PatchError> {
144        let span = node.span();
145        self.add_patch(Patch::Insert {
146            at: span.range().ok_or(PatchError::SpanWithoutRange)?.end,
147            text: text.into(),
148        });
149
150        Ok(())
151    }
152
153    pub fn replace_node(
154        &mut self,
155        node: &impl HasSpan,
156        text: impl Into<EcoString>,
157    ) -> Result<(), PatchError> {
158        let span = node.span();
159        self.add_patch(Patch::Replace {
160            range: span.range().ok_or(PatchError::SpanWithoutRange)?,
161            text: text.into(),
162        });
163
164        Ok(())
165    }
166
167    pub fn delete_node(&mut self, node: &impl HasSpan) -> Result<(), PatchError> {
168        self.add_patch(Patch::Delete {
169            range: node.span().range().ok_or(PatchError::SpanWithoutRange)?,
170        });
171
172        Ok(())
173    }
174
175    fn check_conflicts(&self) -> Result<(), PatchError> {
176        for (i, p1) in self.patches.iter().enumerate() {
177            for p2 in self.patches.iter().skip(i + 1) {
178                if p1.conflicts_with(p2) {
179                    return Err(PatchError::Conflict {
180                        a: Arc::new(p1.clone()),
181                        b: Arc::new(p2.clone()),
182                    });
183                }
184            }
185        }
186        Ok(())
187    }
188
189    pub fn apply_all(self, source: &str) -> Result<String, PatchError> {
190        self.apply_all_with_offset(source, 0)
191    }
192
193    pub fn apply_all_with_offset(mut self, snippet: &str, base_offset: usize) -> Result<String, PatchError> {
194        self.check_conflicts()?;
195
196        let mut result = snippet.to_string();
197        self.patches.sort_by_key(|p| p.start_pos());
198        self.patches.reverse();
199
200        let offset = -(base_offset as isize);
201        for patch in self.patches.iter().filter(|p| {
202            p.range().start >= base_offset && p.range().end <= base_offset + snippet.len()
203        }) {
204            patch.clone().shifted(offset)?.apply(&mut result);
205        }
206
207        Ok(result)
208    }
209
210    pub(crate) fn get_patches(&self) -> EcoVec<Patch> {
211        self.patches.iter().cloned().collect()
212    }
213}