diff options
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 5 | ||||
-rw-r--r-- | crates/ra_assists/src/doc_tests.rs | 6 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 34 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/completion_context.rs | 4 | ||||
-rw-r--r-- | crates/ra_ide/src/completion/completion_item.rs | 4 | ||||
-rw-r--r-- | crates/ra_ide/src/diagnostics.rs | 24 | ||||
-rw-r--r-- | crates/ra_ide/src/join_lines.rs | 6 | ||||
-rw-r--r-- | crates/ra_ide/src/references/rename.rs | 32 | ||||
-rw-r--r-- | crates/ra_ide/src/ssr.rs | 32 | ||||
-rw-r--r-- | crates/ra_ide/src/test_utils.rs | 6 | ||||
-rw-r--r-- | crates/ra_ide/src/typing.rs | 9 | ||||
-rw-r--r-- | crates/ra_ide/src/typing/on_enter.rs | 3 | ||||
-rw-r--r-- | crates/ra_ide_db/src/line_index_utils.rs | 6 | ||||
-rw-r--r-- | crates/ra_syntax/src/fuzz.rs | 6 | ||||
-rw-r--r-- | crates/ra_syntax/src/lib.rs | 15 | ||||
-rw-r--r-- | crates/ra_syntax/src/parsing/reparsing.rs | 28 | ||||
-rw-r--r-- | crates/ra_text_edit/src/lib.rs | 146 | ||||
-rw-r--r-- | crates/ra_text_edit/src/text_edit.rs | 102 | ||||
-rw-r--r-- | crates/rust-analyzer/src/conv.rs | 29 | ||||
-rw-r--r-- | xtask/tests/tidy-tests/main.rs | 1 |
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; | |||
4 | use ra_fmt::{leading_indent, reindent}; | 4 | use ra_fmt::{leading_indent, reindent}; |
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use 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 | }; |
11 | use ra_text_edit::TextEditBuilder; | 11 | use ra_text_edit::TextEditBuilder; |
12 | 12 | ||
13 | use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist}; | 13 | use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist}; |
14 | use algo::SyntaxRewriter; | ||
15 | 14 | ||
16 | #[derive(Clone, Debug)] | 15 | #[derive(Clone, Debug)] |
17 | pub(crate) struct Assist(pub(crate) Vec<AssistInfo>); | 16 | pub(crate) struct Assist(pub(crate) Vec<AssistInfo>); |
@@ -42,8 +41,6 @@ impl AssistInfo { | |||
42 | } | 41 | } |
43 | } | 42 | } |
44 | 43 | ||
45 | pub(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; | |||
23 | use ra_syntax::{TextRange, TextSize}; | 23 | use ra_syntax::{TextRange, TextSize}; |
24 | use ra_text_edit::TextEdit; | 24 | use ra_text_edit::TextEdit; |
25 | 25 | ||
26 | pub(crate) use crate::assist_ctx::{Assist, AssistCtx, AssistHandler}; | 26 | pub(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 | ||
111 | mod handlers { | 111 | mod 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 { | |||
195 | mod helpers { | 199 | mod 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 | }; |
12 | use ra_text_edit::AtomTextEdit; | 12 | use ra_text_edit::Indel; |
13 | 13 | ||
14 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; | 14 | use 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 @@ | |||
10 | use std::convert::TryInto; | 10 | use std::convert::TryInto; |
11 | 11 | ||
12 | use ra_syntax::{TextRange, TextSize}; | 12 | use ra_syntax::{TextRange, TextSize}; |
13 | use ra_text_edit::{AtomTextEdit, TextEdit}; | 13 | use ra_text_edit::{Indel, TextEdit}; |
14 | 14 | ||
15 | use crate::line_index::{LineCol, LineIndex, Utf16Char}; | 15 | use crate::line_index::{LineCol, LineIndex, Utf16Char}; |
16 | 16 | ||
@@ -182,14 +182,14 @@ struct TranslatedEdit<'a> { | |||
182 | } | 182 | } |
183 | 183 | ||
184 | struct Edits<'a> { | 184 | struct 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 | ||
190 | impl<'a> Edits<'a> { | 190 | impl<'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 | ||
8 | use ra_text_edit::AtomTextEdit; | 8 | use ra_text_edit::Indel; |
9 | 9 | ||
10 | use crate::{validation, AstNode, SourceFile, TextRange}; | 10 | use 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)] |
23 | pub struct CheckReparse { | 23 | pub 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 | ||
40 | use std::{marker::PhantomData, sync::Arc}; | 40 | use std::{marker::PhantomData, sync::Arc}; |
41 | 41 | ||
42 | use ra_text_edit::AtomTextEdit; | 42 | use ra_text_edit::Indel; |
43 | use stdx::format_to; | 43 | use stdx::format_to; |
44 | 44 | ||
45 | use crate::syntax_node::GreenNode; | 45 | use 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 | ||
9 | use ra_parser::Reparser; | 9 | use ra_parser::Reparser; |
10 | use ra_text_edit::AtomTextEdit; | 10 | use ra_text_edit::Indel; |
11 | 11 | ||
12 | use crate::{ | 12 | use crate::{ |
13 | algo, | 13 | algo, |
@@ -24,7 +24,7 @@ use crate::{ | |||
24 | 24 | ||
25 | pub(crate) fn incremental_reparse( | 25 | pub(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 | ||
40 | fn reparse_token<'node>( | 40 | fn 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 | ||
89 | fn reparse_block<'node>( | 89 | fn 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 | ||
111 | fn get_text_after_edit(element: SyntaxElement, edit: &AtomTextEdit) -> String { | 111 | fn 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 | ||
122 | fn is_contextual_kw(text: &str) -> bool { | 122 | fn 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 | //! | |
3 | mod 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 | ||
5 | use text_size::{TextRange, TextSize}; | 7 | use text_size::{TextRange, TextSize}; |
6 | 8 | ||
7 | pub 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)] |
11 | pub struct AtomTextEdit { | 13 | pub 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 | ||
17 | impl AtomTextEdit { | 19 | #[derive(Debug, Clone)] |
18 | pub fn replace(range: TextRange, replace_with: String) -> AtomTextEdit { | 20 | pub 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()) | 25 | pub struct TextEditBuilder { |
24 | } | 26 | indels: Vec<Indel>, |
27 | } | ||
25 | 28 | ||
26 | pub fn insert(offset: TextSize, text: String) -> AtomTextEdit { | 29 | impl 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 | |||
47 | impl 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 | |||
128 | impl 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 | |||
3 | use crate::AtomTextEdit; | ||
4 | |||
5 | use text_size::{TextRange, TextSize}; | ||
6 | |||
7 | #[derive(Debug, Clone)] | ||
8 | pub struct TextEdit { | ||
9 | atoms: Vec<AtomTextEdit>, | ||
10 | } | ||
11 | |||
12 | #[derive(Debug, Default)] | ||
13 | pub struct TextEditBuilder { | ||
14 | atoms: Vec<AtomTextEdit>, | ||
15 | } | ||
16 | |||
17 | impl 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 | |||
35 | impl 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 | }; |
17 | use ra_syntax::{SyntaxKind, TextRange, TextSize}; | 17 | use ra_syntax::{SyntaxKind, TextRange, TextSize}; |
18 | use ra_text_edit::{AtomTextEdit, TextEdit}; | 18 | use ra_text_edit::{Indel, TextEdit}; |
19 | use ra_vfs::LineEndings; | 19 | use ra_vfs::LineEndings; |
20 | 20 | ||
21 | use crate::{ | 21 | use 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 | ||
264 | impl ConvWith<(&LineIndex, LineEndings)> for &AtomTextEdit { | 263 | impl 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 | ]; |