aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_text_edit
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_text_edit')
-rw-r--r--crates/ra_text_edit/src/lib.rs152
-rw-r--r--crates/ra_text_edit/src/text_edit.rs102
2 files changed, 132 insertions, 122 deletions
diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs
index e656260c7..3409713ff 100644
--- a/crates/ra_text_edit/src/lib.rs
+++ b/crates/ra_text_edit/src/lib.rs
@@ -1,36 +1,148 @@
1//! FIXME: write short doc here 1//! Representation of a `TextEdit`.
2//!
3//! `rust-analyzer` never mutates text itself and only sends diffs to clients,
4//! so `TextEdit` is the ultimate representation of the work done by
5//! rust-analyzer.
2 6
3mod text_edit; 7pub use text_size::{TextRange, TextSize};
4 8
5use text_size::{TextRange, TextSize}; 9/// `InsertDelete` -- a single "atomic" change to text
6 10///
7pub use crate::text_edit::{TextEdit, TextEditBuilder}; 11/// Must not overlap with other `InDel`s
8
9/// Must not overlap with other `AtomTextEdit`s
10#[derive(Debug, Clone)] 12#[derive(Debug, Clone)]
11pub struct AtomTextEdit { 13pub struct Indel {
14 pub insert: String,
12 /// Refers to offsets in the original text 15 /// Refers to offsets in the original text
13 pub delete: TextRange, 16 pub delete: TextRange,
14 pub insert: String,
15} 17}
16 18
17impl AtomTextEdit { 19#[derive(Debug, Clone)]
18 pub fn replace(range: TextRange, replace_with: String) -> AtomTextEdit { 20pub struct TextEdit {
19 AtomTextEdit { delete: range, insert: replace_with } 21 indels: Vec<Indel>,
20 } 22}
21 23
22 pub fn delete(range: TextRange) -> AtomTextEdit { 24#[derive(Debug, Default)]
23 AtomTextEdit::replace(range, String::new()) 25pub struct TextEditBuilder {
24 } 26 indels: Vec<Indel>,
27}
25 28
26 pub fn insert(offset: TextSize, text: String) -> AtomTextEdit { 29impl Indel {
27 AtomTextEdit::replace(TextRange::empty(offset), text) 30 pub fn insert(offset: TextSize, text: String) -> Indel {
31 Indel::replace(TextRange::empty(offset), text)
32 }
33 pub fn delete(range: TextRange) -> Indel {
34 Indel::replace(range, String::new())
35 }
36 pub fn replace(range: TextRange, replace_with: String) -> Indel {
37 Indel { delete: range, insert: replace_with }
28 } 38 }
29 39
30 pub fn apply(&self, mut text: String) -> String { 40 pub fn apply(&self, text: &mut String) {
31 let start: usize = self.delete.start().into(); 41 let start: usize = self.delete.start().into();
32 let end: usize = self.delete.end().into(); 42 let end: usize = self.delete.end().into();
33 text.replace_range(start..end, &self.insert); 43 text.replace_range(start..end, &self.insert);
34 text 44 }
45}
46
47impl TextEdit {
48 pub fn insert(offset: TextSize, text: String) -> TextEdit {
49 let mut builder = TextEditBuilder::default();
50 builder.insert(offset, text);
51 builder.finish()
52 }
53
54 pub fn delete(range: TextRange) -> TextEdit {
55 let mut builder = TextEditBuilder::default();
56 builder.delete(range);
57 builder.finish()
58 }
59
60 pub fn replace(range: TextRange, replace_with: String) -> TextEdit {
61 let mut builder = TextEditBuilder::default();
62 builder.replace(range, replace_with);
63 builder.finish()
64 }
65
66 pub(crate) fn from_indels(mut indels: Vec<Indel>) -> TextEdit {
67 indels.sort_by_key(|a| (a.delete.start(), a.delete.end()));
68 for (a1, a2) in indels.iter().zip(indels.iter().skip(1)) {
69 assert!(a1.delete.end() <= a2.delete.start())
70 }
71 TextEdit { indels }
72 }
73
74 pub fn is_empty(&self) -> bool {
75 self.indels.is_empty()
76 }
77
78 pub fn as_indels(&self) -> &[Indel] {
79 &self.indels
80 }
81
82 pub fn apply(&self, text: &mut String) {
83 match self.indels.len() {
84 0 => return,
85 1 => {
86 self.indels[0].apply(text);
87 return;
88 }
89 _ => (),
90 }
91
92 let mut total_len = TextSize::of(&*text);
93 for indel in self.indels.iter() {
94 total_len += TextSize::of(&indel.insert);
95 total_len -= indel.delete.end() - indel.delete.start();
96 }
97 let mut buf = String::with_capacity(total_len.into());
98 let mut prev = 0;
99 for indel in self.indels.iter() {
100 let start: usize = indel.delete.start().into();
101 let end: usize = indel.delete.end().into();
102 if start > prev {
103 buf.push_str(&text[prev..start]);
104 }
105 buf.push_str(&indel.insert);
106 prev = end;
107 }
108 buf.push_str(&text[prev..text.len()]);
109 assert_eq!(TextSize::of(&buf), total_len);
110
111 // FIXME: figure out a way to mutate the text in-place or reuse the
112 // memory in some other way
113 *text = buf
114 }
115
116 pub fn apply_to_offset(&self, offset: TextSize) -> Option<TextSize> {
117 let mut res = offset;
118 for indel in self.indels.iter() {
119 if indel.delete.start() >= offset {
120 break;
121 }
122 if offset < indel.delete.end() {
123 return None;
124 }
125 res += TextSize::of(&indel.insert);
126 res -= indel.delete.len();
127 }
128 Some(res)
129 }
130}
131
132impl TextEditBuilder {
133 pub fn replace(&mut self, range: TextRange, replace_with: String) {
134 self.indels.push(Indel::replace(range, replace_with))
135 }
136 pub fn delete(&mut self, range: TextRange) {
137 self.indels.push(Indel::delete(range))
138 }
139 pub fn insert(&mut self, offset: TextSize, text: String) {
140 self.indels.push(Indel::insert(offset, text))
141 }
142 pub fn finish(self) -> TextEdit {
143 TextEdit::from_indels(self.indels)
144 }
145 pub fn invalidates_offset(&self, offset: TextSize) -> bool {
146 self.indels.iter().any(|indel| indel.delete.contains_inclusive(offset))
35 } 147 }
36} 148}
diff --git a/crates/ra_text_edit/src/text_edit.rs b/crates/ra_text_edit/src/text_edit.rs
deleted file mode 100644
index eabab4b4d..000000000
--- a/crates/ra_text_edit/src/text_edit.rs
+++ /dev/null
@@ -1,102 +0,0 @@
1//! FIXME: write short doc here
2
3use crate::AtomTextEdit;
4
5use text_size::{TextRange, TextSize};
6
7#[derive(Debug, Clone)]
8pub struct TextEdit {
9 atoms: Vec<AtomTextEdit>,
10}
11
12#[derive(Debug, Default)]
13pub struct TextEditBuilder {
14 atoms: Vec<AtomTextEdit>,
15}
16
17impl TextEditBuilder {
18 pub fn replace(&mut self, range: TextRange, replace_with: String) {
19 self.atoms.push(AtomTextEdit::replace(range, replace_with))
20 }
21 pub fn delete(&mut self, range: TextRange) {
22 self.atoms.push(AtomTextEdit::delete(range))
23 }
24 pub fn insert(&mut self, offset: TextSize, text: String) {
25 self.atoms.push(AtomTextEdit::insert(offset, text))
26 }
27 pub fn finish(self) -> TextEdit {
28 TextEdit::from_atoms(self.atoms)
29 }
30 pub fn invalidates_offset(&self, offset: TextSize) -> bool {
31 self.atoms.iter().any(|atom| atom.delete.contains_inclusive(offset))
32 }
33}
34
35impl TextEdit {
36 pub fn insert(offset: TextSize, text: String) -> TextEdit {
37 let mut builder = TextEditBuilder::default();
38 builder.insert(offset, text);
39 builder.finish()
40 }
41
42 pub fn delete(range: TextRange) -> TextEdit {
43 let mut builder = TextEditBuilder::default();
44 builder.delete(range);
45 builder.finish()
46 }
47
48 pub fn replace(range: TextRange, replace_with: String) -> TextEdit {
49 let mut builder = TextEditBuilder::default();
50 builder.replace(range, replace_with);
51 builder.finish()
52 }
53
54 pub(crate) fn from_atoms(mut atoms: Vec<AtomTextEdit>) -> TextEdit {
55 atoms.sort_by_key(|a| (a.delete.start(), a.delete.end()));
56 for (a1, a2) in atoms.iter().zip(atoms.iter().skip(1)) {
57 assert!(a1.delete.end() <= a2.delete.start())
58 }
59 TextEdit { atoms }
60 }
61
62 pub fn as_atoms(&self) -> &[AtomTextEdit] {
63 &self.atoms
64 }
65
66 pub fn apply(&self, text: &str) -> String {
67 let mut total_len = TextSize::of(text);
68 for atom in self.atoms.iter() {
69 total_len += TextSize::of(&atom.insert);
70 total_len -= atom.delete.end() - atom.delete.start();
71 }
72 let mut buf = String::with_capacity(total_len.into());
73 let mut prev = 0;
74 for atom in self.atoms.iter() {
75 let start: usize = atom.delete.start().into();
76 let end: usize = atom.delete.end().into();
77 if start > prev {
78 buf.push_str(&text[prev..start]);
79 }
80 buf.push_str(&atom.insert);
81 prev = end;
82 }
83 buf.push_str(&text[prev..text.len()]);
84 assert_eq!(TextSize::of(&buf), total_len);
85 buf
86 }
87
88 pub fn apply_to_offset(&self, offset: TextSize) -> Option<TextSize> {
89 let mut res = offset;
90 for atom in self.atoms.iter() {
91 if atom.delete.start() >= offset {
92 break;
93 }
94 if offset < atom.delete.end() {
95 return None;
96 }
97 res += TextSize::of(&atom.insert);
98 res -= atom.delete.len();
99 }
100 Some(res)
101 }
102}