aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_editor
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_editor')
-rw-r--r--crates/ra_editor/Cargo.toml8
-rw-r--r--crates/ra_editor/benches/translate_offset_with_edit_benchmark.rs88
-rw-r--r--crates/ra_editor/src/lib.rs4
-rw-r--r--crates/ra_editor/src/line_index_utils.rs218
4 files changed, 119 insertions, 199 deletions
diff --git a/crates/ra_editor/Cargo.toml b/crates/ra_editor/Cargo.toml
index 1ad99af28..039688d7d 100644
--- a/crates/ra_editor/Cargo.toml
+++ b/crates/ra_editor/Cargo.toml
@@ -18,3 +18,11 @@ proptest = "0.8.7"
18 18
19[dev-dependencies] 19[dev-dependencies]
20test_utils = { path = "../test_utils" } 20test_utils = { path = "../test_utils" }
21criterion = "0.2"
22rand = "*"
23rand_xorshift = "*"
24lazy_static = "*"
25
26[[bench]]
27name = "translate_offset_with_edit_benchmark"
28harness = false \ No newline at end of file
diff --git a/crates/ra_editor/benches/translate_offset_with_edit_benchmark.rs b/crates/ra_editor/benches/translate_offset_with_edit_benchmark.rs
new file mode 100644
index 000000000..b345a91ae
--- /dev/null
+++ b/crates/ra_editor/benches/translate_offset_with_edit_benchmark.rs
@@ -0,0 +1,88 @@
1use criterion::{criterion_group, criterion_main};
2use criterion::Criterion;
3use criterion::Fun;
4use ra_text_edit::AtomTextEdit;
5use ra_text_edit::test_utils::{arb_edits_custom, arb_offset};
6use ra_editor::line_index_utils;
7use ra_editor::LineIndex;
8use ra_syntax::TextUnit;
9use proptest::test_runner;
10use proptest::string::string_regex;
11use proptest::strategy::{Strategy, ValueTree};
12use rand_xorshift::XorShiftRng;
13use rand::SeedableRng;
14use lazy_static::lazy_static;
15
16#[derive(Debug)]
17struct Data {
18 text: String,
19 line_index: LineIndex,
20 edits: Vec<AtomTextEdit>,
21 offset: TextUnit,
22}
23
24fn setup_data() -> Data {
25 let mut runner = test_runner::TestRunner::default();
26 {
27 struct TestRng {
28 rng: XorShiftRng,
29 }
30 // HACK to be able to manually seed the TestRunner
31 let rng: &mut TestRng = unsafe { std::mem::transmute(runner.rng()) };
32 rng.rng = XorShiftRng::seed_from_u64(0);
33 }
34
35 let text = {
36 let arb = string_regex("([a-zA-Z_0-9]{10,50}.{1,5}\n){100,500}").unwrap();
37 let tree = arb.new_tree(&mut runner).unwrap();
38 tree.current()
39 };
40
41 let edits = {
42 let arb = arb_edits_custom(&text, 99, 100);
43 let tree = arb.new_tree(&mut runner).unwrap();
44 tree.current()
45 };
46
47 let offset = {
48 let arb = arb_offset(&text);
49 let tree = arb.new_tree(&mut runner).unwrap();
50 tree.current()
51 };
52
53 let line_index = LineIndex::new(&text);
54
55 Data {
56 text,
57 line_index,
58 edits,
59 offset,
60 }
61}
62
63lazy_static! {
64 static ref DATA: Data = setup_data();
65}
66
67fn compare_translates(c: &mut Criterion) {
68 let f1 = Fun::new("translate_after_edit", |b, _| {
69 b.iter(|| {
70 let d = &*DATA;
71 line_index_utils::translate_after_edit(&d.text, d.offset, d.edits.clone());
72 })
73 });
74
75 let f2 = Fun::new("count_newlines", |b, _| {
76 b.iter(|| {
77 let d = &*DATA;
78 line_index_utils::count_newlines(d.offset, &d.line_index, &d.edits);
79 })
80 });
81
82 let functions = vec![f1, f2];
83
84 c.bench_functions("translate", functions, ());
85}
86
87criterion_group!(benches, compare_translates);
88criterion_main!(benches);
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs
index 619497f0b..7145c6cfb 100644
--- a/crates/ra_editor/src/lib.rs
+++ b/crates/ra_editor/src/lib.rs
@@ -2,7 +2,8 @@ mod code_actions;
2mod extend_selection; 2mod extend_selection;
3mod folding_ranges; 3mod folding_ranges;
4mod line_index; 4mod line_index;
5mod line_index_utils; 5// public for benchmarkig
6pub mod line_index_utils;
6mod symbols; 7mod symbols;
7#[cfg(test)] 8#[cfg(test)]
8mod test_utils; 9mod test_utils;
@@ -13,7 +14,6 @@ pub use self::{
13 extend_selection::extend_selection, 14 extend_selection::extend_selection,
14 folding_ranges::{folding_ranges, Fold, FoldKind}, 15 folding_ranges::{folding_ranges, Fold, FoldKind},
15 line_index::{LineCol, LineIndex}, 16 line_index::{LineCol, LineIndex},
16 line_index_utils::translate_offset_with_edit,
17 symbols::{file_structure, file_symbols, FileSymbol, StructureNode}, 17 symbols::{file_structure, file_symbols, FileSymbol, StructureNode},
18 typing::{join_lines, on_enter, on_eq_typed}, 18 typing::{join_lines, on_enter, on_eq_typed},
19}; 19};
diff --git a/crates/ra_editor/src/line_index_utils.rs b/crates/ra_editor/src/line_index_utils.rs
index 5ce2446c1..e62c5089d 100644
--- a/crates/ra_editor/src/line_index_utils.rs
+++ b/crates/ra_editor/src/line_index_utils.rs
@@ -25,136 +25,6 @@ impl<'a> Iterator for OffsetNewlineIter<'a> {
25} 25}
26 26
27#[derive(Debug)] 27#[derive(Debug)]
28struct AltEdit<'a> {
29 insert_newlines: OffsetNewlineIter<'a>,
30 delete: TextRange,
31 diff: i64,
32}
33
34fn translate_range_by(range: TextRange, diff: i64) -> TextRange {
35 if diff == 0 {
36 range
37 } else {
38 let start = translate_by(range.start(), diff);
39 let end = translate_by(range.end(), diff);
40 TextRange::from_to(start, end)
41 }
42}
43
44fn translate_by(x: TextUnit, diff: i64) -> TextUnit {
45 if diff == 0 {
46 x
47 } else {
48 TextUnit::from((x.to_usize() as i64 + diff) as u32)
49 }
50}
51
52fn to_alt_edits<'a>(offset: TextUnit, edits: &'a [AtomTextEdit]) -> Vec<AltEdit<'a>> {
53 let mut xs: Vec<AltEdit<'a>> = Vec::with_capacity(edits.len());
54 // collect and sort edits
55 for edit in edits {
56 // TODO discard after translating?
57 // if edit.delete.start() >= offset {
58 // continue;
59 // }
60 let insert_index = xs.upper_bound_by_key(&edit.delete.start(), |x| x.delete.start());
61 let diff = edit.insert.len() as i64 - edit.delete.len().to_usize() as i64;
62 xs.insert(
63 insert_index,
64 AltEdit {
65 insert_newlines: OffsetNewlineIter {
66 offset: edit.delete.start(),
67 text: &edit.insert,
68 },
69 delete: edit.delete,
70 diff: diff,
71 },
72 );
73 }
74 // translate edits by previous edits
75 for i in 1..xs.len() {
76 let (x, prevs) = xs[0..=i].split_last_mut().unwrap();
77 for prev in prevs {
78 x.delete = translate_range_by(x.delete, prev.diff);
79 x.insert_newlines.offset = translate_by(x.insert_newlines.offset, prev.diff);
80 }
81 }
82 xs
83}
84
85#[derive(Debug)]
86enum NextNewline {
87 Use,
88 Discard,
89 Replace(TextUnit),
90 New(TextUnit),
91}
92
93fn next_newline(candidate: Option<TextUnit>, edits: &mut [AltEdit]) -> NextNewline {
94 let mut candidate = match candidate {
95 None => {
96 for edit in edits {
97 if let Some(inserted) = edit.insert_newlines.next() {
98 return NextNewline::New(inserted);
99 }
100 }
101 return NextNewline::Use; // END
102 }
103 Some(x) => x,
104 };
105
106 for edit in edits {
107 if candidate <= edit.delete.start() {
108 return NextNewline::Replace(candidate);
109 } else if candidate <= edit.delete.end() {
110 return match edit.insert_newlines.next() {
111 Some(x) => NextNewline::Replace(x),
112 None => NextNewline::Discard,
113 };
114 } else {
115 if let Some(inserted) = edit.insert_newlines.next() {
116 return NextNewline::New(inserted);
117 }
118 candidate = translate_by(candidate, edit.diff);
119 }
120 }
121 return NextNewline::Replace(candidate);
122}
123
124pub fn count_newlines(offset: TextUnit, line_index: &LineIndex, edits: &[AtomTextEdit]) -> u32 {
125 let mut edits = to_alt_edits(offset, edits);
126 let mut orig_newlines = line_index.newlines().iter().map(|x| *x).peekable();
127
128 let mut count = 0;
129
130 loop {
131 let res = next_newline(orig_newlines.peek().map(|x| *x), &mut edits);
132 let next = match res {
133 NextNewline::Use => orig_newlines.next(),
134 NextNewline::Discard => {
135 orig_newlines.next();
136 continue;
137 }
138 NextNewline::Replace(new) => {
139 orig_newlines.next();
140 Some(new)
141 }
142 NextNewline::New(new) => Some(new),
143 };
144 match next {
145 Some(n) if n <= offset => {
146 count += 1;
147 }
148 _ => {
149 break;
150 }
151 }
152 }
153
154 count
155}
156
157#[derive(Debug)]
158enum NextNewlines<'a> { 28enum NextNewlines<'a> {
159 Use, 29 Use,
160 ReplaceMany(OffsetNewlineIter<'a>), 30 ReplaceMany(OffsetNewlineIter<'a>),
@@ -188,7 +58,7 @@ impl<'a, 'b> Edits<'a, 'b> {
188 self.acc_diff += self.current.as_ref().map_or(0, |x| x.diff); 58 self.acc_diff += self.current.as_ref().map_or(0, |x| x.diff);
189 match self.edits.split_first() { 59 match self.edits.split_first() {
190 Some((next, rest)) => { 60 Some((next, rest)) => {
191 let delete = translate_range_by(next.delete, self.acc_diff); 61 let delete = self.translate_range(next.delete);
192 let diff = next.insert.len() as i64 - next.delete.len().to_usize() as i64; 62 let diff = next.insert.len() as i64 - next.delete.len().to_usize() as i64;
193 self.current = Some(TranslatedEdit { 63 self.current = Some(TranslatedEdit {
194 delete, 64 delete,
@@ -241,9 +111,27 @@ impl<'a, 'b> Edits<'a, 'b> {
241 }; 111 };
242 res 112 res
243 } 113 }
114
115 fn translate_range(&self, range: TextRange) -> TextRange {
116 if self.acc_diff == 0 {
117 range
118 } else {
119 let start = self.translate(range.start());
120 let end = self.translate(range.end());
121 TextRange::from_to(start, end)
122 }
123 }
124
125 fn translate(&self, x: TextUnit) -> TextUnit {
126 if self.acc_diff == 0 {
127 x
128 } else {
129 TextUnit::from((x.to_usize() as i64 + self.acc_diff) as u32)
130 }
131 }
244} 132}
245 133
246pub fn count_newlines_alt(offset: TextUnit, line_index: &LineIndex, edits: &[AtomTextEdit]) -> u32 { 134pub fn count_newlines(offset: TextUnit, line_index: &LineIndex, edits: &[AtomTextEdit]) -> u32 {
247 let mut sorted_edits: Vec<&AtomTextEdit> = Vec::with_capacity(edits.len()); 135 let mut sorted_edits: Vec<&AtomTextEdit> = Vec::with_capacity(edits.len());
248 for edit in edits { 136 for edit in edits {
249 let insert_index = 137 let insert_index =
@@ -257,7 +145,7 @@ pub fn count_newlines_alt(offset: TextUnit, line_index: &LineIndex, edits: &[Ato
257 145
258 for &orig_newline in line_index.newlines() { 146 for &orig_newline in line_index.newlines() {
259 loop { 147 loop {
260 let translated_newline = translate_by(orig_newline, state.acc_diff); 148 let translated_newline = state.translate(orig_newline);
261 match state.next_newlines(translated_newline) { 149 match state.next_newlines(translated_newline) {
262 NextNewlines::Use => { 150 NextNewlines::Use => {
263 if offset < translated_newline { 151 if offset < translated_newline {
@@ -369,69 +257,5 @@ mod test {
369 let actual_lines = count_newlines(x.offset, &line_index, &x.edits); 257 let actual_lines = count_newlines(x.offset, &line_index, &x.edits);
370 assert_eq!(actual_lines, expected.line); 258 assert_eq!(actual_lines, expected.line);
371 } 259 }
372
373 #[test]
374 fn test_translate_offset_with_edit_alt(x in arb_text_with_offset_and_edits()) {
375 let line_index = LineIndex::new(&x.text);
376 let expected = translate_after_edit(&x.text, x.offset, x.edits.clone());
377 let actual_lines = count_newlines_alt(x.offset, &line_index, &x.edits);
378 assert_eq!(actual_lines, expected.line);
379 }
380 }
381
382 #[test]
383 fn test_translate_offset_with_edit_alt_1() {
384 let x = ArbTextWithOffsetAndEdits {
385 text: String::from("aA\n"),
386 offset: 2.into(),
387 edits: vec![AtomTextEdit::delete(TextRange::from_to(1.into(), 2.into()))],
388 };
389 let line_index = LineIndex::new(&x.text);
390 let expected = translate_after_edit(&x.text, x.offset, x.edits.clone());
391 let actual_lines = count_newlines_alt(x.offset, &line_index, &x.edits);
392 assert_eq!(actual_lines, expected.line);
393 }
394
395 #[test]
396 fn test_translate_offset_with_edit_alt_2() {
397 let x = ArbTextWithOffsetAndEdits {
398 text: String::from("\nqꀸ#"),
399 offset: 5.into(),
400 edits: vec![AtomTextEdit::insert(1.into(), "\n".into())],
401 };
402 let line_index = LineIndex::new(&x.text);
403 let expected = translate_after_edit(&x.text, x.offset, x.edits.clone());
404 let actual_lines = count_newlines_alt(x.offset, &line_index, &x.edits);
405 assert_eq!(actual_lines, expected.line);
406 }
407
408 #[test]
409 fn test_translate_offset_with_edit_alt_3() {
410 let x = ArbTextWithOffsetAndEdits {
411 text: String::from("\n\n\n"),
412 offset: 0.into(),
413 edits: vec![AtomTextEdit::delete(TextRange::from_to(0.into(), 2.into()))],
414 };
415 let line_index = LineIndex::new(&x.text);
416 let expected = translate_after_edit(&x.text, x.offset, x.edits.clone());
417 let actual_lines = count_newlines_alt(x.offset, &line_index, &x.edits);
418 assert_eq!(actual_lines, expected.line);
419 } 260 }
420
421 #[test]
422 fn test_translate_offset_with_edit_alt_4() {
423 let x = ArbTextWithOffsetAndEdits {
424 text: String::from("☻54翑\"A"),
425 offset: 5.into(),
426 edits: vec![
427 AtomTextEdit::delete(TextRange::from_to(0.into(), 8.into())),
428 AtomTextEdit::insert(9.into(), String::from("\n")),
429 ],
430 };
431 let line_index = LineIndex::new(&x.text);
432 let expected = translate_after_edit(&x.text, x.offset, x.edits.clone());
433 let actual_lines = count_newlines_alt(x.offset, &line_index, &x.edits);
434 assert_eq!(actual_lines, expected.line);
435 }
436
437} 261}