aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide_api_light/src/assists.rs11
-rw-r--r--crates/ra_ide_api_light/src/formatting.rs10
-rw-r--r--crates/ra_ide_api_light/src/typing.rs225
3 files changed, 121 insertions, 125 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};
15use ra_syntax::{ 15use 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};
20use itertools::Itertools; 19use itertools::Itertools;
21 20
21use crate::formatting::leading_indent;
22
22pub use self::{ 23pub 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
185fn 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
191fn reindent(text: &str, indent: &str) -> String { 186fn 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 @@
1use ra_syntax::{ 1use 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.
8pub(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
6pub(crate) fn extract_trivial_expression(block: &ast::Block) -> Option<&ast::Expr> { 14pub(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..05845a0cc 100644
--- a/crates/ra_ide_api_light/src/typing.rs
+++ b/crates/ra_ide_api_light/src/typing.rs
@@ -1,11 +1,11 @@
1use ra_syntax::{ 1use 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
8use crate::{LocalEdit, TextEditBuilder}; 8use crate::{LocalEdit, TextEditBuilder, formatting::leading_indent};
9 9
10pub fn on_enter(file: &SourceFile, offset: TextUnit) -> Option<LocalEdit> { 10pub 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
56pub fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option<LocalEdit> { 56pub 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
87pub fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option<LocalEdit> { 88pub 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)
133fn 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"
164fn foo() { 155fn 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