diff options
Diffstat (limited to 'crates/ra_ide_api_light')
-rw-r--r-- | crates/ra_ide_api_light/src/assists.rs | 11 | ||||
-rw-r--r-- | crates/ra_ide_api_light/src/formatting.rs | 10 | ||||
-rw-r--r-- | crates/ra_ide_api_light/src/typing.rs | 284 |
3 files changed, 153 insertions, 152 deletions
diff --git a/crates/ra_ide_api_light/src/assists.rs b/crates/ra_ide_api_light/src/assists.rs index 83eabfc85..3495ad967 100644 --- a/crates/ra_ide_api_light/src/assists.rs +++ b/crates/ra_ide_api_light/src/assists.rs | |||
@@ -15,10 +15,11 @@ use ra_text_edit::{TextEdit, TextEditBuilder}; | |||
15 | use ra_syntax::{ | 15 | use ra_syntax::{ |
16 | Direction, SyntaxNode, TextUnit, TextRange, SourceFile, AstNode, | 16 | Direction, SyntaxNode, TextUnit, TextRange, SourceFile, AstNode, |
17 | algo::{find_leaf_at_offset, find_node_at_offset, find_covering_node, LeafAtOffset}, | 17 | algo::{find_leaf_at_offset, find_node_at_offset, find_covering_node, LeafAtOffset}, |
18 | ast::{self, AstToken}, | ||
19 | }; | 18 | }; |
20 | use itertools::Itertools; | 19 | use itertools::Itertools; |
21 | 20 | ||
21 | use crate::formatting::leading_indent; | ||
22 | |||
22 | pub use self::{ | 23 | pub use self::{ |
23 | flip_comma::flip_comma, | 24 | flip_comma::flip_comma, |
24 | add_derive::add_derive, | 25 | add_derive::add_derive, |
@@ -165,7 +166,7 @@ impl AssistBuilder { | |||
165 | } | 166 | } |
166 | fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into<String>) { | 167 | fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into<String>) { |
167 | let mut replace_with = replace_with.into(); | 168 | let mut replace_with = replace_with.into(); |
168 | if let Some(indent) = calc_indent(node) { | 169 | if let Some(indent) = leading_indent(node) { |
169 | replace_with = reindent(&replace_with, indent) | 170 | replace_with = reindent(&replace_with, indent) |
170 | } | 171 | } |
171 | self.replace(node.range(), replace_with) | 172 | self.replace(node.range(), replace_with) |
@@ -182,12 +183,6 @@ impl AssistBuilder { | |||
182 | } | 183 | } |
183 | } | 184 | } |
184 | 185 | ||
185 | fn calc_indent(node: &SyntaxNode) -> Option<&str> { | ||
186 | let prev = node.prev_sibling()?; | ||
187 | let ws_text = ast::Whitespace::cast(prev)?.text(); | ||
188 | ws_text.rfind('\n').map(|pos| &ws_text[pos + 1..]) | ||
189 | } | ||
190 | |||
191 | fn reindent(text: &str, indent: &str) -> String { | 186 | fn reindent(text: &str, indent: &str) -> String { |
192 | let indent = format!("\n{}", indent); | 187 | let indent = format!("\n{}", indent); |
193 | text.lines().intersperse(&indent).collect() | 188 | text.lines().intersperse(&indent).collect() |
diff --git a/crates/ra_ide_api_light/src/formatting.rs b/crates/ra_ide_api_light/src/formatting.rs index 1f3769209..4635fbd60 100644 --- a/crates/ra_ide_api_light/src/formatting.rs +++ b/crates/ra_ide_api_light/src/formatting.rs | |||
@@ -1,8 +1,16 @@ | |||
1 | use ra_syntax::{ | 1 | use ra_syntax::{ |
2 | ast, AstNode, | 2 | AstNode, |
3 | SyntaxNode, SyntaxKind::*, | 3 | SyntaxNode, SyntaxKind::*, |
4 | ast::{self, AstToken}, | ||
4 | }; | 5 | }; |
5 | 6 | ||
7 | /// If the node is on the begining of the line, calculate indent. | ||
8 | pub(crate) fn leading_indent(node: &SyntaxNode) -> Option<&str> { | ||
9 | let prev = node.prev_sibling()?; | ||
10 | let ws_text = ast::Whitespace::cast(prev)?.text(); | ||
11 | ws_text.rfind('\n').map(|pos| &ws_text[pos + 1..]) | ||
12 | } | ||
13 | |||
6 | pub(crate) fn extract_trivial_expression(block: &ast::Block) -> Option<&ast::Expr> { | 14 | pub(crate) fn extract_trivial_expression(block: &ast::Block) -> Option<&ast::Expr> { |
7 | let expr = block.expr()?; | 15 | let expr = block.expr()?; |
8 | if expr.syntax().text().contains('\n') { | 16 | if expr.syntax().text().contains('\n') { |
diff --git a/crates/ra_ide_api_light/src/typing.rs b/crates/ra_ide_api_light/src/typing.rs index 5726209cc..c8f3dfe44 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)] |
@@ -142,12 +133,18 @@ mod tests { | |||
142 | 133 | ||
143 | #[test] | 134 | #[test] |
144 | fn test_on_eq_typed() { | 135 | fn test_on_eq_typed() { |
145 | fn do_check(before: &str, after: &str) { | 136 | fn type_eq(before: &str, after: &str) { |
146 | let (offset, before) = extract_offset(before); | 137 | let (offset, before) = extract_offset(before); |
138 | let mut edit = TextEditBuilder::default(); | ||
139 | edit.insert(offset, "=".to_string()); | ||
140 | let before = edit.finish().apply(&before); | ||
147 | let file = SourceFile::parse(&before); | 141 | let file = SourceFile::parse(&before); |
148 | let result = on_eq_typed(&file, offset).unwrap(); | 142 | if let Some(result) = on_eq_typed(&file, offset) { |
149 | let actual = result.edit.apply(&before); | 143 | let actual = result.edit.apply(&before); |
150 | assert_eq_text!(after, &actual); | 144 | assert_eq_text!(after, &actual); |
145 | } else { | ||
146 | assert_eq_text!(&before, after) | ||
147 | }; | ||
151 | } | 148 | } |
152 | 149 | ||
153 | // do_check(r" | 150 | // do_check(r" |
@@ -159,10 +156,10 @@ mod tests { | |||
159 | // let foo =; | 156 | // let foo =; |
160 | // } | 157 | // } |
161 | // "); | 158 | // "); |
162 | do_check( | 159 | type_eq( |
163 | r" | 160 | r" |
164 | fn foo() { | 161 | fn foo() { |
165 | let foo =<|> 1 + 1 | 162 | let foo <|> 1 + 1 |
166 | } | 163 | } |
167 | ", | 164 | ", |
168 | r" | 165 | r" |
@@ -184,112 +181,113 @@ fn foo() { | |||
184 | // "); | 181 | // "); |
185 | } | 182 | } |
186 | 183 | ||
184 | fn type_dot(before: &str, after: &str) { | ||
185 | let (offset, before) = extract_offset(before); | ||
186 | let mut edit = TextEditBuilder::default(); | ||
187 | edit.insert(offset, ".".to_string()); | ||
188 | let before = edit.finish().apply(&before); | ||
189 | let file = SourceFile::parse(&before); | ||
190 | if let Some(result) = on_dot_typed(&file, offset) { | ||
191 | let actual = result.edit.apply(&before); | ||
192 | assert_eq_text!(after, &actual); | ||
193 | } else { | ||
194 | assert_eq_text!(&before, after) | ||
195 | }; | ||
196 | } | ||
197 | |||
187 | #[test] | 198 | #[test] |
188 | fn test_on_dot_typed() { | 199 | fn indents_new_chain_call() { |
189 | fn do_check(before: &str, after: &str) { | 200 | type_dot( |
190 | let (offset, before) = extract_offset(before); | ||
191 | let file = SourceFile::parse(&before); | ||
192 | if let Some(result) = on_eq_typed(&file, offset) { | ||
193 | let actual = result.edit.apply(&before); | ||
194 | assert_eq_text!(after, &actual); | ||
195 | }; | ||
196 | } | ||
197 | // indent if continuing chain call | ||
198 | do_check( | ||
199 | r" | 201 | r" |
200 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 202 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
201 | self.child_impl(db, name) | 203 | self.child_impl(db, name) |
202 | .<|> | 204 | <|> |
203 | } | 205 | } |
204 | ", | 206 | ", |
205 | r" | 207 | r" |
206 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 208 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
207 | self.child_impl(db, name) | 209 | self.child_impl(db, name) |
208 | . | 210 | . |
209 | } | 211 | } |
210 | ", | 212 | ", |
211 | ); | 213 | ); |
212 | 214 | type_dot( | |
213 | // do not indent if already indented | ||
214 | do_check( | ||
215 | r" | 215 | r" |
216 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 216 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
217 | self.child_impl(db, name) | 217 | self.child_impl(db, name) |
218 | .<|> | 218 | <|> |
219 | } | 219 | } |
220 | ", | 220 | ", |
221 | r" | 221 | r" |
222 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 222 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
223 | self.child_impl(db, name) | 223 | self.child_impl(db, name) |
224 | . | 224 | . |
225 | } | ||
226 | ", | ||
227 | ) | ||
225 | } | 228 | } |
226 | ", | ||
227 | ); | ||
228 | 229 | ||
229 | // indent if the previous line is already indented | 230 | #[test] |
230 | do_check( | 231 | fn indents_continued_chain_call() { |
232 | type_dot( | ||
231 | r" | 233 | r" |
232 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 234 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
233 | self.child_impl(db, name) | 235 | self.child_impl(db, name) |
234 | .first() | 236 | .first() |
235 | .<|> | 237 | <|> |
236 | } | 238 | } |
237 | ", | 239 | ", |
238 | r" | 240 | r" |
239 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 241 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
240 | self.child_impl(db, name) | 242 | self.child_impl(db, name) |
241 | .first() | 243 | .first() |
242 | . | 244 | . |
243 | } | 245 | } |
244 | ", | 246 | ", |
245 | ); | 247 | ); |
246 | 248 | type_dot( | |
247 | // don't indent if indent matches previous line | ||
248 | do_check( | ||
249 | r" | 249 | r" |
250 | 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>> { |
251 | self.child_impl(db, name) | 251 | self.child_impl(db, name) |
252 | .first() | 252 | .first() |
253 | .<|> | 253 | <|> |
254 | } | 254 | } |
255 | ", | 255 | ", |
256 | r" | 256 | r" |
257 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 257 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
258 | self.child_impl(db, name) | 258 | self.child_impl(db, name) |
259 | .first() | 259 | .first() |
260 | . | 260 | . |
261 | } | 261 | } |
262 | ", | 262 | ", |
263 | ); | 263 | ); |
264 | } | ||
264 | 265 | ||
265 | // don't indent if there is no method call on previous line | 266 | #[test] |
266 | do_check( | 267 | fn dont_indent_freestanding_dot() { |
268 | type_dot( | ||
267 | r" | 269 | r" |
268 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 270 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
269 | .<|> | 271 | <|> |
270 | } | 272 | } |
271 | ", | 273 | ", |
272 | r" | 274 | r" |
273 | 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>> { |
274 | . | 276 | . |
275 | } | 277 | } |
276 | ", | 278 | ", |
277 | ); | 279 | ); |
278 | 280 | type_dot( | |
279 | // indent to match previous expr | ||
280 | do_check( | ||
281 | r" | 281 | r" |
282 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 282 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
283 | self.child_impl(db, name) | 283 | <|> |
284 | .<|> | 284 | } |
285 | } | 285 | ", |
286 | ", | ||
287 | r" | 286 | r" |
288 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { | 287 | pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { |
289 | self.child_impl(db, name) | ||
290 | . | 288 | . |
291 | } | 289 | } |
292 | ", | 290 | ", |
293 | ); | 291 | ); |
294 | } | 292 | } |
295 | 293 | ||