1use crate::edit::{Bias, Edit};
2use imbl::Vector;
3use ropey::Rope;
4
5pub 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 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 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}