on master help sign in
OT: changeset: Implement compose and apply.
commit23109f15129e3c3e89181164197c384861324b48 authorBlaž Hrastnik <blaz@mxxn.io> dateTue 26 May 2020, 18:11 (6 years ago) parent44ff4d3
5 files changed+330−22
Cargo.lock+41−3 helix-core/Cargo.toml+3−1 helix-core/src/lib.rs+1−1 helix-core/src/selection.rs+8−8 helix-core/src/transaction.rs+277−9
Cargo.lock+41−3
@@ -172,6 +172,16 @@ version = "1.0.7"
172172source = "registry+https://github.com/rust-lang/crates.io-index"
173173checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
174174
175+[[package]]
176+name = "futf"
177+version = "0.1.4"
178+source = "registry+https://github.com/rust-lang/crates.io-index"
179+checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b"
180+dependencies = [
181+ "mac",
182+ "new_debug_unreachable",
183+]
184+
175185[[package]]
176186name = "getrandom"
177187version = "0.1.14"
@@ -199,6 +209,7 @@ dependencies = [
199209 "anyhow",
200210 "ropey",
201211 "smallvec 1.4.0",
212+ "tendril",
202213]
203214
204215[[package]]
@@ -232,6 +243,12 @@ dependencies = [
232243 "cfg-if",
233244]
234245
246+[[package]]
247+name = "mac"
248+version = "0.1.1"
249+source = "registry+https://github.com/rust-lang/crates.io-index"
250+checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
251+
235252[[package]]
236253name = "maybe-uninit"
237254version = "2.0.0"
@@ -250,6 +267,12 @@ version = "0.1.1"
250267source = "registry+https://github.com/rust-lang/crates.io-index"
251268checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15"
252269
270+[[package]]
271+name = "new_debug_unreachable"
272+version = "1.0.4"
273+source = "registry+https://github.com/rust-lang/crates.io-index"
274+checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
275+
253276[[package]]
254277name = "nom"
255278version = "5.1.1"
@@ -458,10 +481,9 @@ checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
458481[[package]]
459482name = "ropey"
460483version = "1.1.0"
461−source = "registry+https://github.com/rust-lang/crates.io-index"
462−checksum = "ba326a8508a4add47e7b260333aa2d896213a5f3572fde11ed6e9130241b7f71"
484+source = "git+https://github.com/cessen/ropey#083c34949274ef9800267e6bc64b76a45e401807"
463485dependencies = [
464− "smallvec 0.6.13",
486+ "smallvec 1.4.0",
465487]
466488
467489[[package]]
@@ -554,6 +576,16 @@ dependencies = [
554576 "unicode-xid 0.2.0",
555577]
556578
579+[[package]]
580+name = "tendril"
581+version = "0.4.1"
582+source = "git+https://github.com/servo/tendril#08f7f292ab82c00e9cf491b5918a76e53af92c8c"
583+dependencies = [
584+ "futf",
585+ "mac",
586+ "utf-8",
587+]
588+
557589[[package]]
558590name = "terminfo"
559591version = "0.7.3"
@@ -639,6 +671,12 @@ version = "0.2.0"
639671source = "registry+https://github.com/rust-lang/crates.io-index"
640672checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
641673
674+[[package]]
675+name = "utf-8"
676+version = "0.7.5"
677+source = "registry+https://github.com/rust-lang/crates.io-index"
678+checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
679+
642680[[package]]
643681name = "utf8parse"
644682version = "0.1.1"
helix-core/Cargo.toml+3−1
@@ -7,7 +7,9 @@ edition = "2018"
77# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
88
99[dependencies]
10−ropey = "1.1.0"
10+# ropey = "1.1.0"
11+ropey = { git = "https://github.com/cessen/ropey" }
1112anyhow = "1.0.31"
1213smallvec = "1.4.0"
14+tendril = { git = "https://github.com/servo/tendril" }
1315# slab = "0.4.2"
helix-core/src/lib.rs+1−1
@@ -10,4 +10,4 @@ pub use selection::Selection;
1010
1111pub use state::State;
1212
13−pub use transaction::{Change, Transaction};
13+pub use transaction::{Change, ChangeSet, Transaction};
helix-core/src/selection.rs+8−8
@@ -83,7 +83,7 @@ impl Range {
8383/// A selection consists of one or more selection ranges.
8484pub struct Selection {
8585 // TODO: decide how many ranges to inline SmallVec<[Range; 1]>
86− ranges: Vec<Range>,
86+ ranges: SmallVec<[Range; 1]>,
8787 primary_index: usize,
8888}
8989
@@ -100,7 +100,7 @@ impl Selection {
100100 self
101101 } else {
102102 Self {
103− ranges: vec![self.ranges[self.primary_index]],
103+ ranges: smallvec![self.ranges[self.primary_index]],
104104 primary_index: 0,
105105 }
106106 }
@@ -112,18 +112,18 @@ impl Selection {
112112 /// Constructs a selection holding a single range.
113113 pub fn single(anchor: usize, head: usize) -> Self {
114114 Self {
115− ranges: vec![Range { anchor, head }],
115+ ranges: smallvec![Range { anchor, head }],
116116 primary_index: 0,
117117 }
118118 }
119119
120− pub fn new(ranges: Vec<Range>, primary_index: usize) -> Self {
121− fn normalize(mut ranges: Vec<Range>, primary_index: usize) -> Selection {
120+ pub fn new(ranges: SmallVec<[Range; 1]>, primary_index: usize) -> Self {
121+ fn normalize(mut ranges: SmallVec<[Range; 1]>, primary_index: usize) -> Selection {
122122 let primary = ranges[primary_index];
123123 ranges.sort_unstable_by_key(|range| range.from());
124124 let mut primary_index = ranges.iter().position(|&range| range == primary).unwrap();
125125
126− let mut result: Vec<Range> = Vec::new();
126+ let mut result: SmallVec<[Range; 1]> = SmallVec::new();
127127
128128 // TODO: we could do with one vec by removing elements as we mutate
129129
@@ -174,7 +174,7 @@ mod test {
174174 #[test]
175175 fn test_create_normalizes_and_merges() {
176176 let sel = Selection::new(
177− vec![
177+ smallvec![
178178 Range::new(10, 12),
179179 Range::new(6, 7),
180180 Range::new(4, 5),
@@ -200,7 +200,7 @@ mod test {
200200 #[test]
201201 fn test_create_merges_adjacent_points() {
202202 let sel = Selection::new(
203− vec![
203+ smallvec![
204204 Range::new(10, 12),
205205 Range::new(12, 12),
206206 Range::new(12, 12),
helix-core/src/transaction.rs+277−9
@@ -1,25 +1,293 @@
1−pub struct Change {
2− from: usize,
3− to: usize,
4− insert: Option<String>,
1+// pub struct Change {
2+// from: usize,
3+// to: usize,
4+// insert: Option<String>,
5+// }
6+
7+// 40 bytes (8 + 24 + 8) -> strings are really big 24 as String, 16 as &str
8+// pub struct Change {
9+// /// old extent
10+// old_extent: usize,
11+// /// inserted text, new extent equal to insert length
12+// insert: Option<String>,
13+// /// distance from the previous change
14+// distance: usize,
15+// }
16+
17+use crate::{Buffer, Selection};
18+
19+use ropey::Rope;
20+use tendril::StrTendril as Tendril;
21+
22+// TODO: divided into three different operations, I sort of like having just
23+// Splice { extent, Option<text>, distance } better.
24+// insert: Splice { extent: 0, text: Some("a"), distance: 2 }
25+// delete: Splice { extent: 2, text: None, distance: 2 }
26+// replace: Splice { extent: 2, text: Some("abc"), distance: 2 }
27+// unchanged?: Splice { extent: 0, text: None, distance: 2 }
28+// harder to compose though.
29+#[derive(Debug, Clone, PartialEq, Eq)]
30+pub enum Change {
31+ /// Move cursor by n characters.
32+ Retain(usize),
33+ /// Delete n characters.
34+ Delete(usize),
35+ /// Insert text at position.
36+ Insert(Tendril),
537}
638
739impl Change {
8− pub fn new(from: usize, to: usize, insert: Option<String>) {
40+ pub fn new(from: usize, to: usize, insert: Option<Tendril>) {
941 // old_extent, new_extent, insert
1042 }
1143}
1244
13−pub struct Transaction {}
14−
1545// ChangeSpec = Change | ChangeSet | Vec<Change>
1646// ChangeDesc as a ChangeSet without text: can't be applied, cheaper to store.
1747// ChangeSet = ChangeDesc with Text
48+#[derive(Debug)]
1849pub struct ChangeSet {
1950 // basically Vec<ChangeDesc> where ChangeDesc = (current len, replacement len?)
2051 // (0, n>0) for insertion, (n>0, 0) for deletion, (>0, >0) for replacement
21− sections: Vec<(usize, isize)>,
52+ // sections: Vec<(usize, isize)>,
53+ changes: Vec<Change>,
54+ /// The required document length. Will refuse to apply changes unless it matches.
55+ len: usize,
2256}
23−//
57+
58+impl ChangeSet {
59+ pub fn new(buf: &Buffer) -> Self {
60+ let len = buf.contents.len_chars();
61+ Self {
62+ changes: vec![Change::Retain(len)],
63+ len,
64+ }
65+ }
66+
67+ // TODO: from iter
68+
69+ /// Combine two changesets together.
70+ /// In other words, If `this` goes `docA` → `docB` and `other` represents `docB` → `docC`, the
71+ /// returned value will represent the change `docA` → `docC`.
72+ pub fn compose(self, other: ChangeSet) -> Result<Self, ()> {
73+ if self.len != other.len {
74+ // length mismatch
75+ return Err(());
76+ }
77+
78+ let len = self.changes.len();
79+
80+ let mut changes_a = self.changes.into_iter();
81+ let mut changes_b = other.changes.into_iter();
82+
83+ let mut head_a = changes_a.next();
84+ let mut head_b = changes_b.next();
85+
86+ let mut changes: Vec<Change> = Vec::with_capacity(len); // TODO: max(a, b), shrink_to_fit() afterwards
87+
88+ loop {
89+ use std::cmp::Ordering;
90+ use Change::*;
91+ match (head_a, head_b) {
92+ // we are done
93+ (None, None) => {
94+ break;
95+ }
96+ // deletion in A
97+ (Some(change @ Delete(..)), b) => {
98+ changes.push(change);
99+ head_a = changes_a.next();
100+ head_b = b;
101+ }
102+ // insertion in B
103+ (a, Some(change @ Insert(..))) => {
104+ changes.push(change);
105+ head_a = a;
106+ head_b = changes_b.next();
107+ }
108+ (None, _) => return Err(()),
109+ (_, None) => return Err(()),
110+ (Some(Retain(i)), Some(Retain(j))) => match i.cmp(&j) {
111+ Ordering::Less => {
112+ changes.push(Retain(i));
113+ head_a = changes_a.next();
114+ head_b = Some(Retain(j - i));
115+ }
116+ Ordering::Equal => {
117+ changes.push(Retain(i));
118+ head_a = changes_a.next();
119+ head_b = changes_b.next();
120+ }
121+ Ordering::Greater => {
122+ changes.push(Retain(j));
123+ head_a = Some(Retain(i - j));
124+ head_b = changes_b.next();
125+ }
126+ },
127+ (Some(Insert(mut s)), Some(Delete(j))) => {
128+ let len = s.chars().count();
129+ match len.cmp(&j) {
130+ Ordering::Less => {
131+ head_a = changes_a.next();
132+ head_b = Some(Delete(j - len));
133+ }
134+ Ordering::Equal => {
135+ head_a = changes_a.next();
136+ head_b = changes_b.next();
137+ }
138+ Ordering::Greater => {
139+ // figure out the byte index of the truncated string end
140+ let (pos, _) = s.char_indices().nth(len - j).unwrap();
141+ // calculate the difference
142+ let to_drop = s.len() - pos;
143+ s.pop_back(to_drop as u32);
144+ head_a = Some(Insert(s));
145+ head_b = changes_b.next();
146+ }
147+ }
148+ }
149+ (Some(Insert(mut s)), Some(Retain(j))) => {
150+ let len = s.chars().count();
151+ match len.cmp(&j) {
152+ Ordering::Less => {
153+ changes.push(Insert(s));
154+ head_a = changes_a.next();
155+ head_b = Some(Retain(j - len));
156+ }
157+ Ordering::Equal => {
158+ changes.push(Insert(s));
159+ head_a = changes_a.next();
160+ head_b = changes_b.next();
161+ }
162+ Ordering::Greater => {
163+ // figure out the byte index of the truncated string end
164+ let (pos, _) = s.char_indices().nth(j).unwrap();
165+ // calculate the difference
166+ let to_drop = s.len() - pos;
167+ s.pop_back(to_drop as u32);
168+ head_a = Some(Insert(s));
169+ head_b = changes_b.next();
170+ }
171+ }
172+ }
173+ (Some(Retain(i)), Some(Delete(j))) => match i.cmp(&j) {
174+ Ordering::Less => {
175+ changes.push(Delete(i));
176+ head_a = changes_a.next();
177+ head_b = Some(Delete(j - i));
178+ }
179+ Ordering::Equal => {
180+ changes.push(Delete(j));
181+ head_a = changes_a.next();
182+ head_b = changes_b.next();
183+ }
184+ Ordering::Greater => {
185+ changes.push(Delete(j));
186+ head_a = Some(Retain(i - j));
187+ head_b = changes_b.next();
188+ }
189+ },
190+ };
191+ }
192+
193+ Ok(Self {
194+ len: self.len,
195+ changes,
196+ })
197+ }
198+
199+ /// Given another change set starting in the same document, maps this
200+ /// change set over the other, producing a new change set that can be
201+ /// applied to the document produced by applying `other`. When
202+ /// `before` is `true`, order changes as if `this` comes before
203+ /// `other`, otherwise (the default) treat `other` as coming first.
204+ ///
205+ /// Given two changes `A` and `B`, `A.compose(B.map(A))` and
206+ /// `B.compose(A.map(B, true))` will produce the same document. This
207+ /// provides a basic form of [operational
208+ /// transformation](https://en.wikipedia.org/wiki/Operational_transformation),
209+ /// and can be used for collaborative editing.
210+ pub fn map(self, other: Self) -> Self {
211+ unimplemented!()
212+ }
213+
214+ /// Returns a new changeset that reverts this one. Useful for `undo` implementation.
215+ pub fn invert(self) -> Self {
216+ unimplemented!()
217+ }
218+
219+ pub fn apply(&self, text: &mut Rope) {
220+ // TODO: validate text.chars() == self.len
221+
222+ let mut pos = 0;
223+
224+ for change in self.changes.iter() {
225+ use Change::*;
226+ match change {
227+ Retain(n) => {
228+ pos += n;
229+ }
230+ Delete(n) => {
231+ text.remove(pos..pos + *n);
232+ // pos += n;
233+ }
234+ Insert(s) => {
235+ text.insert(pos, s);
236+ pos += s.len();
237+ }
238+ }
239+ }
240+ }
241+
242+ // iter over changes
243+}
244+
24245// trait Transaction
25246// trait StrictTransaction
247+
248+/// Transaction represents a single undoable unit of changes. Several changes can be grouped into
249+/// a single transaction.
250+pub struct Transaction {
251+ /// Changes made to the buffer.
252+ changes: ChangeSet,
253+ /// When set, explicitly updates the selection.
254+ selection: Option<Selection>,
255+ // effects, annotations
256+ // scroll_into_view
257+}
258+
259+#[cfg(test)]
260+mod test {
261+ use super::*;
262+
263+ #[test]
264+ fn composition() {
265+ use Change::*;
266+
267+ let a = ChangeSet {
268+ changes: vec![
269+ Retain(5),
270+ Insert("!".into()),
271+ Retain(1),
272+ Delete(2),
273+ Insert("ab".into()),
274+ ],
275+ len: 7,
276+ };
277+
278+ let b = ChangeSet {
279+ changes: vec![Delete(5), Insert("world".into()), Retain(4)],
280+ len: 7,
281+ };
282+
283+ let mut text = Rope::from("hello xz");
284+
285+ // should probably return cloned text
286+ a.compose(b).unwrap().apply(&mut text);
287+
288+ unimplemented!("{:?}", text);
289+ }
290+
291+ #[test]
292+ fn map() {}
293+}