aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api/src')
-rw-r--r--crates/ra_ide_api/src/inlay_hints.rs470
-rw-r--r--crates/ra_ide_api/src/lib.rs86
-rw-r--r--crates/ra_ide_api/src/references.rs3
-rw-r--r--crates/ra_ide_api/src/syntax_tree.rs14
-rw-r--r--crates/ra_ide_api/src/typing.rs4
5 files changed, 452 insertions, 125 deletions
diff --git a/crates/ra_ide_api/src/inlay_hints.rs b/crates/ra_ide_api/src/inlay_hints.rs
index 174662beb..a524e014f 100644
--- a/crates/ra_ide_api/src/inlay_hints.rs
+++ b/crates/ra_ide_api/src/inlay_hints.rs
@@ -1,16 +1,22 @@
1use crate::{db::RootDatabase, FileId}; 1use crate::{db::RootDatabase, FileId};
2use hir::{HirDisplay, Ty}; 2use hir::{HirDisplay, SourceAnalyzer, Ty};
3use ra_syntax::ast::Pat;
4use ra_syntax::{ 3use ra_syntax::{
5 algo::visit::{visitor, Visitor}, 4 algo::visit::{visitor, Visitor},
6 ast::{self, PatKind, TypeAscriptionOwner}, 5 ast::{
7 AstNode, SmolStr, SourceFile, SyntaxNode, TextRange, 6 AstNode, ForExpr, IfExpr, LambdaExpr, LetStmt, MatchArmList, Pat, PatKind, SourceFile,
7 TypeAscriptionOwner, WhileExpr,
8 },
9 SmolStr, SyntaxKind, SyntaxNode, TextRange,
8}; 10};
9 11
10#[derive(Debug, PartialEq, Eq)] 12#[derive(Debug, PartialEq, Eq, Clone)]
11pub enum InlayKind { 13pub enum InlayKind {
12 LetBindingType, 14 LetBindingType,
13 ClosureParameterType, 15 ClosureParameterType,
16 ForExpressionBindingType,
17 IfExpressionType,
18 WhileLetExpressionType,
19 MatchArmType,
14} 20}
15 21
16#[derive(Debug)] 22#[derive(Debug)]
@@ -34,68 +40,142 @@ fn get_inlay_hints(
34 node: &SyntaxNode, 40 node: &SyntaxNode,
35) -> Option<Vec<InlayHint>> { 41) -> Option<Vec<InlayHint>> {
36 visitor() 42 visitor()
37 .visit(|let_statement: ast::LetStmt| { 43 .visit(|let_statement: LetStmt| {
38 let let_syntax = let_statement.syntax();
39
40 if let_statement.ascribed_type().is_some() { 44 if let_statement.ascribed_type().is_some() {
41 return None; 45 return None;
42 } 46 }
43 47 let pat = let_statement.pat()?;
44 let let_pat = let_statement.pat()?; 48 let analyzer = SourceAnalyzer::new(db, file_id, let_statement.syntax(), None);
45 let inlay_type_string = get_node_displayable_type(db, file_id, let_syntax, &let_pat)? 49 Some(get_pat_hints(db, &analyzer, pat, InlayKind::LetBindingType, false))
46 .display(db)
47 .to_string()
48 .into();
49
50 let pat_range = match let_pat.kind() {
51 PatKind::BindPat(bind_pat) => bind_pat.syntax().text_range(),
52 PatKind::TuplePat(tuple_pat) => tuple_pat.syntax().text_range(),
53 _ => return None,
54 };
55
56 Some(vec![InlayHint {
57 range: pat_range,
58 kind: InlayKind::LetBindingType,
59 label: inlay_type_string,
60 }])
61 }) 50 })
62 .visit(|closure_parameter: ast::LambdaExpr| match closure_parameter.param_list() { 51 .visit(|closure_parameter: LambdaExpr| {
63 Some(param_list) => Some( 52 let analyzer = SourceAnalyzer::new(db, file_id, closure_parameter.syntax(), None);
53 closure_parameter.param_list().map(|param_list| {
64 param_list 54 param_list
65 .params() 55 .params()
66 .filter(|closure_param| closure_param.ascribed_type().is_none()) 56 .filter(|closure_param| closure_param.ascribed_type().is_none())
67 .filter_map(|closure_param| { 57 .filter_map(|closure_param| closure_param.pat())
68 let closure_param_syntax = closure_param.syntax(); 58 .map(|root_pat| {
69 let inlay_type_string = get_node_displayable_type( 59 get_pat_hints(
70 db, 60 db,
71 file_id, 61 &analyzer,
72 closure_param_syntax, 62 root_pat,
73 &closure_param.pat()?, 63 InlayKind::ClosureParameterType,
74 )? 64 false,
75 .display(db) 65 )
76 .to_string()
77 .into();
78
79 Some(InlayHint {
80 range: closure_param_syntax.text_range(),
81 kind: InlayKind::ClosureParameterType,
82 label: inlay_type_string,
83 })
84 }) 66 })
67 .flatten()
68 .collect()
69 })
70 })
71 .visit(|for_expression: ForExpr| {
72 let pat = for_expression.pat()?;
73 let analyzer = SourceAnalyzer::new(db, file_id, for_expression.syntax(), None);
74 Some(get_pat_hints(db, &analyzer, pat, InlayKind::ForExpressionBindingType, false))
75 })
76 .visit(|if_expr: IfExpr| {
77 let pat = if_expr.condition()?.pat()?;
78 let analyzer = SourceAnalyzer::new(db, file_id, if_expr.syntax(), None);
79 Some(get_pat_hints(db, &analyzer, pat, InlayKind::IfExpressionType, true))
80 })
81 .visit(|while_expr: WhileExpr| {
82 let pat = while_expr.condition()?.pat()?;
83 let analyzer = SourceAnalyzer::new(db, file_id, while_expr.syntax(), None);
84 Some(get_pat_hints(db, &analyzer, pat, InlayKind::WhileLetExpressionType, true))
85 })
86 .visit(|match_arm_list: MatchArmList| {
87 let analyzer = SourceAnalyzer::new(db, file_id, match_arm_list.syntax(), None);
88 Some(
89 match_arm_list
90 .arms()
91 .map(|match_arm| match_arm.pats())
92 .flatten()
93 .map(|root_pat| {
94 get_pat_hints(db, &analyzer, root_pat, InlayKind::MatchArmType, true)
95 })
96 .flatten()
85 .collect(), 97 .collect(),
86 ), 98 )
87 None => None,
88 }) 99 })
89 .accept(&node)? 100 .accept(&node)?
90} 101}
91 102
103fn get_pat_hints(
104 db: &RootDatabase,
105 analyzer: &SourceAnalyzer,
106 root_pat: Pat,
107 kind: InlayKind,
108 skip_root_pat_hint: bool,
109) -> Vec<InlayHint> {
110 let original_pat = &root_pat.clone();
111
112 get_leaf_pats(root_pat)
113 .into_iter()
114 .filter(|pat| !skip_root_pat_hint || pat != original_pat)
115 .filter_map(|pat| {
116 get_node_displayable_type(db, &analyzer, &pat)
117 .map(|pat_type| (pat.syntax().text_range(), pat_type))
118 })
119 .map(|(range, pat_type)| InlayHint {
120 range,
121 kind: kind.clone(),
122 label: pat_type.display(db).to_string().into(),
123 })
124 .collect()
125}
126
127fn get_leaf_pats(root_pat: Pat) -> Vec<Pat> {
128 let mut pats_to_process = std::collections::VecDeque::<Pat>::new();
129 pats_to_process.push_back(root_pat);
130
131 let mut leaf_pats = Vec::new();
132
133 while let Some(maybe_leaf_pat) = pats_to_process.pop_front() {
134 match maybe_leaf_pat.kind() {
135 PatKind::BindPat(bind_pat) => {
136 if let Some(pat) = bind_pat.pat() {
137 pats_to_process.push_back(pat);
138 } else {
139 leaf_pats.push(maybe_leaf_pat);
140 }
141 }
142 PatKind::TuplePat(tuple_pat) => {
143 for arg_pat in tuple_pat.args() {
144 pats_to_process.push_back(arg_pat);
145 }
146 }
147 PatKind::StructPat(struct_pat) => {
148 if let Some(pat_list) = struct_pat.field_pat_list() {
149 pats_to_process.extend(
150 pat_list
151 .field_pats()
152 .filter_map(|field_pat| {
153 field_pat
154 .pat()
155 .filter(|pat| pat.syntax().kind() != SyntaxKind::BIND_PAT)
156 })
157 .chain(pat_list.bind_pats().map(|bind_pat| {
158 bind_pat.pat().unwrap_or_else(|| Pat::from(bind_pat))
159 })),
160 );
161 }
162 }
163 PatKind::TupleStructPat(tuple_struct_pat) => {
164 for arg_pat in tuple_struct_pat.args() {
165 pats_to_process.push_back(arg_pat);
166 }
167 }
168 _ => (),
169 }
170 }
171 leaf_pats
172}
173
92fn get_node_displayable_type( 174fn get_node_displayable_type(
93 db: &RootDatabase, 175 db: &RootDatabase,
94 file_id: FileId, 176 analyzer: &SourceAnalyzer,
95 node_syntax: &SyntaxNode,
96 node_pat: &Pat, 177 node_pat: &Pat,
97) -> Option<Ty> { 178) -> Option<Ty> {
98 let analyzer = hir::SourceAnalyzer::new(db, file_id, node_syntax, None);
99 analyzer.type_of_pat(db, node_pat).and_then(|resolved_type| { 179 analyzer.type_of_pat(db, node_pat).and_then(|resolved_type| {
100 if let Ty::Apply(_) = resolved_type { 180 if let Ty::Apply(_) = resolved_type {
101 Some(resolved_type) 181 Some(resolved_type)
@@ -111,68 +191,306 @@ mod tests {
111 use insta::assert_debug_snapshot_matches; 191 use insta::assert_debug_snapshot_matches;
112 192
113 #[test] 193 #[test]
114 fn test_inlay_hints() { 194 fn let_statement() {
115 let (analysis, file_id) = single_file( 195 let (analysis, file_id) = single_file(
116 r#" 196 r#"
117struct OuterStruct {} 197#[derive(PartialEq)]
198enum CustomOption<T> {
199 None,
200 Some(T),
201}
202
203#[derive(PartialEq)]
204struct Test {
205 a: CustomOption<u32>,
206 b: u8,
207}
118 208
119fn main() { 209fn main() {
120 struct InnerStruct {} 210 struct InnerStruct {}
121 211
122 let test = 54; 212 let test = 54;
123 let test = InnerStruct {}; 213 let test: i32 = 33;
124 let test = OuterStruct {};
125 let test = vec![222];
126 let mut test = Vec::new();
127 test.push(333);
128 let test = test.into_iter().map(|i| i * i).collect::<Vec<_>>();
129 let mut test = 33; 214 let mut test = 33;
130 let _ = 22; 215 let _ = 22;
131 let test: Vec<_> = (0..3).collect(); 216 let test = "test";
217 let test = InnerStruct {};
132 218
133 let _ = (0..23).map(|i: u32| { 219 let test = vec![222];
134 let i_squared = i * i; 220 let test: Vec<_> = (0..3).collect();
135 i_squared 221 let test = (0..3).collect::<Vec<i128>>();
136 }); 222 let test = (0..3).collect::<Vec<_>>();
137 223
138 let test: i32 = 33; 224 let mut test = Vec::new();
225 test.push(333);
139 226
140 let (x, c) = (42, 'a');
141 let test = (42, 'a'); 227 let test = (42, 'a');
142} 228 let (a, (b, c, (d, e), f)) = (2, (3, 4, (6.6, 7.7), 5));
143"#, 229}"#,
144 ); 230 );
145 231
146 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[ 232 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[
147 InlayHint { 233 InlayHint {
148 range: [71; 75), 234 range: [193; 197),
149 kind: LetBindingType, 235 kind: LetBindingType,
150 label: "i32", 236 label: "i32",
151 }, 237 },
152 InlayHint { 238 InlayHint {
153 range: [121; 125), 239 range: [236; 244),
154 kind: LetBindingType, 240 kind: LetBindingType,
155 label: "OuterStruct", 241 label: "i32",
156 }, 242 },
157 InlayHint { 243 InlayHint {
158 range: [297; 305), 244 range: [275; 279),
245 kind: LetBindingType,
246 label: "&str",
247 },
248 InlayHint {
249 range: [539; 543),
250 kind: LetBindingType,
251 label: "(i32, char)",
252 },
253 InlayHint {
254 range: [566; 567),
159 kind: LetBindingType, 255 kind: LetBindingType,
160 label: "i32", 256 label: "i32",
161 }, 257 },
162 InlayHint { 258 InlayHint {
163 range: [417; 426), 259 range: [570; 571),
164 kind: LetBindingType, 260 kind: LetBindingType,
165 label: "u32", 261 label: "i32",
166 }, 262 },
167 InlayHint { 263 InlayHint {
168 range: [496; 502), 264 range: [573; 574),
169 kind: LetBindingType, 265 kind: LetBindingType,
170 label: "(i32, char)", 266 label: "i32",
171 }, 267 },
172 InlayHint { 268 InlayHint {
173 range: [524; 528), 269 range: [584; 585),
174 kind: LetBindingType, 270 kind: LetBindingType,
175 label: "(i32, char)", 271 label: "i32",
272 },
273 InlayHint {
274 range: [577; 578),
275 kind: LetBindingType,
276 label: "f64",
277 },
278 InlayHint {
279 range: [580; 581),
280 kind: LetBindingType,
281 label: "f64",
282 },
283]"#
284 );
285 }
286
287 #[test]
288 fn closure_parameter() {
289 let (analysis, file_id) = single_file(
290 r#"
291fn main() {
292 let mut start = 0;
293 (0..2).for_each(|increment| {
294 start += increment;
295 })
296}"#,
297 );
298
299 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[
300 InlayHint {
301 range: [21; 30),
302 kind: LetBindingType,
303 label: "i32",
304 },
305 InlayHint {
306 range: [57; 66),
307 kind: ClosureParameterType,
308 label: "i32",
309 },
310]"#
311 );
312 }
313
314 #[test]
315 fn for_expression() {
316 let (analysis, file_id) = single_file(
317 r#"
318fn main() {
319 let mut start = 0;
320 for increment in 0..2 {
321 start += increment;
322 }
323}"#,
324 );
325
326 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[
327 InlayHint {
328 range: [21; 30),
329 kind: LetBindingType,
330 label: "i32",
331 },
332 InlayHint {
333 range: [44; 53),
334 kind: ForExpressionBindingType,
335 label: "i32",
336 },
337]"#
338 );
339 }
340
341 #[test]
342 fn if_expr() {
343 let (analysis, file_id) = single_file(
344 r#"
345#[derive(PartialEq)]
346enum CustomOption<T> {
347 None,
348 Some(T),
349}
350
351#[derive(PartialEq)]
352struct Test {
353 a: CustomOption<u32>,
354 b: u8,
355}
356
357fn main() {
358 let test = CustomOption::Some(Test { a: CustomOption::Some(3), b: 1 });
359 if let CustomOption::None = &test {};
360 if let test = &test {};
361 if let CustomOption::Some(test) = &test {};
362 if let CustomOption::Some(Test { a, b }) = &test {};
363 if let CustomOption::Some(Test { a: x, b: y }) = &test {};
364 if let CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) = &test {};
365 if let CustomOption::Some(Test { a: CustomOption::None, b: y }) = &test {};
366 if let CustomOption::Some(Test { b: y, .. }) = &test {};
367
368 if test == CustomOption::None {}
369}"#,
370 );
371
372 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[
373 InlayHint {
374 range: [166; 170),
375 kind: LetBindingType,
376 label: "CustomOption<Test>",
377 },
378 InlayHint {
379 range: [334; 338),
380 kind: IfExpressionType,
381 label: "&Test",
382 },
383 InlayHint {
384 range: [389; 390),
385 kind: IfExpressionType,
386 label: "&CustomOption<u32>",
387 },
388 InlayHint {
389 range: [392; 393),
390 kind: IfExpressionType,
391 label: "&u8",
392 },
393 InlayHint {
394 range: [531; 532),
395 kind: IfExpressionType,
396 label: "&u32",
397 },
398]"#
399 );
400 }
401
402 #[test]
403 fn while_expr() {
404 let (analysis, file_id) = single_file(
405 r#"
406#[derive(PartialEq)]
407enum CustomOption<T> {
408 None,
409 Some(T),
410}
411
412#[derive(PartialEq)]
413struct Test {
414 a: CustomOption<u32>,
415 b: u8,
416}
417
418fn main() {
419 let test = CustomOption::Some(Test { a: CustomOption::Some(3), b: 1 });
420 while let CustomOption::None = &test {};
421 while let test = &test {};
422 while let CustomOption::Some(test) = &test {};
423 while let CustomOption::Some(Test { a, b }) = &test {};
424 while let CustomOption::Some(Test { a: x, b: y }) = &test {};
425 while let CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) = &test {};
426 while let CustomOption::Some(Test { a: CustomOption::None, b: y }) = &test {};
427 while let CustomOption::Some(Test { b: y, .. }) = &test {};
428
429 while test == CustomOption::None {}
430}"#,
431 );
432
433 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[
434 InlayHint {
435 range: [166; 170),
436 kind: LetBindingType,
437 label: "CustomOption<Test>",
438 },
439]"#
440 );
441 }
442
443 #[test]
444 fn match_arm_list() {
445 let (analysis, file_id) = single_file(
446 r#"
447#[derive(PartialEq)]
448enum CustomOption<T> {
449 None,
450 Some(T),
451}
452
453#[derive(PartialEq)]
454struct Test {
455 a: CustomOption<u32>,
456 b: u8,
457}
458
459fn main() {
460 match CustomOption::Some(Test { a: CustomOption::Some(3), b: 1 }) {
461 CustomOption::None => (),
462 test => (),
463 CustomOption::Some(test) => (),
464 CustomOption::Some(Test { a, b }) => (),
465 CustomOption::Some(Test { a: x, b: y }) => (),
466 CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) => (),
467 CustomOption::Some(Test { a: CustomOption::None, b: y }) => (),
468 CustomOption::Some(Test { b: y, .. }) => (),
469 _ => {}
470 }
471}"#,
472 );
473
474 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[
475 InlayHint {
476 range: [312; 316),
477 kind: MatchArmType,
478 label: "Test",
479 },
480 InlayHint {
481 range: [359; 360),
482 kind: MatchArmType,
483 label: "CustomOption<u32>",
484 },
485 InlayHint {
486 range: [362; 363),
487 kind: MatchArmType,
488 label: "u8",
489 },
490 InlayHint {
491 range: [485; 486),
492 kind: MatchArmType,
493 label: "u32",
176 }, 494 },
177]"# 495]"#
178 ); 496 );
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs
index 16ffb03ce..edb646c11 100644
--- a/crates/ra_ide_api/src/lib.rs
+++ b/crates/ra_ide_api/src/lib.rs
@@ -317,24 +317,24 @@ impl Analysis {
317 } 317 }
318 318
319 /// Debug info about the current state of the analysis 319 /// Debug info about the current state of the analysis
320 pub fn status(&self) -> String { 320 pub fn status(&self) -> Cancelable<String> {
321 status::status(&*self.db) 321 self.with_db(|db| status::status(&*db))
322 } 322 }
323 323
324 /// Gets the text of the source file. 324 /// Gets the text of the source file.
325 pub fn file_text(&self, file_id: FileId) -> Arc<String> { 325 pub fn file_text(&self, file_id: FileId) -> Cancelable<Arc<String>> {
326 self.db.file_text(file_id) 326 self.with_db(|db| db.file_text(file_id))
327 } 327 }
328 328
329 /// Gets the syntax tree of the file. 329 /// Gets the syntax tree of the file.
330 pub fn parse(&self, file_id: FileId) -> SourceFile { 330 pub fn parse(&self, file_id: FileId) -> Cancelable<SourceFile> {
331 self.db.parse(file_id).tree() 331 self.with_db(|db| db.parse(file_id).tree())
332 } 332 }
333 333
334 /// Gets the file's `LineIndex`: data structure to convert between absolute 334 /// Gets the file's `LineIndex`: data structure to convert between absolute
335 /// offsets and line/column representation. 335 /// offsets and line/column representation.
336 pub fn file_line_index(&self, file_id: FileId) -> Arc<LineIndex> { 336 pub fn file_line_index(&self, file_id: FileId) -> Cancelable<Arc<LineIndex>> {
337 self.db.line_index(file_id) 337 self.with_db(|db| db.line_index(file_id))
338 } 338 }
339 339
340 /// Selects the next syntactic nodes encompassing the range. 340 /// Selects the next syntactic nodes encompassing the range.
@@ -344,58 +344,67 @@ impl Analysis {
344 344
345 /// Returns position of the matching brace (all types of braces are 345 /// Returns position of the matching brace (all types of braces are
346 /// supported). 346 /// supported).
347 pub fn matching_brace(&self, position: FilePosition) -> Option<TextUnit> { 347 pub fn matching_brace(&self, position: FilePosition) -> Cancelable<Option<TextUnit>> {
348 let parse = self.db.parse(position.file_id); 348 self.with_db(|db| {
349 let file = parse.tree(); 349 let parse = db.parse(position.file_id);
350 matching_brace::matching_brace(&file, position.offset) 350 let file = parse.tree();
351 matching_brace::matching_brace(&file, position.offset)
352 })
351 } 353 }
352 354
353 /// Returns a syntax tree represented as `String`, for debug purposes. 355 /// Returns a syntax tree represented as `String`, for debug purposes.
354 // FIXME: use a better name here. 356 // FIXME: use a better name here.
355 pub fn syntax_tree(&self, file_id: FileId, text_range: Option<TextRange>) -> String { 357 pub fn syntax_tree(
356 syntax_tree::syntax_tree(&self.db, file_id, text_range) 358 &self,
359 file_id: FileId,
360 text_range: Option<TextRange>,
361 ) -> Cancelable<String> {
362 self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range))
357 } 363 }
358 364
359 /// Returns an edit to remove all newlines in the range, cleaning up minor 365 /// Returns an edit to remove all newlines in the range, cleaning up minor
360 /// stuff like trailing commas. 366 /// stuff like trailing commas.
361 pub fn join_lines(&self, frange: FileRange) -> SourceChange { 367 pub fn join_lines(&self, frange: FileRange) -> Cancelable<SourceChange> {
362 let parse = self.db.parse(frange.file_id); 368 self.with_db(|db| {
363 let file_edit = SourceFileEdit { 369 let parse = db.parse(frange.file_id);
364 file_id: frange.file_id, 370 let file_edit = SourceFileEdit {
365 edit: join_lines::join_lines(&parse.tree(), frange.range), 371 file_id: frange.file_id,
366 }; 372 edit: join_lines::join_lines(&parse.tree(), frange.range),
367 SourceChange::source_file_edit("join lines", file_edit) 373 };
374 SourceChange::source_file_edit("join lines", file_edit)
375 })
368 } 376 }
369 377
370 /// Returns an edit which should be applied when opening a new line, fixing 378 /// Returns an edit which should be applied when opening a new line, fixing
371 /// up minor stuff like continuing the comment. 379 /// up minor stuff like continuing the comment.
372 pub fn on_enter(&self, position: FilePosition) -> Option<SourceChange> { 380 pub fn on_enter(&self, position: FilePosition) -> Cancelable<Option<SourceChange>> {
373 typing::on_enter(&self.db, position) 381 self.with_db(|db| typing::on_enter(&db, position))
374 } 382 }
375 383
376 /// Returns an edit which should be applied after `=` was typed. Primarily, 384 /// Returns an edit which should be applied after `=` was typed. Primarily,
377 /// this works when adding `let =`. 385 /// this works when adding `let =`.
378 // FIXME: use a snippet completion instead of this hack here. 386 // FIXME: use a snippet completion instead of this hack here.
379 pub fn on_eq_typed(&self, position: FilePosition) -> Option<SourceChange> { 387 pub fn on_eq_typed(&self, position: FilePosition) -> Cancelable<Option<SourceChange>> {
380 let parse = self.db.parse(position.file_id); 388 self.with_db(|db| {
381 let file = parse.tree(); 389 let parse = db.parse(position.file_id);
382 let edit = typing::on_eq_typed(&file, position.offset)?; 390 let file = parse.tree();
383 Some(SourceChange::source_file_edit( 391 let edit = typing::on_eq_typed(&file, position.offset)?;
384 "add semicolon", 392 Some(SourceChange::source_file_edit(
385 SourceFileEdit { edit, file_id: position.file_id }, 393 "add semicolon",
386 )) 394 SourceFileEdit { edit, file_id: position.file_id },
395 ))
396 })
387 } 397 }
388 398
389 /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. 399 /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
390 pub fn on_dot_typed(&self, position: FilePosition) -> Option<SourceChange> { 400 pub fn on_dot_typed(&self, position: FilePosition) -> Cancelable<Option<SourceChange>> {
391 typing::on_dot_typed(&self.db, position) 401 self.with_db(|db| typing::on_dot_typed(&db, position))
392 } 402 }
393 403
394 /// Returns a tree representation of symbols in the file. Useful to draw a 404 /// Returns a tree representation of symbols in the file. Useful to draw a
395 /// file outline. 405 /// file outline.
396 pub fn file_structure(&self, file_id: FileId) -> Vec<StructureNode> { 406 pub fn file_structure(&self, file_id: FileId) -> Cancelable<Vec<StructureNode>> {
397 let parse = self.db.parse(file_id); 407 self.with_db(|db| file_structure(&db.parse(file_id).tree()))
398 file_structure(&parse.tree())
399 } 408 }
400 409
401 /// Returns a list of the places in the file where type hints can be displayed. 410 /// Returns a list of the places in the file where type hints can be displayed.
@@ -404,9 +413,8 @@ impl Analysis {
404 } 413 }
405 414
406 /// Returns the set of folding ranges. 415 /// Returns the set of folding ranges.
407 pub fn folding_ranges(&self, file_id: FileId) -> Vec<Fold> { 416 pub fn folding_ranges(&self, file_id: FileId) -> Cancelable<Vec<Fold>> {
408 let parse = self.db.parse(file_id); 417 self.with_db(|db| folding_ranges::folding_ranges(&db.parse(file_id).tree()))
409 folding_ranges::folding_ranges(&parse.tree())
410 } 418 }
411 419
412 /// Fuzzy searches for a symbol. 420 /// Fuzzy searches for a symbol.
diff --git a/crates/ra_ide_api/src/references.rs b/crates/ra_ide_api/src/references.rs
index 379dd422e..2118e7ad3 100644
--- a/crates/ra_ide_api/src/references.rs
+++ b/crates/ra_ide_api/src/references.rs
@@ -437,7 +437,8 @@ mod tests {
437 } 437 }
438 } 438 }
439 } 439 }
440 let result = text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap())); 440 let result =
441 text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap());
441 assert_eq_text!(expected, &*result); 442 assert_eq_text!(expected, &*result);
442 } 443 }
443} 444}
diff --git a/crates/ra_ide_api/src/syntax_tree.rs b/crates/ra_ide_api/src/syntax_tree.rs
index a07e670fa..dd31b9093 100644
--- a/crates/ra_ide_api/src/syntax_tree.rs
+++ b/crates/ra_ide_api/src/syntax_tree.rs
@@ -101,7 +101,7 @@ mod tests {
101 fn test_syntax_tree_without_range() { 101 fn test_syntax_tree_without_range() {
102 // Basic syntax 102 // Basic syntax
103 let (analysis, file_id) = single_file(r#"fn foo() {}"#); 103 let (analysis, file_id) = single_file(r#"fn foo() {}"#);
104 let syn = analysis.syntax_tree(file_id, None); 104 let syn = analysis.syntax_tree(file_id, None).unwrap();
105 105
106 assert_eq_text!( 106 assert_eq_text!(
107 syn.trim(), 107 syn.trim(),
@@ -133,7 +133,7 @@ fn test() {
133}"# 133}"#
134 .trim(), 134 .trim(),
135 ); 135 );
136 let syn = analysis.syntax_tree(file_id, None); 136 let syn = analysis.syntax_tree(file_id, None).unwrap();
137 137
138 assert_eq_text!( 138 assert_eq_text!(
139 syn.trim(), 139 syn.trim(),
@@ -176,7 +176,7 @@ SOURCE_FILE@[0; 60)
176 #[test] 176 #[test]
177 fn test_syntax_tree_with_range() { 177 fn test_syntax_tree_with_range() {
178 let (analysis, range) = single_file_with_range(r#"<|>fn foo() {}<|>"#.trim()); 178 let (analysis, range) = single_file_with_range(r#"<|>fn foo() {}<|>"#.trim());
179 let syn = analysis.syntax_tree(range.file_id, Some(range.range)); 179 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
180 180
181 assert_eq_text!( 181 assert_eq_text!(
182 syn.trim(), 182 syn.trim(),
@@ -206,7 +206,7 @@ FN_DEF@[0; 11)
206}"# 206}"#
207 .trim(), 207 .trim(),
208 ); 208 );
209 let syn = analysis.syntax_tree(range.file_id, Some(range.range)); 209 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
210 210
211 assert_eq_text!( 211 assert_eq_text!(
212 syn.trim(), 212 syn.trim(),
@@ -244,7 +244,7 @@ fn bar() {
244}"# 244}"#
245 .trim(), 245 .trim(),
246 ); 246 );
247 let syn = analysis.syntax_tree(range.file_id, Some(range.range)); 247 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
248 assert_eq_text!( 248 assert_eq_text!(
249 syn.trim(), 249 syn.trim(),
250 r#" 250 r#"
@@ -278,7 +278,7 @@ fn bar() {
278}"### 278}"###
279 .trim(), 279 .trim(),
280 ); 280 );
281 let syn = analysis.syntax_tree(range.file_id, Some(range.range)); 281 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
282 assert_eq_text!( 282 assert_eq_text!(
283 syn.trim(), 283 syn.trim(),
284 r#" 284 r#"
@@ -311,7 +311,7 @@ fn bar() {
311}"### 311}"###
312 .trim(), 312 .trim(),
313 ); 313 );
314 let syn = analysis.syntax_tree(range.file_id, Some(range.range)); 314 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
315 assert_eq_text!( 315 assert_eq_text!(
316 syn.trim(), 316 syn.trim(),
317 r#" 317 r#"
diff --git a/crates/ra_ide_api/src/typing.rs b/crates/ra_ide_api/src/typing.rs
index 6b3fd5904..2d4491442 100644
--- a/crates/ra_ide_api/src/typing.rs
+++ b/crates/ra_ide_api/src/typing.rs
@@ -195,7 +195,7 @@ fn foo() {
195 edit.insert(offset, ".".to_string()); 195 edit.insert(offset, ".".to_string());
196 let before = edit.finish().apply(&before); 196 let before = edit.finish().apply(&before);
197 let (analysis, file_id) = single_file(&before); 197 let (analysis, file_id) = single_file(&before);
198 if let Some(result) = analysis.on_dot_typed(FilePosition { offset, file_id }) { 198 if let Some(result) = analysis.on_dot_typed(FilePosition { offset, file_id }).unwrap() {
199 assert_eq!(result.source_file_edits.len(), 1); 199 assert_eq!(result.source_file_edits.len(), 1);
200 let actual = result.source_file_edits[0].edit.apply(&before); 200 let actual = result.source_file_edits[0].edit.apply(&before);
201 assert_eq_text!(after, &actual); 201 assert_eq_text!(after, &actual);
@@ -377,7 +377,7 @@ fn foo() {
377 fn apply_on_enter(before: &str) -> Option<String> { 377 fn apply_on_enter(before: &str) -> Option<String> {
378 let (offset, before) = extract_offset(before); 378 let (offset, before) = extract_offset(before);
379 let (analysis, file_id) = single_file(&before); 379 let (analysis, file_id) = single_file(&before);
380 let result = analysis.on_enter(FilePosition { offset, file_id })?; 380 let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?;
381 381
382 assert_eq!(result.source_file_edits.len(), 1); 382 assert_eq!(result.source_file_edits.len(), 1);
383 let actual = result.source_file_edits[0].edit.apply(&before); 383 let actual = result.source_file_edits[0].edit.apply(&before);