aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_text_edit
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2018-12-27 12:19:19 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2018-12-27 12:19:19 +0000
commite422c2e2f4117cf977d28a40a9c8e4dc4cfee811 (patch)
tree9ea1dc365e420c286834b40923deb95a0ca291b9 /crates/ra_text_edit
parent55ab0c602e391537f5e1a84a617fdd817e6a4200 (diff)
parent1cda43aafd623b400f5916b1d3727b56c136081b (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.toml1
-rw-r--r--crates/ra_text_edit/src/lib.rs3
-rw-r--r--crates/ra_text_edit/src/test_utils.rs85
-rw-r--r--crates/ra_text_edit/src/text_edit.rs15
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]
9text_unit = "0.1.5" 9text_unit = "0.1.5"
10proptest = "0.8.7"
10 11
11[dev-dependencies] 12[dev-dependencies]
12test_utils = { path = "../test_utils" } 13test_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 @@
1mod text_edit; 1mod text_edit;
2pub mod text_utils; 2pub mod text_utils;
3pub mod test_utils;
3 4
4pub use crate::text_edit::{TextEdit, TextEditBuilder}; 5pub use crate::text_edit::{TextEdit, TextEditBuilder};
5 6
6use text_unit::{TextRange, TextUnit}; 7use text_unit::{TextRange, TextUnit};
7 8
9/// Must not overlap with other `AtomTextEdit`s
8#[derive(Debug, Clone)] 10#[derive(Debug, Clone)]
9pub struct AtomTextEdit { 11pub 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 @@
1use proptest::prelude::*;
2use text_unit::{TextUnit, TextRange};
3use crate::{AtomTextEdit, TextEdit};
4
5pub fn arb_text() -> proptest::string::RegexGeneratorStrategy<String> {
6 // generate multiple newlines
7 proptest::string::string_regex("(.*\n?)*").unwrap()
8}
9
10fn text_offsets(text: &str) -> Vec<TextUnit> {
11 text.char_indices()
12 .map(|(i, _)| TextUnit::from_usize(i))
13 .collect()
14}
15
16pub 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
26pub 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)]
72pub struct ArbTextWithEdit {
73 pub text: String,
74 pub edit: TextEdit,
75}
76
77pub 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
43impl TextEdit { 38impl 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 }