aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2019-10-25 09:49:38 +0100
committerAleksey Kladov <[email protected]>2019-10-25 09:49:38 +0100
commit6f00bb1cb0e5fb72fac092d63c07f8652091d4d9 (patch)
treee001a5ca6beb12dd41cc046fbec88f872f3ce036 /crates
parentb112430ca73f646b6cb779ab09a3f691aad22442 (diff)
introduce SingleFileChange
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_ide_api/src/source_change.rs19
-rw-r--r--crates/ra_ide_api/src/typing.rs62
-rw-r--r--crates/ra_text_edit/src/text_edit.rs18
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 @@
6use ra_text_edit::TextEdit; 6use ra_text_edit::TextEdit;
7use relative_path::RelativePathBuf; 7use relative_path::RelativePathBuf;
8 8
9use crate::{FileId, FilePosition, SourceRootId}; 9use crate::{FileId, FilePosition, SourceRootId, TextUnit};
10 10
11#[derive(Debug)] 11#[derive(Debug)]
12pub struct SourceChange { 12pub 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
104pub(crate) struct SingleFileChange {
105 pub label: String,
106 pub edit: TextEdit,
107 pub cursor_position: Option<TextUnit>,
108}
109
110impl 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};
25use ra_text_edit::{TextEdit, TextEditBuilder}; 25use ra_text_edit::{TextEdit, TextEditBuilder};
26 26
27use crate::{db::RootDatabase, SourceChange, SourceFileEdit}; 27use crate::{db::RootDatabase, source_change::SingleFileChange, SourceChange, SourceFileEdit};
28 28
29pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<SourceChange> { 29pub(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.
116fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option<TextEdit> { 103fn 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.
140fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option<(TextEdit, TextUnit)> { 129fn 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
34impl TextEdit { 34impl 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)) {