aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2018-10-15 17:41:57 +0100
committerbors[bot] <bors[bot]@users.noreply.github.com>2018-10-15 17:41:57 +0100
commite031b65f93f73164a5729cf81ff60299708bc931 (patch)
tree1a891b75af3b436549381e8726c48ec5028a8341
parent8cec1d986161298dbe9cc53f5477e87bfe5d0f47 (diff)
parentc9909f42ba4adf55b1e73e7118b48f1b10c80ac6 (diff)
Merge #110
110: Signature help r=matklad a=kjeremy @matklad Once this is in shape I would like to add tests. I think a separate PR should be done for returning documentation information and markdown. Fixes #102 Co-authored-by: Jeremy A. Kolb <[email protected]>
-rw-r--r--crates/ra_analysis/src/descriptors.rs56
-rw-r--r--crates/ra_analysis/src/imp.rs112
-rw-r--r--crates/ra_analysis/src/lib.rs6
-rw-r--r--crates/ra_analysis/tests/tests.rs102
-rw-r--r--crates/ra_lsp_server/src/caps.rs5
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs36
-rw-r--r--crates/ra_lsp_server/src/main_loop/mod.rs1
-rw-r--r--crates/ra_lsp_server/src/req.rs1
-rw-r--r--crates/ra_syntax/src/ast/generated.rs6
-rw-r--r--crates/ra_syntax/src/grammar.ron2
10 files changed, 315 insertions, 12 deletions
diff --git a/crates/ra_analysis/src/descriptors.rs b/crates/ra_analysis/src/descriptors.rs
index 0731b5572..faf945a41 100644
--- a/crates/ra_analysis/src/descriptors.rs
+++ b/crates/ra_analysis/src/descriptors.rs
@@ -4,7 +4,8 @@ use std::{
4use relative_path::RelativePathBuf; 4use relative_path::RelativePathBuf;
5use ra_syntax::{ 5use ra_syntax::{
6 SmolStr, 6 SmolStr,
7 ast::{self, NameOwner}, 7 ast::{self, NameOwner, AstNode},
8 text_utils::is_subrange
8}; 9};
9use { 10use {
10 FileId, 11 FileId,
@@ -218,3 +219,56 @@ fn resolve_submodule(
218 } 219 }
219 (points_to, problem) 220 (points_to, problem)
220} 221}
222
223#[derive(Debug, Clone)]
224pub struct FnDescriptor {
225 pub name: String,
226 pub label : String,
227 pub ret_type: Option<String>,
228 pub params: Vec<String>,
229}
230
231impl FnDescriptor {
232 pub fn new(node: ast::FnDef) -> Option<Self> {
233 let name = node.name()?.text().to_string();
234
235 // Strip the body out for the label.
236 let label : String = if let Some(body) = node.body() {
237 let body_range = body.syntax().range();
238 let label : String = node.syntax().children()
239 .filter(|child| !is_subrange(body_range, child.range()))
240 .map(|node| node.text().to_string())
241 .collect();
242 label
243 } else {
244 node.syntax().text().to_string()
245 };
246
247 let params = FnDescriptor::param_list(node);
248 let ret_type = node.ret_type().map(|r| r.syntax().text().to_string());
249
250 Some(FnDescriptor {
251 name,
252 ret_type,
253 params,
254 label
255 })
256 }
257
258 fn param_list(node: ast::FnDef) -> Vec<String> {
259 let mut res = vec![];
260 if let Some(param_list) = node.param_list() {
261 if let Some(self_param) = param_list.self_param() {
262 res.push(self_param.syntax().text().to_string())
263 }
264
265 // Maybe use param.pat here? See if we can just extract the name?
266 //res.extend(param_list.params().map(|p| p.syntax().text().to_string()));
267 res.extend(param_list.params()
268 .filter_map(|p| p.pat())
269 .map(|pat| pat.syntax().text().to_string())
270 );
271 }
272 res
273 }
274} \ No newline at end of file
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs
index 47bc0032b..aad54b977 100644
--- a/crates/ra_analysis/src/imp.rs
+++ b/crates/ra_analysis/src/imp.rs
@@ -12,19 +12,18 @@ use relative_path::RelativePath;
12use rustc_hash::FxHashSet; 12use rustc_hash::FxHashSet;
13use ra_editor::{self, FileSymbol, LineIndex, find_node_at_offset, LocalEdit, resolve_local_name}; 13use ra_editor::{self, FileSymbol, LineIndex, find_node_at_offset, LocalEdit, resolve_local_name};
14use ra_syntax::{ 14use ra_syntax::{
15 TextUnit, TextRange, SmolStr, File, AstNode, 15 TextUnit, TextRange, SmolStr, File, AstNode, SyntaxNodeRef,
16 SyntaxKind::*, 16 SyntaxKind::*,
17 ast::{self, NameOwner}, 17 ast::{self, NameOwner, ArgListOwner, Expr},
18}; 18};
19 19
20use { 20use {
21 FileId, FileResolver, Query, Diagnostic, SourceChange, SourceFileEdit, Position, FileSystemEdit, 21 FileId, FileResolver, Query, Diagnostic, SourceChange, SourceFileEdit, Position, FileSystemEdit,
22 JobToken, CrateGraph, CrateId, 22 JobToken, CrateGraph, CrateId,
23 roots::{SourceRoot, ReadonlySourceRoot, WritableSourceRoot}, 23 roots::{SourceRoot, ReadonlySourceRoot, WritableSourceRoot},
24 descriptors::{ModuleTreeDescriptor, Problem}, 24 descriptors::{FnDescriptor, ModuleTreeDescriptor, Problem},
25}; 25};
26 26
27
28#[derive(Clone, Debug)] 27#[derive(Clone, Debug)]
29pub(crate) struct FileResolverImp { 28pub(crate) struct FileResolverImp {
30 inner: Arc<FileResolver> 29 inner: Arc<FileResolver>
@@ -306,6 +305,68 @@ impl AnalysisImpl {
306 .collect() 305 .collect()
307 } 306 }
308 307
308 pub fn resolve_callable(&self, file_id: FileId, offset: TextUnit, token: &JobToken)
309 -> Option<(FnDescriptor, Option<usize>)> {
310
311 let root = self.root(file_id);
312 let file = root.syntax(file_id);
313 let syntax = file.syntax();
314
315 // Find the calling expression and it's NameRef
316 let calling_node = FnCallNode::with_node(syntax, offset)?;
317 let name_ref = calling_node.name_ref()?;
318
319 // Resolve the function's NameRef (NOTE: this isn't entirely accurate).
320 let file_symbols = self.index_resolve(name_ref, token);
321 for (_, fs) in file_symbols {
322 if fs.kind == FN_DEF {
323 if let Some(fn_def) = find_node_at_offset(syntax, fs.node_range.start()) {
324 if let Some(descriptor) = FnDescriptor::new(fn_def) {
325 // If we have a calling expression let's find which argument we are on
326 let mut current_parameter = None;
327
328 let num_params = descriptor.params.len();
329 let has_self = fn_def.param_list()
330 .and_then(|l| l.self_param())
331 .is_some();
332
333 if num_params == 1 {
334 if !has_self {
335 current_parameter = Some(1);
336 }
337 } else if num_params > 1 {
338 // Count how many parameters into the call we are.
339 // TODO: This is best effort for now and should be fixed at some point.
340 // It may be better to see where we are in the arg_list and then check
341 // where offset is in that list (or beyond).
342 // Revisit this after we get documentation comments in.
343 if let Some(ref arg_list) = calling_node.arg_list() {
344 let start = arg_list.syntax().range().start();
345
346 let range_search = TextRange::from_to(start, offset);
347 let mut commas: usize = arg_list.syntax().text()
348 .slice(range_search).to_string()
349 .matches(",")
350 .count();
351
352 // If we have a method call eat the first param since it's just self.
353 if has_self {
354 commas = commas + 1;
355 }
356
357 current_parameter = Some(commas);
358 }
359 }
360
361 return Some((descriptor, current_parameter));
362 }
363 }
364 }
365 }
366
367 None
368 }
369
309 fn index_resolve(&self, name_ref: ast::NameRef, token: &JobToken) -> Vec<(FileId, FileSymbol)> { 370 fn index_resolve(&self, name_ref: ast::NameRef, token: &JobToken) -> Vec<(FileId, FileSymbol)> {
310 let name = name_ref.text(); 371 let name = name_ref.text();
311 let mut query = Query::new(name.to_string()); 372 let mut query = Query::new(name.to_string());
@@ -355,3 +416,46 @@ impl CrateGraph {
355 Some(crate_id) 416 Some(crate_id)
356 } 417 }
357} 418}
419
420enum FnCallNode<'a> {
421 CallExpr(ast::CallExpr<'a>),
422 MethodCallExpr(ast::MethodCallExpr<'a>)
423}
424
425impl<'a> FnCallNode<'a> {
426 pub fn with_node(syntax: SyntaxNodeRef, offset: TextUnit) -> Option<FnCallNode> {
427 if let Some(expr) = find_node_at_offset::<ast::CallExpr>(syntax, offset) {
428 return Some(FnCallNode::CallExpr(expr));
429 }
430 if let Some(expr) = find_node_at_offset::<ast::MethodCallExpr>(syntax, offset) {
431 return Some(FnCallNode::MethodCallExpr(expr));
432 }
433 None
434 }
435
436 pub fn name_ref(&self) -> Option<ast::NameRef> {
437 match *self {
438 FnCallNode::CallExpr(call_expr) => {
439 Some(match call_expr.expr()? {
440 Expr::PathExpr(path_expr) => {
441 path_expr.path()?.segment()?.name_ref()?
442 },
443 _ => return None
444 })
445 },
446
447 FnCallNode::MethodCallExpr(call_expr) => {
448 call_expr.syntax().children()
449 .filter_map(ast::NameRef::cast)
450 .nth(0)
451 }
452 }
453 }
454
455 pub fn arg_list(&self) -> Option<ast::ArgList> {
456 match *self {
457 FnCallNode::CallExpr(expr) => expr.arg_list(),
458 FnCallNode::MethodCallExpr(expr) => expr.arg_list()
459 }
460 }
461} \ No newline at end of file
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs
index 849fd93e4..1aca72ae0 100644
--- a/crates/ra_analysis/src/lib.rs
+++ b/crates/ra_analysis/src/lib.rs
@@ -38,6 +38,7 @@ pub use ra_editor::{
38 Fold, FoldKind 38 Fold, FoldKind
39}; 39};
40pub use job::{JobToken, JobHandle}; 40pub use job::{JobToken, JobHandle};
41pub use descriptors::FnDescriptor;
41 42
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 43#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
43pub struct FileId(pub u32); 44pub struct FileId(pub u32);
@@ -236,6 +237,11 @@ impl Analysis {
236 let file = self.imp.file_syntax(file_id); 237 let file = self.imp.file_syntax(file_id);
237 ra_editor::folding_ranges(&file) 238 ra_editor::folding_ranges(&file)
238 } 239 }
240
241 pub fn resolve_callable(&self, file_id: FileId, offset: TextUnit, token: &JobToken)
242 -> Option<(FnDescriptor, Option<usize>)> {
243 self.imp.resolve_callable(file_id, offset, token)
244 }
239} 245}
240 246
241#[derive(Debug)] 247#[derive(Debug)]
diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs
index a886cd0ff..755640fb4 100644
--- a/crates/ra_analysis/tests/tests.rs
+++ b/crates/ra_analysis/tests/tests.rs
@@ -1,6 +1,8 @@
1extern crate relative_path; 1extern crate relative_path;
2extern crate ra_analysis; 2extern crate ra_analysis;
3extern crate rustc_hash; 3extern crate rustc_hash;
4extern crate ra_editor;
5extern crate ra_syntax;
4extern crate test_utils; 6extern crate test_utils;
5 7
6use std::{ 8use std::{
@@ -9,8 +11,8 @@ use std::{
9 11
10use rustc_hash::FxHashMap; 12use rustc_hash::FxHashMap;
11use relative_path::{RelativePath, RelativePathBuf}; 13use relative_path::{RelativePath, RelativePathBuf};
12use ra_analysis::{Analysis, AnalysisHost, FileId, FileResolver, JobHandle, CrateGraph, CrateId}; 14use ra_analysis::{Analysis, AnalysisHost, FileId, FileResolver, JobHandle, CrateGraph, CrateId, FnDescriptor};
13use test_utils::assert_eq_dbg; 15use test_utils::{assert_eq_dbg, extract_offset};
14 16
15#[derive(Debug)] 17#[derive(Debug)]
16struct FileMap(Vec<(FileId, RelativePathBuf)>); 18struct FileMap(Vec<(FileId, RelativePathBuf)>);
@@ -39,7 +41,7 @@ impl FileResolver for FileMap {
39 } 41 }
40} 42}
41 43
42fn analysis_host(files: &'static [(&'static str, &'static str)]) -> AnalysisHost { 44fn analysis_host(files: &[(&str, &str)]) -> AnalysisHost {
43 let mut host = AnalysisHost::new(); 45 let mut host = AnalysisHost::new();
44 let mut file_map = Vec::new(); 46 let mut file_map = Vec::new();
45 for (id, &(path, contents)) in files.iter().enumerate() { 47 for (id, &(path, contents)) in files.iter().enumerate() {
@@ -53,10 +55,20 @@ fn analysis_host(files: &'static [(&'static str, &'static str)]) -> AnalysisHost
53 host 55 host
54} 56}
55 57
56fn analysis(files: &'static [(&'static str, &'static str)]) -> Analysis { 58fn analysis(files: &[(&str, &str)]) -> Analysis {
57 analysis_host(files).analysis() 59 analysis_host(files).analysis()
58} 60}
59 61
62fn get_signature(text: &str) -> (FnDescriptor, Option<usize>) {
63 let (offset, code) = extract_offset(text);
64 let code = code.as_str();
65
66 let (_handle, token) = JobHandle::new();
67 let snap = analysis(&[("/lib.rs", code)]);
68
69 snap.resolve_callable(FileId(1), offset, &token).unwrap()
70}
71
60#[test] 72#[test]
61fn test_resolve_module() { 73fn test_resolve_module() {
62 let snap = analysis(&[ 74 let snap = analysis(&[
@@ -145,3 +157,85 @@ fn test_resolve_crate_root() {
145 vec![CrateId(1)], 157 vec![CrateId(1)],
146 ); 158 );
147} 159}
160
161#[test]
162fn test_fn_signature_two_args_first() {
163 let (desc, param) = get_signature(
164r#"fn foo(x: u32, y: u32) -> u32 {x + y}
165fn bar() { foo(<|>3, ); }"#);
166
167 assert_eq!(desc.name, "foo".to_string());
168 assert_eq!(desc.params, vec!("x".to_string(),"y".to_string()));
169 assert_eq!(desc.ret_type, Some("-> u32".into()));
170 assert_eq!(param, Some(0));
171}
172
173#[test]
174fn test_fn_signature_two_args_second() {
175 let (desc, param) = get_signature(
176 r#"fn foo(x: u32, y: u32) -> u32 {x + y}
177fn bar() { foo(3, <|>); }"#);
178
179 assert_eq!(desc.name, "foo".to_string());
180 assert_eq!(desc.params, vec!("x".to_string(),"y".to_string()));
181 assert_eq!(desc.ret_type, Some("-> u32".into()));
182 assert_eq!(param, Some(1));
183}
184
185#[test]
186fn test_fn_signature_for_impl() {
187 let (desc, param) = get_signature(
188r#"struct F; impl F { pub fn new() { F{}} }
189fn bar() {let _ : F = F::new(<|>);}"#);
190
191 assert_eq!(desc.name, "new".to_string());
192 assert_eq!(desc.params, Vec::<String>::new());
193 assert_eq!(desc.ret_type, None);
194 assert_eq!(param, None);
195}
196
197#[test]
198fn test_fn_signature_for_method_self() {
199 let (desc, param) = get_signature(
200r#"struct F;
201impl F {
202 pub fn new() -> F{
203 F{}
204 }
205
206 pub fn do_it(&self) {}
207}
208
209fn bar() {
210 let f : F = F::new();
211 f.do_it(<|>);
212}"#);
213
214 assert_eq!(desc.name, "do_it".to_string());
215 assert_eq!(desc.params, vec!["&self".to_string()]);
216 assert_eq!(desc.ret_type, None);
217 assert_eq!(param, None);
218}
219
220#[test]
221fn test_fn_signature_for_method_with_arg() {
222 let (desc, param) = get_signature(
223r#"struct F;
224impl F {
225 pub fn new() -> F{
226 F{}
227 }
228
229 pub fn do_it(&self, x: i32) {}
230}
231
232fn bar() {
233 let f : F = F::new();
234 f.do_it(<|>);
235}"#);
236
237 assert_eq!(desc.name, "do_it".to_string());
238 assert_eq!(desc.params, vec!["&self".to_string(), "x".to_string()]);
239 assert_eq!(desc.ret_type, None);
240 assert_eq!(param, Some(1));
241} \ No newline at end of file
diff --git a/crates/ra_lsp_server/src/caps.rs b/crates/ra_lsp_server/src/caps.rs
index 3c628f29c..5598ec75f 100644
--- a/crates/ra_lsp_server/src/caps.rs
+++ b/crates/ra_lsp_server/src/caps.rs
@@ -7,6 +7,7 @@ use languageserver_types::{
7 TextDocumentSyncKind, 7 TextDocumentSyncKind,
8 ExecuteCommandOptions, 8 ExecuteCommandOptions,
9 CompletionOptions, 9 CompletionOptions,
10 SignatureHelpOptions,
10 DocumentOnTypeFormattingOptions, 11 DocumentOnTypeFormattingOptions,
11}; 12};
12 13
@@ -26,7 +27,9 @@ pub fn server_capabilities() -> ServerCapabilities {
26 resolve_provider: None, 27 resolve_provider: None,
27 trigger_characters: None, 28 trigger_characters: None,
28 }), 29 }),
29 signature_help_provider: None, 30 signature_help_provider: Some(SignatureHelpOptions {
31 trigger_characters: Some(vec!["(".to_string(), ",".to_string()])
32 }),
30 definition_provider: Some(true), 33 definition_provider: Some(true),
31 type_definition_provider: None, 34 type_definition_provider: None,
32 implementation_provider: None, 35 implementation_provider: None,
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index ab8be15e9..f65e2a889 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -411,6 +411,42 @@ pub fn handle_folding_range(
411 Ok(res) 411 Ok(res)
412} 412}
413 413
414pub fn handle_signature_help(
415 world: ServerWorld,
416 params: req::TextDocumentPositionParams,
417 token: JobToken,
418) -> Result<Option<req::SignatureHelp>> {
419 use languageserver_types::{ParameterInformation, SignatureInformation};
420
421 let file_id = params.text_document.try_conv_with(&world)?;
422 let line_index = world.analysis().file_line_index(file_id);
423 let offset = params.position.conv_with(&line_index);
424
425 if let Some((descriptor, active_param)) = world.analysis().resolve_callable(file_id, offset, &token) {
426 let parameters : Vec<ParameterInformation> =
427 descriptor.params.iter().map(|param|
428 ParameterInformation {
429 label: param.clone(),
430 documentation: None
431 }
432 ).collect();
433
434 let sig_info = SignatureInformation {
435 label: descriptor.label,
436 documentation: None,
437 parameters: Some(parameters)
438 };
439
440 Ok(Some(req::SignatureHelp {
441 signatures: vec![sig_info],
442 active_signature: Some(0),
443 active_parameter: active_param.map(|a| a as u64)
444 }))
445 } else {
446 Ok(None)
447 }
448}
449
414pub fn handle_code_action( 450pub fn handle_code_action(
415 world: ServerWorld, 451 world: ServerWorld,
416 params: req::CodeActionParams, 452 params: req::CodeActionParams,
diff --git a/crates/ra_lsp_server/src/main_loop/mod.rs b/crates/ra_lsp_server/src/main_loop/mod.rs
index 402615e42..f4e7cfc33 100644
--- a/crates/ra_lsp_server/src/main_loop/mod.rs
+++ b/crates/ra_lsp_server/src/main_loop/mod.rs
@@ -255,6 +255,7 @@ fn on_request(
255 .on::<req::Completion>(handlers::handle_completion)? 255 .on::<req::Completion>(handlers::handle_completion)?
256 .on::<req::CodeActionRequest>(handlers::handle_code_action)? 256 .on::<req::CodeActionRequest>(handlers::handle_code_action)?
257 .on::<req::FoldingRangeRequest>(handlers::handle_folding_range)? 257 .on::<req::FoldingRangeRequest>(handlers::handle_folding_range)?
258 .on::<req::SignatureHelpRequest>(handlers::handle_signature_help)?
258 .finish(); 259 .finish();
259 match req { 260 match req {
260 Ok((id, handle)) => { 261 Ok((id, handle)) => {
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs
index f80957589..1630edf7f 100644
--- a/crates/ra_lsp_server/src/req.rs
+++ b/crates/ra_lsp_server/src/req.rs
@@ -14,6 +14,7 @@ pub use languageserver_types::{
14 CompletionParams, CompletionResponse, 14 CompletionParams, CompletionResponse,
15 DocumentOnTypeFormattingParams, 15 DocumentOnTypeFormattingParams,
16 TextDocumentEdit, 16 TextDocumentEdit,
17 SignatureHelp, Hover
17}; 18};
18 19
19pub enum SyntaxTree {} 20pub enum SyntaxTree {}
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs
index ef7b5b1a1..48c9038dc 100644
--- a/crates/ra_syntax/src/ast/generated.rs
+++ b/crates/ra_syntax/src/ast/generated.rs
@@ -1387,7 +1387,11 @@ impl<'a> AstNode<'a> for PathExpr<'a> {
1387 fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax } 1387 fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax }
1388} 1388}
1389 1389
1390impl<'a> PathExpr<'a> {} 1390impl<'a> PathExpr<'a> {
1391 pub fn path(self) -> Option<Path<'a>> {
1392 super::child_opt(self)
1393 }
1394}
1391 1395
1392// PathPat 1396// PathPat
1393#[derive(Debug, Clone, Copy)] 1397#[derive(Debug, Clone, Copy)]
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron
index 9da0c2c13..a904f7505 100644
--- a/crates/ra_syntax/src/grammar.ron
+++ b/crates/ra_syntax/src/grammar.ron
@@ -342,7 +342,7 @@ Grammar(
342 "TupleExpr": (), 342 "TupleExpr": (),
343 "ArrayExpr": (), 343 "ArrayExpr": (),
344 "ParenExpr": (), 344 "ParenExpr": (),
345 "PathExpr": (), 345 "PathExpr": (options: ["Path"]),
346 "LambdaExpr": ( 346 "LambdaExpr": (
347 options: [ 347 options: [
348 "ParamList", 348 "ParamList",