Skip to main content

indigo_kernel/
document.rs

1use crate::edit::{Bias, Edit};
2use imbl::Vector;
3use ropey::Rope;
4
5// TODO: Consider this a first draft of the "document" concept. The next iteration will:
6//
7// Follow "Concurrent Programming with Revisions and Isolation Types", use `Join` and `Apply`
8// traits from `src/merge.rs` with a single `Document` type (i.e. no `Draft` and `Snapshot`), maybe
9// also wait to switch to FugueMax `Replica` if working with OT `Edit` isn't appealing.
10
11pub struct Document {
12    id: usize,
13    rope: Rope,
14    commits: Vector<Commit>,
15}
16
17impl Document {
18    #[must_use]
19    pub fn new(id: usize) -> Self {
20        Self::from_rope(id, Rope::new())
21    }
22
23    #[must_use]
24    pub fn from_str(id: usize, str: &str) -> Self {
25        Self::from_rope(id, Rope::from(str))
26    }
27
28    #[must_use]
29    pub fn from_rope(id: usize, rope: Rope) -> Self {
30        Self {
31            id,
32            rope,
33            commits: Vector::new(),
34        }
35    }
36
37    #[must_use]
38    pub fn version(&self) -> Version {
39        Version {
40            document_id: self.id,
41            commit_index: self.commits.len(),
42        }
43    }
44
45    #[must_use]
46    pub fn rope(&self) -> &Rope {
47        &self.rope
48    }
49
50    #[must_use]
51    pub fn commits(&self) -> &Vector<Commit> {
52        &self.commits
53    }
54
55    #[must_use]
56    pub fn create_snapshot(&self) -> Snapshot {
57        Snapshot {
58            version: self.version(),
59            rope: self.rope.clone(),
60            commits: self.commits.clone(),
61        }
62    }
63
64    #[must_use]
65    pub fn create_draft(&self) -> Draft {
66        self.create_snapshot().into_draft()
67    }
68
69    pub fn apply_edit(&mut self, edit: Edit) -> anyhow::Result<()> {
70        edit.apply(&mut self.rope)?;
71        self.commits.push_back(Commit {
72            original_base_version: self.version(),
73            original_edit: edit.clone(),
74            rebased_edit: edit,
75        });
76        Ok(())
77    }
78
79    pub fn apply_draft(&mut self, draft: Draft) -> anyhow::Result<()> {
80        self.assert_compatible_version(draft.base_version)?;
81        let concurrent_edit = self.compose_since(draft.base_version)?;
82        let rebased_edit = draft.edit.rebase(&concurrent_edit, Bias::Forward)?;
83        rebased_edit.apply(&mut self.rope)?;
84        self.commits.push_back(Commit {
85            original_base_version: draft.base_version,
86            original_edit: draft.edit,
87            rebased_edit,
88        });
89        Ok(())
90    }
91
92    pub fn create_anchor(&self, byte_index: usize, bias: Bias) -> anyhow::Result<Anchor> {
93        Anchor::create(self.version(), &self.rope, byte_index, bias)
94    }
95
96    pub fn resolve_anchor(&self, anchor: Anchor) -> anyhow::Result<usize> {
97        anchor.resolve(self.version(), &self.commits)
98    }
99
100    fn compose_since(&self, base_version: Version) -> anyhow::Result<Edit> {
101        // NOTE: Version compatibility must be checked by caller
102        let mut commits = self
103            .commits
104            .focus()
105            .narrow(base_version.commit_index..)
106            .into_iter();
107        if let Some(first_commit) = commits.next() {
108            let mut edit = first_commit.rebased_edit.clone();
109            for commit in commits {
110                edit = edit.compose(&commit.rebased_edit)?;
111            }
112            Ok(edit)
113        } else {
114            Ok(Edit::identity(&self.rope))
115        }
116    }
117
118    fn assert_compatible_version(&self, version: Version) -> anyhow::Result<()> {
119        anyhow::ensure!(
120            version.document_id == self.version().document_id,
121            "version does not apply to this document"
122        );
123        anyhow::ensure!(
124            version.commit_index <= self.version().commit_index,
125            "version is ahead of document version"
126        );
127        Ok(())
128    }
129}
130
131#[derive(Clone, Copy, Eq, PartialEq)]
132pub struct Version {
133    document_id: usize,
134    commit_index: usize,
135}
136
137#[derive(Clone)]
138pub struct Snapshot {
139    version: Version,
140    rope: Rope,
141    commits: Vector<Commit>,
142}
143
144impl Snapshot {
145    #[must_use]
146    pub fn version(&self) -> Version {
147        self.version
148    }
149
150    #[must_use]
151    pub fn rope(&self) -> &Rope {
152        &self.rope
153    }
154
155    #[must_use]
156    pub fn into_draft(self) -> Draft {
157        Draft {
158            base_version: self.version,
159            edit: Edit::identity(&self.rope),
160            rope: self.rope,
161            commits: self.commits,
162        }
163    }
164
165    #[must_use]
166    pub fn create_draft(&self) -> Draft {
167        self.clone().into_draft()
168    }
169
170    pub fn create_anchor(&self, byte_index: usize, bias: Bias) -> anyhow::Result<Anchor> {
171        Anchor::create(self.version, &self.rope, byte_index, bias)
172    }
173
174    pub fn resolve_anchor(&self, anchor: Anchor) -> anyhow::Result<usize> {
175        anchor.resolve(self.version, &self.commits)
176    }
177}
178
179pub struct Draft {
180    base_version: Version,
181    rope: Rope,
182    edit: Edit,
183    commits: Vector<Commit>,
184}
185
186impl Draft {
187    #[must_use]
188    pub fn base_version(&self) -> Version {
189        self.base_version
190    }
191
192    #[must_use]
193    pub fn rope(&self) -> &Rope {
194        &self.rope
195    }
196
197    #[must_use]
198    pub fn edit(&self) -> &Edit {
199        &self.edit
200    }
201
202    pub fn apply_edit(&mut self, edit: &Edit) -> anyhow::Result<()> {
203        let composed = self.edit.compose(edit)?;
204        edit.apply(&mut self.rope)?;
205        self.edit = composed;
206        Ok(())
207    }
208
209    // No `create_anchor` for `Draft`
210
211    pub fn resolve_anchor(&self, anchor: Anchor) -> anyhow::Result<usize> {
212        let byte_index = anchor.resolve(self.base_version, &self.commits)?;
213        Ok(self.edit.map_position(byte_index, anchor.bias))
214    }
215}
216
217#[derive(Clone)]
218pub struct Commit {
219    original_base_version: Version,
220    original_edit: Edit,
221    rebased_edit: Edit,
222}
223
224impl Commit {
225    #[must_use]
226    pub fn original_base_version(&self) -> Version {
227        self.original_base_version
228    }
229
230    #[must_use]
231    pub fn original_edit(&self) -> &Edit {
232        &self.original_edit
233    }
234
235    #[must_use]
236    pub fn rebased_edit(&self) -> &Edit {
237        &self.rebased_edit
238    }
239}
240
241#[derive(Clone, Copy)]
242pub struct Anchor {
243    base_version: Version,
244    byte_index: usize,
245    bias: Bias,
246}
247
248impl Anchor {
249    fn create(
250        base_version: Version,
251        rope: &Rope,
252        byte_index: usize,
253        bias: Bias,
254    ) -> anyhow::Result<Self> {
255        anyhow::ensure!(
256            byte_index <= rope.len(),
257            "anchor byte index {} is beyond rope length {}",
258            byte_index,
259            rope.len()
260        );
261        Ok(Self {
262            base_version,
263            byte_index,
264            bias,
265        })
266    }
267
268    fn resolve(self, version: Version, commits: &Vector<Commit>) -> anyhow::Result<usize> {
269        debug_assert_eq!(version.commit_index, commits.len());
270        anyhow::ensure!(
271            self.base_version.document_id == version.document_id,
272            "anchor does not apply to this document"
273        );
274        anyhow::ensure!(
275            self.base_version.commit_index <= version.commit_index,
276            "anchor version is ahead of resolver version"
277        );
278        let mut byte_index = self.byte_index;
279        for commit in commits.focus().narrow(self.base_version.commit_index..) {
280            byte_index = commit.rebased_edit.map_position(byte_index, self.bias);
281        }
282        Ok(byte_index)
283    }
284}