diff options
author | Aleksey Kladov <[email protected]> | 2019-10-25 09:49:38 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-10-25 09:49:38 +0100 |
commit | 6f00bb1cb0e5fb72fac092d63c07f8652091d4d9 (patch) | |
tree | e001a5ca6beb12dd41cc046fbec88f872f3ce036 | |
parent | b112430ca73f646b6cb779ab09a3f691aad22442 (diff) |
introduce SingleFileChange
-rw-r--r-- | crates/ra_ide_api/src/source_change.rs | 19 | ||||
-rw-r--r-- | crates/ra_ide_api/src/typing.rs | 62 | ||||
-rw-r--r-- | crates/ra_text_edit/src/text_edit.rs | 18 |
3 files changed, 64 insertions, 35 deletions
diff --git a/crates/ra_ide_api/src/source_change.rs b/crates/ra_ide_api/src/source_change.rs index 80e8821b0..4e63bbf6f 100644 --- a/crates/ra_ide_api/src/source_change.rs +++ b/crates/ra_ide_api/src/source_change.rs | |||
@@ -6,7 +6,7 @@ | |||
6 | use ra_text_edit::TextEdit; | 6 | use ra_text_edit::TextEdit; |
7 | use relative_path::RelativePathBuf; | 7 | use relative_path::RelativePathBuf; |
8 | 8 | ||
9 | use crate::{FileId, FilePosition, SourceRootId}; | 9 | use crate::{FileId, FilePosition, SourceRootId, TextUnit}; |
10 | 10 | ||
11 | #[derive(Debug)] | 11 | #[derive(Debug)] |
12 | pub struct SourceChange { | 12 | pub struct SourceChange { |
@@ -100,3 +100,20 @@ pub enum FileSystemEdit { | |||
100 | CreateFile { source_root: SourceRootId, path: RelativePathBuf }, | 100 | CreateFile { source_root: SourceRootId, path: RelativePathBuf }, |
101 | MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf }, | 101 | MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf }, |
102 | } | 102 | } |
103 | |||
104 | pub(crate) struct SingleFileChange { | ||
105 | pub label: String, | ||
106 | pub edit: TextEdit, | ||
107 | pub cursor_position: Option<TextUnit>, | ||
108 | } | ||
109 | |||
110 | impl SingleFileChange { | ||
111 | pub(crate) fn into_source_change(self, file_id: FileId) -> SourceChange { | ||
112 | SourceChange { | ||
113 | label: self.label, | ||
114 | source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }], | ||
115 | file_system_edits: Vec::new(), | ||
116 | cursor_position: self.cursor_position.map(|offset| FilePosition { file_id, offset }), | ||
117 | } | ||
118 | } | ||
119 | } | ||
diff --git a/crates/ra_ide_api/src/typing.rs b/crates/ra_ide_api/src/typing.rs index 44cc46147..c5ec6c1c1 100644 --- a/crates/ra_ide_api/src/typing.rs +++ b/crates/ra_ide_api/src/typing.rs | |||
@@ -24,7 +24,7 @@ use ra_syntax::{ | |||
24 | }; | 24 | }; |
25 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 25 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
26 | 26 | ||
27 | use crate::{db::RootDatabase, SourceChange, SourceFileEdit}; | 27 | use crate::{db::RootDatabase, source_change::SingleFileChange, SourceChange, SourceFileEdit}; |
28 | 28 | ||
29 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<SourceChange> { | 29 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<SourceChange> { |
30 | let parse = db.parse(position.file_id); | 30 | let parse = db.parse(position.file_id); |
@@ -88,32 +88,19 @@ pub(crate) fn on_char_typed( | |||
88 | ) -> Option<SourceChange> { | 88 | ) -> Option<SourceChange> { |
89 | let file = &db.parse(position.file_id).tree(); | 89 | let file = &db.parse(position.file_id).tree(); |
90 | assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); | 90 | assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); |
91 | match char_typed { | 91 | let single_file_change = match char_typed { |
92 | '=' => { | 92 | '=' => on_eq_typed(file, position.offset)?, |
93 | let edit = on_eq_typed(file, position.offset)?; | 93 | '.' => on_dot_typed(file, position.offset)?, |
94 | Some(SourceChange::source_file_edit( | 94 | _ => return None, |
95 | "add semicolon", | 95 | }; |
96 | SourceFileEdit { edit, file_id: position.file_id }, | 96 | |
97 | )) | 97 | Some(single_file_change.into_source_change(position.file_id)) |
98 | } | ||
99 | '.' => { | ||
100 | let (edit, cursor_offset) = on_dot_typed(file, position.offset)?; | ||
101 | Some( | ||
102 | SourceChange::source_file_edit( | ||
103 | "reindent dot", | ||
104 | SourceFileEdit { edit, file_id: position.file_id }, | ||
105 | ) | ||
106 | .with_cursor(FilePosition { file_id: position.file_id, offset: cursor_offset }), | ||
107 | ) | ||
108 | } | ||
109 | _ => None, | ||
110 | } | ||
111 | } | 98 | } |
112 | 99 | ||
113 | /// Returns an edit which should be applied after `=` was typed. Primarily, | 100 | /// Returns an edit which should be applied after `=` was typed. Primarily, |
114 | /// this works when adding `let =`. | 101 | /// this works when adding `let =`. |
115 | // FIXME: use a snippet completion instead of this hack here. | 102 | // FIXME: use a snippet completion instead of this hack here. |
116 | fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option<TextEdit> { | 103 | fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option<SingleFileChange> { |
117 | assert_eq!(file.syntax().text().char_at(offset), Some('=')); | 104 | assert_eq!(file.syntax().text().char_at(offset), Some('=')); |
118 | let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; | 105 | let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; |
119 | if let_stmt.has_semi() { | 106 | if let_stmt.has_semi() { |
@@ -131,13 +118,15 @@ fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option<TextEdit> { | |||
131 | return None; | 118 | return None; |
132 | } | 119 | } |
133 | let offset = let_stmt.syntax().text_range().end(); | 120 | let offset = let_stmt.syntax().text_range().end(); |
134 | let mut edit = TextEditBuilder::default(); | 121 | Some(SingleFileChange { |
135 | edit.insert(offset, ";".to_string()); | 122 | label: "add semicolon".to_string(), |
136 | Some(edit.finish()) | 123 | edit: TextEdit::insert(offset, ";".to_string()), |
124 | cursor_position: None, | ||
125 | }) | ||
137 | } | 126 | } |
138 | 127 | ||
139 | /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. | 128 | /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. |
140 | fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option<(TextEdit, TextUnit)> { | 129 | fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option<SingleFileChange> { |
141 | assert_eq!(file.syntax().text().char_at(offset), Some('.')); | 130 | assert_eq!(file.syntax().text().char_at(offset), Some('.')); |
142 | let whitespace = | 131 | let whitespace = |
143 | file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; | 132 | file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; |
@@ -157,12 +146,17 @@ fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option<(TextEdit, TextUn | |||
157 | if current_indent_len == target_indent_len { | 146 | if current_indent_len == target_indent_len { |
158 | return None; | 147 | return None; |
159 | } | 148 | } |
160 | let mut edit = TextEditBuilder::default(); | ||
161 | edit.replace(TextRange::from_to(offset - current_indent_len, offset), target_indent); | ||
162 | |||
163 | let cursor_offset = offset + target_indent_len - current_indent_len + TextUnit::of_char('.'); | ||
164 | 149 | ||
165 | Some((edit.finish(), cursor_offset)) | 150 | Some(SingleFileChange { |
151 | label: "reindent dot".to_string(), | ||
152 | edit: TextEdit::replace( | ||
153 | TextRange::from_to(offset - current_indent_len, offset), | ||
154 | target_indent, | ||
155 | ), | ||
156 | cursor_position: Some( | ||
157 | offset + target_indent_len - current_indent_len + TextUnit::of_char('.'), | ||
158 | ), | ||
159 | }) | ||
166 | } | 160 | } |
167 | 161 | ||
168 | #[cfg(test)] | 162 | #[cfg(test)] |
@@ -182,7 +176,7 @@ mod tests { | |||
182 | let before = edit.finish().apply(&before); | 176 | let before = edit.finish().apply(&before); |
183 | let parse = SourceFile::parse(&before); | 177 | let parse = SourceFile::parse(&before); |
184 | if let Some(result) = on_eq_typed(&parse.tree(), offset) { | 178 | if let Some(result) = on_eq_typed(&parse.tree(), offset) { |
185 | let actual = result.apply(&before); | 179 | let actual = result.edit.apply(&before); |
186 | assert_eq_text!(after, &actual); | 180 | assert_eq_text!(after, &actual); |
187 | } else { | 181 | } else { |
188 | assert_eq_text!(&before, after) | 182 | assert_eq_text!(&before, after) |
@@ -230,8 +224,8 @@ fn foo() { | |||
230 | let before = edit.finish().apply(&before); | 224 | let before = edit.finish().apply(&before); |
231 | let (analysis, file_id) = single_file(&before); | 225 | let (analysis, file_id) = single_file(&before); |
232 | let file = analysis.parse(file_id).unwrap(); | 226 | let file = analysis.parse(file_id).unwrap(); |
233 | if let Some((edit, _cursor_offset)) = on_dot_typed(&file, offset) { | 227 | if let Some(result) = on_dot_typed(&file, offset) { |
234 | let actual = edit.apply(&before); | 228 | let actual = result.edit.apply(&before); |
235 | assert_eq_text!(after, &actual); | 229 | assert_eq_text!(after, &actual); |
236 | } else { | 230 | } else { |
237 | assert_eq_text!(&before, after) | 231 | assert_eq_text!(&before, after) |
diff --git a/crates/ra_text_edit/src/text_edit.rs b/crates/ra_text_edit/src/text_edit.rs index 0381ea000..413c7d782 100644 --- a/crates/ra_text_edit/src/text_edit.rs +++ b/crates/ra_text_edit/src/text_edit.rs | |||
@@ -32,6 +32,24 @@ impl TextEditBuilder { | |||
32 | } | 32 | } |
33 | 33 | ||
34 | impl TextEdit { | 34 | impl TextEdit { |
35 | pub fn insert(offset: TextUnit, text: String) -> TextEdit { | ||
36 | let mut builder = TextEditBuilder::default(); | ||
37 | builder.insert(offset, text); | ||
38 | builder.finish() | ||
39 | } | ||
40 | |||
41 | pub fn delete(range: TextRange) -> TextEdit { | ||
42 | let mut builder = TextEditBuilder::default(); | ||
43 | builder.delete(range); | ||
44 | builder.finish() | ||
45 | } | ||
46 | |||
47 | pub fn replace(range: TextRange, replace_with: String) -> TextEdit { | ||
48 | let mut builder = TextEditBuilder::default(); | ||
49 | builder.replace(range, replace_with); | ||
50 | builder.finish() | ||
51 | } | ||
52 | |||
35 | pub(crate) fn from_atoms(mut atoms: Vec<AtomTextEdit>) -> TextEdit { | 53 | pub(crate) fn from_atoms(mut atoms: Vec<AtomTextEdit>) -> TextEdit { |
36 | atoms.sort_by_key(|a| (a.delete.start(), a.delete.end())); | 54 | atoms.sort_by_key(|a| (a.delete.start(), a.delete.end())); |
37 | for (a1, a2) in atoms.iter().zip(atoms.iter().skip(1)) { | 55 | for (a1, a2) in atoms.iter().zip(atoms.iter().skip(1)) { |