aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api_light
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api_light')
-rw-r--r--crates/ra_ide_api_light/src/lib.rs140
-rw-r--r--crates/ra_ide_api_light/src/snapshots/tests__highlighting.snap32
-rw-r--r--crates/ra_ide_api_light/src/typing.rs407
3 files changed, 1 insertions, 578 deletions
diff --git a/crates/ra_ide_api_light/src/lib.rs b/crates/ra_ide_api_light/src/lib.rs
index f21a91e18..df7f144b6 100644
--- a/crates/ra_ide_api_light/src/lib.rs
+++ b/crates/ra_ide_api_light/src/lib.rs
@@ -4,147 +4,9 @@
4//! an edit or some auxiliary info. 4//! an edit or some auxiliary info.
5 5
6mod structure; 6mod structure;
7mod typing;
8 7
9use rustc_hash::FxHashSet; 8use ra_syntax::TextRange;
10use ra_text_edit::TextEditBuilder;
11use ra_syntax::{
12 SourceFile, SyntaxNode, TextRange, TextUnit, Direction,
13 algo::find_leaf_at_offset,
14 SyntaxKind::{self, *},
15 ast::{self, AstNode},
16};
17 9
18pub use crate::{ 10pub use crate::{
19 structure::{file_structure, StructureNode}, 11 structure::{file_structure, StructureNode},
20 typing::{on_enter, on_dot_typed, on_eq_typed},
21}; 12};
22
23#[derive(Debug)]
24pub struct LocalEdit {
25 pub label: String,
26 pub edit: ra_text_edit::TextEdit,
27 pub cursor_position: Option<TextUnit>,
28}
29
30#[derive(Debug)]
31pub struct HighlightedRange {
32 pub range: TextRange,
33 pub tag: &'static str,
34}
35
36#[derive(Debug, Copy, Clone)]
37pub enum Severity {
38 Error,
39 WeakWarning,
40}
41
42#[derive(Debug)]
43pub struct Diagnostic {
44 pub range: TextRange,
45 pub msg: String,
46 pub severity: Severity,
47 pub fix: Option<LocalEdit>,
48}
49
50pub fn matching_brace(file: &SourceFile, offset: TextUnit) -> Option<TextUnit> {
51 const BRACES: &[SyntaxKind] =
52 &[L_CURLY, R_CURLY, L_BRACK, R_BRACK, L_PAREN, R_PAREN, L_ANGLE, R_ANGLE];
53 let (brace_node, brace_idx) = find_leaf_at_offset(file.syntax(), offset)
54 .filter_map(|node| {
55 let idx = BRACES.iter().position(|&brace| brace == node.kind())?;
56 Some((node, idx))
57 })
58 .next()?;
59 let parent = brace_node.parent()?;
60 let matching_kind = BRACES[brace_idx ^ 1];
61 let matching_node = parent.children().find(|node| node.kind() == matching_kind)?;
62 Some(matching_node.range().start())
63}
64
65pub fn highlight(root: &SyntaxNode) -> Vec<HighlightedRange> {
66 // Visited nodes to handle highlighting priorities
67 let mut highlighted = FxHashSet::default();
68 let mut res = Vec::new();
69 for node in root.descendants() {
70 if highlighted.contains(&node) {
71 continue;
72 }
73 let tag = match node.kind() {
74 COMMENT => "comment",
75 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => "string",
76 ATTR => "attribute",
77 NAME_REF => "text",
78 NAME => "function",
79 INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal",
80 LIFETIME => "parameter",
81 k if k.is_keyword() => "keyword",
82 _ => {
83 if let Some(macro_call) = ast::MacroCall::cast(node) {
84 if let Some(path) = macro_call.path() {
85 if let Some(segment) = path.segment() {
86 if let Some(name_ref) = segment.name_ref() {
87 highlighted.insert(name_ref.syntax());
88 let range_start = name_ref.syntax().range().start();
89 let mut range_end = name_ref.syntax().range().end();
90 for sibling in path.syntax().siblings(Direction::Next) {
91 match sibling.kind() {
92 EXCL | IDENT => range_end = sibling.range().end(),
93 _ => (),
94 }
95 }
96 res.push(HighlightedRange {
97 range: TextRange::from_to(range_start, range_end),
98 tag: "macro",
99 })
100 }
101 }
102 }
103 }
104 continue;
105 }
106 };
107 res.push(HighlightedRange { range: node.range(), tag })
108 }
109 res
110}
111
112#[cfg(test)]
113mod tests {
114 use ra_syntax::AstNode;
115 use insta::assert_debug_snapshot_matches;
116
117 use test_utils::{add_cursor, assert_eq_text, extract_offset};
118
119 use super::*;
120
121 #[test]
122 fn test_highlighting() {
123 let file = SourceFile::parse(
124 r#"
125// comment
126fn main() {}
127 println!("Hello, {}!", 92);
128"#,
129 );
130 let hls = highlight(file.syntax());
131 assert_debug_snapshot_matches!("highlighting", hls);
132 }
133
134 #[test]
135 fn test_matching_brace() {
136 fn do_check(before: &str, after: &str) {
137 let (pos, before) = extract_offset(before);
138 let file = SourceFile::parse(&before);
139 let new_pos = match matching_brace(&file, pos) {
140 None => pos,
141 Some(pos) => pos,
142 };
143 let actual = add_cursor(&before, new_pos);
144 assert_eq_text!(after, &actual);
145 }
146
147 do_check("struct Foo { a: i32, }<|>", "struct Foo <|>{ a: i32, }");
148 }
149
150}
diff --git a/crates/ra_ide_api_light/src/snapshots/tests__highlighting.snap b/crates/ra_ide_api_light/src/snapshots/tests__highlighting.snap
deleted file mode 100644
index ef306a7a0..000000000
--- a/crates/ra_ide_api_light/src/snapshots/tests__highlighting.snap
+++ /dev/null
@@ -1,32 +0,0 @@
1---
2created: "2019-01-22T14:45:01.959724300+00:00"
3creator: [email protected]
4expression: hls
5source: "crates\\ra_ide_api_light\\src\\lib.rs"
6---
7[
8 HighlightedRange {
9 range: [1; 11),
10 tag: "comment"
11 },
12 HighlightedRange {
13 range: [12; 14),
14 tag: "keyword"
15 },
16 HighlightedRange {
17 range: [15; 19),
18 tag: "function"
19 },
20 HighlightedRange {
21 range: [29; 37),
22 tag: "macro"
23 },
24 HighlightedRange {
25 range: [38; 50),
26 tag: "string"
27 },
28 HighlightedRange {
29 range: [52; 54),
30 tag: "literal"
31 }
32]
diff --git a/crates/ra_ide_api_light/src/typing.rs b/crates/ra_ide_api_light/src/typing.rs
deleted file mode 100644
index c69270333..000000000
--- a/crates/ra_ide_api_light/src/typing.rs
+++ /dev/null
@@ -1,407 +0,0 @@
1use ra_syntax::{
2 AstNode, SourceFile, SyntaxKind::*,
3 SyntaxNode, TextUnit, TextRange,
4 algo::{find_node_at_offset, find_leaf_at_offset, LeafAtOffset},
5 ast::{self, AstToken},
6};
7use ra_fmt::leading_indent;
8use crate::{LocalEdit, TextEditBuilder};
9
10pub fn on_enter(file: &SourceFile, offset: TextUnit) -> Option<LocalEdit> {
11 let comment =
12 find_leaf_at_offset(file.syntax(), offset).left_biased().and_then(ast::Comment::cast)?;
13
14 if let ast::CommentFlavor::Multiline = comment.flavor() {
15 return None;
16 }
17
18 let prefix = comment.prefix();
19 if offset < comment.syntax().range().start() + TextUnit::of_str(prefix) + TextUnit::from(1) {
20 return None;
21 }
22
23 let indent = node_indent(file, comment.syntax())?;
24 let inserted = format!("\n{}{} ", indent, prefix);
25 let cursor_position = offset + TextUnit::of_str(&inserted);
26 let mut edit = TextEditBuilder::default();
27 edit.insert(offset, inserted);
28 Some(LocalEdit {
29 label: "on enter".to_string(),
30 edit: edit.finish(),
31 cursor_position: Some(cursor_position),
32 })
33}
34
35fn node_indent<'a>(file: &'a SourceFile, node: &SyntaxNode) -> Option<&'a str> {
36 let ws = match find_leaf_at_offset(file.syntax(), node.range().start()) {
37 LeafAtOffset::Between(l, r) => {
38 assert!(r == node);
39 l
40 }
41 LeafAtOffset::Single(n) => {
42 assert!(n == node);
43 return Some("");
44 }
45 LeafAtOffset::None => unreachable!(),
46 };
47 if ws.kind() != WHITESPACE {
48 return None;
49 }
50 let text = ws.leaf_text().unwrap();
51 let pos = text.as_str().rfind('\n').map(|it| it + 1).unwrap_or(0);
52 Some(&text[pos..])
53}
54
55pub fn on_eq_typed(file: &SourceFile, eq_offset: TextUnit) -> Option<LocalEdit> {
56 assert_eq!(file.syntax().text().char_at(eq_offset), Some('='));
57 let let_stmt: &ast::LetStmt = find_node_at_offset(file.syntax(), eq_offset)?;
58 if let_stmt.has_semi() {
59 return None;
60 }
61 if let Some(expr) = let_stmt.initializer() {
62 let expr_range = expr.syntax().range();
63 if expr_range.contains(eq_offset) && eq_offset != expr_range.start() {
64 return None;
65 }
66 if file.syntax().text().slice(eq_offset..expr_range.start()).contains('\n') {
67 return None;
68 }
69 } else {
70 return None;
71 }
72 let offset = let_stmt.syntax().range().end();
73 let mut edit = TextEditBuilder::default();
74 edit.insert(offset, ";".to_string());
75 Some(LocalEdit {
76 label: "add semicolon".to_string(),
77 edit: edit.finish(),
78 cursor_position: None,
79 })
80}
81
82pub fn on_dot_typed(file: &SourceFile, dot_offset: TextUnit) -> Option<LocalEdit> {
83 assert_eq!(file.syntax().text().char_at(dot_offset), Some('.'));
84
85 let whitespace = find_leaf_at_offset(file.syntax(), dot_offset)
86 .left_biased()
87 .and_then(ast::Whitespace::cast)?;
88
89 let current_indent = {
90 let text = whitespace.text();
91 let newline = text.rfind('\n')?;
92 &text[newline + 1..]
93 };
94 let current_indent_len = TextUnit::of_str(current_indent);
95
96 // Make sure dot is a part of call chain
97 let field_expr = whitespace.syntax().parent().and_then(ast::FieldExpr::cast)?;
98 let prev_indent = leading_indent(field_expr.syntax())?;
99 let target_indent = format!(" {}", prev_indent);
100 let target_indent_len = TextUnit::of_str(&target_indent);
101 if current_indent_len == target_indent_len {
102 return None;
103 }
104 let mut edit = TextEditBuilder::default();
105 edit.replace(
106 TextRange::from_to(dot_offset - current_indent_len, dot_offset),
107 target_indent.into(),
108 );
109 let res = LocalEdit {
110 label: "reindent dot".to_string(),
111 edit: edit.finish(),
112 cursor_position: Some(
113 dot_offset + target_indent_len - current_indent_len + TextUnit::of_char('.'),
114 ),
115 };
116 Some(res)
117}
118
119#[cfg(test)]
120mod tests {
121 use test_utils::{add_cursor, assert_eq_text, extract_offset};
122
123 use super::*;
124
125 #[test]
126 fn test_on_eq_typed() {
127 fn type_eq(before: &str, after: &str) {
128 let (offset, before) = extract_offset(before);
129 let mut edit = TextEditBuilder::default();
130 edit.insert(offset, "=".to_string());
131 let before = edit.finish().apply(&before);
132 let file = SourceFile::parse(&before);
133 if let Some(result) = on_eq_typed(&file, offset) {
134 let actual = result.edit.apply(&before);
135 assert_eq_text!(after, &actual);
136 } else {
137 assert_eq_text!(&before, after)
138 };
139 }
140
141 // do_check(r"
142 // fn foo() {
143 // let foo =<|>
144 // }
145 // ", r"
146 // fn foo() {
147 // let foo =;
148 // }
149 // ");
150 type_eq(
151 r"
152fn foo() {
153 let foo <|> 1 + 1
154}
155",
156 r"
157fn foo() {
158 let foo = 1 + 1;
159}
160",
161 );
162 // do_check(r"
163 // fn foo() {
164 // let foo =<|>
165 // let bar = 1;
166 // }
167 // ", r"
168 // fn foo() {
169 // let foo =;
170 // let bar = 1;
171 // }
172 // ");
173 }
174
175 fn type_dot(before: &str, after: &str) {
176 let (offset, before) = extract_offset(before);
177 let mut edit = TextEditBuilder::default();
178 edit.insert(offset, ".".to_string());
179 let before = edit.finish().apply(&before);
180 let file = SourceFile::parse(&before);
181 if let Some(result) = on_dot_typed(&file, offset) {
182 let actual = result.edit.apply(&before);
183 assert_eq_text!(after, &actual);
184 } else {
185 assert_eq_text!(&before, after)
186 };
187 }
188
189 #[test]
190 fn indents_new_chain_call() {
191 type_dot(
192 r"
193 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
194 self.child_impl(db, name)
195 <|>
196 }
197 ",
198 r"
199 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
200 self.child_impl(db, name)
201 .
202 }
203 ",
204 );
205 type_dot(
206 r"
207 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
208 self.child_impl(db, name)
209 <|>
210 }
211 ",
212 r"
213 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
214 self.child_impl(db, name)
215 .
216 }
217 ",
218 )
219 }
220
221 #[test]
222 fn indents_new_chain_call_with_semi() {
223 type_dot(
224 r"
225 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
226 self.child_impl(db, name)
227 <|>;
228 }
229 ",
230 r"
231 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
232 self.child_impl(db, name)
233 .;
234 }
235 ",
236 );
237 type_dot(
238 r"
239 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
240 self.child_impl(db, name)
241 <|>;
242 }
243 ",
244 r"
245 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
246 self.child_impl(db, name)
247 .;
248 }
249 ",
250 )
251 }
252
253 #[test]
254 fn indents_continued_chain_call() {
255 type_dot(
256 r"
257 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
258 self.child_impl(db, name)
259 .first()
260 <|>
261 }
262 ",
263 r"
264 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
265 self.child_impl(db, name)
266 .first()
267 .
268 }
269 ",
270 );
271 type_dot(
272 r"
273 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
274 self.child_impl(db, name)
275 .first()
276 <|>
277 }
278 ",
279 r"
280 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
281 self.child_impl(db, name)
282 .first()
283 .
284 }
285 ",
286 );
287 }
288
289 #[test]
290 fn indents_middle_of_chain_call() {
291 type_dot(
292 r"
293 fn source_impl() {
294 let var = enum_defvariant_list().unwrap()
295 <|>
296 .nth(92)
297 .unwrap();
298 }
299 ",
300 r"
301 fn source_impl() {
302 let var = enum_defvariant_list().unwrap()
303 .
304 .nth(92)
305 .unwrap();
306 }
307 ",
308 );
309 type_dot(
310 r"
311 fn source_impl() {
312 let var = enum_defvariant_list().unwrap()
313 <|>
314 .nth(92)
315 .unwrap();
316 }
317 ",
318 r"
319 fn source_impl() {
320 let var = enum_defvariant_list().unwrap()
321 .
322 .nth(92)
323 .unwrap();
324 }
325 ",
326 );
327 }
328
329 #[test]
330 fn dont_indent_freestanding_dot() {
331 type_dot(
332 r"
333 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
334 <|>
335 }
336 ",
337 r"
338 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
339 .
340 }
341 ",
342 );
343 type_dot(
344 r"
345 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
346 <|>
347 }
348 ",
349 r"
350 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
351 .
352 }
353 ",
354 );
355 }
356
357 #[test]
358 fn test_on_enter() {
359 fn apply_on_enter(before: &str) -> Option<String> {
360 let (offset, before) = extract_offset(before);
361 let file = SourceFile::parse(&before);
362 let result = on_enter(&file, offset)?;
363 let actual = result.edit.apply(&before);
364 let actual = add_cursor(&actual, result.cursor_position.unwrap());
365 Some(actual)
366 }
367
368 fn do_check(before: &str, after: &str) {
369 let actual = apply_on_enter(before).unwrap();
370 assert_eq_text!(after, &actual);
371 }
372
373 fn do_check_noop(text: &str) {
374 assert!(apply_on_enter(text).is_none())
375 }
376
377 do_check(
378 r"
379/// Some docs<|>
380fn foo() {
381}
382",
383 r"
384/// Some docs
385/// <|>
386fn foo() {
387}
388",
389 );
390 do_check(
391 r"
392impl S {
393 /// Some<|> docs.
394 fn foo() {}
395}
396",
397 r"
398impl S {
399 /// Some
400 /// <|> docs.
401 fn foo() {}
402}
403",
404 );
405 do_check_noop(r"<|>//! docz");
406 }
407}