aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ARCHITECTURE.md133
-rw-r--r--CONTRIBUTING.md64
-rw-r--r--README.md88
-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
13 files changed, 467 insertions, 145 deletions
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
new file mode 100644
index 000000000..b497cc5d7
--- /dev/null
+++ b/ARCHITECTURE.md
@@ -0,0 +1,133 @@
1# Architecture
2
3This document describes high-level architecture of rust-analyzer.
4If you want to familiarize yourself with the code base, you are just
5in the right place!
6
7
8## Code generation
9
10Some of the components of this repository are generated through automatic
11processes. These are outlined below:
12
13- `gen-kinds`: The kinds of tokens are reused in several places, so a generator
14 is used. We use tera templates to generate the files listed below, based on
15 the grammar described in [grammar.ron]:
16 - [ast/generated.rs][ast generated] in `ra_syntax` based on
17 [ast/generated.tera.rs][ast source]
18 - [syntax_kinds/generated.rs][syntax_kinds generated] in `ra_syntax` based on
19 [syntax_kinds/generated.tera.rs][syntax_kinds source]
20
21[tera]: https://tera.netlify.com/
22[grammar.ron]: ./crates/ra_syntax/src/grammar.ron
23[ast generated]: ./crates/ra_syntax/src/ast/generated.rs
24[ast source]: ./crates/ra_syntax/src/ast/generated.rs.tera
25[syntax_kinds generated]: ./crates/ra_syntax/src/syntax_kinds/generated.rs
26[syntax_kinds source]: ./crates/ra_syntax/src/syntax_kinds/generated.rs.tera
27
28
29## Code Walk-Through
30
31### `crates/ra_syntax`
32
33Rust syntax tree structure and parser. See
34[RFC](https://github.com/rust-lang/rfcs/pull/2256) for some design
35notes.
36
37- [rowan](https://github.com/rust-analyzer/rowan) library is used for constructing syntax trees.
38- `grammar` module is the actual parser. It is a hand-written recursive descent parsers, which
39 produces a sequence of events like "start node X", "finish not Y". It works similarly to [kotlin parser](https://github.com/JetBrains/kotlin/blob/4d951de616b20feca92f3e9cc9679b2de9e65195/compiler/frontend/src/org/jetbrains/kotlin/parsing/KotlinParsing.java),
40 which is a good source for inspiration for dealing with syntax errors and incomplete input. Original [libsyntax parser](https://github.com/rust-lang/rust/blob/6b99adeb11313197f409b4f7c4083c2ceca8a4fe/src/libsyntax/parse/parser.rs)
41 is what we use for the definition of the Rust language.
42- `parser_api/parser_impl` bridges the tree-agnostic parser from `grammar` with `rowan` trees.
43 This is the thing that turns a flat list of events into a tree (see `EventProcessor`)
44- `ast` a type safe API on top of the raw `rowan` tree.
45- `grammar.ron` RON description of the grammar, which is used to
46 generate `syntax_kinds` and `ast` modules, using `cargo gen-kinds` command.
47- `algo`: generic tree algorithms, including `walk` for O(1) stack
48 space tree traversal (this is cool) and `visit` for type-driven
49 visiting the nodes (this is double plus cool, if you understand how
50 `Visitor` works, you understand rust-analyzer).
51
52Test for ra_syntax are mostly data-driven: `tests/data/parser` contains a bunch of `.rs`
53(test vectors) and `.txt` files with corresponding syntax trees. During testing, we check
54`.rs` against `.txt`. If the `.txt` file is missing, it is created (this is how you update
55tests). Additionally, running `cargo gen-tests` will walk the grammar module and collect
56all `//test test_name` comments into files inside `tests/data` directory.
57
58See [#93](https://github.com/rust-analyzer/rust-analyzer/pull/93) for an example PR which
59fixes a bug in the grammar.
60
61
62### `crates/ra_editor`
63
64All IDE features which can be implemented if you only have access to a
65single file. `ra_editor` could be used to enhance editing of Rust code
66without the need to fiddle with build-systems, file
67synchronization and such.
68
69In a sense, `ra_editor` is just a bunch of pure functions which take a
70syntax tree as an input.
71
72The tests for `ra_editor` are `#[cfg(test)] mod tests` unit-tests spread
73throughout its modules.
74
75### `crates/salsa`
76
77An implementation of red-green incremental compilation algorithm from
78rust compiler. It makes all rust-analyzer features on-demand. To be replaced
79with `salsa-rs/salsa` soon.
80
81
82### `crates/ra_analysis`
83
84A stateful library for analyzing many Rust files as they change.
85`AnalysisHost` is a mutable entity (clojure's atom) which holds
86current state, incorporates changes and handles out `Analysis` --- an
87immutable consistent snapshot of world state at a point in time, which
88actually powers analysis.
89
90
91### `crates/ra_lsp_server`
92
93An LSP implementation which uses `ra_analysis` for managing state and
94`ra_editor` for actually doing useful stuff.
95
96See [#79](https://github.com/rust-analyzer/rust-analyzer/pull/79/) as an
97example of PR which adds a new feature to `ra_editor` and exposes it
98to `ra_lsp_server`.
99
100
101### `crates/cli`
102
103A CLI interface to rust-analyzer.
104
105### `crate/tools`
106
107Custom Cargo tasks used to develop rust-analyzer:
108
109- `cargo gen-kinds` -- generate `ast` and `syntax_kinds`
110- `cargo gen-tests` -- collect inline tests from grammar
111- `cargo install-code` -- build and install VS Code extension and server
112
113### `editors/code`
114
115VS Code plugin
116
117
118## Common workflows
119
120To try out VS Code extensions, run `cargo install-code`. This installs both the
121`ra_lsp_server` binary and VS Code extension. To install only the binary, `use
122cargo install --path crates/ra_lsp_server --force`
123
124To see logs from the language server, set `RUST_LOG=info` env variable. To see
125all communication between the server and the client, use
126`RUST_LOG=gen_lsp_server=debug` (will print quite a bit of stuff).
127
128To run tests, just `cargo test`.
129
130To work on VS Code extension, launch code inside `editors/code` and use `F5` to
131launch/debug. To automatically apply formatter and linter suggestions, use `npm
132run fix`.
133
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c952078cf..a2efc7afa 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,58 +1,18 @@
1The project is in its early stages: contributions are welcome and would be 1The project is in its early stages: contributions are welcome and would be
2**very** helpful, but the project is not _yet_ optimized for contribution. 2**very** helpful, but the project is not _yet_ optimized for contribution.
3Moreover, it is doubly experimental, so there's no guarantee that any work here 3Moreover, it is doubly experimental, so there's no guarantee that any work here
4would reach production. That said, here are some areas where contributions would 4would reach production.
5be **especially** welcome:
6 5
7- Designing internal data structures: RFC only outlines the constraints, it's an 6To get an idea of how rust-analyzer works, take a look at the [ARCHITECTURE.md](./ARCHITECTURE.md)
8 open question how to satisfy them in the optimal way. See `ARCHITECTURE.md` 7document.
9 for current design questions.
10 8
11- Porting libsyntax parser to rust-analyzer: currently rust-analyzer parses only 9Useful labels on the issue tracker:
12 a tiny subset of Rust. This should be fixed by porting parsing functions from 10 * [E-mentor](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-mentor)
13 libsyntax one by one. Take a look at the [libsyntax parser] for "what to port" 11 issues have links to the code in question and tests,
14 and at the [Kotlin parser] for "how to port". 12 * [E-easy](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy),
13 [E-medium](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-medium),
14 [E-hard](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-hard),
15 labels are *estimates* for how hard would be to write a fix.
15 16
16- Writing validators: by design, rust-analyzer is very lax about the input. For 17There's no formal PR check list: everything that passes CI (we use [bors](https://bors.tech/)) is valid,
17 example, the lexer happily accepts unclosed strings. The idea is that there 18but it's a good idea to write nice commit messages, test code thoroughly, maintain consistent style, etc.
18 should be a higher level visitor, which walks the syntax tree after parsing
19 and produces all the warnings. Alas, there's no such visitor yet :( Would you
20 like to write one? :)
21
22- Creating tests: it would be tremendously helpful to read each of libsyntax and
23 rust-analyzer parser functions and crate a small separate test cases to cover
24 each and every edge case.
25
26- Building stuff with rust-analyzer: it would be really cool to compile
27 rust-analyzer to WASM and add _client side_ syntax validation to rust
28 playground!
29
30Do take a look at the issue tracker.
31
32If you don't know where to start, or have _any_ questions or suggestions, don't
33hesitate to chat at [Gitter]!
34
35# Code generation
36
37Some of the components of this repository are generated through automatic
38processes. These are outlined below:
39
40- `gen-kinds`: The kinds of tokens are reused in several places, so a generator
41 is used. This process uses [tera] to generate, using data in [grammar.ron],
42 the files:
43 - [ast/generated.rs][ast generated] in `ra_syntax` based on
44 [ast/generated.tera.rs][ast source]
45 - [syntax_kinds/generated.rs][syntax_kinds generated] in `ra_syntax` based on
46 [syntax_kinds/generated.tera.rs][syntax_kinds source]
47
48[libsyntax parser]:
49 https://github.com/rust-lang/rust/blob/6b99adeb11313197f409b4f7c4083c2ceca8a4fe/src/libsyntax/parse/parser.rs
50[kotlin parser]:
51 https://github.com/JetBrains/kotlin/blob/4d951de616b20feca92f3e9cc9679b2de9e65195/compiler/frontend/src/org/jetbrains/kotlin/parsing/KotlinParsing.java
52[gitter]: https://gitter.im/libsyntax2/Lobby
53[tera]: https://tera.netlify.com/
54[grammar.ron]: ./crates/ra_syntax/src/grammar.ron
55[ast generated]: ./crates/ra_syntax/src/ast/generated.rs
56[ast source]: ./crates/ra_syntax/src/ast/generated.tera.rs
57[syntax_kinds generated]: ./crates/ra_syntax/src/syntax_kinds/generated.rs
58[syntax_kinds source]: ./crates/ra_syntax/src/syntax_kinds/generated.tera.rs
diff --git a/README.md b/README.md
index 481d065b0..1c6facbbd 100644
--- a/README.md
+++ b/README.md
@@ -61,99 +61,20 @@ fold:
61* to quickly bootstrap usable and useful language server: solution 61* to quickly bootstrap usable and useful language server: solution
62 that covers 80% of Rust code will be useful for IDEs, and will be 62 that covers 80% of Rust code will be useful for IDEs, and will be
63 vastly simpler than 100% solution. 63 vastly simpler than 100% solution.
64 64
65* to understand how the consumer-side of compiler API should look like 65* to understand how the consumer-side of compiler API should look like
66 (especially it's on-demand aspects). If you have 66 (especially it's on-demand aspects). If you have
67 `get_expression_type` function, you can write a ton of purely-IDE 67 `get_expression_type` function, you can write a ton of purely-IDE
68 features on top of it, even if the function is only partially 68 features on top of it, even if the function is only partially
69 correct. Plugin in the precise function afterwards should just make 69 correct. Plugin in the precise function afterwards should just make
70 IDE features more reliable. 70 IDE features more reliable.
71 71
72The long term plan is to merge with the mainline rustc compiler, 72The long term plan is to merge with the mainline rustc compiler,
73probably around the HIR boundary? That is, use rust analyzer for 73probably around the HIR boundary? That is, use rust analyzer for
74parsing, macro expansion and related bits of name resolution, but 74parsing, macro expansion and related bits of name resolution, but
75leave the rest (including type inference and trait selection) to the 75leave the rest (including type inference and trait selection) to the
76existing rustc. 76existing rustc.
77 77
78## Code Walk-Through
79
80### `crates/ra_syntax`
81
82Rust syntax tree structure and parser. See
83[RFC](https://github.com/rust-lang/rfcs/pull/2256) for some design
84notes.
85
86- `yellow`, red/green syntax tree, heavily inspired [by this](https://github.com/apple/swift/tree/ab68f0d4cbf99cdfa672f8ffe18e433fddc8b371/lib/Syntax)
87- `grammar`, the actual parser
88- `parser_api/parser_impl` bridges the tree-agnostic parser from `grammar` with `yellow` trees
89- `grammar.ron` RON description of the grammar, which is used to
90 generate `syntax_kinds` and `ast` modules.
91- `algo`: generic tree algorithms, including `walk` for O(1) stack
92 space tree traversal (this is cool) and `visit` for type-driven
93 visiting the nodes (this is double plus cool, if you understand how
94 `Visitor` works, you understand rust-analyzer).
95
96
97### `crates/ra_editor`
98
99All IDE features which can be implemented if you only have access to a
100single file. `ra_editor` could be used to enhance editing of Rust code
101without the need to fiddle with build-systems, file
102synchronization and such.
103
104In a sense, `ra_editor` is just a bunch of pure functions which take a
105syntax tree as an input.
106
107### `crates/salsa`
108
109An implementation of red-green incremental compilation algorithm from
110rust compiler. It makes all rust-analyzer features on-demand.
111
112
113### `crates/ra_analysis`
114
115A stateful library for analyzing many Rust files as they change.
116`AnalysisHost` is a mutable entity (clojure's atom) which holds
117current state, incorporates changes and handles out `Analysis` --- an
118immutable consistent snapshot of world state at a point in time, which
119actually powers analysis.
120
121
122### `crates/ra_lsp_server`
123
124An LSP implementation which uses `ra_analysis` for managing state and
125`ra_editor` for actually doing useful stuff.
126
127
128### `crates/cli`
129
130A CLI interface to libsyntax
131
132### `crate/tools`
133
134Code-gen tasks, used to develop rust-analyzer:
135
136- `cargo gen-kinds` -- generate `ast` and `syntax_kinds`
137- `cargo gen-tests` -- collect inline tests from grammar
138- `cargo install-code` -- build and install VS Code extension and server
139
140### `editors/code`
141
142VS Code plugin
143
144
145## Performance
146
147Non-incremental, but seems pretty fast:
148
149```
150$ cargo build --release --package ra_cli
151$ wc -l ~/projects/rust/src/libsyntax/parse/parser.rs
1527546 /home/matklad/projects/rust/src/libsyntax/parse/parser.rs
153$ ./target/release/ra_cli parse < ~/projects/rust/src/libsyntax/parse/parser.rs --no-dump > /dev/null
154parsing: 21.067065ms
155```
156
157## Getting in touch 78## Getting in touch
158 79
159@matklad can be found at Rust 80@matklad can be found at Rust
@@ -161,6 +82,11 @@ parsing: 21.067065ms
161#ides-and-editors. 82#ides-and-editors.
162 83
163 84
85## Contributing
86
87See [CONTRIBUTING.md](./CONTRIBUTING.md) and [ARCHITECTURE.md](./ARCHITECTURE.md)
88
89
164## License 90## License
165 91
166Rust analyzer is primarily distributed under the terms of both the MIT 92Rust analyzer is primarily distributed under the terms of both the MIT
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 85aa5e0dd..4db1bcbf9 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 d538739de..ea8063d3b 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",