diff options
author | Aleksey Kladov <[email protected]> | 2019-01-11 11:57:19 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-01-11 11:57:19 +0000 |
commit | 4aa632761f1db5f18338c9bd568156c0259b9252 (patch) | |
tree | d977aab44ae138ba2427b0588efafd8fa7e7fd71 /crates/ra_ide_api_light/src/typing.rs | |
parent | dd122145b57513cf57076ae3235d70215b226039 (diff) |
make on dot typed actually work
Diffstat (limited to 'crates/ra_ide_api_light/src/typing.rs')
-rw-r--r-- | crates/ra_ide_api_light/src/typing.rs | 225 |
1 files changed, 109 insertions, 116 deletions
diff --git a/crates/ra_ide_api_light/src/typing.rs b/crates/ra_ide_api_light/src/typing.rs index 5726209cc..05845a0cc 100644 --- a/crates/ra_ide_api_light/src/typing.rs +++ b/crates/ra_ide_api_light/src/typing.rs | |||
@@ -1,11 +1,11 @@ | |||
1 | use ra_syntax::{ | 1 | use ra_syntax::{ |
2 | AstNode, SourceFile, SyntaxKind::*, | ||
3 | SyntaxNode, TextUnit, TextRange, | ||
2 | algo::{find_node_at_offset, find_leaf_at_offset, LeafAtOffset}, | 4 | algo::{find_node_at_offset, find_leaf_at_offset, LeafAtOffset}, |
3 | ast, | 5 | ast::{self, AstToken}, |
4 | AstNode, Direction, SourceFile, SyntaxKind::*, | ||
5 | SyntaxNode, TextUnit, | ||
6 | }; | 6 | }; |
7 | 7 | ||
8 | use crate::{LocalEdit, TextEditBuilder}; | 8 | use crate::{LocalEdit, TextEditBuilder, formatting::leading_indent}; |
9 | 9 | ||
10 | pub fn on_enter(file: &SourceFile, offset: TextUnit) -> Option<LocalEdit> { | 10 | pub fn on_enter(file: &SourceFile, offset: TextUnit) -> Option<LocalEdit> { |
11 | let comment = find_leaf_at_offset(file.syntax(), offset) | 11 | let comment = find_leaf_at_offset(file.syntax(), offset) |
@@ -53,20 +53,21 @@ fn node_indent<'a>(file: &'a SourceFile, node: &SyntaxNode) -> Option<&'a str> { | |||
53 | Some(&text[pos..]) | 53 | Some(&text[pos..]) |
54 | } | 54 | } |
55 | 55 | ||
56 | pub fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option<LocalEdit> { | 56 | pub fn on_eq_typed(file: &SourceFile, eq_offset: TextUnit) -> Option<LocalEdit> { |
57 | let let_stmt: &ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; | 57 | assert_eq!(file.syntax().text().char_at(eq_offset), Some('=')); |
58 | let let_stmt: &ast::LetStmt = find_node_at_offset(file.syntax(), eq_offset)?; | ||
58 | if let_stmt.has_semi() { | 59 | if let_stmt.has_semi() { |
59 | return None; | 60 | return None; |
60 | } | 61 | } |
61 | if let Some(expr) = let_stmt.initializer() { | 62 | if let Some(expr) = let_stmt.initializer() { |
62 | let expr_range = expr.syntax().range(); | 63 | let expr_range = expr.syntax().range(); |
63 | if expr_range.contains(offset) && offset != expr_range.start() { | 64 | if expr_range.contains(eq_offset) && eq_offset != expr_range.start() { |
64 | return None; | 65 | return None; |
65 | } | 66 | } |
66 | if file | 67 | if file |
67 | .syntax() | 68 | .syntax() |
68 | .text() | 69 | .text() |
69 | .slice(offset..expr_range.start()) | 70 | .slice(eq_offset..expr_range.start()) |
70 | .contains('\n') | 71 | .contains('\n') |
71 | { | 72 | { |
72 | return None; | 73 | return None; |
@@ -84,54 +85,44 @@ pub fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option<LocalEdit> { | |||
84 | }) | 85 | }) |
85 | } | 86 | } |
86 | 87 | ||
87 | pub fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option<LocalEdit> { | 88 | pub fn on_dot_typed(file: &SourceFile, dot_offset: TextUnit) -> Option<LocalEdit> { |
88 | let before_dot_offset = offset - TextUnit::of_char('.'); | 89 | assert_eq!(file.syntax().text().char_at(dot_offset), Some('.')); |
89 | 90 | ||
90 | let whitespace = find_leaf_at_offset(file.syntax(), before_dot_offset).left_biased()?; | 91 | let whitespace = find_leaf_at_offset(file.syntax(), dot_offset) |
91 | 92 | .left_biased() | |
92 | // find whitespace just left of the dot | 93 | .and_then(ast::Whitespace::cast)?; |
93 | ast::Whitespace::cast(whitespace)?; | ||
94 | |||
95 | // make sure there is a method call | ||
96 | let method_call = whitespace | ||
97 | .siblings(Direction::Prev) | ||
98 | // first is whitespace | ||
99 | .skip(1) | ||
100 | .next()?; | ||
101 | |||
102 | ast::MethodCallExpr::cast(method_call)?; | ||
103 | |||
104 | // find how much the _method call is indented | ||
105 | let method_chain_indent = method_call | ||
106 | .parent()? | ||
107 | .siblings(Direction::Prev) | ||
108 | .skip(1) | ||
109 | .next()? | ||
110 | .leaf_text() | ||
111 | .map(|x| last_line_indent_in_whitespace(x))?; | ||
112 | |||
113 | let current_indent = TextUnit::of_str(last_line_indent_in_whitespace(whitespace.leaf_text()?)); | ||
114 | // TODO: indent is always 4 spaces now. A better heuristic could look on the previous line(s) | ||
115 | |||
116 | let target_indent = TextUnit::of_str(method_chain_indent) + TextUnit::from_usize(4); | ||
117 | |||
118 | let diff = target_indent - current_indent; | ||
119 | |||
120 | let indent = "".repeat(diff.to_usize()); | ||
121 | 94 | ||
122 | let cursor_position = offset + diff; | 95 | let current_indent = { |
96 | let text = whitespace.text(); | ||
97 | let newline = text.rfind('\n')?; | ||
98 | &text[newline + 1..] | ||
99 | }; | ||
100 | let current_indent_len = TextUnit::of_str(current_indent); | ||
101 | |||
102 | // Make sure dot is a part of call chain | ||
103 | let field_expr = whitespace | ||
104 | .syntax() | ||
105 | .parent() | ||
106 | .and_then(ast::FieldExpr::cast)?; | ||
107 | let prev_indent = leading_indent(field_expr.syntax())?; | ||
108 | let target_indent = format!(" {}", prev_indent); | ||
109 | let target_indent_len = TextUnit::of_str(&target_indent); | ||
110 | if current_indent_len == target_indent_len { | ||
111 | return None; | ||
112 | } | ||
123 | let mut edit = TextEditBuilder::default(); | 113 | let mut edit = TextEditBuilder::default(); |
124 | edit.insert(before_dot_offset, indent); | 114 | edit.replace( |
125 | Some(LocalEdit { | 115 | TextRange::from_to(dot_offset - current_indent_len, dot_offset), |
126 | label: "indent dot".to_string(), | 116 | target_indent.into(), |
117 | ); | ||
118 | let res = LocalEdit { | ||
119 | label: "reindent dot".to_string(), | ||
127 | edit: edit.finish(), | 120 | edit: edit.finish(), |
128 | cursor_position: Some(cursor_position), | 121 | cursor_position: Some( |
129 | }) | 122 | dot_offset + target_indent_len - current_indent_len + TextUnit::of_char('.'), |
130 | } | 123 | ), |
131 | 124 | }; | |
132 | /// Finds the last line in the whitespace | 125 | Some(res) |
133 | fn last_line_indent_in_whitespace(ws: &str) -> &str { | ||
134 | ws.split('\n').last().unwrap_or("") | ||
135 | } | 126 | } |
136 | 127 | ||
137 | #[cfg(test)] | 128 | #[cfg(test)] |
@@ -162,7 +153,7 @@ mod tests { | |||
162 | do_check( | 153 | do_check( |
163 | r" | 154 | r" |
164 | fn foo() { | 155 | fn foo() { |
165 | let foo =<|> 1 + 1 | 156 | let foo <|>= 1 + 1 |
166 | } | 157 | } |
167 | ", | 158 | ", |
168 | r" | 159 | r" |
@@ -189,107 +180,109 @@ fn foo() { | |||
189 | fn do_check(before: &str, after: &str) { | 180 | fn do_check(before: &str, after: &str) { |
190 | let (offset, before) = extract_offset(before); | 181 | let (offset, before) = extract_offset(before); |
191 | let file = SourceFile::parse(&before); | 182 | let file = SourceFile::parse(&before); |
192 | if let Some(result) = on_eq_typed(&file, offset) { | 183 | if let Some(result) = on_dot_typed(&file, offset) { |
193 | let actual = result.edit.apply(&before); | 184 | let actual = result.edit.apply(&before); |
194 | assert_eq_text!(after, &actual); | 185 | assert_eq_text!(after, &actual); |
186 | } else { | ||
187 | assert_eq_text!(&before, after) | ||
195 | }; | 188 | }; |
196 | } | 189 | } |
197 | // indent if continuing chain call | 190 | // indent if continuing chain call |
198 | do_check( | 191 | do_check( |
199 | r" | 192 | r" |
200 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 193 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
201 | self.child_impl(db, name) | 194 | self.child_impl(db, name) |
202 | .<|> | 195 | <|>. |
203 | } | 196 | } |
204 | ", | 197 | ", |
205 | r" | 198 | r" |
206 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 199 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
207 | self.child_impl(db, name) | 200 | self.child_impl(db, name) |
208 | . | 201 | . |
209 | } | 202 | } |
210 | ", | 203 | ", |
211 | ); | 204 | ); |
212 | 205 | ||
213 | // do not indent if already indented | 206 | // do not indent if already indented |
214 | do_check( | 207 | do_check( |
215 | r" | 208 | r" |
216 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 209 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
217 | self.child_impl(db, name) | 210 | self.child_impl(db, name) |
218 | .<|> | 211 | <|>. |
219 | } | 212 | } |
220 | ", | 213 | ", |
221 | r" | 214 | r" |
222 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 215 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
223 | self.child_impl(db, name) | 216 | self.child_impl(db, name) |
224 | . | 217 | . |
225 | } | 218 | } |
226 | ", | 219 | ", |
227 | ); | 220 | ); |
228 | 221 | ||
229 | // indent if the previous line is already indented | 222 | // indent if the previous line is already indented |
230 | do_check( | 223 | do_check( |
231 | r" | 224 | r" |
232 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 225 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
233 | self.child_impl(db, name) | 226 | self.child_impl(db, name) |
234 | .first() | 227 | .first() |
235 | .<|> | 228 | <|>. |
236 | } | 229 | } |
237 | ", | 230 | ", |
238 | r" | 231 | r" |
239 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 232 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
240 | self.child_impl(db, name) | 233 | self.child_impl(db, name) |
241 | .first() | 234 | .first() |
242 | . | 235 | . |
243 | } | 236 | } |
244 | ", | 237 | ", |
245 | ); | 238 | ); |
246 | 239 | ||
247 | // don't indent if indent matches previous line | 240 | // don't indent if indent matches previous line |
248 | do_check( | 241 | do_check( |
249 | r" | 242 | r" |
250 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 243 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
251 | self.child_impl(db, name) | 244 | self.child_impl(db, name) |
252 | .first() | 245 | .first() |
253 | .<|> | 246 | <|>. |
254 | } | 247 | } |
255 | ", | 248 | ", |
256 | r" | 249 | r" |
257 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 250 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
258 | self.child_impl(db, name) | 251 | self.child_impl(db, name) |
259 | .first() | 252 | .first() |
260 | . | 253 | . |
261 | } | 254 | } |
262 | ", | 255 | ", |
263 | ); | 256 | ); |
264 | 257 | ||
265 | // don't indent if there is no method call on previous line | 258 | // don't indent if there is no method call on previous line |
266 | do_check( | 259 | do_check( |
267 | r" | 260 | r" |
268 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 261 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
269 | .<|> | 262 | <|>. |
270 | } | 263 | } |
271 | ", | 264 | ", |
272 | r" | 265 | r" |
273 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 266 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
274 | . | 267 | . |
275 | } | 268 | } |
276 | ", | 269 | ", |
277 | ); | 270 | ); |
278 | 271 | ||
279 | // indent to match previous expr | 272 | // indent to match previous expr |
280 | do_check( | 273 | do_check( |
281 | r" | 274 | r" |
282 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 275 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
283 | self.child_impl(db, name) | 276 | self.child_impl(db, name) |
284 | .<|> | 277 | <|>. |
285 | } | 278 | } |
286 | ", | 279 | ", |
287 | r" | 280 | r" |
288 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 281 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
289 | self.child_impl(db, name) | 282 | self.child_impl(db, name) |
290 | . | 283 | . |
291 | } | 284 | } |
292 | ", | 285 | ", |
293 | ); | 286 | ); |
294 | } | 287 | } |
295 | 288 | ||