aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-05-05 22:50:47 +0100
committerGitHub <[email protected]>2020-05-05 22:50:47 +0100
commit30eb458b4fa8adcecd8cbf731bd1cfa9a7a8b88b (patch)
tree1ee318a9568c8bf1d0b314ef996581bb4e518b9e
parent78c82eff95cae43b389dbd6e590d7f09bac8f3f1 (diff)
parentca9e0f5fe9ad29ab0c5a0771a0d0eaec97e4104b (diff)
Merge #4332
4332: Refactor TextEdit r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r--crates/ra_assists/src/assist_ctx.rs5
-rw-r--r--crates/ra_assists/src/doc_tests.rs6
-rw-r--r--crates/ra_assists/src/lib.rs34
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs4
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs4
-rw-r--r--crates/ra_ide/src/diagnostics.rs24
-rw-r--r--crates/ra_ide/src/join_lines.rs6
-rw-r--r--crates/ra_ide/src/references/rename.rs32
-rw-r--r--crates/ra_ide/src/ssr.rs32
-rw-r--r--crates/ra_ide/src/test_utils.rs6
-rw-r--r--crates/ra_ide/src/typing.rs9
-rw-r--r--crates/ra_ide/src/typing/on_enter.rs3
-rw-r--r--crates/ra_ide_db/src/line_index_utils.rs6
-rw-r--r--crates/ra_syntax/src/fuzz.rs6
-rw-r--r--crates/ra_syntax/src/lib.rs15
-rw-r--r--crates/ra_syntax/src/parsing/reparsing.rs28
-rw-r--r--crates/ra_text_edit/src/lib.rs146
-rw-r--r--crates/ra_text_edit/src/text_edit.rs102
-rw-r--r--crates/rust-analyzer/src/conv.rs29
-rw-r--r--xtask/tests/tidy-tests/main.rs1
20 files changed, 271 insertions, 227 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs
index 82f61bc8f..83dd270c6 100644
--- a/crates/ra_assists/src/assist_ctx.rs
+++ b/crates/ra_assists/src/assist_ctx.rs
@@ -4,14 +4,13 @@ use ra_db::FileRange;
4use ra_fmt::{leading_indent, reindent}; 4use ra_fmt::{leading_indent, reindent};
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{
7 algo::{self, find_covering_element, find_node_at_offset}, 7 algo::{self, find_covering_element, find_node_at_offset, SyntaxRewriter},
8 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize, 8 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
9 TokenAtOffset, 9 TokenAtOffset,
10}; 10};
11use ra_text_edit::TextEditBuilder; 11use ra_text_edit::TextEditBuilder;
12 12
13use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist}; 13use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
14use algo::SyntaxRewriter;
15 14
16#[derive(Clone, Debug)] 15#[derive(Clone, Debug)]
17pub(crate) struct Assist(pub(crate) Vec<AssistInfo>); 16pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);
@@ -42,8 +41,6 @@ impl AssistInfo {
42 } 41 }
43} 42}
44 43
45pub(crate) type AssistHandler = fn(AssistCtx) -> Option<Assist>;
46
47/// `AssistCtx` allows to apply an assist or check if it could be applied. 44/// `AssistCtx` allows to apply an assist or check if it could be applied.
48/// 45///
49/// Assists use a somewhat over-engineered approach, given the current needs. The 46/// Assists use a somewhat over-engineered approach, given the current needs. The
diff --git a/crates/ra_assists/src/doc_tests.rs b/crates/ra_assists/src/doc_tests.rs
index c0f9bc1fb..f627f31dc 100644
--- a/crates/ra_assists/src/doc_tests.rs
+++ b/crates/ra_assists/src/doc_tests.rs
@@ -30,6 +30,10 @@ fn check(assist_id: &str, before: &str, after: &str) {
30 ) 30 )
31 }); 31 });
32 32
33 let actual = assist.action.edit.apply(&before); 33 let actual = {
34 let mut actual = before.clone();
35 assist.action.edit.apply(&mut actual);
36 actual
37 };
34 assert_eq_text!(after, &actual); 38 assert_eq_text!(after, &actual);
35} 39}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 5cec10088..0f94f5ee8 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -23,7 +23,7 @@ use ra_ide_db::RootDatabase;
23use ra_syntax::{TextRange, TextSize}; 23use ra_syntax::{TextRange, TextSize};
24use ra_text_edit::TextEdit; 24use ra_text_edit::TextEdit;
25 25
26pub(crate) use crate::assist_ctx::{Assist, AssistCtx, AssistHandler}; 26pub(crate) use crate::assist_ctx::{Assist, AssistCtx};
27 27
28/// Unique identifier of the assist, should not be shown to the user 28/// Unique identifier of the assist, should not be shown to the user
29/// directly. 29/// directly.
@@ -109,7 +109,9 @@ pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssi
109} 109}
110 110
111mod handlers { 111mod handlers {
112 use crate::AssistHandler; 112 use crate::{Assist, AssistCtx};
113
114 pub(crate) type Handler = fn(AssistCtx) -> Option<Assist>;
113 115
114 mod add_custom_impl; 116 mod add_custom_impl;
115 mod add_derive; 117 mod add_derive;
@@ -145,12 +147,13 @@ mod handlers {
145 mod reorder_fields; 147 mod reorder_fields;
146 mod unwrap_block; 148 mod unwrap_block;
147 149
148 pub(crate) fn all() -> &'static [AssistHandler] { 150 pub(crate) fn all() -> &'static [Handler] {
149 &[ 151 &[
150 // These are alphabetic for the foolish consistency 152 // These are alphabetic for the foolish consistency
151 add_custom_impl::add_custom_impl, 153 add_custom_impl::add_custom_impl,
152 add_derive::add_derive, 154 add_derive::add_derive,
153 add_explicit_type::add_explicit_type, 155 add_explicit_type::add_explicit_type,
156 add_from_impl_for_enum::add_from_impl_for_enum,
154 add_function::add_function, 157 add_function::add_function,
155 add_impl::add_impl, 158 add_impl::add_impl,
156 add_new::add_new, 159 add_new::add_new,
@@ -176,17 +179,18 @@ mod handlers {
176 raw_string::remove_hash, 179 raw_string::remove_hash,
177 remove_dbg::remove_dbg, 180 remove_dbg::remove_dbg,
178 remove_mut::remove_mut, 181 remove_mut::remove_mut,
182 reorder_fields::reorder_fields,
179 replace_if_let_with_match::replace_if_let_with_match, 183 replace_if_let_with_match::replace_if_let_with_match,
180 replace_let_with_if_let::replace_let_with_if_let, 184 replace_let_with_if_let::replace_let_with_if_let,
181 replace_qualified_name_with_use::replace_qualified_name_with_use, 185 replace_qualified_name_with_use::replace_qualified_name_with_use,
182 replace_unwrap_with_match::replace_unwrap_with_match, 186 replace_unwrap_with_match::replace_unwrap_with_match,
183 split_import::split_import, 187 split_import::split_import,
184 add_from_impl_for_enum::add_from_impl_for_enum,
185 unwrap_block::unwrap_block, 188 unwrap_block::unwrap_block,
186 // These are manually sorted for better priorities 189 // These are manually sorted for better priorities
187 add_missing_impl_members::add_missing_impl_members, 190 add_missing_impl_members::add_missing_impl_members,
188 add_missing_impl_members::add_missing_default_members, 191 add_missing_impl_members::add_missing_default_members,
189 reorder_fields::reorder_fields, 192 // Are you sure you want to add new assist here, and not to the
193 // sorted list above?
190 ] 194 ]
191 } 195 }
192} 196}
@@ -195,12 +199,12 @@ mod handlers {
195mod helpers { 199mod helpers {
196 use std::sync::Arc; 200 use std::sync::Arc;
197 201
202 use hir::Semantics;
198 use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; 203 use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
199 use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; 204 use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
200 use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset}; 205 use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset};
201 206
202 use crate::{AssistCtx, AssistFile, AssistHandler}; 207 use crate::{handlers::Handler, AssistCtx, AssistFile};
203 use hir::Semantics;
204 208
205 pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { 209 pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
206 let (mut db, file_id) = RootDatabase::with_single_file(text); 210 let (mut db, file_id) = RootDatabase::with_single_file(text);
@@ -210,22 +214,18 @@ mod helpers {
210 (db, file_id) 214 (db, file_id)
211 } 215 }
212 216
213 pub(crate) fn check_assist( 217 pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
214 assist: AssistHandler,
215 ra_fixture_before: &str,
216 ra_fixture_after: &str,
217 ) {
218 check(assist, ra_fixture_before, ExpectedResult::After(ra_fixture_after)); 218 check(assist, ra_fixture_before, ExpectedResult::After(ra_fixture_after));
219 } 219 }
220 220
221 // FIXME: instead of having a separate function here, maybe use 221 // FIXME: instead of having a separate function here, maybe use
222 // `extract_ranges` and mark the target as `<target> </target>` in the 222 // `extract_ranges` and mark the target as `<target> </target>` in the
223 // fixuture? 223 // fixuture?
224 pub(crate) fn check_assist_target(assist: AssistHandler, ra_fixture: &str, target: &str) { 224 pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) {
225 check(assist, ra_fixture, ExpectedResult::Target(target)); 225 check(assist, ra_fixture, ExpectedResult::Target(target));
226 } 226 }
227 227
228 pub(crate) fn check_assist_not_applicable(assist: AssistHandler, ra_fixture: &str) { 228 pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
229 check(assist, ra_fixture, ExpectedResult::NotApplicable); 229 check(assist, ra_fixture, ExpectedResult::NotApplicable);
230 } 230 }
231 231
@@ -235,7 +235,7 @@ mod helpers {
235 Target(&'a str), 235 Target(&'a str),
236 } 236 }
237 237
238 fn check(assist: AssistHandler, before: &str, expected: ExpectedResult) { 238 fn check(assist: Handler, before: &str, expected: ExpectedResult) {
239 let (text_without_caret, file_with_caret_id, range_or_offset, db) = 239 let (text_without_caret, file_with_caret_id, range_or_offset, db) =
240 if before.contains("//-") { 240 if before.contains("//-") {
241 let (mut db, position) = RootDatabase::with_position(before); 241 let (mut db, position) = RootDatabase::with_position(before);
@@ -261,13 +261,13 @@ mod helpers {
261 (Some(assist), ExpectedResult::After(after)) => { 261 (Some(assist), ExpectedResult::After(after)) => {
262 let action = assist.0[0].action.clone().unwrap(); 262 let action = assist.0[0].action.clone().unwrap();
263 263
264 let assisted_file_text = if let AssistFile::TargetFile(file_id) = action.file { 264 let mut actual = if let AssistFile::TargetFile(file_id) = action.file {
265 db.file_text(file_id).as_ref().to_owned() 265 db.file_text(file_id).as_ref().to_owned()
266 } else { 266 } else {
267 text_without_caret 267 text_without_caret
268 }; 268 };
269 action.edit.apply(&mut actual);
269 270
270 let mut actual = action.edit.apply(&assisted_file_text);
271 match action.cursor_position { 271 match action.cursor_position {
272 None => { 272 None => {
273 if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { 273 if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
index dd87bd119..b6b9627de 100644
--- a/crates/ra_ide/src/completion/completion_context.rs
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -9,7 +9,7 @@ use ra_syntax::{
9 SyntaxKind::*, 9 SyntaxKind::*,
10 SyntaxNode, SyntaxToken, TextRange, TextSize, 10 SyntaxNode, SyntaxToken, TextRange, TextSize,
11}; 11};
12use ra_text_edit::AtomTextEdit; 12use ra_text_edit::Indel;
13 13
14use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; 14use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition};
15 15
@@ -76,7 +76,7 @@ impl<'a> CompletionContext<'a> {
76 // actual completion. 76 // actual completion.
77 let file_with_fake_ident = { 77 let file_with_fake_ident = {
78 let parse = db.parse(position.file_id); 78 let parse = db.parse(position.file_id);
79 let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string()); 79 let edit = Indel::insert(position.offset, "intellijRulezz".to_string());
80 parse.reparse(&edit).tree() 80 parse.reparse(&edit).tree()
81 }; 81 };
82 let fake_ident_token = 82 let fake_ident_token =
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs
index 5936fb8f7..383b23ac4 100644
--- a/crates/ra_ide/src/completion/completion_item.rs
+++ b/crates/ra_ide/src/completion/completion_item.rs
@@ -62,8 +62,8 @@ impl fmt::Debug for CompletionItem {
62 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 62 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63 let mut s = f.debug_struct("CompletionItem"); 63 let mut s = f.debug_struct("CompletionItem");
64 s.field("label", &self.label()).field("source_range", &self.source_range()); 64 s.field("label", &self.label()).field("source_range", &self.source_range());
65 if self.text_edit().as_atoms().len() == 1 { 65 if self.text_edit().as_indels().len() == 1 {
66 let atom = &self.text_edit().as_atoms()[0]; 66 let atom = &self.text_edit().as_indels()[0];
67 s.field("delete", &atom.delete); 67 s.field("delete", &atom.delete);
68 s.field("insert", &atom.insert); 68 s.field("insert", &atom.insert);
69 } else { 69 } else {
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index 4c04cee07..87a0b80f1 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -241,7 +241,11 @@ mod tests {
241 diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); 241 diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before));
242 let mut fix = diagnostic.fix.unwrap(); 242 let mut fix = diagnostic.fix.unwrap();
243 let edit = fix.source_file_edits.pop().unwrap().edit; 243 let edit = fix.source_file_edits.pop().unwrap().edit;
244 let actual = edit.apply(&before); 244 let actual = {
245 let mut actual = before.to_string();
246 edit.apply(&mut actual);
247 actual
248 };
245 assert_eq_text!(after, &actual); 249 assert_eq_text!(after, &actual);
246 } 250 }
247 251
@@ -256,7 +260,11 @@ mod tests {
256 let mut fix = diagnostic.fix.unwrap(); 260 let mut fix = diagnostic.fix.unwrap();
257 let edit = fix.source_file_edits.pop().unwrap().edit; 261 let edit = fix.source_file_edits.pop().unwrap().edit;
258 let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); 262 let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
259 let actual = edit.apply(&target_file_contents); 263 let actual = {
264 let mut actual = target_file_contents.to_string();
265 edit.apply(&mut actual);
266 actual
267 };
260 268
261 // Strip indent and empty lines from `after`, to match the behaviour of 269 // Strip indent and empty lines from `after`, to match the behaviour of
262 // `parse_fixture` called from `analysis_and_position`. 270 // `parse_fixture` called from `analysis_and_position`.
@@ -288,7 +296,11 @@ mod tests {
288 let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); 296 let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap();
289 let mut fix = diagnostic.fix.unwrap(); 297 let mut fix = diagnostic.fix.unwrap();
290 let edit = fix.source_file_edits.pop().unwrap().edit; 298 let edit = fix.source_file_edits.pop().unwrap().edit;
291 let actual = edit.apply(&before); 299 let actual = {
300 let mut actual = before.to_string();
301 edit.apply(&mut actual);
302 actual
303 };
292 assert_eq_text!(after, &actual); 304 assert_eq_text!(after, &actual);
293 } 305 }
294 306
@@ -662,10 +674,10 @@ mod tests {
662 1, 674 1,
663 ), 675 ),
664 edit: TextEdit { 676 edit: TextEdit {
665 atoms: [ 677 indels: [
666 AtomTextEdit { 678 Indel {
667 delete: 3..9,
668 insert: "{a:42, b: ()}", 679 insert: "{a:42, b: ()}",
680 delete: 3..9,
669 }, 681 },
670 ], 682 ],
671 }, 683 },
diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs
index 63fd6b3e4..d3af780c4 100644
--- a/crates/ra_ide/src/join_lines.rs
+++ b/crates/ra_ide/src/join_lines.rs
@@ -569,7 +569,11 @@ fn foo() {
569 let (sel, before) = extract_range(before); 569 let (sel, before) = extract_range(before);
570 let parse = SourceFile::parse(&before); 570 let parse = SourceFile::parse(&before);
571 let result = join_lines(&parse.tree(), sel); 571 let result = join_lines(&parse.tree(), sel);
572 let actual = result.apply(&before); 572 let actual = {
573 let mut actual = before.to_string();
574 result.apply(&mut actual);
575 actual
576 };
573 assert_eq_text!(after, &actual); 577 assert_eq_text!(after, &actual);
574 } 578 }
575 579
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index 52e55b0a0..0398d53bc 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -537,10 +537,10 @@ mod tests {
537 2, 537 2,
538 ), 538 ),
539 edit: TextEdit { 539 edit: TextEdit {
540 atoms: [ 540 indels: [
541 AtomTextEdit { 541 Indel {
542 delete: 4..7,
543 insert: "foo2", 542 insert: "foo2",
543 delete: 4..7,
544 }, 544 },
545 ], 545 ],
546 }, 546 },
@@ -589,10 +589,10 @@ mod tests {
589 1, 589 1,
590 ), 590 ),
591 edit: TextEdit { 591 edit: TextEdit {
592 atoms: [ 592 indels: [
593 AtomTextEdit { 593 Indel {
594 delete: 4..7,
595 insert: "foo2", 594 insert: "foo2",
595 delete: 4..7,
596 }, 596 },
597 ], 597 ],
598 }, 598 },
@@ -672,10 +672,10 @@ mod tests {
672 2, 672 2,
673 ), 673 ),
674 edit: TextEdit { 674 edit: TextEdit {
675 atoms: [ 675 indels: [
676 AtomTextEdit { 676 Indel {
677 delete: 8..11,
678 insert: "foo2", 677 insert: "foo2",
678 delete: 8..11,
679 }, 679 },
680 ], 680 ],
681 }, 681 },
@@ -685,10 +685,10 @@ mod tests {
685 1, 685 1,
686 ), 686 ),
687 edit: TextEdit { 687 edit: TextEdit {
688 atoms: [ 688 indels: [
689 AtomTextEdit { 689 Indel {
690 delete: 27..30,
691 insert: "foo2", 690 insert: "foo2",
691 delete: 27..30,
692 }, 692 },
693 ], 693 ],
694 }, 694 },
@@ -720,13 +720,13 @@ mod tests {
720 if let Some(change) = source_change { 720 if let Some(change) = source_change {
721 for edit in change.info.source_file_edits { 721 for edit in change.info.source_file_edits {
722 file_id = Some(edit.file_id); 722 file_id = Some(edit.file_id);
723 for atom in edit.edit.as_atoms() { 723 for indel in edit.edit.as_indels() {
724 text_edit_builder.replace(atom.delete, atom.insert.clone()); 724 text_edit_builder.replace(indel.delete, indel.insert.clone());
725 } 725 }
726 } 726 }
727 } 727 }
728 let result = 728 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string();
729 text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap()); 729 text_edit_builder.finish().apply(&mut result);
730 assert_eq_text!(expected, &*result); 730 assert_eq_text!(expected, &*result);
731 } 731 }
732} 732}
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 7b93ff2d2..8bf52d0fa 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -401,16 +401,22 @@ fn render_replace(
401 ignored_comments: &Vec<Comment>, 401 ignored_comments: &Vec<Comment>,
402 template: &SsrTemplate, 402 template: &SsrTemplate,
403) -> String { 403) -> String {
404 let mut builder = TextEditBuilder::default(); 404 let edit = {
405 for element in template.template.descendants() { 405 let mut builder = TextEditBuilder::default();
406 if let Some(var) = template.placeholders.get(&element) { 406 for element in template.template.descendants() {
407 builder.replace(element.text_range(), binding[var].to_string()) 407 if let Some(var) = template.placeholders.get(&element) {
408 builder.replace(element.text_range(), binding[var].to_string())
409 }
408 } 410 }
409 } 411 for comment in ignored_comments {
410 for comment in ignored_comments { 412 builder.insert(template.template.text_range().end(), comment.syntax().to_string())
411 builder.insert(template.template.text_range().end(), comment.syntax().to_string()) 413 }
412 } 414 builder.finish()
413 builder.finish().apply(&template.template.text().to_string()) 415 };
416
417 let mut text = template.template.text().to_string();
418 edit.apply(&mut text);
419 text
414} 420}
415 421
416#[cfg(test)] 422#[cfg(test)]
@@ -505,7 +511,9 @@ mod tests {
505 ); 511 );
506 512
507 let edit = replace(&matches, &query.template); 513 let edit = replace(&matches, &query.template);
508 assert_eq!(edit.apply(input), "fn main() { bar(1+2); }"); 514 let mut after = input.to_string();
515 edit.apply(&mut after);
516 assert_eq!(after, "fn main() { bar(1+2); }");
509 } 517 }
510 518
511 fn assert_ssr_transform(query: &str, input: &str, result: &str) { 519 fn assert_ssr_transform(query: &str, input: &str, result: &str) {
@@ -513,7 +521,9 @@ mod tests {
513 let code = SourceFile::parse(input).tree(); 521 let code = SourceFile::parse(input).tree();
514 let matches = find(&query.pattern, code.syntax()); 522 let matches = find(&query.pattern, code.syntax());
515 let edit = replace(&matches, &query.template); 523 let edit = replace(&matches, &query.template);
516 assert_eq!(edit.apply(input), result); 524 let mut after = input.to_string();
525 edit.apply(&mut after);
526 assert_eq!(after, result);
517 } 527 }
518 528
519 #[test] 529 #[test]
diff --git a/crates/ra_ide/src/test_utils.rs b/crates/ra_ide/src/test_utils.rs
index f14533e14..48c8fd1f4 100644
--- a/crates/ra_ide/src/test_utils.rs
+++ b/crates/ra_ide/src/test_utils.rs
@@ -13,7 +13,11 @@ pub fn check_action<F: Fn(&SourceFile, TextSize) -> Option<TextEdit>>(
13 let (before_cursor_pos, before) = extract_offset(before); 13 let (before_cursor_pos, before) = extract_offset(before);
14 let file = SourceFile::parse(&before).ok().unwrap(); 14 let file = SourceFile::parse(&before).ok().unwrap();
15 let result = f(&file, before_cursor_pos).expect("code action is not applicable"); 15 let result = f(&file, before_cursor_pos).expect("code action is not applicable");
16 let actual = result.apply(&before); 16 let actual = {
17 let mut actual = before.to_string();
18 result.apply(&mut actual);
19 actual
20 };
17 let actual_cursor_pos = 21 let actual_cursor_pos =
18 result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit"); 22 result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit");
19 let actual = add_cursor(&actual, actual_cursor_pos); 23 let actual = add_cursor(&actual, actual_cursor_pos);
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs
index 2a8b4327f..a03da4693 100644
--- a/crates/ra_ide/src/typing.rs
+++ b/crates/ra_ide/src/typing.rs
@@ -142,10 +142,13 @@ mod tests {
142 fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> { 142 fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> {
143 let (offset, before) = extract_offset(before); 143 let (offset, before) = extract_offset(before);
144 let edit = TextEdit::insert(offset, char_typed.to_string()); 144 let edit = TextEdit::insert(offset, char_typed.to_string());
145 let before = edit.apply(&before); 145 let mut before = before.to_string();
146 edit.apply(&mut before);
146 let parse = SourceFile::parse(&before); 147 let parse = SourceFile::parse(&before);
147 on_char_typed_inner(&parse.tree(), offset, char_typed) 148 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {
148 .map(|it| (it.edit.apply(&before), it)) 149 it.edit.apply(&mut before);
150 (before.to_string(), it)
151 })
149 } 152 }
150 153
151 fn type_char(char_typed: char, before: &str, after: &str) { 154 fn type_char(char_typed: char, before: &str, after: &str) {
diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs
index 725237464..78a40cc94 100644
--- a/crates/ra_ide/src/typing/on_enter.rs
+++ b/crates/ra_ide/src/typing/on_enter.rs
@@ -96,7 +96,8 @@ mod tests {
96 let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; 96 let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?;
97 97
98 assert_eq!(result.source_file_edits.len(), 1); 98 assert_eq!(result.source_file_edits.len(), 1);
99 let actual = result.source_file_edits[0].edit.apply(&before); 99 let mut actual = before.to_string();
100 result.source_file_edits[0].edit.apply(&mut actual);
100 let actual = add_cursor(&actual, result.cursor_position.unwrap().offset); 101 let actual = add_cursor(&actual, result.cursor_position.unwrap().offset);
101 Some(actual) 102 Some(actual)
102 } 103 }
diff --git a/crates/ra_ide_db/src/line_index_utils.rs b/crates/ra_ide_db/src/line_index_utils.rs
index 039a12c0d..7fa6fc448 100644
--- a/crates/ra_ide_db/src/line_index_utils.rs
+++ b/crates/ra_ide_db/src/line_index_utils.rs
@@ -10,7 +10,7 @@
10use std::convert::TryInto; 10use std::convert::TryInto;
11 11
12use ra_syntax::{TextRange, TextSize}; 12use ra_syntax::{TextRange, TextSize};
13use ra_text_edit::{AtomTextEdit, TextEdit}; 13use ra_text_edit::{Indel, TextEdit};
14 14
15use crate::line_index::{LineCol, LineIndex, Utf16Char}; 15use crate::line_index::{LineCol, LineIndex, Utf16Char};
16 16
@@ -182,14 +182,14 @@ struct TranslatedEdit<'a> {
182} 182}
183 183
184struct Edits<'a> { 184struct Edits<'a> {
185 edits: &'a [AtomTextEdit], 185 edits: &'a [Indel],
186 current: Option<TranslatedEdit<'a>>, 186 current: Option<TranslatedEdit<'a>>,
187 acc_diff: i64, 187 acc_diff: i64,
188} 188}
189 189
190impl<'a> Edits<'a> { 190impl<'a> Edits<'a> {
191 fn from_text_edit(text_edit: &'a TextEdit) -> Edits<'a> { 191 fn from_text_edit(text_edit: &'a TextEdit) -> Edits<'a> {
192 let mut x = Edits { edits: text_edit.as_atoms(), current: None, acc_diff: 0 }; 192 let mut x = Edits { edits: text_edit.as_indels(), current: None, acc_diff: 0 };
193 x.advance_edit(); 193 x.advance_edit();
194 x 194 x
195 } 195 }
diff --git a/crates/ra_syntax/src/fuzz.rs b/crates/ra_syntax/src/fuzz.rs
index 10fbe3176..39f9b12ab 100644
--- a/crates/ra_syntax/src/fuzz.rs
+++ b/crates/ra_syntax/src/fuzz.rs
@@ -5,7 +5,7 @@ use std::{
5 str::{self, FromStr}, 5 str::{self, FromStr},
6}; 6};
7 7
8use ra_text_edit::AtomTextEdit; 8use ra_text_edit::Indel;
9 9
10use crate::{validation, AstNode, SourceFile, TextRange}; 10use crate::{validation, AstNode, SourceFile, TextRange};
11 11
@@ -22,7 +22,7 @@ pub fn check_parser(text: &str) {
22#[derive(Debug, Clone)] 22#[derive(Debug, Clone)]
23pub struct CheckReparse { 23pub struct CheckReparse {
24 text: String, 24 text: String,
25 edit: AtomTextEdit, 25 edit: Indel,
26 edited_text: String, 26 edited_text: String,
27} 27}
28 28
@@ -43,7 +43,7 @@ impl CheckReparse {
43 TextRange::at(delete_start.try_into().unwrap(), delete_len.try_into().unwrap()); 43 TextRange::at(delete_start.try_into().unwrap(), delete_len.try_into().unwrap());
44 let edited_text = 44 let edited_text =
45 format!("{}{}{}", &text[..delete_start], &insert, &text[delete_start + delete_len..]); 45 format!("{}{}{}", &text[..delete_start], &insert, &text[delete_start + delete_len..]);
46 let edit = AtomTextEdit { delete, insert }; 46 let edit = Indel { delete, insert };
47 Some(CheckReparse { text, edit, edited_text }) 47 Some(CheckReparse { text, edit, edited_text })
48 } 48 }
49 49
diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs
index d0234cada..61e686da5 100644
--- a/crates/ra_syntax/src/lib.rs
+++ b/crates/ra_syntax/src/lib.rs
@@ -39,7 +39,7 @@ pub mod fuzz;
39 39
40use std::{marker::PhantomData, sync::Arc}; 40use std::{marker::PhantomData, sync::Arc};
41 41
42use ra_text_edit::AtomTextEdit; 42use ra_text_edit::Indel;
43use stdx::format_to; 43use stdx::format_to;
44 44
45use crate::syntax_node::GreenNode; 45use crate::syntax_node::GreenNode;
@@ -126,13 +126,13 @@ impl Parse<SourceFile> {
126 buf 126 buf
127 } 127 }
128 128
129 pub fn reparse(&self, edit: &AtomTextEdit) -> Parse<SourceFile> { 129 pub fn reparse(&self, indel: &Indel) -> Parse<SourceFile> {
130 self.incremental_reparse(edit).unwrap_or_else(|| self.full_reparse(edit)) 130 self.incremental_reparse(indel).unwrap_or_else(|| self.full_reparse(indel))
131 } 131 }
132 132
133 fn incremental_reparse(&self, edit: &AtomTextEdit) -> Option<Parse<SourceFile>> { 133 fn incremental_reparse(&self, indel: &Indel) -> Option<Parse<SourceFile>> {
134 // FIXME: validation errors are not handled here 134 // FIXME: validation errors are not handled here
135 parsing::incremental_reparse(self.tree().syntax(), edit, self.errors.to_vec()).map( 135 parsing::incremental_reparse(self.tree().syntax(), indel, self.errors.to_vec()).map(
136 |(green_node, errors, _reparsed_range)| Parse { 136 |(green_node, errors, _reparsed_range)| Parse {
137 green: green_node, 137 green: green_node,
138 errors: Arc::new(errors), 138 errors: Arc::new(errors),
@@ -141,8 +141,9 @@ impl Parse<SourceFile> {
141 ) 141 )
142 } 142 }
143 143
144 fn full_reparse(&self, edit: &AtomTextEdit) -> Parse<SourceFile> { 144 fn full_reparse(&self, indel: &Indel) -> Parse<SourceFile> {
145 let text = edit.apply(self.tree().syntax().text().to_string()); 145 let mut text = self.tree().syntax().text().to_string();
146 indel.apply(&mut text);
146 SourceFile::parse(&text) 147 SourceFile::parse(&text)
147 } 148 }
148} 149}
diff --git a/crates/ra_syntax/src/parsing/reparsing.rs b/crates/ra_syntax/src/parsing/reparsing.rs
index ffff0a7b2..edbc190f8 100644
--- a/crates/ra_syntax/src/parsing/reparsing.rs
+++ b/crates/ra_syntax/src/parsing/reparsing.rs
@@ -7,7 +7,7 @@
7//! and try to parse only this block. 7//! and try to parse only this block.
8 8
9use ra_parser::Reparser; 9use ra_parser::Reparser;
10use ra_text_edit::AtomTextEdit; 10use ra_text_edit::Indel;
11 11
12use crate::{ 12use crate::{
13 algo, 13 algo,
@@ -24,7 +24,7 @@ use crate::{
24 24
25pub(crate) fn incremental_reparse( 25pub(crate) fn incremental_reparse(
26 node: &SyntaxNode, 26 node: &SyntaxNode,
27 edit: &AtomTextEdit, 27 edit: &Indel,
28 errors: Vec<SyntaxError>, 28 errors: Vec<SyntaxError>,
29) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> { 29) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
30 if let Some((green, new_errors, old_range)) = reparse_token(node, &edit) { 30 if let Some((green, new_errors, old_range)) = reparse_token(node, &edit) {
@@ -39,7 +39,7 @@ pub(crate) fn incremental_reparse(
39 39
40fn reparse_token<'node>( 40fn reparse_token<'node>(
41 root: &'node SyntaxNode, 41 root: &'node SyntaxNode,
42 edit: &AtomTextEdit, 42 edit: &Indel,
43) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> { 43) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
44 let prev_token = algo::find_covering_element(root, edit.delete).as_token()?.clone(); 44 let prev_token = algo::find_covering_element(root, edit.delete).as_token()?.clone();
45 let prev_token_kind = prev_token.kind(); 45 let prev_token_kind = prev_token.kind();
@@ -88,7 +88,7 @@ fn reparse_token<'node>(
88 88
89fn reparse_block<'node>( 89fn reparse_block<'node>(
90 root: &'node SyntaxNode, 90 root: &'node SyntaxNode,
91 edit: &AtomTextEdit, 91 edit: &Indel,
92) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> { 92) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
93 let (node, reparser) = find_reparsable_node(root, edit.delete)?; 93 let (node, reparser) = find_reparsable_node(root, edit.delete)?;
94 let text = get_text_after_edit(node.clone().into(), edit); 94 let text = get_text_after_edit(node.clone().into(), edit);
@@ -108,15 +108,15 @@ fn reparse_block<'node>(
108 Some((node.replace_with(green), new_parser_errors, node.text_range())) 108 Some((node.replace_with(green), new_parser_errors, node.text_range()))
109} 109}
110 110
111fn get_text_after_edit(element: SyntaxElement, edit: &AtomTextEdit) -> String { 111fn get_text_after_edit(element: SyntaxElement, edit: &Indel) -> String {
112 let edit = 112 let edit = Indel::replace(edit.delete - element.text_range().start(), edit.insert.clone());
113 AtomTextEdit::replace(edit.delete - element.text_range().start(), edit.insert.clone());
114 113
115 let text = match element { 114 let mut text = match element {
116 NodeOrToken::Token(token) => token.text().to_string(), 115 NodeOrToken::Token(token) => token.text().to_string(),
117 NodeOrToken::Node(node) => node.text().to_string(), 116 NodeOrToken::Node(node) => node.text().to_string(),
118 }; 117 };
119 edit.apply(text) 118 edit.apply(&mut text);
119 text
120} 120}
121 121
122fn is_contextual_kw(text: &str) -> bool { 122fn is_contextual_kw(text: &str) -> bool {
@@ -167,7 +167,7 @@ fn merge_errors(
167 old_errors: Vec<SyntaxError>, 167 old_errors: Vec<SyntaxError>,
168 new_errors: Vec<SyntaxError>, 168 new_errors: Vec<SyntaxError>,
169 range_before_reparse: TextRange, 169 range_before_reparse: TextRange,
170 edit: &AtomTextEdit, 170 edit: &Indel,
171) -> Vec<SyntaxError> { 171) -> Vec<SyntaxError> {
172 let mut res = Vec::new(); 172 let mut res = Vec::new();
173 173
@@ -198,8 +198,12 @@ mod tests {
198 198
199 fn do_check(before: &str, replace_with: &str, reparsed_len: u32) { 199 fn do_check(before: &str, replace_with: &str, reparsed_len: u32) {
200 let (range, before) = extract_range(before); 200 let (range, before) = extract_range(before);
201 let edit = AtomTextEdit::replace(range, replace_with.to_owned()); 201 let edit = Indel::replace(range, replace_with.to_owned());
202 let after = edit.apply(before.clone()); 202 let after = {
203 let mut after = before.clone();
204 edit.apply(&mut after);
205 after
206 };
203 207
204 let fully_reparsed = SourceFile::parse(&after); 208 let fully_reparsed = SourceFile::parse(&after);
205 let incrementally_reparsed: Parse<SourceFile> = { 209 let incrementally_reparsed: Parse<SourceFile> = {
diff --git a/crates/ra_text_edit/src/lib.rs b/crates/ra_text_edit/src/lib.rs
index e656260c7..7138bbc65 100644
--- a/crates/ra_text_edit/src/lib.rs
+++ b/crates/ra_text_edit/src/lib.rs
@@ -1,36 +1,144 @@
1//! FIXME: write short doc here 1//! Representation of a `TextEdit`.
2 2//!
3mod text_edit; 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.
4 6
5use text_size::{TextRange, TextSize}; 7use text_size::{TextRange, TextSize};
6 8
7pub use crate::text_edit::{TextEdit, TextEditBuilder}; 9/// `InsertDelete` -- a single "atomic" change to text
8 10///
9/// Must not overlap with other `AtomTextEdit`s 11/// Must not overlap with other `InDel`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 as_indels(&self) -> &[Indel] {
75 &self.indels
76 }
77
78 pub fn apply(&self, text: &mut String) {
79 match self.indels.len() {
80 0 => return,
81 1 => {
82 self.indels[0].apply(text);
83 return;
84 }
85 _ => (),
86 }
87
88 let mut total_len = TextSize::of(&*text);
89 for indel in self.indels.iter() {
90 total_len += TextSize::of(&indel.insert);
91 total_len -= indel.delete.end() - indel.delete.start();
92 }
93 let mut buf = String::with_capacity(total_len.into());
94 let mut prev = 0;
95 for indel in self.indels.iter() {
96 let start: usize = indel.delete.start().into();
97 let end: usize = indel.delete.end().into();
98 if start > prev {
99 buf.push_str(&text[prev..start]);
100 }
101 buf.push_str(&indel.insert);
102 prev = end;
103 }
104 buf.push_str(&text[prev..text.len()]);
105 assert_eq!(TextSize::of(&buf), total_len);
106
107 // FIXME: figure out a way to mutate the text in-place or reuse the
108 // memory in some other way
109 *text = buf
110 }
111
112 pub fn apply_to_offset(&self, offset: TextSize) -> Option<TextSize> {
113 let mut res = offset;
114 for indel in self.indels.iter() {
115 if indel.delete.start() >= offset {
116 break;
117 }
118 if offset < indel.delete.end() {
119 return None;
120 }
121 res += TextSize::of(&indel.insert);
122 res -= indel.delete.len();
123 }
124 Some(res)
125 }
126}
127
128impl TextEditBuilder {
129 pub fn replace(&mut self, range: TextRange, replace_with: String) {
130 self.indels.push(Indel::replace(range, replace_with))
131 }
132 pub fn delete(&mut self, range: TextRange) {
133 self.indels.push(Indel::delete(range))
134 }
135 pub fn insert(&mut self, offset: TextSize, text: String) {
136 self.indels.push(Indel::insert(offset, text))
137 }
138 pub fn finish(self) -> TextEdit {
139 TextEdit::from_indels(self.indels)
140 }
141 pub fn invalidates_offset(&self, offset: TextSize) -> bool {
142 self.indels.iter().any(|indel| indel.delete.contains_inclusive(offset))
35 } 143 }
36} 144}
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}
diff --git a/crates/rust-analyzer/src/conv.rs b/crates/rust-analyzer/src/conv.rs
index 7be5ebcdb..f64c90b5b 100644
--- a/crates/rust-analyzer/src/conv.rs
+++ b/crates/rust-analyzer/src/conv.rs
@@ -15,7 +15,7 @@ use ra_ide::{
15 ReferenceAccess, Severity, SourceChange, SourceFileEdit, 15 ReferenceAccess, Severity, SourceChange, SourceFileEdit,
16}; 16};
17use ra_syntax::{SyntaxKind, TextRange, TextSize}; 17use ra_syntax::{SyntaxKind, TextRange, TextSize};
18use ra_text_edit::{AtomTextEdit, TextEdit}; 18use ra_text_edit::{Indel, TextEdit};
19use ra_vfs::LineEndings; 19use ra_vfs::LineEndings;
20 20
21use crate::{ 21use crate::{
@@ -124,23 +124,22 @@ impl ConvWith<(&LineIndex, LineEndings)> for CompletionItem {
124 let mut text_edit = None; 124 let mut text_edit = None;
125 // LSP does not allow arbitrary edits in completion, so we have to do a 125 // LSP does not allow arbitrary edits in completion, so we have to do a
126 // non-trivial mapping here. 126 // non-trivial mapping here.
127 for atom_edit in self.text_edit().as_atoms() { 127 for indel in self.text_edit().as_indels() {
128 if atom_edit.delete.contains_range(self.source_range()) { 128 if indel.delete.contains_range(self.source_range()) {
129 text_edit = Some(if atom_edit.delete == self.source_range() { 129 text_edit = Some(if indel.delete == self.source_range() {
130 atom_edit.conv_with((ctx.0, ctx.1)) 130 indel.conv_with((ctx.0, ctx.1))
131 } else { 131 } else {
132 assert!(self.source_range().end() == atom_edit.delete.end()); 132 assert!(self.source_range().end() == indel.delete.end());
133 let range1 = 133 let range1 = TextRange::new(indel.delete.start(), self.source_range().start());
134 TextRange::new(atom_edit.delete.start(), self.source_range().start());
135 let range2 = self.source_range(); 134 let range2 = self.source_range();
136 let edit1 = AtomTextEdit::replace(range1, String::new()); 135 let edit1 = Indel::replace(range1, String::new());
137 let edit2 = AtomTextEdit::replace(range2, atom_edit.insert.clone()); 136 let edit2 = Indel::replace(range2, indel.insert.clone());
138 additional_text_edits.push(edit1.conv_with((ctx.0, ctx.1))); 137 additional_text_edits.push(edit1.conv_with((ctx.0, ctx.1)));
139 edit2.conv_with((ctx.0, ctx.1)) 138 edit2.conv_with((ctx.0, ctx.1))
140 }) 139 })
141 } else { 140 } else {
142 assert!(self.source_range().intersect(atom_edit.delete).is_none()); 141 assert!(self.source_range().intersect(indel.delete).is_none());
143 additional_text_edits.push(atom_edit.conv_with((ctx.0, ctx.1))); 142 additional_text_edits.push(indel.conv_with((ctx.0, ctx.1)));
144 } 143 }
145 } 144 }
146 let text_edit = text_edit.unwrap(); 145 let text_edit = text_edit.unwrap();
@@ -257,11 +256,11 @@ impl ConvWith<(&LineIndex, LineEndings)> for TextEdit {
257 type Output = Vec<lsp_types::TextEdit>; 256 type Output = Vec<lsp_types::TextEdit>;
258 257
259 fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> Vec<lsp_types::TextEdit> { 258 fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> Vec<lsp_types::TextEdit> {
260 self.as_atoms().iter().map_conv_with(ctx).collect() 259 self.as_indels().iter().map_conv_with(ctx).collect()
261 } 260 }
262} 261}
263 262
264impl ConvWith<(&LineIndex, LineEndings)> for &AtomTextEdit { 263impl ConvWith<(&LineIndex, LineEndings)> for &Indel {
265 type Output = lsp_types::TextEdit; 264 type Output = lsp_types::TextEdit;
266 265
267 fn conv_with( 266 fn conv_with(
@@ -522,7 +521,7 @@ impl TryConvWith<&WorldSnapshot> for SourceFileEdit {
522 let line_index = world.analysis().file_line_index(self.file_id)?; 521 let line_index = world.analysis().file_line_index(self.file_id)?;
523 let line_endings = world.file_line_endings(self.file_id); 522 let line_endings = world.file_line_endings(self.file_id);
524 let edits = 523 let edits =
525 self.edit.as_atoms().iter().map_conv_with((&line_index, line_endings)).collect(); 524 self.edit.as_indels().iter().map_conv_with((&line_index, line_endings)).collect();
526 Ok(TextDocumentEdit { text_document, edits }) 525 Ok(TextDocumentEdit { text_document, edits })
527 } 526 }
528} 527}
diff --git a/xtask/tests/tidy-tests/main.rs b/xtask/tests/tidy-tests/main.rs
index ead642acc..3213c4dfa 100644
--- a/xtask/tests/tidy-tests/main.rs
+++ b/xtask/tests/tidy-tests/main.rs
@@ -115,7 +115,6 @@ impl TidyDocs {
115 "ra_prof", 115 "ra_prof",
116 "ra_project_model", 116 "ra_project_model",
117 "ra_syntax", 117 "ra_syntax",
118 "ra_text_edit",
119 "ra_tt", 118 "ra_tt",
120 "ra_hir_ty", 119 "ra_hir_ty",
121 ]; 120 ];