aboutsummaryrefslogtreecommitdiff
path: root/crates/libeditor/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/libeditor/src')
-rw-r--r--crates/libeditor/src/completion.rs123
1 files changed, 105 insertions, 18 deletions
diff --git a/crates/libeditor/src/completion.rs b/crates/libeditor/src/completion.rs
index 7e8669822..65527db62 100644
--- a/crates/libeditor/src/completion.rs
+++ b/crates/libeditor/src/completion.rs
@@ -12,7 +12,8 @@ use {
12}; 12};
13 13
14#[derive(Debug)] 14#[derive(Debug)]
15pub struct CompletionItem { 15pub struct
16 CompletionItem {
16 pub name: String, 17 pub name: String,
17 pub snippet: Option<String> 18 pub snippet: Option<String>
18} 19}
@@ -25,10 +26,17 @@ pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionI
25 file.incremental_reparse(&edit)? 26 file.incremental_reparse(&edit)?
26 }; 27 };
27 let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), offset)?; 28 let name_ref = find_node_at_offset::<ast::NameRef>(file.syntax(), offset)?;
29 if !is_ident_expr(name_ref) {
30 return None;
31 }
32
28 let mut res = Vec::new(); 33 let mut res = Vec::new();
29 if let Some(fn_def) = ancestors(name_ref.syntax()).filter_map(ast::FnDef::cast).next() { 34 if let Some(fn_def) = ancestors(name_ref.syntax()).filter_map(ast::FnDef::cast).next() {
35 complete_keywords(&file, Some(fn_def), name_ref, &mut res);
30 let scopes = FnScopes::new(fn_def); 36 let scopes = FnScopes::new(fn_def);
31 complete_fn(name_ref, &scopes, &mut res); 37 complete_fn(name_ref, &scopes, &mut res);
38 } else {
39 complete_keywords(&file, None, name_ref, &mut res);
32 } 40 }
33 if let Some(root) = ancestors(name_ref.syntax()).filter_map(ast::Root::cast).next() { 41 if let Some(root) = ancestors(name_ref.syntax()).filter_map(ast::Root::cast).next() {
34 let scope = ModuleScope::new(root); 42 let scope = ModuleScope::new(root);
@@ -43,6 +51,42 @@ pub fn scope_completion(file: &File, offset: TextUnit) -> Option<Vec<CompletionI
43 Some(res) 51 Some(res)
44} 52}
45 53
54fn is_ident_expr(name_ref: ast::NameRef) -> bool {
55 match ancestors(name_ref.syntax()).filter_map(ast::Expr::cast).next() {
56 None => false,
57 Some(expr) => {
58 expr.syntax().range() == name_ref.syntax().range()
59 }
60 }
61}
62
63fn complete_keywords(file: &File, fn_def: Option<ast::FnDef>, name_ref: ast::NameRef, acc: &mut Vec<CompletionItem>) {
64 acc.push(keyword("if", "if $0 { }"));
65 acc.push(keyword("match", "match $0 { }"));
66 acc.push(keyword("while", "while $0 { }"));
67 acc.push(keyword("loop", "loop {$0}"));
68
69 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
70 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
71 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
72 acc.push(keyword("else", "else {$0}"));
73 acc.push(keyword("else if", "else if $0 { }"));
74 }
75 }
76 }
77
78 // if let Some(fn_def) = fn_def {
79 // acc.push(keyword("return", ""))
80 // }
81
82 fn keyword(kw: &str, snip: &str) -> CompletionItem {
83 CompletionItem {
84 name: kw.to_string(),
85 snippet: Some(snip.to_string()),
86 }
87 }
88}
89
46fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) { 90fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) {
47 acc.extend( 91 acc.extend(
48 scopes.scope_chain(name_ref.syntax()) 92 scopes.scope_chain(name_ref.syntax())
@@ -59,29 +103,44 @@ mod tests {
59 use super::*; 103 use super::*;
60 use test_utils::{assert_eq_dbg, extract_offset}; 104 use test_utils::{assert_eq_dbg, extract_offset};
61 105
62 fn do_check(code: &str, expected_completions: &str) { 106 fn check_scope_completion(code: &str, expected_completions: &str) {
63 let (off, code) = extract_offset(&code); 107 let (off, code) = extract_offset(&code);
64 let file = File::parse(&code); 108 let file = File::parse(&code);
65 let completions = scope_completion(&file, off).unwrap(); 109 let completions = scope_completion(&file, off)
110 .unwrap()
111 .into_iter()
112 .filter(|c| c.snippet.is_none())
113 .collect::<Vec<_>>();
114 assert_eq_dbg(expected_completions, &completions);
115 }
116
117 fn check_snippet_completion(code: &str, expected_completions: &str) {
118 let (off, code) = extract_offset(&code);
119 let file = File::parse(&code);
120 let completions = scope_completion(&file, off)
121 .unwrap()
122 .into_iter()
123 .filter(|c| c.snippet.is_some())
124 .collect::<Vec<_>>();
66 assert_eq_dbg(expected_completions, &completions); 125 assert_eq_dbg(expected_completions, &completions);
67 } 126 }
68 127
69 #[test] 128 #[test]
70 fn test_completion_let_scope() { 129 fn test_completion_let_scope() {
71 do_check(r" 130 check_scope_completion(r"
72 fn quux(x: i32) { 131 fn quux(x: i32) {
73 let y = 92; 132 let y = 92;
74 1 + <|>; 133 1 + <|>;
75 let z = (); 134 let z = ();
76 } 135 }
77 ", r#"[CompletionItem { name: "y" }, 136 ", r#"[CompletionItem { name: "y", snippet: None },
78 CompletionItem { name: "x" }, 137 CompletionItem { name: "x", snippet: None },
79 CompletionItem { name: "quux" }]"#); 138 CompletionItem { name: "quux", snippet: None }]"#);
80 } 139 }
81 140
82 #[test] 141 #[test]
83 fn test_completion_if_let_scope() { 142 fn test_completion_if_let_scope() {
84 do_check(r" 143 check_scope_completion(r"
85 fn quux() { 144 fn quux() {
86 if let Some(x) = foo() { 145 if let Some(x) = foo() {
87 let y = 92; 146 let y = 92;
@@ -91,33 +150,61 @@ mod tests {
91 1 + <|> 150 1 + <|>
92 } 151 }
93 } 152 }
94 ", r#"[CompletionItem { name: "b" }, 153 ", r#"[CompletionItem { name: "b", snippet: None },
95 CompletionItem { name: "a" }, 154 CompletionItem { name: "a", snippet: None },
96 CompletionItem { name: "quux" }]"#); 155 CompletionItem { name: "quux", snippet: None }]"#);
97 } 156 }
98 157
99 #[test] 158 #[test]
100 fn test_completion_for_scope() { 159 fn test_completion_for_scope() {
101 do_check(r" 160 check_scope_completion(r"
102 fn quux() { 161 fn quux() {
103 for x in &[1, 2, 3] { 162 for x in &[1, 2, 3] {
104 <|> 163 <|>
105 } 164 }
106 } 165 }
107 ", r#"[CompletionItem { name: "x" }, 166 ", r#"[CompletionItem { name: "x", snippet: None },
108 CompletionItem { name: "quux" }]"#); 167 CompletionItem { name: "quux", snippet: None }]"#);
109 } 168 }
110 169
111 #[test] 170 #[test]
112 fn test_completion_mod_scope() { 171 fn test_completion_mod_scope() {
113 do_check(r" 172 check_scope_completion(r"
114 struct Foo; 173 struct Foo;
115 enum Baz {} 174 enum Baz {}
116 fn quux() { 175 fn quux() {
117 <|> 176 <|>
118 } 177 }
119 ", r#"[CompletionItem { name: "Foo" }, 178 ", r#"[CompletionItem { name: "Foo", snippet: None },
120 CompletionItem { name: "Baz" }, 179 CompletionItem { name: "Baz", snippet: None },
121 CompletionItem { name: "quux" }]"#); 180 CompletionItem { name: "quux", snippet: None }]"#);
181 }
182
183 #[test]
184 fn test_completion_kewords() {
185 check_snippet_completion(r"
186 fn quux() {
187 <|>
188 }
189 ", r#"[CompletionItem { name: "if", snippet: Some("if $0 { }") },
190 CompletionItem { name: "match", snippet: Some("match $0 { }") },
191 CompletionItem { name: "while", snippet: Some("while $0 { }") },
192 CompletionItem { name: "loop", snippet: Some("loop {$0}") }]"#);
193 }
194
195 #[test]
196 fn test_completion_else() {
197 check_snippet_completion(r"
198 fn quux() {
199 if true {
200 ()
201 } <|>
202 }
203 ", r#"[CompletionItem { name: "if", snippet: Some("if $0 { }") },
204 CompletionItem { name: "match", snippet: Some("match $0 { }") },
205 CompletionItem { name: "while", snippet: Some("while $0 { }") },
206 CompletionItem { name: "loop", snippet: Some("loop {$0}") },
207 CompletionItem { name: "else", snippet: Some("else {$0}") },
208 CompletionItem { name: "else if", snippet: Some("else if $0 { }") }]"#);
122 } 209 }
123} 210}