aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/typing.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/typing.rs')
-rw-r--r--crates/ra_ide/src/typing.rs67
1 files changed, 28 insertions, 39 deletions
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs
index 2a8b4327f..67e2c33a0 100644
--- a/crates/ra_ide/src/typing.rs
+++ b/crates/ra_ide/src/typing.rs
@@ -23,14 +23,22 @@ use ra_syntax::{
23 ast::{self, AstToken}, 23 ast::{self, AstToken},
24 AstNode, SourceFile, TextRange, TextSize, 24 AstNode, SourceFile, TextRange, TextSize,
25}; 25};
26
26use ra_text_edit::TextEdit; 27use ra_text_edit::TextEdit;
27 28
28use crate::{source_change::SingleFileChange, SourceChange}; 29use crate::SourceChange;
29 30
30pub(crate) use on_enter::on_enter; 31pub(crate) use on_enter::on_enter;
31 32
32pub(crate) const TRIGGER_CHARS: &str = ".=>"; 33pub(crate) const TRIGGER_CHARS: &str = ".=>";
33 34
35// Feature: On Typing Assists
36//
37// Some features trigger on typing certain characters:
38//
39// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
40// - Enter inside comments automatically inserts `///`
41// - typing `.` in a chain method call auto-indents
34pub(crate) fn on_char_typed( 42pub(crate) fn on_char_typed(
35 db: &RootDatabase, 43 db: &RootDatabase,
36 position: FilePosition, 44 position: FilePosition,
@@ -39,15 +47,11 @@ pub(crate) fn on_char_typed(
39 assert!(TRIGGER_CHARS.contains(char_typed)); 47 assert!(TRIGGER_CHARS.contains(char_typed));
40 let file = &db.parse(position.file_id).tree(); 48 let file = &db.parse(position.file_id).tree();
41 assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); 49 assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed));
42 let single_file_change = on_char_typed_inner(file, position.offset, char_typed)?; 50 let text_edit = on_char_typed_inner(file, position.offset, char_typed)?;
43 Some(single_file_change.into_source_change(position.file_id)) 51 Some(SourceChange::source_file_edit_from(position.file_id, text_edit))
44} 52}
45 53
46fn on_char_typed_inner( 54fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> {
47 file: &SourceFile,
48 offset: TextSize,
49 char_typed: char,
50) -> Option<SingleFileChange> {
51 assert!(TRIGGER_CHARS.contains(char_typed)); 55 assert!(TRIGGER_CHARS.contains(char_typed));
52 match char_typed { 56 match char_typed {
53 '.' => on_dot_typed(file, offset), 57 '.' => on_dot_typed(file, offset),
@@ -60,7 +64,7 @@ fn on_char_typed_inner(
60/// Returns an edit which should be applied after `=` was typed. Primarily, 64/// Returns an edit which should be applied after `=` was typed. Primarily,
61/// this works when adding `let =`. 65/// this works when adding `let =`.
62// FIXME: use a snippet completion instead of this hack here. 66// FIXME: use a snippet completion instead of this hack here.
63fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { 67fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
64 assert_eq!(file.syntax().text().char_at(offset), Some('=')); 68 assert_eq!(file.syntax().text().char_at(offset), Some('='));
65 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; 69 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
66 if let_stmt.semicolon_token().is_some() { 70 if let_stmt.semicolon_token().is_some() {
@@ -78,15 +82,11 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange>
78 return None; 82 return None;
79 } 83 }
80 let offset = let_stmt.syntax().text_range().end(); 84 let offset = let_stmt.syntax().text_range().end();
81 Some(SingleFileChange { 85 Some(TextEdit::insert(offset, ";".to_string()))
82 label: "add semicolon".to_string(),
83 edit: TextEdit::insert(offset, ";".to_string()),
84 cursor_position: None,
85 })
86} 86}
87 87
88/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. 88/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
89fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { 89fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
90 assert_eq!(file.syntax().text().char_at(offset), Some('.')); 90 assert_eq!(file.syntax().text().char_at(offset), Some('.'));
91 let whitespace = 91 let whitespace =
92 file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; 92 file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?;
@@ -107,15 +107,11 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange>
107 return None; 107 return None;
108 } 108 }
109 109
110 Some(SingleFileChange { 110 Some(TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent))
111 label: "reindent dot".to_string(),
112 edit: TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent),
113 cursor_position: Some(offset + target_indent_len - current_indent_len + TextSize::of('.')),
114 })
115} 111}
116 112
117/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` 113/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
118fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { 114fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
119 let file_text = file.syntax().text(); 115 let file_text = file.syntax().text();
120 assert_eq!(file_text.char_at(offset), Some('>')); 116 assert_eq!(file_text.char_at(offset), Some('>'));
121 let after_arrow = offset + TextSize::of('>'); 117 let after_arrow = offset + TextSize::of('>');
@@ -126,11 +122,7 @@ fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChang
126 return None; 122 return None;
127 } 123 }
128 124
129 Some(SingleFileChange { 125 Some(TextEdit::insert(after_arrow, " ".to_string()))
130 label: "add space after return type".to_string(),
131 edit: TextEdit::insert(after_arrow, " ".to_string()),
132 cursor_position: Some(after_arrow),
133 })
134} 126}
135 127
136#[cfg(test)] 128#[cfg(test)]
@@ -139,26 +131,23 @@ mod tests {
139 131
140 use super::*; 132 use super::*;
141 133
142 fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> { 134 fn do_type_char(char_typed: char, before: &str) -> Option<String> {
143 let (offset, before) = extract_offset(before); 135 let (offset, before) = extract_offset(before);
144 let edit = TextEdit::insert(offset, char_typed.to_string()); 136 let edit = TextEdit::insert(offset, char_typed.to_string());
145 let before = edit.apply(&before); 137 let mut before = before.to_string();
138 edit.apply(&mut before);
146 let parse = SourceFile::parse(&before); 139 let parse = SourceFile::parse(&before);
147 on_char_typed_inner(&parse.tree(), offset, char_typed) 140 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {
148 .map(|it| (it.edit.apply(&before), it)) 141 it.apply(&mut before);
142 before.to_string()
143 })
149 } 144 }
150 145
151 fn type_char(char_typed: char, before: &str, after: &str) { 146 fn type_char(char_typed: char, before: &str, after: &str) {
152 let (actual, file_change) = do_type_char(char_typed, before) 147 let actual = do_type_char(char_typed, before)
153 .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed)); 148 .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed));
154 149
155 if after.contains("<|>") { 150 assert_eq_text!(after, &actual);
156 let (offset, after) = extract_offset(after);
157 assert_eq_text!(&after, &actual);
158 assert_eq!(file_change.cursor_position, Some(offset))
159 } else {
160 assert_eq_text!(after, &actual);
161 }
162 } 151 }
163 152
164 fn type_char_noop(char_typed: char, before: &str) { 153 fn type_char_noop(char_typed: char, before: &str) {
@@ -346,6 +335,6 @@ fn foo() {
346 335
347 #[test] 336 #[test]
348 fn adds_space_after_return_type() { 337 fn adds_space_after_return_type() {
349 type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -><|> { 92 }") 338 type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -> { 92 }")
350 } 339 }
351} 340}