aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis/src
diff options
context:
space:
mode:
authorJeremy A. Kolb <[email protected]>2018-10-09 15:08:17 +0100
committerJeremy A. Kolb <[email protected]>2018-10-11 21:40:46 +0100
commitf8a2b533045757c42c206b2596448baf4737f1f0 (patch)
tree587e681ec5323d8deabcc6d533de033e7aa79be7 /crates/ra_analysis/src
parent2ba6f18586d02a6dbc32e0bea88f7b4236277ea1 (diff)
Language Server: textDocument/signatureHelp
Implements a pretty barebones function signature help mechanism in the language server. Users can use `Analysis::resolve_callback()` to get basic information about a call site. Fixes #102
Diffstat (limited to 'crates/ra_analysis/src')
-rw-r--r--crates/ra_analysis/src/descriptors.rs56
-rw-r--r--crates/ra_analysis/src/imp.rs114
-rw-r--r--crates/ra_analysis/src/lib.rs6
3 files changed, 171 insertions, 5 deletions
diff --git a/crates/ra_analysis/src/descriptors.rs b/crates/ra_analysis/src/descriptors.rs
index 0731b5572..4dcac1aa2 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, TypeParamsOwner},
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: Option<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) -> Self {
233 let name = node.name().map(|name| 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 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..9e3ae2b03 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,70 @@ 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 let descriptor = FnDescriptor::new(fn_def);
325
326 // If we have a calling expression let's find which argument we are on
327 let mut current_parameter = None;
328
329 let num_params = descriptor.params.len();
330 let has_self = fn_def.param_list()
331 .and_then(|l| l.self_param())
332 .is_some();
333
334
335 if num_params == 1 {
336 if !has_self {
337 current_parameter = Some(1);
338 }
339 }
340 else if num_params > 1 {
341 // Count how many parameters into the call we are.
342 // TODO: This is best effort for now and should be fixed at some point.
343 // It may be better to see where we are in the arg_list and then check
344 // where offset is in that list (or beyond).
345 // Revisit this after we get documentation comments in.
346 if let Some(ref arg_list) = calling_node.arg_list() {
347 let start = arg_list.syntax().range().start();
348
349 let range_search = TextRange::from_to(start, offset);
350 let mut commas : usize = arg_list.syntax().text()
351 .slice(range_search).to_string()
352 .matches(",")
353 .count();
354
355 // If we have a method call eat the first param since it's just self.
356 if has_self {
357 commas = commas + 1;
358 }
359
360 current_parameter = Some(commas);
361 }
362 }
363
364 return Some((descriptor, current_parameter));
365 }
366 }
367 }
368
369 None
370 }
371
309 fn index_resolve(&self, name_ref: ast::NameRef, token: &JobToken) -> Vec<(FileId, FileSymbol)> { 372 fn index_resolve(&self, name_ref: ast::NameRef, token: &JobToken) -> Vec<(FileId, FileSymbol)> {
310 let name = name_ref.text(); 373 let name = name_ref.text();
311 let mut query = Query::new(name.to_string()); 374 let mut query = Query::new(name.to_string());
@@ -355,3 +418,46 @@ impl CrateGraph {
355 Some(crate_id) 418 Some(crate_id)
356 } 419 }
357} 420}
421
422enum FnCallNode<'a> {
423 CallExpr(ast::CallExpr<'a>),
424 MethodCallExpr(ast::MethodCallExpr<'a>)
425}
426
427impl<'a> FnCallNode<'a> {
428 pub fn with_node(syntax: SyntaxNodeRef, offset: TextUnit) -> Option<FnCallNode> {
429 if let Some(expr) = find_node_at_offset::<ast::CallExpr>(syntax, offset) {
430 return Some(FnCallNode::CallExpr(expr));
431 }
432 if let Some(expr) = find_node_at_offset::<ast::MethodCallExpr>(syntax, offset) {
433 return Some(FnCallNode::MethodCallExpr(expr));
434 }
435 None
436 }
437
438 pub fn name_ref(&self) -> Option<ast::NameRef> {
439 match *self {
440 FnCallNode::CallExpr(call_expr) => {
441 Some(match call_expr.expr()? {
442 Expr::PathExpr(path_expr) => {
443 path_expr.path()?.segment()?.name_ref()?
444 },
445 _ => return None
446 })
447 },
448
449 FnCallNode::MethodCallExpr(call_expr) => {
450 call_expr.syntax().children()
451 .filter_map(ast::NameRef::cast)
452 .nth(0)
453 }
454 }
455 }
456
457 pub fn arg_list(&self) -> Option<ast::ArgList> {
458 match *self {
459 FnCallNode::CallExpr(expr) => expr.arg_list(),
460 FnCallNode::MethodCallExpr(expr) => expr.arg_list()
461 }
462 }
463} \ 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)]