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.rs284
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs47
4 files changed, 174 insertions, 178 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..c8f3dfe44 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)]
@@ -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"
164fn foo() { 161fn 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
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 0dda9548a..5f4b27149 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -9,7 +9,7 @@ use languageserver_types::{
9 SignatureInformation, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit, 9 SignatureInformation, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit,
10}; 10};
11use ra_ide_api::{ 11use ra_ide_api::{
12 FileId, FilePosition, FileRange, FoldKind, Query, RunnableKind, Severity, SourceChange, 12 FileId, FilePosition, FileRange, FoldKind, Query, RunnableKind, Severity,
13}; 13};
14use ra_syntax::{TextUnit, AstNode}; 14use ra_syntax::{TextUnit, AstNode};
15use rustc_hash::FxHashMap; 15use rustc_hash::FxHashMap;
@@ -92,35 +92,30 @@ pub fn handle_on_type_formatting(
92 world: ServerWorld, 92 world: ServerWorld,
93 params: req::DocumentOnTypeFormattingParams, 93 params: req::DocumentOnTypeFormattingParams,
94) -> Result<Option<Vec<TextEdit>>> { 94) -> Result<Option<Vec<TextEdit>>> {
95 let analysis: Option<Box<Fn(FilePosition) -> Option<SourceChange>>> = match params.ch.as_str() { 95 let file_id = params.text_document.try_conv_with(&world)?;
96 "=" => Some(Box::new(|pos| world.analysis().on_eq_typed(pos))), 96 let line_index = world.analysis().file_line_index(file_id);
97 "." => Some(Box::new(|pos| world.analysis().on_dot_typed(pos))), 97 let position = FilePosition {
98 _ => None, 98 file_id,
99 /// in `ra_ide_api`, the `on_type` invariant is that
100 /// `text.char_at(position) == typed_char`.
101 offset: params.position.conv_with(&line_index) - TextUnit::of_char('.'),
99 }; 102 };
100 103
101 if let Some(ana) = analysis { 104 let edit = match params.ch.as_str() {
102 let file_id = params.text_document.try_conv_with(&world)?; 105 "=" => world.analysis().on_eq_typed(position),
103 let line_index = world.analysis().file_line_index(file_id); 106 "." => world.analysis().on_dot_typed(position),
104 let position = FilePosition { 107 _ => return Ok(None),
105 file_id, 108 };
106 offset: params.position.conv_with(&line_index), 109 let mut edit = match edit {
107 }; 110 Some(it) => it,
111 None => return Ok(None),
112 };
108 113
109 if let Some(mut action) = ana(position) { 114 // This should be a single-file edit
110 let change: Vec<TextEdit> = action 115 let edit = edit.source_file_edits.pop().unwrap();
111 .source_file_edits
112 .pop()
113 .unwrap()
114 .edit
115 .as_atoms()
116 .iter()
117 .map_conv_with(&line_index)
118 .collect();
119 return Ok(Some(change));
120 }
121 }
122 116
123 return Ok(None); 117 let change: Vec<TextEdit> = edit.edit.conv_with(&line_index);
118 return Ok(Some(change));
124} 119}
125 120
126pub fn handle_document_symbol( 121pub fn handle_document_symbol(