diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2018-12-27 12:19:19 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2018-12-27 12:19:19 +0000 |
commit | e422c2e2f4117cf977d28a40a9c8e4dc4cfee811 (patch) | |
tree | 9ea1dc365e420c286834b40923deb95a0ca291b9 /crates/ra_text_edit | |
parent | 55ab0c602e391537f5e1a84a617fdd817e6a4200 (diff) | |
parent | 1cda43aafd623b400f5916b1d3727b56c136081b (diff) |
Merge #325
325: implement translate_offset_with_edit r=matklad a=vemoo
- Implement `translate_offset_with_edit` to resolve #105
- Add proptest impls for text, offsets and edits and use them in tests for `translate_offset_with_edit` and `LineIndex`
- Added benchmark for `translate_offset_with_edit`
Co-authored-by: Bernardo <[email protected]>
Diffstat (limited to 'crates/ra_text_edit')
-rw-r--r-- | crates/ra_text_edit/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_text_edit/src/lib.rs | 3 | ||||
-rw-r--r-- | crates/ra_text_edit/src/test_utils.rs | 85 | ||||
-rw-r--r-- | crates/ra_text_edit/src/text_edit.rs | 15 |
4 files changed, 98 insertions, 6 deletions
diff --git a/crates/ra_text_edit/Cargo.toml b/crates/ra_text_edit/Cargo.toml index 3c4157a4e..e0db49688 100644 --- a/crates/ra_text_edit/Cargo.toml +++ b/crates/ra_text_edit/Cargo.toml | |||
@@ -7,6 +7,7 @@ publish = false | |||
7 | 7 | ||
8 | [dependencies] | 8 | [dependencies] |
9 | text_unit = "0.1.5" | 9 | text_unit = "0.1.5" |
10 | proptest = "0.8.7" | ||
10 | 11 | ||
11 | [dev-dependencies] | 12 | [dev-dependencies] |
12 | test_utils = { path = "../test_utils" } | 13 | test_utils = { path = "../test_utils" } |
diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs index 13e20f6fb..8acf10448 100644 --- a/crates/ra_text_edit/src/lib.rs +++ b/crates/ra_text_edit/src/lib.rs | |||
@@ -1,12 +1,15 @@ | |||
1 | mod text_edit; | 1 | mod text_edit; |
2 | pub mod text_utils; | 2 | pub mod text_utils; |
3 | pub mod test_utils; | ||
3 | 4 | ||
4 | pub use crate::text_edit::{TextEdit, TextEditBuilder}; | 5 | pub use crate::text_edit::{TextEdit, TextEditBuilder}; |
5 | 6 | ||
6 | use text_unit::{TextRange, TextUnit}; | 7 | use text_unit::{TextRange, TextUnit}; |
7 | 8 | ||
9 | /// Must not overlap with other `AtomTextEdit`s | ||
8 | #[derive(Debug, Clone)] | 10 | #[derive(Debug, Clone)] |
9 | pub struct AtomTextEdit { | 11 | pub struct AtomTextEdit { |
12 | /// Refers to offsets in the original text | ||
10 | pub delete: TextRange, | 13 | pub delete: TextRange, |
11 | pub insert: String, | 14 | pub insert: String, |
12 | } | 15 | } |
diff --git a/crates/ra_text_edit/src/test_utils.rs b/crates/ra_text_edit/src/test_utils.rs new file mode 100644 index 000000000..745f21c93 --- /dev/null +++ b/crates/ra_text_edit/src/test_utils.rs | |||
@@ -0,0 +1,85 @@ | |||
1 | use proptest::prelude::*; | ||
2 | use text_unit::{TextUnit, TextRange}; | ||
3 | use crate::{AtomTextEdit, TextEdit}; | ||
4 | |||
5 | pub fn arb_text() -> proptest::string::RegexGeneratorStrategy<String> { | ||
6 | // generate multiple newlines | ||
7 | proptest::string::string_regex("(.*\n?)*").unwrap() | ||
8 | } | ||
9 | |||
10 | fn text_offsets(text: &str) -> Vec<TextUnit> { | ||
11 | text.char_indices() | ||
12 | .map(|(i, _)| TextUnit::from_usize(i)) | ||
13 | .collect() | ||
14 | } | ||
15 | |||
16 | pub fn arb_offset(text: &str) -> BoxedStrategy<TextUnit> { | ||
17 | let offsets = text_offsets(text); | ||
18 | // this is necessary to avoid "Uniform::new called with `low >= high`" panic | ||
19 | if offsets.is_empty() { | ||
20 | Just(TextUnit::from(0)).boxed() | ||
21 | } else { | ||
22 | prop::sample::select(offsets).boxed() | ||
23 | } | ||
24 | } | ||
25 | |||
26 | pub fn arb_text_edit(text: &str) -> BoxedStrategy<TextEdit> { | ||
27 | if text.is_empty() { | ||
28 | // only valid edits | ||
29 | return Just(vec![]) | ||
30 | .boxed() | ||
31 | .prop_union( | ||
32 | arb_text() | ||
33 | .prop_map(|text| vec![AtomTextEdit::insert(TextUnit::from(0), text)]) | ||
34 | .boxed(), | ||
35 | ) | ||
36 | .prop_map(TextEdit::from_atoms) | ||
37 | .boxed(); | ||
38 | } | ||
39 | |||
40 | let offsets = text_offsets(text); | ||
41 | let max_cuts = 7.min(offsets.len()); | ||
42 | |||
43 | proptest::sample::subsequence(offsets, 0..max_cuts) | ||
44 | .prop_flat_map(|cuts| { | ||
45 | let strategies: Vec<_> = cuts | ||
46 | .chunks(2) | ||
47 | .map(|chunk| match chunk { | ||
48 | &[from, to] => { | ||
49 | let range = TextRange::from_to(from, to); | ||
50 | Just(AtomTextEdit::delete(range)) | ||
51 | .boxed() | ||
52 | .prop_union( | ||
53 | arb_text() | ||
54 | .prop_map(move |text| AtomTextEdit::replace(range, text)) | ||
55 | .boxed(), | ||
56 | ) | ||
57 | .boxed() | ||
58 | } | ||
59 | &[x] => arb_text() | ||
60 | .prop_map(move |text| AtomTextEdit::insert(x, text)) | ||
61 | .boxed(), | ||
62 | _ => unreachable!(), | ||
63 | }) | ||
64 | .collect(); | ||
65 | strategies | ||
66 | }) | ||
67 | .prop_map(TextEdit::from_atoms) | ||
68 | .boxed() | ||
69 | } | ||
70 | |||
71 | #[derive(Debug, Clone)] | ||
72 | pub struct ArbTextWithEdit { | ||
73 | pub text: String, | ||
74 | pub edit: TextEdit, | ||
75 | } | ||
76 | |||
77 | pub fn arb_text_with_edit() -> BoxedStrategy<ArbTextWithEdit> { | ||
78 | let text = arb_text(); | ||
79 | text.prop_flat_map(|s| { | ||
80 | let edit = arb_text_edit(&s); | ||
81 | (Just(s), edit) | ||
82 | }) | ||
83 | .prop_map(|(text, edit)| ArbTextWithEdit { text, edit }) | ||
84 | .boxed() | ||
85 | } | ||
diff --git a/crates/ra_text_edit/src/text_edit.rs b/crates/ra_text_edit/src/text_edit.rs index 392968d63..0881f3e1c 100644 --- a/crates/ra_text_edit/src/text_edit.rs +++ b/crates/ra_text_edit/src/text_edit.rs | |||
@@ -26,12 +26,7 @@ impl TextEditBuilder { | |||
26 | self.atoms.push(AtomTextEdit::insert(offset, text)) | 26 | self.atoms.push(AtomTextEdit::insert(offset, text)) |
27 | } | 27 | } |
28 | pub fn finish(self) -> TextEdit { | 28 | pub fn finish(self) -> TextEdit { |
29 | let mut atoms = self.atoms; | 29 | TextEdit::from_atoms(self.atoms) |
30 | atoms.sort_by_key(|a| (a.delete.start(), a.delete.end())); | ||
31 | for (a1, a2) in atoms.iter().zip(atoms.iter().skip(1)) { | ||
32 | assert!(a1.delete.end() <= a2.delete.start()) | ||
33 | } | ||
34 | TextEdit { atoms } | ||
35 | } | 30 | } |
36 | pub fn invalidates_offset(&self, offset: TextUnit) -> bool { | 31 | pub fn invalidates_offset(&self, offset: TextUnit) -> bool { |
37 | self.atoms | 32 | self.atoms |
@@ -41,6 +36,14 @@ impl TextEditBuilder { | |||
41 | } | 36 | } |
42 | 37 | ||
43 | impl TextEdit { | 38 | impl TextEdit { |
39 | pub(crate) fn from_atoms(mut atoms: Vec<AtomTextEdit>) -> TextEdit { | ||
40 | atoms.sort_by_key(|a| (a.delete.start(), a.delete.end())); | ||
41 | for (a1, a2) in atoms.iter().zip(atoms.iter().skip(1)) { | ||
42 | assert!(a1.delete.end() <= a2.delete.start()) | ||
43 | } | ||
44 | TextEdit { atoms } | ||
45 | } | ||
46 | |||
44 | pub fn as_atoms(&self) -> &[AtomTextEdit] { | 47 | pub fn as_atoms(&self) -> &[AtomTextEdit] { |
45 | &self.atoms | 48 | &self.atoms |
46 | } | 49 | } |