aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md18
-rw-r--r--Cargo.lock1
-rw-r--r--README.md116
-rw-r--r--ROADMAP.md77
-rw-r--r--crates/ra_cli/Cargo.toml1
-rw-r--r--crates/ra_cli/src/main.rs22
-rw-r--r--crates/ra_db/src/lib.rs2
-rw-r--r--crates/ra_db/src/loc2id.rs10
-rw-r--r--crates/ra_hir/src/expr.rs7
-rw-r--r--crates/ra_hir/src/ids.rs34
-rw-r--r--crates/ra_hir/src/name.rs5
-rw-r--r--crates/ra_hir/src/nameres/collector.rs2
-rw-r--r--crates/ra_hir/src/ty/lower.rs4
-rw-r--r--crates/ra_hir/src/ty/primitive.rs66
-rw-r--r--crates/ra_ide_api/src/extend_selection.rs377
-rw-r--r--crates/ra_ide_api/src/lib.rs17
-rw-r--r--crates/ra_ide_api_light/src/extend_selection.rs369
-rw-r--r--crates/ra_ide_api_light/src/lib.rs34
-rw-r--r--crates/ra_parser/src/event.rs4
-rw-r--r--crates/ra_parser/src/grammar/expressions.rs94
-rw-r--r--crates/ra_parser/src/grammar/expressions/atom.rs4
-rw-r--r--crates/ra_parser/src/parser.rs34
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.rs4
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.txt55
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.rs6
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.txt88
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.rs4
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.txt54
-rw-r--r--docs/dev/README.md124
-rw-r--r--docs/dev/architecture.md (renamed from ARCHITECTURE.md)63
-rw-r--r--docs/dev/debugging.md (renamed from DEBUGGING.md)0
-rw-r--r--docs/dev/guide.md (renamed from guide.md)0
-rw-r--r--docs/dev/lsp-features.md74
-rw-r--r--docs/user/README.md77
-rw-r--r--docs/user/features.md359
-rw-r--r--editors/README.md241
36 files changed, 1498 insertions, 949 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index a2efc7afa..000000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,18 +0,0 @@
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.
3Moreover, it is doubly experimental, so there's no guarantee that any work here
4would reach production.
5
6To get an idea of how rust-analyzer works, take a look at the [ARCHITECTURE.md](./ARCHITECTURE.md)
7document.
8
9Useful labels on the issue tracker:
10 * [E-mentor](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-mentor)
11 issues have links to the code in question and tests,
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.
16
17There's no formal PR check list: everything that passes CI (we use [bors](https://bors.tech/)) is valid,
18but it's a good idea to write nice commit messages, test code thoroughly, maintain consistent style, etc.
diff --git a/Cargo.lock b/Cargo.lock
index 55bef8cb9..50fe1fa23 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -945,6 +945,7 @@ dependencies = [
945 "ra_batch 0.1.0", 945 "ra_batch 0.1.0",
946 "ra_db 0.1.0", 946 "ra_db 0.1.0",
947 "ra_hir 0.1.0", 947 "ra_hir 0.1.0",
948 "ra_ide_api 0.1.0",
948 "ra_ide_api_light 0.1.0", 949 "ra_ide_api_light 0.1.0",
949 "ra_syntax 0.1.0", 950 "ra_syntax 0.1.0",
950 "tools 0.1.0", 951 "tools 0.1.0",
diff --git a/README.md b/README.md
index 5bc90a3f0..3a0c9dee1 100644
--- a/README.md
+++ b/README.md
@@ -13,32 +13,37 @@ Work on the Rust Analyzer is sponsored by
13 13
14[![Ferrous Systems](https://ferrous-systems.com/images/ferrous-logo-text.svg)](https://ferrous-systems.com/) 14[![Ferrous Systems](https://ferrous-systems.com/images/ferrous-logo-text.svg)](https://ferrous-systems.com/)
15 15
16## Quick Start 16## Language Server Quick Start
17 17
18Rust analyzer builds on Rust >= 1.31.0 and uses the 2018 edition. 18Rust Analyzer is a work-in-progress, so you'll have to build it from source, and
19you might encounter critical bugs. That said, it is complete enough to provide a
20useful IDE experience and some people use it as a daily driver.
19 21
20``` 22To build rust-analyzer, you need:
21# run tests
22$ cargo test
23 23
24# show syntax tree of a Rust file 24* latest stable rust for language server itself
25$ cargo run --package ra_cli parse < crates/ra_syntax/src/lib.rs 25* latest stable npm and VS Code for VS Code extension (`code` should be in path)
26 26
27# show symbols of a Rust file 27For setup for other editors, see [./docs/user](./docs/user).
28$ cargo run --package ra_cli symbols < crates/ra_syntax/src/lib.rs
29 28
30# install the language server
31$ cargo install-lsp
32or
33$ cargo install --path crates/ra_lsp_server
34``` 29```
30# clone the repo
31$ git clone https://github.com/rust-analyzer/rust-analyzer && cd rust-analyzer
32
33# install both the language server and VS Code extension
34$ cargo install-code
35 35
36See [these instructions](./editors/README.md) for VS Code setup and the list of 36# alternatively, install only the server. Binary name is `ra_lsp_server`.
37features (some of which are VS Code specific). 37$ cargo install-lsp
38```
39## Documentation
38 40
39## Debugging 41If you want to **contribute** to rust-analyzer or just curious about how things work
42under the hood, check the [./docs/dev](./docs/dev) folder.
40 43
41See [these instructions](./DEBUGGING.md) on how to debug the vscode extension and the lsp server. 44If you want to **use** rust-analyzer's language server with your editor of
45choice, check [./docs/user](./docs/user) folder. It also contains some tips & tricks to help
46you be more productive when using rust-analyzer.
42 47
43## Getting in touch 48## Getting in touch
44 49
@@ -46,83 +51,6 @@ We are on the rust-lang Zulip!
46 51
47https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frls-2.2E0 52https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frls-2.2E0
48 53
49## Contributing
50
51See [CONTRIBUTING.md](./CONTRIBUTING.md) and [ARCHITECTURE.md](./ARCHITECTURE.md)
52
53## Supported LSP features
54
55### General
56- [x] [initialize](https://microsoft.github.io/language-server-protocol/specification#initialize)
57- [x] [initialized](https://microsoft.github.io/language-server-protocol/specification#initialized)
58- [x] [shutdown](https://microsoft.github.io/language-server-protocol/specification#shutdown)
59- [ ] [exit](https://microsoft.github.io/language-server-protocol/specification#exit)
60- [x] [$/cancelRequest](https://microsoft.github.io/language-server-protocol/specification#cancelRequest)
61
62### Workspace
63- [ ] [workspace/workspaceFolders](https://microsoft.github.io/language-server-protocol/specification#workspace_workspaceFolders)
64- [ ] [workspace/didChangeWorkspaceFolders](https://microsoft.github.io/language-server-protocol/specification#workspace_didChangeWorkspaceFolders)
65- [x] [workspace/didChangeConfiguration](https://microsoft.github.io/language-server-protocol/specification#workspace_didChangeConfiguration)
66- [ ] [workspace/configuration](https://microsoft.github.io/language-server-protocol/specification#workspace_configuration)
67- [x] [workspace/didChangeWatchedFiles](https://microsoft.github.io/language-server-protocol/specification#workspace_didChangeWatchedFiles)
68- [x] [workspace/symbol](https://microsoft.github.io/language-server-protocol/specification#workspace_symbol)
69- [x] [workspace/executeCommand](https://microsoft.github.io/language-server-protocol/specification#workspace_executeCommand)
70 - `apply_code_action`
71- [ ] [workspace/applyEdit](https://microsoft.github.io/language-server-protocol/specification#workspace_applyEdit)
72
73### Text Synchronization
74- [x] [textDocument/didOpen](https://microsoft.github.io/language-server-protocol/specification#textDocument_didOpen)
75- [x] [textDocument/didChange](https://microsoft.github.io/language-server-protocol/specification#textDocument_didChange)
76- [ ] [textDocument/willSave](https://microsoft.github.io/language-server-protocol/specification#textDocument_willSave)
77- [ ] [textDocument/willSaveWaitUntil](https://microsoft.github.io/language-server-protocol/specification#textDocument_willSaveWaitUntil)
78- [x] [textDocument/didSave](https://microsoft.github.io/language-server-protocol/specification#textDocument_didSave)
79- [x] [textDocument/didClose](https://microsoft.github.io/language-server-protocol/specification#textDocument_didClose)
80
81### Diagnostics
82- [x] [textDocument/publishDiagnostics](https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics)
83
84### Lanuguage Features
85- [x] [textDocument/completion](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion)
86 - open close: false
87 - change: Full
88 - will save: false
89 - will save wait until: false
90 - save: false
91- [x] [completionItem/resolve](https://microsoft.github.io/language-server-protocol/specification#completionItem_resolve)
92 - resolve provider: none
93 - trigger characters: `:`, `.`
94- [x] [textDocument/hover](https://microsoft.github.io/language-server-protocol/specification#textDocument_hover)
95- [x] [textDocument/signatureHelp](https://microsoft.github.io/language-server-protocol/specification#textDocument_signatureHelp)
96 - trigger characters: `(`, `,`, `)`
97- [ ] [textDocument/declaration](https://microsoft.github.io/language-server-protocol/specification#textDocument_declaration)
98- [x] [textDocument/definition](https://microsoft.github.io/language-server-protocol/specification#textDocument_definition)
99- [ ] [textDocument/typeDefinition](https://microsoft.github.io/language-server-protocol/specification#textDocument_typeDefinition)
100- [x] [textDocument/implementation](https://microsoft.github.io/language-server-protocol/specification#textDocument_implementation)
101- [x] [textDocument/references](https://microsoft.github.io/language-server-protocol/specification#textDocument_references)
102- [x] [textDocument/documentHighlight](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentHighlight)
103- [x] [textDocument/documentSymbol](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol)
104- [x] [textDocument/codeAction](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction)
105 - rust-analyzer.syntaxTree
106 - rust-analyzer.extendSelection
107 - rust-analyzer.matchingBrace
108 - rust-analyzer.parentModule
109 - rust-analyzer.joinLines
110 - rust-analyzer.run
111 - rust-analyzer.analyzerStatus
112- [x] [textDocument/codeLens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens)
113- [ ] [textDocument/documentLink](https://microsoft.github.io/language-server-protocol/specification#codeLens_resolve)
114- [ ] [documentLink/resolve](https://microsoft.github.io/language-server-protocol/specification#documentLink_resolve)
115- [ ] [textDocument/documentColor](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentColor)
116- [ ] [textDocument/colorPresentation](https://microsoft.github.io/language-server-protocol/specification#textDocument_colorPresentation)
117- [x] [textDocument/formatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting)
118- [ ] [textDocument/rangeFormatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_rangeFormatting)
119- [x] [textDocument/onTypeFormatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_onTypeFormatting)
120 - first trigger character: `=`
121 - more trigger character `.`
122- [x] [textDocument/rename](https://microsoft.github.io/language-server-protocol/specification#textDocument_rename)
123- [x] [textDocument/prepareRename](https://microsoft.github.io/language-server-protocol/specification#textDocument_prepareRename)
124- [x] [textDocument/foldingRange](https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange)
125
126## License 54## License
127 55
128Rust analyzer is primarily distributed under the terms of both the MIT 56Rust analyzer is primarily distributed under the terms of both the MIT
diff --git a/ROADMAP.md b/ROADMAP.md
deleted file mode 100644
index 3856ebc5b..000000000
--- a/ROADMAP.md
+++ /dev/null
@@ -1,77 +0,0 @@
1# Rust Analyzer Roadmap 01
2
3Written on 2018-11-06, extends approximately to February 2019.
4After that, we should coordinate with the compiler/rls developers to align goals and share code and experience.
5
6
7# Overall Goals
8
9The mission is:
10 * Provide an excellent "code analyzed as you type" IDE experience for the Rust language,
11 * Implement the bulk of the features in Rust itself.
12
13
14High-level architecture constraints:
15 * Long-term, replace the current rustc frontend.
16 It's *obvious* that the code should be shared, but OTOH, all great IDEs started as from-scratch rewrites.
17 * Don't hard-code a particular protocol or mode of operation.
18 Produce a library which could be used for implementing an LSP server, or for in-process embedding.
19 * As long as possible, stick with stable Rust.
20
21
22# Current Goals
23
24Ideally, we would be coordinating with the compiler/rls teams, but they are busy working on making Rust 2018 at the moment.
25The sync-up point will happen some time after the edition, probably early 2019.
26In the meantime, the goal is to **experiment**, specifically, to figure out how a from-scratch written RLS might look like.
27
28
29## Data Storage and Protocol implementation
30
31The fundamental part of any architecture is who owns which data, how the data is mutated and how the data is exposed to user.
32For storage we use the [salsa](http://github.com/salsa-rs/salsa) library, which provides a solid model that seems to be the way to go.
33
34Modification to source files is mostly driven by the language client, but we also should support watching the file system. The current
35file watching implementation is a stub.
36
37**Action Item:** implement reliable file watching service.
38
39We also should extract LSP bits as a reusable library. There's already `gen_lsp_server`, but it is pretty limited.
40
41**Action Item:** try using `gen_lsp_server` in more than one language server, for example for TOML and Nix.
42
43The ideal architecture for `gen_lsp_server` is still unclear. I'd rather avoid futures: they bring significant runtime complexity
44(call stacks become insane) and the performance benefits are negligible for our use case (one thread per request is perfectly OK given
45the low amount of requests a language server receives). The current interface is based on crossbeam-channel, but it's not clear
46if that is the best choice.
47
48
49## Low-effort, high payoff features
50
51Implementing 20% of type inference will give use 80% of completion.
52Thus it makes sense to partially implement name resolution, type inference and trait matching, even though there is a chance that
53this code is replaced later on when we integrate with the compiler
54
55Specifically, we need to:
56
57* **Action Item:** implement path resolution, so that we get completion in imports and such.
58* **Action Item:** implement simple type inference, so that we get completion for inherent methods.
59* **Action Item:** implement nicer completion infrastructure, so that we have icons, snippets, doc comments, after insert callbacks, ...
60
61
62## Dragons to kill
63
64To make experiments most effective, we should try to prototype solutions for the hardest problems.
65In the case of Rust, the two hardest problems are:
66 * Conditional compilation and source/model mismatch.
67 A single source file might correspond to several entities in the semantic model.
68 For example, different cfg flags produce effectively different crates from the same source.
69 * Macros are intertwined with name resolution in a single fix-point iteration algorithm.
70 This is just plain hard to implement, but also interacts poorly with on-demand.
71
72
73For the first bullet point, we need to design descriptors infra and explicit mapping step between sources and semantic model, which is intentionally fuzzy in one direction.
74The **action item** here is basically "write code, see what works, keep high-level picture in mind".
75
76For the second bullet point, there's hope that salsa with its deep memoization will result in a fast enough solution even without being fully on-demand.
77Again, the **action item** is to write the code and see what works. Salsa itself uses macros heavily, so it should be a great test.
diff --git a/crates/ra_cli/Cargo.toml b/crates/ra_cli/Cargo.toml
index ff30bf0b3..4c666f556 100644
--- a/crates/ra_cli/Cargo.toml
+++ b/crates/ra_cli/Cargo.toml
@@ -13,6 +13,7 @@ flexi_logger = "0.11.0"
13indicatif = "0.11.0" 13indicatif = "0.11.0"
14 14
15ra_syntax = { path = "../ra_syntax" } 15ra_syntax = { path = "../ra_syntax" }
16ra_ide_api = { path = "../ra_ide_api" }
16ra_ide_api_light = { path = "../ra_ide_api_light" } 17ra_ide_api_light = { path = "../ra_ide_api_light" }
17tools = { path = "../tools" } 18tools = { path = "../tools" }
18ra_batch = { path = "../ra_batch" } 19ra_batch = { path = "../ra_batch" }
diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs
index 294f4b8af..5285f1f28 100644
--- a/crates/ra_cli/src/main.rs
+++ b/crates/ra_cli/src/main.rs
@@ -4,7 +4,8 @@ use std::{fs, io::Read, path::Path, time::Instant};
4 4
5use clap::{App, Arg, SubCommand}; 5use clap::{App, Arg, SubCommand};
6use join_to_string::join; 6use join_to_string::join;
7use ra_ide_api_light::{extend_selection, file_structure}; 7use ra_ide_api::{Analysis, FileRange};
8use ra_ide_api_light::file_structure;
8use ra_syntax::{SourceFile, TextRange, TreeArc, AstNode}; 9use ra_syntax::{SourceFile, TextRange, TreeArc, AstNode};
9use tools::collect_tests; 10use tools::collect_tests;
10use flexi_logger::Logger; 11use flexi_logger::Logger;
@@ -59,8 +60,8 @@ fn main() -> Result<()> {
59 ("extend-selection", Some(matches)) => { 60 ("extend-selection", Some(matches)) => {
60 let start: u32 = matches.value_of("start").unwrap().parse()?; 61 let start: u32 = matches.value_of("start").unwrap().parse()?;
61 let end: u32 = matches.value_of("end").unwrap().parse()?; 62 let end: u32 = matches.value_of("end").unwrap().parse()?;
62 let file = file()?; 63 let text = read_stdin()?;
63 let sels = selections(&file, start, end); 64 let sels = selections(text, start, end);
64 println!("{}", sels) 65 println!("{}", sels)
65 } 66 }
66 ("analysis-stats", Some(matches)) => { 67 ("analysis-stats", Some(matches)) => {
@@ -98,12 +99,17 @@ fn render_test(file: &Path, line: usize) -> Result<(String, String)> {
98 Ok((test.text, tree)) 99 Ok((test.text, tree))
99} 100}
100 101
101fn selections(file: &SourceFile, start: u32, end: u32) -> String { 102fn selections(text: String, start: u32, end: u32) -> String {
103 let (analysis, file_id) = Analysis::from_single_file(text);
102 let mut ranges = Vec::new(); 104 let mut ranges = Vec::new();
103 let mut cur = Some(TextRange::from_to((start - 1).into(), (end - 1).into())); 105 let mut range = TextRange::from_to((start - 1).into(), (end - 1).into());
104 while let Some(r) = cur { 106 loop {
105 ranges.push(r); 107 ranges.push(range);
106 cur = extend_selection(file.syntax(), r); 108 let next = analysis.extend_selection(FileRange { file_id, range }).unwrap();
109 if range == next {
110 break;
111 }
112 range = next;
107 } 113 }
108 let ranges = ranges 114 let ranges = ranges
109 .iter() 115 .iter()
diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs
index e006c6d27..f3389c91f 100644
--- a/crates/ra_db/src/lib.rs
+++ b/crates/ra_db/src/lib.rs
@@ -16,7 +16,7 @@ pub use crate::{
16 input::{ 16 input::{
17 FileId, CrateId, SourceRoot, SourceRootId, CrateGraph, Dependency, Edition, 17 FileId, CrateId, SourceRoot, SourceRootId, CrateGraph, Dependency, Edition,
18 }, 18 },
19 loc2id::LocationIntener, 19 loc2id::LocationInterner,
20}; 20};
21 21
22pub trait CheckCanceled: panic::RefUnwindSafe { 22pub trait CheckCanceled: panic::RefUnwindSafe {
diff --git a/crates/ra_db/src/loc2id.rs b/crates/ra_db/src/loc2id.rs
index d27fa7682..eae64a4eb 100644
--- a/crates/ra_db/src/loc2id.rs
+++ b/crates/ra_db/src/loc2id.rs
@@ -59,7 +59,7 @@ where
59} 59}
60 60
61#[derive(Debug)] 61#[derive(Debug)]
62pub struct LocationIntener<LOC, ID> 62pub struct LocationInterner<LOC, ID>
63where 63where
64 ID: ArenaId + Clone, 64 ID: ArenaId + Clone,
65 LOC: Clone + Eq + Hash, 65 LOC: Clone + Eq + Hash,
@@ -67,7 +67,7 @@ where
67 map: Mutex<Loc2IdMap<LOC, ID>>, 67 map: Mutex<Loc2IdMap<LOC, ID>>,
68} 68}
69 69
70impl<LOC, ID> panic::RefUnwindSafe for LocationIntener<LOC, ID> 70impl<LOC, ID> panic::RefUnwindSafe for LocationInterner<LOC, ID>
71where 71where
72 ID: ArenaId + Clone, 72 ID: ArenaId + Clone,
73 LOC: Clone + Eq + Hash, 73 LOC: Clone + Eq + Hash,
@@ -76,17 +76,17 @@ where
76{ 76{
77} 77}
78 78
79impl<LOC, ID> Default for LocationIntener<LOC, ID> 79impl<LOC, ID> Default for LocationInterner<LOC, ID>
80where 80where
81 ID: ArenaId + Clone, 81 ID: ArenaId + Clone,
82 LOC: Clone + Eq + Hash, 82 LOC: Clone + Eq + Hash,
83{ 83{
84 fn default() -> Self { 84 fn default() -> Self {
85 LocationIntener { map: Default::default() } 85 LocationInterner { map: Default::default() }
86 } 86 }
87} 87}
88 88
89impl<LOC, ID> LocationIntener<LOC, ID> 89impl<LOC, ID> LocationInterner<LOC, ID>
90where 90where
91 ID: ArenaId + Clone, 91 ID: ArenaId + Clone,
92 LOC: Clone + Eq + Hash, 92 LOC: Clone + Eq + Hash,
diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs
index 6c7489e63..486314cc5 100644
--- a/crates/ra_hir/src/expr.rs
+++ b/crates/ra_hir/src/expr.rs
@@ -723,8 +723,7 @@ impl ExprCollector {
723 723
724 let lit = match child.flavor() { 724 let lit = match child.flavor() {
725 LiteralFlavor::IntNumber { suffix } => { 725 LiteralFlavor::IntNumber { suffix } => {
726 let known_name = 726 let known_name = suffix.and_then(|it| UncertainIntTy::from_suffix(&it));
727 suffix.map(Name::new).and_then(|name| UncertainIntTy::from_name(&name));
728 727
729 Literal::Int( 728 Literal::Int(
730 Default::default(), 729 Default::default(),
@@ -732,9 +731,7 @@ impl ExprCollector {
732 ) 731 )
733 } 732 }
734 LiteralFlavor::FloatNumber { suffix } => { 733 LiteralFlavor::FloatNumber { suffix } => {
735 let known_name = suffix 734 let known_name = suffix.and_then(|it| UncertainFloatTy::from_suffix(&it));
736 .map(Name::new)
737 .and_then(|name| UncertainFloatTy::from_name(&name));
738 735
739 Literal::Float( 736 Literal::Float(
740 Default::default(), 737 Default::default(),
diff --git a/crates/ra_hir/src/ids.rs b/crates/ra_hir/src/ids.rs
index 9596488d3..3d0a881c2 100644
--- a/crates/ra_hir/src/ids.rs
+++ b/crates/ra_hir/src/ids.rs
@@ -4,7 +4,7 @@ use std::{
4 sync::Arc, 4 sync::Arc,
5}; 5};
6 6
7use ra_db::{LocationIntener, FileId}; 7use ra_db::{LocationInterner, FileId};
8use ra_syntax::{TreeArc, SyntaxNode, SourceFile, AstNode, SyntaxNodePtr, ast}; 8use ra_syntax::{TreeArc, SyntaxNode, SourceFile, AstNode, SyntaxNodePtr, ast};
9use ra_arena::{Arena, RawId, ArenaId, impl_arena_id}; 9use ra_arena::{Arena, RawId, ArenaId, impl_arena_id};
10 10
@@ -15,14 +15,14 @@ use crate::{
15 15
16#[derive(Debug, Default)] 16#[derive(Debug, Default)]
17pub struct HirInterner { 17pub struct HirInterner {
18 macros: LocationIntener<MacroCallLoc, MacroCallId>, 18 macros: LocationInterner<MacroCallLoc, MacroCallId>,
19 fns: LocationIntener<ItemLoc<ast::FnDef>, FunctionId>, 19 fns: LocationInterner<ItemLoc<ast::FnDef>, FunctionId>,
20 structs: LocationIntener<ItemLoc<ast::StructDef>, StructId>, 20 structs: LocationInterner<ItemLoc<ast::StructDef>, StructId>,
21 enums: LocationIntener<ItemLoc<ast::EnumDef>, EnumId>, 21 enums: LocationInterner<ItemLoc<ast::EnumDef>, EnumId>,
22 consts: LocationIntener<ItemLoc<ast::ConstDef>, ConstId>, 22 consts: LocationInterner<ItemLoc<ast::ConstDef>, ConstId>,
23 statics: LocationIntener<ItemLoc<ast::StaticDef>, StaticId>, 23 statics: LocationInterner<ItemLoc<ast::StaticDef>, StaticId>,
24 traits: LocationIntener<ItemLoc<ast::TraitDef>, TraitId>, 24 traits: LocationInterner<ItemLoc<ast::TraitDef>, TraitId>,
25 types: LocationIntener<ItemLoc<ast::TypeAliasDef>, TypeId>, 25 types: LocationInterner<ItemLoc<ast::TypeAliasDef>, TypeId>,
26} 26}
27 27
28impl HirInterner { 28impl HirInterner {
@@ -204,7 +204,7 @@ impl<'a, DB: PersistentHirDatabase> LocationCtx<&'a DB> {
204} 204}
205 205
206pub(crate) trait AstItemDef<N: AstNode>: ArenaId + Clone { 206pub(crate) trait AstItemDef<N: AstNode>: ArenaId + Clone {
207 fn interner(interner: &HirInterner) -> &LocationIntener<ItemLoc<N>, Self>; 207 fn interner(interner: &HirInterner) -> &LocationInterner<ItemLoc<N>, Self>;
208 fn from_ast(ctx: LocationCtx<&impl PersistentHirDatabase>, ast: &N) -> Self { 208 fn from_ast(ctx: LocationCtx<&impl PersistentHirDatabase>, ast: &N) -> Self {
209 let items = ctx.db.file_items(ctx.file_id); 209 let items = ctx.db.file_items(ctx.file_id);
210 let item_id = items.id_of(ctx.file_id, ast.syntax()); 210 let item_id = items.id_of(ctx.file_id, ast.syntax());
@@ -238,7 +238,7 @@ pub(crate) trait AstItemDef<N: AstNode>: ArenaId + Clone {
238pub struct FunctionId(RawId); 238pub struct FunctionId(RawId);
239impl_arena_id!(FunctionId); 239impl_arena_id!(FunctionId);
240impl AstItemDef<ast::FnDef> for FunctionId { 240impl AstItemDef<ast::FnDef> for FunctionId {
241 fn interner(interner: &HirInterner) -> &LocationIntener<ItemLoc<ast::FnDef>, Self> { 241 fn interner(interner: &HirInterner) -> &LocationInterner<ItemLoc<ast::FnDef>, Self> {
242 &interner.fns 242 &interner.fns
243 } 243 }
244} 244}
@@ -247,7 +247,7 @@ impl AstItemDef<ast::FnDef> for FunctionId {
247pub struct StructId(RawId); 247pub struct StructId(RawId);
248impl_arena_id!(StructId); 248impl_arena_id!(StructId);
249impl AstItemDef<ast::StructDef> for StructId { 249impl AstItemDef<ast::StructDef> for StructId {
250 fn interner(interner: &HirInterner) -> &LocationIntener<ItemLoc<ast::StructDef>, Self> { 250 fn interner(interner: &HirInterner) -> &LocationInterner<ItemLoc<ast::StructDef>, Self> {
251 &interner.structs 251 &interner.structs
252 } 252 }
253} 253}
@@ -256,7 +256,7 @@ impl AstItemDef<ast::StructDef> for StructId {
256pub struct EnumId(RawId); 256pub struct EnumId(RawId);
257impl_arena_id!(EnumId); 257impl_arena_id!(EnumId);
258impl AstItemDef<ast::EnumDef> for EnumId { 258impl AstItemDef<ast::EnumDef> for EnumId {
259 fn interner(interner: &HirInterner) -> &LocationIntener<ItemLoc<ast::EnumDef>, Self> { 259 fn interner(interner: &HirInterner) -> &LocationInterner<ItemLoc<ast::EnumDef>, Self> {
260 &interner.enums 260 &interner.enums
261 } 261 }
262} 262}
@@ -265,7 +265,7 @@ impl AstItemDef<ast::EnumDef> for EnumId {
265pub struct ConstId(RawId); 265pub struct ConstId(RawId);
266impl_arena_id!(ConstId); 266impl_arena_id!(ConstId);
267impl AstItemDef<ast::ConstDef> for ConstId { 267impl AstItemDef<ast::ConstDef> for ConstId {
268 fn interner(interner: &HirInterner) -> &LocationIntener<ItemLoc<ast::ConstDef>, Self> { 268 fn interner(interner: &HirInterner) -> &LocationInterner<ItemLoc<ast::ConstDef>, Self> {
269 &interner.consts 269 &interner.consts
270 } 270 }
271} 271}
@@ -274,7 +274,7 @@ impl AstItemDef<ast::ConstDef> for ConstId {
274pub struct StaticId(RawId); 274pub struct StaticId(RawId);
275impl_arena_id!(StaticId); 275impl_arena_id!(StaticId);
276impl AstItemDef<ast::StaticDef> for StaticId { 276impl AstItemDef<ast::StaticDef> for StaticId {
277 fn interner(interner: &HirInterner) -> &LocationIntener<ItemLoc<ast::StaticDef>, Self> { 277 fn interner(interner: &HirInterner) -> &LocationInterner<ItemLoc<ast::StaticDef>, Self> {
278 &interner.statics 278 &interner.statics
279 } 279 }
280} 280}
@@ -283,7 +283,7 @@ impl AstItemDef<ast::StaticDef> for StaticId {
283pub struct TraitId(RawId); 283pub struct TraitId(RawId);
284impl_arena_id!(TraitId); 284impl_arena_id!(TraitId);
285impl AstItemDef<ast::TraitDef> for TraitId { 285impl AstItemDef<ast::TraitDef> for TraitId {
286 fn interner(interner: &HirInterner) -> &LocationIntener<ItemLoc<ast::TraitDef>, Self> { 286 fn interner(interner: &HirInterner) -> &LocationInterner<ItemLoc<ast::TraitDef>, Self> {
287 &interner.traits 287 &interner.traits
288 } 288 }
289} 289}
@@ -292,7 +292,7 @@ impl AstItemDef<ast::TraitDef> for TraitId {
292pub struct TypeId(RawId); 292pub struct TypeId(RawId);
293impl_arena_id!(TypeId); 293impl_arena_id!(TypeId);
294impl AstItemDef<ast::TypeAliasDef> for TypeId { 294impl AstItemDef<ast::TypeAliasDef> for TypeId {
295 fn interner(interner: &HirInterner) -> &LocationIntener<ItemLoc<ast::TypeAliasDef>, Self> { 295 fn interner(interner: &HirInterner) -> &LocationInterner<ItemLoc<ast::TypeAliasDef>, Self> {
296 &interner.types 296 &interner.types
297 } 297 }
298} 298}
diff --git a/crates/ra_hir/src/name.rs b/crates/ra_hir/src/name.rs
index 06bafa6f0..677d18efc 100644
--- a/crates/ra_hir/src/name.rs
+++ b/crates/ra_hir/src/name.rs
@@ -23,7 +23,10 @@ impl fmt::Debug for Name {
23} 23}
24 24
25impl Name { 25impl Name {
26 pub(crate) fn new(text: SmolStr) -> Name { 26 /// Note: this is private to make creating name from random string hard.
27 /// Hopefully, this should allow us to integrate hygiene cleaner in the
28 /// future, and to switch to interned representation of names.
29 fn new(text: SmolStr) -> Name {
27 Name { text } 30 Name { text }
28 } 31 }
29 32
diff --git a/crates/ra_hir/src/nameres/collector.rs b/crates/ra_hir/src/nameres/collector.rs
index 12ed49a0a..3ea8d592c 100644
--- a/crates/ra_hir/src/nameres/collector.rs
+++ b/crates/ra_hir/src/nameres/collector.rs
@@ -124,7 +124,7 @@ where
124 } 124 }
125 125
126 fn resolve_import( 126 fn resolve_import(
127 &mut self, 127 &self,
128 module_id: CrateModuleId, 128 module_id: CrateModuleId,
129 import: &raw::ImportData, 129 import: &raw::ImportData,
130 ) -> (PerNs<ModuleDef>, ReachedFixedPoint) { 130 ) -> (PerNs<ModuleDef>, ReachedFixedPoint) {
diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs
index 278f592d3..389a2fc68 100644
--- a/crates/ra_hir/src/ty/lower.rs
+++ b/crates/ra_hir/src/ty/lower.rs
@@ -63,9 +63,9 @@ impl Ty {
63 pub(crate) fn from_hir_path(db: &impl HirDatabase, resolver: &Resolver, path: &Path) -> Self { 63 pub(crate) fn from_hir_path(db: &impl HirDatabase, resolver: &Resolver, path: &Path) -> Self {
64 if let Some(name) = path.as_ident() { 64 if let Some(name) = path.as_ident() {
65 // TODO handle primitive type names in resolver as well? 65 // TODO handle primitive type names in resolver as well?
66 if let Some(int_ty) = primitive::UncertainIntTy::from_name(name) { 66 if let Some(int_ty) = primitive::UncertainIntTy::from_type_name(name) {
67 return Ty::Int(int_ty); 67 return Ty::Int(int_ty);
68 } else if let Some(float_ty) = primitive::UncertainFloatTy::from_name(name) { 68 } else if let Some(float_ty) = primitive::UncertainFloatTy::from_type_name(name) {
69 return Ty::Float(float_ty); 69 return Ty::Float(float_ty);
70 } else if let Some(known) = name.as_known_name() { 70 } else if let Some(known) = name.as_known_name() {
71 match known { 71 match known {
diff --git a/crates/ra_hir/src/ty/primitive.rs b/crates/ra_hir/src/ty/primitive.rs
index 30aeac48e..421f7e980 100644
--- a/crates/ra_hir/src/ty/primitive.rs
+++ b/crates/ra_hir/src/ty/primitive.rs
@@ -10,10 +10,20 @@ pub enum UncertainIntTy {
10} 10}
11 11
12impl UncertainIntTy { 12impl UncertainIntTy {
13 pub fn from_name(name: &Name) -> Option<UncertainIntTy> { 13 pub(crate) fn from_type_name(name: &Name) -> Option<UncertainIntTy> {
14 if let Some(ty) = IntTy::from_name(name) { 14 if let Some(ty) = IntTy::from_type_name(name) {
15 Some(UncertainIntTy::Signed(ty)) 15 Some(UncertainIntTy::Signed(ty))
16 } else if let Some(ty) = UintTy::from_name(name) { 16 } else if let Some(ty) = UintTy::from_type_name(name) {
17 Some(UncertainIntTy::Unsigned(ty))
18 } else {
19 None
20 }
21 }
22
23 pub(crate) fn from_suffix(suffix: &str) -> Option<UncertainIntTy> {
24 if let Some(ty) = IntTy::from_suffix(suffix) {
25 Some(UncertainIntTy::Signed(ty))
26 } else if let Some(ty) = UintTy::from_suffix(suffix) {
17 Some(UncertainIntTy::Unsigned(ty)) 27 Some(UncertainIntTy::Unsigned(ty))
18 } else { 28 } else {
19 None 29 None
@@ -38,12 +48,12 @@ pub enum UncertainFloatTy {
38} 48}
39 49
40impl UncertainFloatTy { 50impl UncertainFloatTy {
41 pub fn from_name(name: &Name) -> Option<UncertainFloatTy> { 51 pub(crate) fn from_type_name(name: &Name) -> Option<UncertainFloatTy> {
42 if let Some(ty) = FloatTy::from_name(name) { 52 FloatTy::from_type_name(name).map(UncertainFloatTy::Known)
43 Some(UncertainFloatTy::Known(ty)) 53 }
44 } else { 54
45 None 55 pub(crate) fn from_suffix(suffix: &str) -> Option<UncertainFloatTy> {
46 } 56 FloatTy::from_suffix(suffix).map(UncertainFloatTy::Known)
47 } 57 }
48} 58}
49 59
@@ -87,7 +97,7 @@ impl fmt::Display for IntTy {
87} 97}
88 98
89impl IntTy { 99impl IntTy {
90 pub fn from_name(name: &Name) -> Option<IntTy> { 100 fn from_type_name(name: &Name) -> Option<IntTy> {
91 match name.as_known_name()? { 101 match name.as_known_name()? {
92 KnownName::Isize => Some(IntTy::Isize), 102 KnownName::Isize => Some(IntTy::Isize),
93 KnownName::I8 => Some(IntTy::I8), 103 KnownName::I8 => Some(IntTy::I8),
@@ -98,6 +108,18 @@ impl IntTy {
98 _ => None, 108 _ => None,
99 } 109 }
100 } 110 }
111
112 fn from_suffix(suffix: &str) -> Option<IntTy> {
113 match suffix {
114 "isize" => Some(IntTy::Isize),
115 "i8" => Some(IntTy::I8),
116 "i16" => Some(IntTy::I16),
117 "i32" => Some(IntTy::I32),
118 "i64" => Some(IntTy::I64),
119 "i128" => Some(IntTy::I128),
120 _ => None,
121 }
122 }
101} 123}
102 124
103#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)] 125#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
@@ -125,7 +147,7 @@ impl fmt::Display for UintTy {
125} 147}
126 148
127impl UintTy { 149impl UintTy {
128 pub fn from_name(name: &Name) -> Option<UintTy> { 150 fn from_type_name(name: &Name) -> Option<UintTy> {
129 match name.as_known_name()? { 151 match name.as_known_name()? {
130 KnownName::Usize => Some(UintTy::Usize), 152 KnownName::Usize => Some(UintTy::Usize),
131 KnownName::U8 => Some(UintTy::U8), 153 KnownName::U8 => Some(UintTy::U8),
@@ -136,6 +158,18 @@ impl UintTy {
136 _ => None, 158 _ => None,
137 } 159 }
138 } 160 }
161
162 fn from_suffix(suffix: &str) -> Option<UintTy> {
163 match suffix {
164 "usize" => Some(UintTy::Usize),
165 "u8" => Some(UintTy::U8),
166 "u16" => Some(UintTy::U16),
167 "u32" => Some(UintTy::U32),
168 "u64" => Some(UintTy::U64),
169 "u128" => Some(UintTy::U128),
170 _ => None,
171 }
172 }
139} 173}
140 174
141impl fmt::Debug for UintTy { 175impl fmt::Debug for UintTy {
@@ -170,11 +204,19 @@ impl FloatTy {
170 } 204 }
171 } 205 }
172 206
173 pub fn from_name(name: &Name) -> Option<FloatTy> { 207 fn from_type_name(name: &Name) -> Option<FloatTy> {
174 match name.as_known_name()? { 208 match name.as_known_name()? {
175 KnownName::F32 => Some(FloatTy::F32), 209 KnownName::F32 => Some(FloatTy::F32),
176 KnownName::F64 => Some(FloatTy::F64), 210 KnownName::F64 => Some(FloatTy::F64),
177 _ => None, 211 _ => None,
178 } 212 }
179 } 213 }
214
215 fn from_suffix(suffix: &str) -> Option<FloatTy> {
216 match suffix {
217 "f32" => Some(FloatTy::F32),
218 "f64" => Some(FloatTy::F64),
219 _ => None,
220 }
221 }
180} 222}
diff --git a/crates/ra_ide_api/src/extend_selection.rs b/crates/ra_ide_api/src/extend_selection.rs
index d23290b74..63879a0b5 100644
--- a/crates/ra_ide_api/src/extend_selection.rs
+++ b/crates/ra_ide_api/src/extend_selection.rs
@@ -1,13 +1,378 @@
1use ra_db::SourceDatabase; 1use ra_db::SourceDatabase;
2use ra_syntax::AstNode; 2use ra_syntax::{
3 3 Direction, SyntaxNode, TextRange, TextUnit, AstNode,
4use crate::{ 4 algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset},
5 TextRange, FileRange, 5 SyntaxKind::*,
6 db::RootDatabase,
7}; 6};
8 7
8use crate::{FileRange, db::RootDatabase};
9
9// FIXME: restore macro support 10// FIXME: restore macro support
10pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { 11pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
11 let source_file = db.parse(frange.file_id); 12 let source_file = db.parse(frange.file_id);
12 ra_ide_api_light::extend_selection(source_file.syntax(), frange.range).unwrap_or(frange.range) 13 try_extend_selection(source_file.syntax(), frange.range).unwrap_or(frange.range)
14}
15
16fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> {
17 let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING];
18 let list_kinds = [
19 FIELD_PAT_LIST,
20 MATCH_ARM_LIST,
21 NAMED_FIELD_DEF_LIST,
22 POS_FIELD_DEF_LIST,
23 NAMED_FIELD_LIST,
24 ENUM_VARIANT_LIST,
25 USE_TREE_LIST,
26 TYPE_PARAM_LIST,
27 TYPE_ARG_LIST,
28 PARAM_LIST,
29 ARG_LIST,
30 ARRAY_EXPR,
31 ];
32
33 if range.is_empty() {
34 let offset = range.start();
35 let mut leaves = find_leaf_at_offset(root, offset);
36 if leaves.clone().all(|it| it.kind() == WHITESPACE) {
37 return Some(extend_ws(root, leaves.next()?, offset));
38 }
39 let leaf_range = match leaves {
40 LeafAtOffset::None => return None,
41 LeafAtOffset::Single(l) => {
42 if string_kinds.contains(&l.kind()) {
43 extend_single_word_in_comment_or_string(l, offset).unwrap_or_else(|| l.range())
44 } else {
45 l.range()
46 }
47 }
48 LeafAtOffset::Between(l, r) => pick_best(l, r).range(),
49 };
50 return Some(leaf_range);
51 };
52 let node = find_covering_node(root, range);
53
54 // Using shallowest node with same range allows us to traverse siblings.
55 let node = node.ancestors().take_while(|n| n.range() == node.range()).last().unwrap();
56
57 if range == node.range() {
58 if string_kinds.contains(&node.kind()) {
59 if let Some(range) = extend_comments(node) {
60 return Some(range);
61 }
62 }
63
64 if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) {
65 if let Some(range) = extend_list_item(node) {
66 return Some(range);
67 }
68 }
69 }
70
71 match node.ancestors().skip_while(|n| n.range() == range).next() {
72 None => None,
73 Some(parent) => Some(parent.range()),
74 }
75}
76
77fn extend_single_word_in_comment_or_string(
78 leaf: &SyntaxNode,
79 offset: TextUnit,
80) -> Option<TextRange> {
81 let text: &str = leaf.leaf_text()?;
82 let cursor_position: u32 = (offset - leaf.range().start()).into();
83
84 let (before, after) = text.split_at(cursor_position as usize);
85
86 fn non_word_char(c: char) -> bool {
87 !(c.is_alphanumeric() || c == '_')
88 }
89
90 let start_idx = before.rfind(non_word_char)? as u32;
91 let end_idx = after.find(non_word_char).unwrap_or(after.len()) as u32;
92
93 let from: TextUnit = (start_idx + 1).into();
94 let to: TextUnit = (cursor_position + end_idx).into();
95
96 let range = TextRange::from_to(from, to);
97 if range.is_empty() {
98 None
99 } else {
100 Some(range + leaf.range().start())
101 }
102}
103
104fn extend_ws(root: &SyntaxNode, ws: &SyntaxNode, offset: TextUnit) -> TextRange {
105 let ws_text = ws.leaf_text().unwrap();
106 let suffix = TextRange::from_to(offset, ws.range().end()) - ws.range().start();
107 let prefix = TextRange::from_to(ws.range().start(), offset) - ws.range().start();
108 let ws_suffix = &ws_text.as_str()[suffix];
109 let ws_prefix = &ws_text.as_str()[prefix];
110 if ws_text.contains('\n') && !ws_suffix.contains('\n') {
111 if let Some(node) = ws.next_sibling() {
112 let start = match ws_prefix.rfind('\n') {
113 Some(idx) => ws.range().start() + TextUnit::from((idx + 1) as u32),
114 None => node.range().start(),
115 };
116 let end = if root.text().char_at(node.range().end()) == Some('\n') {
117 node.range().end() + TextUnit::of_char('\n')
118 } else {
119 node.range().end()
120 };
121 return TextRange::from_to(start, end);
122 }
123 }
124 ws.range()
125}
126
127fn pick_best<'a>(l: &'a SyntaxNode, r: &'a SyntaxNode) -> &'a SyntaxNode {
128 return if priority(r) > priority(l) { r } else { l };
129 fn priority(n: &SyntaxNode) -> usize {
130 match n.kind() {
131 WHITESPACE => 0,
132 IDENT | SELF_KW | SUPER_KW | CRATE_KW | LIFETIME => 2,
133 _ => 1,
134 }
135 }
136}
137
138/// Extend list item selection to include nearby comma and whitespace.
139fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
140 fn is_single_line_ws(node: &SyntaxNode) -> bool {
141 node.kind() == WHITESPACE && !node.leaf_text().unwrap().contains('\n')
142 }
143
144 fn nearby_comma(node: &SyntaxNode, dir: Direction) -> Option<&SyntaxNode> {
145 node.siblings(dir)
146 .skip(1)
147 .skip_while(|node| is_single_line_ws(node))
148 .next()
149 .filter(|node| node.kind() == COMMA)
150 }
151
152 if let Some(comma_node) = nearby_comma(node, Direction::Prev) {
153 return Some(TextRange::from_to(comma_node.range().start(), node.range().end()));
154 }
155
156 if let Some(comma_node) = nearby_comma(node, Direction::Next) {
157 // Include any following whitespace when comma if after list item.
158 let final_node = comma_node
159 .siblings(Direction::Next)
160 .skip(1)
161 .next()
162 .filter(|node| is_single_line_ws(node))
163 .unwrap_or(comma_node);
164
165 return Some(TextRange::from_to(node.range().start(), final_node.range().end()));
166 }
167
168 return None;
169}
170
171fn extend_comments(node: &SyntaxNode) -> Option<TextRange> {
172 let prev = adj_comments(node, Direction::Prev);
173 let next = adj_comments(node, Direction::Next);
174 if prev != next {
175 Some(TextRange::from_to(prev.range().start(), next.range().end()))
176 } else {
177 None
178 }
179}
180
181fn adj_comments(node: &SyntaxNode, dir: Direction) -> &SyntaxNode {
182 let mut res = node;
183 for node in node.siblings(dir) {
184 match node.kind() {
185 COMMENT => res = node,
186 WHITESPACE if !node.leaf_text().unwrap().as_str().contains("\n\n") => (),
187 _ => break,
188 }
189 }
190 res
191}
192
193#[cfg(test)]
194mod tests {
195 use ra_syntax::{SourceFile, AstNode};
196 use test_utils::extract_offset;
197
198 use super::*;
199
200 fn do_check(before: &str, afters: &[&str]) {
201 let (cursor, before) = extract_offset(before);
202 let file = SourceFile::parse(&before);
203 let mut range = TextRange::offset_len(cursor, 0.into());
204 for &after in afters {
205 range = try_extend_selection(file.syntax(), range).unwrap();
206 let actual = &before[range];
207 assert_eq!(after, actual);
208 }
209 }
210
211 #[test]
212 fn test_extend_selection_arith() {
213 do_check(r#"fn foo() { <|>1 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]);
214 }
215
216 #[test]
217 fn test_extend_selection_list() {
218 do_check(r#"fn foo(<|>x: i32) {}"#, &["x", "x: i32"]);
219 do_check(r#"fn foo(<|>x: i32, y: i32) {}"#, &["x", "x: i32", "x: i32, "]);
220 do_check(r#"fn foo(<|>x: i32,y: i32) {}"#, &["x", "x: i32", "x: i32,"]);
221 do_check(r#"fn foo(x: i32, <|>y: i32) {}"#, &["y", "y: i32", ", y: i32"]);
222 do_check(r#"fn foo(x: i32, <|>y: i32, ) {}"#, &["y", "y: i32", ", y: i32"]);
223 do_check(r#"fn foo(x: i32,<|>y: i32) {}"#, &["y", "y: i32", ",y: i32"]);
224
225 do_check(r#"const FOO: [usize; 2] = [ 22<|> , 33];"#, &["22", "22 , "]);
226 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|>];"#, &["33", ", 33"]);
227 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|> ,];"#, &["33", ", 33"]);
228
229 do_check(
230 r#"
231const FOO: [usize; 2] = [
232 22,
233 <|>33,
234]"#,
235 &["33", "33,"],
236 );
237
238 do_check(
239 r#"
240const FOO: [usize; 2] = [
241 22
242 , 33<|>,
243]"#,
244 &["33", ", 33"],
245 );
246 }
247
248 #[test]
249 fn test_extend_selection_start_of_the_line() {
250 do_check(
251 r#"
252impl S {
253<|> fn foo() {
254
255 }
256}"#,
257 &[" fn foo() {\n\n }\n"],
258 );
259 }
260
261 #[test]
262 fn test_extend_selection_doc_comments() {
263 do_check(
264 r#"
265struct A;
266
267/// bla
268/// bla
269struct B {
270 <|>
271}
272 "#,
273 &["\n \n", "{\n \n}", "/// bla\n/// bla\nstruct B {\n \n}"],
274 )
275 }
276
277 #[test]
278 fn test_extend_selection_comments() {
279 do_check(
280 r#"
281fn bar(){}
282
283// fn foo() {
284// 1 + <|>1
285// }
286
287// fn foo(){}
288 "#,
289 &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"],
290 );
291
292 do_check(
293 r#"
294// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
295// pub enum Direction {
296// <|> Next,
297// Prev
298// }
299"#,
300 &[
301 "// Next,",
302 "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }",
303 ],
304 );
305
306 do_check(
307 r#"
308/*
309foo
310_bar1<|>*/
311 "#,
312 &["_bar1", "/*\nfoo\n_bar1*/"],
313 );
314
315 do_check(
316 r#"
317//!<|>foo_2 bar
318 "#,
319 &["foo_2", "//!foo_2 bar"],
320 );
321
322 do_check(
323 r#"
324/<|>/foo bar
325 "#,
326 &["//foo bar"],
327 );
328 }
329
330 #[test]
331 fn test_extend_selection_prefer_idents() {
332 do_check(
333 r#"
334fn main() { foo<|>+bar;}
335 "#,
336 &["foo", "foo+bar"],
337 );
338 do_check(
339 r#"
340fn main() { foo+<|>bar;}
341 "#,
342 &["bar", "foo+bar"],
343 );
344 }
345
346 #[test]
347 fn test_extend_selection_prefer_lifetimes() {
348 do_check(r#"fn foo<<|>'a>() {}"#, &["'a", "<'a>"]);
349 do_check(r#"fn foo<'a<|>>() {}"#, &["'a", "<'a>"]);
350 }
351
352 #[test]
353 fn test_extend_selection_select_first_word() {
354 do_check(r#"// foo bar b<|>az quxx"#, &["baz", "// foo bar baz quxx"]);
355 do_check(
356 r#"
357impl S {
358 fn foo() {
359 // hel<|>lo world
360 }
361}
362 "#,
363 &["hello", "// hello world"],
364 );
365 }
366
367 #[test]
368 fn test_extend_selection_string() {
369 do_check(
370 r#"
371fn bar(){}
372
373" fn f<|>oo() {"
374 "#,
375 &["foo", "\" fn foo() {\""],
376 );
377 }
13} 378}
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs
index b8a4adbce..81ca57035 100644
--- a/crates/ra_ide_api/src/lib.rs
+++ b/crates/ra_ide_api/src/lib.rs
@@ -211,6 +211,23 @@ pub struct Analysis {
211// API, the API should in theory be usable as a library, or via a different 211// API, the API should in theory be usable as a library, or via a different
212// protocol. 212// protocol.
213impl Analysis { 213impl Analysis {
214 // Creates an analysis instance for a single file, without any extenal
215 // dependencies, stdlib support or ability to apply changes. See
216 // `AnalysisHost` for creating a fully-featured analysis.
217 pub fn from_single_file(text: String) -> (Analysis, FileId) {
218 let mut host = AnalysisHost::default();
219 let source_root = SourceRootId(0);
220 let mut change = AnalysisChange::new();
221 change.add_root(source_root, true);
222 let mut crate_graph = CrateGraph::default();
223 let file_id = FileId(0);
224 crate_graph.add_crate_root(file_id, Edition::Edition2018);
225 change.add_file(source_root, file_id, "main.rs".into(), Arc::new(text));
226 change.set_crate_graph(crate_graph);
227 host.apply_change(change);
228 (host.analysis(), file_id)
229 }
230
214 /// Debug info about the current state of the analysis 231 /// Debug info about the current state of the analysis
215 pub fn status(&self) -> String { 232 pub fn status(&self) -> String {
216 status::status(&*self.db) 233 status::status(&*self.db)
diff --git a/crates/ra_ide_api_light/src/extend_selection.rs b/crates/ra_ide_api_light/src/extend_selection.rs
deleted file mode 100644
index 28d62f290..000000000
--- a/crates/ra_ide_api_light/src/extend_selection.rs
+++ /dev/null
@@ -1,369 +0,0 @@
1use ra_syntax::{
2 Direction, SyntaxNode, TextRange, TextUnit,
3 algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset},
4 SyntaxKind::*,
5};
6
7pub fn extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> {
8 let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING];
9 let list_kinds = [
10 FIELD_PAT_LIST,
11 MATCH_ARM_LIST,
12 NAMED_FIELD_DEF_LIST,
13 POS_FIELD_DEF_LIST,
14 NAMED_FIELD_LIST,
15 ENUM_VARIANT_LIST,
16 USE_TREE_LIST,
17 TYPE_PARAM_LIST,
18 TYPE_ARG_LIST,
19 PARAM_LIST,
20 ARG_LIST,
21 ARRAY_EXPR,
22 ];
23
24 if range.is_empty() {
25 let offset = range.start();
26 let mut leaves = find_leaf_at_offset(root, offset);
27 if leaves.clone().all(|it| it.kind() == WHITESPACE) {
28 return Some(extend_ws(root, leaves.next()?, offset));
29 }
30 let leaf_range = match leaves {
31 LeafAtOffset::None => return None,
32 LeafAtOffset::Single(l) => {
33 if string_kinds.contains(&l.kind()) {
34 extend_single_word_in_comment_or_string(l, offset).unwrap_or_else(|| l.range())
35 } else {
36 l.range()
37 }
38 }
39 LeafAtOffset::Between(l, r) => pick_best(l, r).range(),
40 };
41 return Some(leaf_range);
42 };
43 let node = find_covering_node(root, range);
44
45 // Using shallowest node with same range allows us to traverse siblings.
46 let node = node.ancestors().take_while(|n| n.range() == node.range()).last().unwrap();
47
48 if range == node.range() {
49 if string_kinds.contains(&node.kind()) {
50 if let Some(range) = extend_comments(node) {
51 return Some(range);
52 }
53 }
54
55 if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) {
56 if let Some(range) = extend_list_item(node) {
57 return Some(range);
58 }
59 }
60 }
61
62 match node.ancestors().skip_while(|n| n.range() == range).next() {
63 None => None,
64 Some(parent) => Some(parent.range()),
65 }
66}
67
68fn extend_single_word_in_comment_or_string(
69 leaf: &SyntaxNode,
70 offset: TextUnit,
71) -> Option<TextRange> {
72 let text: &str = leaf.leaf_text()?;
73 let cursor_position: u32 = (offset - leaf.range().start()).into();
74
75 let (before, after) = text.split_at(cursor_position as usize);
76
77 fn non_word_char(c: char) -> bool {
78 !(c.is_alphanumeric() || c == '_')
79 }
80
81 let start_idx = before.rfind(non_word_char)? as u32;
82 let end_idx = after.find(non_word_char).unwrap_or(after.len()) as u32;
83
84 let from: TextUnit = (start_idx + 1).into();
85 let to: TextUnit = (cursor_position + end_idx).into();
86
87 let range = TextRange::from_to(from, to);
88 if range.is_empty() {
89 None
90 } else {
91 Some(range + leaf.range().start())
92 }
93}
94
95fn extend_ws(root: &SyntaxNode, ws: &SyntaxNode, offset: TextUnit) -> TextRange {
96 let ws_text = ws.leaf_text().unwrap();
97 let suffix = TextRange::from_to(offset, ws.range().end()) - ws.range().start();
98 let prefix = TextRange::from_to(ws.range().start(), offset) - ws.range().start();
99 let ws_suffix = &ws_text.as_str()[suffix];
100 let ws_prefix = &ws_text.as_str()[prefix];
101 if ws_text.contains('\n') && !ws_suffix.contains('\n') {
102 if let Some(node) = ws.next_sibling() {
103 let start = match ws_prefix.rfind('\n') {
104 Some(idx) => ws.range().start() + TextUnit::from((idx + 1) as u32),
105 None => node.range().start(),
106 };
107 let end = if root.text().char_at(node.range().end()) == Some('\n') {
108 node.range().end() + TextUnit::of_char('\n')
109 } else {
110 node.range().end()
111 };
112 return TextRange::from_to(start, end);
113 }
114 }
115 ws.range()
116}
117
118fn pick_best<'a>(l: &'a SyntaxNode, r: &'a SyntaxNode) -> &'a SyntaxNode {
119 return if priority(r) > priority(l) { r } else { l };
120 fn priority(n: &SyntaxNode) -> usize {
121 match n.kind() {
122 WHITESPACE => 0,
123 IDENT | SELF_KW | SUPER_KW | CRATE_KW | LIFETIME => 2,
124 _ => 1,
125 }
126 }
127}
128
129/// Extend list item selection to include nearby comma and whitespace.
130fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
131 fn is_single_line_ws(node: &SyntaxNode) -> bool {
132 node.kind() == WHITESPACE && !node.leaf_text().unwrap().contains('\n')
133 }
134
135 fn nearby_comma(node: &SyntaxNode, dir: Direction) -> Option<&SyntaxNode> {
136 node.siblings(dir)
137 .skip(1)
138 .skip_while(|node| is_single_line_ws(node))
139 .next()
140 .filter(|node| node.kind() == COMMA)
141 }
142
143 if let Some(comma_node) = nearby_comma(node, Direction::Prev) {
144 return Some(TextRange::from_to(comma_node.range().start(), node.range().end()));
145 }
146
147 if let Some(comma_node) = nearby_comma(node, Direction::Next) {
148 // Include any following whitespace when comma if after list item.
149 let final_node = comma_node
150 .siblings(Direction::Next)
151 .skip(1)
152 .next()
153 .filter(|node| is_single_line_ws(node))
154 .unwrap_or(comma_node);
155
156 return Some(TextRange::from_to(node.range().start(), final_node.range().end()));
157 }
158
159 return None;
160}
161
162fn extend_comments(node: &SyntaxNode) -> Option<TextRange> {
163 let prev = adj_comments(node, Direction::Prev);
164 let next = adj_comments(node, Direction::Next);
165 if prev != next {
166 Some(TextRange::from_to(prev.range().start(), next.range().end()))
167 } else {
168 None
169 }
170}
171
172fn adj_comments(node: &SyntaxNode, dir: Direction) -> &SyntaxNode {
173 let mut res = node;
174 for node in node.siblings(dir) {
175 match node.kind() {
176 COMMENT => res = node,
177 WHITESPACE if !node.leaf_text().unwrap().as_str().contains("\n\n") => (),
178 _ => break,
179 }
180 }
181 res
182}
183
184#[cfg(test)]
185mod tests {
186 use ra_syntax::{SourceFile, AstNode};
187 use test_utils::extract_offset;
188
189 use super::*;
190
191 fn do_check(before: &str, afters: &[&str]) {
192 let (cursor, before) = extract_offset(before);
193 let file = SourceFile::parse(&before);
194 let mut range = TextRange::offset_len(cursor, 0.into());
195 for &after in afters {
196 range = extend_selection(file.syntax(), range).unwrap();
197 let actual = &before[range];
198 assert_eq!(after, actual);
199 }
200 }
201
202 #[test]
203 fn test_extend_selection_arith() {
204 do_check(r#"fn foo() { <|>1 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]);
205 }
206
207 #[test]
208 fn test_extend_selection_list() {
209 do_check(r#"fn foo(<|>x: i32) {}"#, &["x", "x: i32"]);
210 do_check(r#"fn foo(<|>x: i32, y: i32) {}"#, &["x", "x: i32", "x: i32, "]);
211 do_check(r#"fn foo(<|>x: i32,y: i32) {}"#, &["x", "x: i32", "x: i32,"]);
212 do_check(r#"fn foo(x: i32, <|>y: i32) {}"#, &["y", "y: i32", ", y: i32"]);
213 do_check(r#"fn foo(x: i32, <|>y: i32, ) {}"#, &["y", "y: i32", ", y: i32"]);
214 do_check(r#"fn foo(x: i32,<|>y: i32) {}"#, &["y", "y: i32", ",y: i32"]);
215
216 do_check(r#"const FOO: [usize; 2] = [ 22<|> , 33];"#, &["22", "22 , "]);
217 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|>];"#, &["33", ", 33"]);
218 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|> ,];"#, &["33", ", 33"]);
219
220 do_check(
221 r#"
222const FOO: [usize; 2] = [
223 22,
224 <|>33,
225]"#,
226 &["33", "33,"],
227 );
228
229 do_check(
230 r#"
231const FOO: [usize; 2] = [
232 22
233 , 33<|>,
234]"#,
235 &["33", ", 33"],
236 );
237 }
238
239 #[test]
240 fn test_extend_selection_start_of_the_line() {
241 do_check(
242 r#"
243impl S {
244<|> fn foo() {
245
246 }
247}"#,
248 &[" fn foo() {\n\n }\n"],
249 );
250 }
251
252 #[test]
253 fn test_extend_selection_doc_comments() {
254 do_check(
255 r#"
256struct A;
257
258/// bla
259/// bla
260struct B {
261 <|>
262}
263 "#,
264 &["\n \n", "{\n \n}", "/// bla\n/// bla\nstruct B {\n \n}"],
265 )
266 }
267
268 #[test]
269 fn test_extend_selection_comments() {
270 do_check(
271 r#"
272fn bar(){}
273
274// fn foo() {
275// 1 + <|>1
276// }
277
278// fn foo(){}
279 "#,
280 &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"],
281 );
282
283 do_check(
284 r#"
285// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
286// pub enum Direction {
287// <|> Next,
288// Prev
289// }
290"#,
291 &[
292 "// Next,",
293 "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }",
294 ],
295 );
296
297 do_check(
298 r#"
299/*
300foo
301_bar1<|>*/
302 "#,
303 &["_bar1", "/*\nfoo\n_bar1*/"],
304 );
305
306 do_check(
307 r#"
308//!<|>foo_2 bar
309 "#,
310 &["foo_2", "//!foo_2 bar"],
311 );
312
313 do_check(
314 r#"
315/<|>/foo bar
316 "#,
317 &["//foo bar"],
318 );
319 }
320
321 #[test]
322 fn test_extend_selection_prefer_idents() {
323 do_check(
324 r#"
325fn main() { foo<|>+bar;}
326 "#,
327 &["foo", "foo+bar"],
328 );
329 do_check(
330 r#"
331fn main() { foo+<|>bar;}
332 "#,
333 &["bar", "foo+bar"],
334 );
335 }
336
337 #[test]
338 fn test_extend_selection_prefer_lifetimes() {
339 do_check(r#"fn foo<<|>'a>() {}"#, &["'a", "<'a>"]);
340 do_check(r#"fn foo<'a<|>>() {}"#, &["'a", "<'a>"]);
341 }
342
343 #[test]
344 fn test_extend_selection_select_first_word() {
345 do_check(r#"// foo bar b<|>az quxx"#, &["baz", "// foo bar baz quxx"]);
346 do_check(
347 r#"
348impl S {
349 fn foo() {
350 // hel<|>lo world
351 }
352}
353 "#,
354 &["hello", "// hello world"],
355 );
356 }
357
358 #[test]
359 fn test_extend_selection_string() {
360 do_check(
361 r#"
362fn bar(){}
363
364" fn f<|>oo() {"
365 "#,
366 &["foo", "\" fn foo() {\""],
367 );
368 }
369}
diff --git a/crates/ra_ide_api_light/src/lib.rs b/crates/ra_ide_api_light/src/lib.rs
index 43cdd6ea4..ca13eb018 100644
--- a/crates/ra_ide_api_light/src/lib.rs
+++ b/crates/ra_ide_api_light/src/lib.rs
@@ -3,7 +3,6 @@
3//! This usually means functions which take syntax tree as an input and produce 3//! This usually means functions which take syntax tree as an input and produce
4//! an edit or some auxiliary info. 4//! an edit or some auxiliary info.
5 5
6mod extend_selection;
7mod folding_ranges; 6mod folding_ranges;
8mod line_index; 7mod line_index;
9mod line_index_utils; 8mod line_index_utils;
@@ -14,15 +13,16 @@ mod join_lines;
14mod typing; 13mod typing;
15mod diagnostics; 14mod diagnostics;
16 15
17#[derive(Debug)] 16use rustc_hash::FxHashSet;
18pub struct LocalEdit { 17use ra_text_edit::TextEditBuilder;
19 pub label: String, 18use ra_syntax::{
20 pub edit: ra_text_edit::TextEdit, 19 SourceFile, SyntaxNode, TextRange, TextUnit, Direction,
21 pub cursor_position: Option<TextUnit>, 20 algo::find_leaf_at_offset,
22} 21 SyntaxKind::{self, *},
22 ast::{self, AstNode},
23};
23 24
24pub use self::{ 25pub use crate::{
25 extend_selection::extend_selection,
26 folding_ranges::{folding_ranges, Fold, FoldKind}, 26 folding_ranges::{folding_ranges, Fold, FoldKind},
27 line_index::{LineCol, LineIndex}, 27 line_index::{LineCol, LineIndex},
28 line_index_utils::translate_offset_with_edit, 28 line_index_utils::translate_offset_with_edit,
@@ -30,16 +30,14 @@ pub use self::{
30 diagnostics::diagnostics, 30 diagnostics::diagnostics,
31 join_lines::join_lines, 31 join_lines::join_lines,
32 typing::{on_enter, on_dot_typed, on_eq_typed}, 32 typing::{on_enter, on_dot_typed, on_eq_typed},
33
34}; 33};
35use ra_text_edit::TextEditBuilder; 34
36use ra_syntax::{ 35#[derive(Debug)]
37 SourceFile, SyntaxNode, TextRange, TextUnit, Direction, 36pub struct LocalEdit {
38 SyntaxKind::{self, *}, 37 pub label: String,
39 ast::{self, AstNode}, 38 pub edit: ra_text_edit::TextEdit,
40 algo::find_leaf_at_offset, 39 pub cursor_position: Option<TextUnit>,
41}; 40}
42use rustc_hash::FxHashSet;
43 41
44#[derive(Debug)] 42#[derive(Debug)]
45pub struct HighlightedRange { 43pub struct HighlightedRange {
diff --git a/crates/ra_parser/src/event.rs b/crates/ra_parser/src/event.rs
index 6361d5d86..c1773e8e0 100644
--- a/crates/ra_parser/src/event.rs
+++ b/crates/ra_parser/src/event.rs
@@ -105,7 +105,9 @@ pub(super) fn process(sink: &mut dyn TreeSink, mut events: Vec<Event>) {
105 // append `A`'s forward_parent `B` 105 // append `A`'s forward_parent `B`
106 fp = match mem::replace(&mut events[idx], Event::tombstone()) { 106 fp = match mem::replace(&mut events[idx], Event::tombstone()) {
107 Event::Start { kind, forward_parent } => { 107 Event::Start { kind, forward_parent } => {
108 forward_parents.push(kind); 108 if kind != TOMBSTONE {
109 forward_parents.push(kind);
110 }
109 forward_parent 111 forward_parent
110 } 112 }
111 _ => unreachable!(), 113 _ => unreachable!(),
diff --git a/crates/ra_parser/src/grammar/expressions.rs b/crates/ra_parser/src/grammar/expressions.rs
index 83812e938..73e1acd5a 100644
--- a/crates/ra_parser/src/grammar/expressions.rs
+++ b/crates/ra_parser/src/grammar/expressions.rs
@@ -8,10 +8,10 @@ const EXPR_FIRST: TokenSet = LHS_FIRST;
8 8
9pub(super) fn expr(p: &mut Parser) -> BlockLike { 9pub(super) fn expr(p: &mut Parser) -> BlockLike {
10 let r = Restrictions { forbid_structs: false, prefer_stmt: false }; 10 let r = Restrictions { forbid_structs: false, prefer_stmt: false };
11 expr_bp(p, r, 1) 11 expr_bp(p, r, 1).1
12} 12}
13 13
14pub(super) fn expr_stmt(p: &mut Parser) -> BlockLike { 14pub(super) fn expr_stmt(p: &mut Parser) -> (Option<CompletedMarker>, BlockLike) {
15 let r = Restrictions { forbid_structs: false, prefer_stmt: true }; 15 let r = Restrictions { forbid_structs: false, prefer_stmt: true };
16 expr_bp(p, r, 1) 16 expr_bp(p, r, 1)
17} 17}
@@ -38,6 +38,13 @@ pub(crate) fn block(p: &mut Parser) {
38 m.complete(p, BLOCK); 38 m.complete(p, BLOCK);
39} 39}
40 40
41fn is_expr_stmt_attr_allowed(kind: SyntaxKind) -> bool {
42 match kind {
43 BIN_EXPR | RANGE_EXPR | IF_EXPR => false,
44 _ => true,
45 }
46}
47
41pub(crate) fn expr_block_contents(p: &mut Parser) { 48pub(crate) fn expr_block_contents(p: &mut Parser) {
42 // This is checked by a validator 49 // This is checked by a validator
43 attributes::inner_attributes(p); 50 attributes::inner_attributes(p);
@@ -55,6 +62,13 @@ pub(crate) fn expr_block_contents(p: &mut Parser) {
55 // test block_items 62 // test block_items
56 // fn a() { fn b() {} } 63 // fn a() { fn b() {} }
57 let m = p.start(); 64 let m = p.start();
65 // test attr_on_expr_stmt
66 // fn foo() {
67 // #[A] foo();
68 // #[B] bar!{}
69 // #[C] #[D] {}
70 // #[D] return ();
71 // }
58 let has_attrs = p.at(POUND); 72 let has_attrs = p.at(POUND);
59 attributes::outer_attributes(p); 73 attributes::outer_attributes(p);
60 if p.at(LET_KW) { 74 if p.at(LET_KW) {
@@ -67,35 +81,51 @@ pub(crate) fn expr_block_contents(p: &mut Parser) {
67 Err(m) => m, 81 Err(m) => m,
68 }; 82 };
69 83
70 if has_attrs { 84 let (cm, blocklike) = expr_stmt(p);
71 m.abandon(p); 85 let kind = cm.as_ref().map(|cm| cm.kind()).unwrap_or(ERROR);
72 p.error("expected a let statement or an item after attributes in block"); 86
73 } else { 87 if has_attrs && !is_expr_stmt_attr_allowed(kind) {
74 let is_blocklike = expressions::expr_stmt(p) == BlockLike::Block; 88 // test_err attr_on_expr_not_allowed
75 if p.at(R_CURLY) { 89 // fn foo() {
90 // #[A] 1 + 2;
91 // #[B] if true {};
92 // }
93 p.error(format!("attributes are not allowed on {:?}", kind));
94 }
95
96 if p.at(R_CURLY) {
97 // test attr_on_last_expr_in_block
98 // fn foo() {
99 // { #[A] bar!()? }
100 // #[B] &()
101 // }
102 if let Some(cm) = cm {
103 cm.undo_completion(p).abandon(p);
104 m.complete(p, kind);
105 } else {
76 m.abandon(p); 106 m.abandon(p);
107 }
108 } else {
109 // test no_semi_after_block
110 // fn foo() {
111 // if true {}
112 // loop {}
113 // match () {}
114 // while true {}
115 // for _ in () {}
116 // {}
117 // {}
118 // macro_rules! test {
119 // () => {}
120 // }
121 // test!{}
122 // }
123 if blocklike.is_block() {
124 p.eat(SEMI);
77 } else { 125 } else {
78 // test no_semi_after_block 126 p.expect(SEMI);
79 // fn foo() {
80 // if true {}
81 // loop {}
82 // match () {}
83 // while true {}
84 // for _ in () {}
85 // {}
86 // {}
87 // macro_rules! test {
88 // () => {}
89 // }
90 // test!{}
91 // }
92 if is_blocklike {
93 p.eat(SEMI);
94 } else {
95 p.expect(SEMI);
96 }
97 m.complete(p, EXPR_STMT);
98 } 127 }
128 m.complete(p, EXPR_STMT);
99 } 129 }
100 } 130 }
101 131
@@ -176,7 +206,7 @@ fn current_op(p: &Parser) -> (u8, Op) {
176} 206}
177 207
178// Parses expression with binding power of at least bp. 208// Parses expression with binding power of at least bp.
179fn expr_bp(p: &mut Parser, r: Restrictions, bp: u8) -> BlockLike { 209fn expr_bp(p: &mut Parser, r: Restrictions, bp: u8) -> (Option<CompletedMarker>, BlockLike) {
180 let mut lhs = match lhs(p, r) { 210 let mut lhs = match lhs(p, r) {
181 Some((lhs, blocklike)) => { 211 Some((lhs, blocklike)) => {
182 // test stmt_bin_expr_ambiguity 212 // test stmt_bin_expr_ambiguity
@@ -185,11 +215,11 @@ fn expr_bp(p: &mut Parser, r: Restrictions, bp: u8) -> BlockLike {
185 // {1} &2; 215 // {1} &2;
186 // } 216 // }
187 if r.prefer_stmt && blocklike.is_block() { 217 if r.prefer_stmt && blocklike.is_block() {
188 return BlockLike::Block; 218 return (Some(lhs), BlockLike::Block);
189 } 219 }
190 lhs 220 lhs
191 } 221 }
192 None => return BlockLike::NotBlock, 222 None => return (None, BlockLike::NotBlock),
193 }; 223 };
194 224
195 loop { 225 loop {
@@ -208,7 +238,7 @@ fn expr_bp(p: &mut Parser, r: Restrictions, bp: u8) -> BlockLike {
208 expr_bp(p, r, op_bp + 1); 238 expr_bp(p, r, op_bp + 1);
209 lhs = m.complete(p, if is_range { RANGE_EXPR } else { BIN_EXPR }); 239 lhs = m.complete(p, if is_range { RANGE_EXPR } else { BIN_EXPR });
210 } 240 }
211 BlockLike::NotBlock 241 (Some(lhs), BlockLike::NotBlock)
212} 242}
213 243
214const LHS_FIRST: TokenSet = 244const LHS_FIRST: TokenSet =
diff --git a/crates/ra_parser/src/grammar/expressions/atom.rs b/crates/ra_parser/src/grammar/expressions/atom.rs
index d933288cd..a23977bfb 100644
--- a/crates/ra_parser/src/grammar/expressions/atom.rs
+++ b/crates/ra_parser/src/grammar/expressions/atom.rs
@@ -392,9 +392,9 @@ fn match_arm(p: &mut Parser) -> BlockLike {
392 match_guard(p); 392 match_guard(p);
393 } 393 }
394 p.expect(FAT_ARROW); 394 p.expect(FAT_ARROW);
395 let ret = expr_stmt(p); 395 let blocklike = expr_stmt(p).1;
396 m.complete(p, MATCH_ARM); 396 m.complete(p, MATCH_ARM);
397 ret 397 blocklike
398} 398}
399 399
400// test match_guard 400// test match_guard
diff --git a/crates/ra_parser/src/parser.rs b/crates/ra_parser/src/parser.rs
index a18458148..3c326452b 100644
--- a/crates/ra_parser/src/parser.rs
+++ b/crates/ra_parser/src/parser.rs
@@ -212,8 +212,9 @@ impl Marker {
212 } 212 }
213 _ => unreachable!(), 213 _ => unreachable!(),
214 } 214 }
215 let finish_pos = p.events.len() as u32;
215 p.push_event(Event::Finish); 216 p.push_event(Event::Finish);
216 CompletedMarker::new(self.pos, kind) 217 CompletedMarker::new(self.pos, finish_pos, kind)
217 } 218 }
218 219
219 /// Abandons the syntax tree node. All its children 220 /// Abandons the syntax tree node. All its children
@@ -230,11 +231,15 @@ impl Marker {
230 } 231 }
231} 232}
232 233
233pub(crate) struct CompletedMarker(u32, SyntaxKind); 234pub(crate) struct CompletedMarker {
235 start_pos: u32,
236 finish_pos: u32,
237 kind: SyntaxKind,
238}
234 239
235impl CompletedMarker { 240impl CompletedMarker {
236 fn new(pos: u32, kind: SyntaxKind) -> Self { 241 fn new(start_pos: u32, finish_pos: u32, kind: SyntaxKind) -> Self {
237 CompletedMarker(pos, kind) 242 CompletedMarker { start_pos, finish_pos, kind }
238 } 243 }
239 244
240 /// This method allows to create a new node which starts 245 /// This method allows to create a new node which starts
@@ -251,17 +256,32 @@ impl CompletedMarker {
251 /// distance to `NEWSTART` into forward_parent(=2 in this case); 256 /// distance to `NEWSTART` into forward_parent(=2 in this case);
252 pub(crate) fn precede(self, p: &mut Parser) -> Marker { 257 pub(crate) fn precede(self, p: &mut Parser) -> Marker {
253 let new_pos = p.start(); 258 let new_pos = p.start();
254 let idx = self.0 as usize; 259 let idx = self.start_pos as usize;
255 match p.events[idx] { 260 match p.events[idx] {
256 Event::Start { ref mut forward_parent, .. } => { 261 Event::Start { ref mut forward_parent, .. } => {
257 *forward_parent = Some(new_pos.pos - self.0); 262 *forward_parent = Some(new_pos.pos - self.start_pos);
258 } 263 }
259 _ => unreachable!(), 264 _ => unreachable!(),
260 } 265 }
261 new_pos 266 new_pos
262 } 267 }
263 268
269 /// Undo this completion and turns into a `Marker`
270 pub(crate) fn undo_completion(self, p: &mut Parser) -> Marker {
271 let start_idx = self.start_pos as usize;
272 let finish_idx = self.finish_pos as usize;
273 match p.events[start_idx] {
274 Event::Start { ref mut kind, forward_parent: None } => *kind = TOMBSTONE,
275 _ => unreachable!(),
276 }
277 match p.events[finish_idx] {
278 ref mut slot @ Event::Finish => *slot = Event::tombstone(),
279 _ => unreachable!(),
280 }
281 Marker::new(self.start_pos)
282 }
283
264 pub(crate) fn kind(&self) -> SyntaxKind { 284 pub(crate) fn kind(&self) -> SyntaxKind {
265 self.1 285 self.kind
266 } 286 }
267} 287}
diff --git a/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.rs b/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.rs
new file mode 100644
index 000000000..d725a07ce
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.rs
@@ -0,0 +1,4 @@
1fn foo() {
2 #[A] 1 + 2;
3 #[B] if true {};
4}
diff --git a/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.txt b/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.txt
new file mode 100644
index 000000000..fdea1ec1e
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/err/0009_attr_on_expr_not_allowed.txt
@@ -0,0 +1,55 @@
1SOURCE_FILE@[0; 48)
2 FN_DEF@[0; 47)
3 FN_KW@[0; 2)
4 WHITESPACE@[2; 3)
5 NAME@[3; 6)
6 IDENT@[3; 6) "foo"
7 PARAM_LIST@[6; 8)
8 L_PAREN@[6; 7)
9 R_PAREN@[7; 8)
10 WHITESPACE@[8; 9)
11 BLOCK@[9; 47)
12 L_CURLY@[9; 10)
13 WHITESPACE@[10; 14)
14 EXPR_STMT@[14; 25)
15 ATTR@[14; 18)
16 POUND@[14; 15)
17 TOKEN_TREE@[15; 18)
18 L_BRACK@[15; 16)
19 IDENT@[16; 17) "A"
20 R_BRACK@[17; 18)
21 WHITESPACE@[18; 19)
22 BIN_EXPR@[19; 24)
23 LITERAL@[19; 20)
24 INT_NUMBER@[19; 20) "1"
25 WHITESPACE@[20; 21)
26 PLUS@[21; 22)
27 WHITESPACE@[22; 23)
28 LITERAL@[23; 24)
29 INT_NUMBER@[23; 24) "2"
30 err: `attributes are not allowed on BIN_EXPR`
31 SEMI@[24; 25)
32 WHITESPACE@[25; 29)
33 EXPR_STMT@[29; 45)
34 ATTR@[29; 33)
35 POUND@[29; 30)
36 TOKEN_TREE@[30; 33)
37 L_BRACK@[30; 31)
38 IDENT@[31; 32) "B"
39 R_BRACK@[32; 33)
40 WHITESPACE@[33; 34)
41 IF_EXPR@[34; 44)
42 IF_KW@[34; 36)
43 WHITESPACE@[36; 37)
44 CONDITION@[37; 41)
45 LITERAL@[37; 41)
46 TRUE_KW@[37; 41)
47 WHITESPACE@[41; 42)
48 BLOCK@[42; 44)
49 L_CURLY@[42; 43)
50 R_CURLY@[43; 44)
51 err: `attributes are not allowed on IF_EXPR`
52 SEMI@[44; 45)
53 WHITESPACE@[45; 46)
54 R_CURLY@[46; 47)
55 WHITESPACE@[47; 48)
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.rs b/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.rs
new file mode 100644
index 000000000..b28c078f9
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.rs
@@ -0,0 +1,6 @@
1fn foo() {
2 #[A] foo();
3 #[B] bar!{}
4 #[C] #[D] {}
5 #[D] return ();
6}
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.txt b/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.txt
new file mode 100644
index 000000000..7cd525cc7
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0126_attr_on_expr_stmt.txt
@@ -0,0 +1,88 @@
1SOURCE_FILE@[0; 82)
2 FN_DEF@[0; 81)
3 FN_KW@[0; 2)
4 WHITESPACE@[2; 3)
5 NAME@[3; 6)
6 IDENT@[3; 6) "foo"
7 PARAM_LIST@[6; 8)
8 L_PAREN@[6; 7)
9 R_PAREN@[7; 8)
10 WHITESPACE@[8; 9)
11 BLOCK@[9; 81)
12 L_CURLY@[9; 10)
13 WHITESPACE@[10; 15)
14 EXPR_STMT@[15; 26)
15 ATTR@[15; 19)
16 POUND@[15; 16)
17 TOKEN_TREE@[16; 19)
18 L_BRACK@[16; 17)
19 IDENT@[17; 18) "A"
20 R_BRACK@[18; 19)
21 WHITESPACE@[19; 20)
22 CALL_EXPR@[20; 25)
23 PATH_EXPR@[20; 23)
24 PATH@[20; 23)
25 PATH_SEGMENT@[20; 23)
26 NAME_REF@[20; 23)
27 IDENT@[20; 23) "foo"
28 ARG_LIST@[23; 25)
29 L_PAREN@[23; 24)
30 R_PAREN@[24; 25)
31 SEMI@[25; 26)
32 WHITESPACE@[26; 31)
33 EXPR_STMT@[31; 42)
34 ATTR@[31; 35)
35 POUND@[31; 32)
36 TOKEN_TREE@[32; 35)
37 L_BRACK@[32; 33)
38 IDENT@[33; 34) "B"
39 R_BRACK@[34; 35)
40 WHITESPACE@[35; 36)
41 MACRO_CALL@[36; 42)
42 PATH@[36; 39)
43 PATH_SEGMENT@[36; 39)
44 NAME_REF@[36; 39)
45 IDENT@[36; 39) "bar"
46 EXCL@[39; 40)
47 TOKEN_TREE@[40; 42)
48 L_CURLY@[40; 41)
49 R_CURLY@[41; 42)
50 WHITESPACE@[42; 47)
51 EXPR_STMT@[47; 59)
52 ATTR@[47; 51)
53 POUND@[47; 48)
54 TOKEN_TREE@[48; 51)
55 L_BRACK@[48; 49)
56 IDENT@[49; 50) "C"
57 R_BRACK@[50; 51)
58 WHITESPACE@[51; 52)
59 ATTR@[52; 56)
60 POUND@[52; 53)
61 TOKEN_TREE@[53; 56)
62 L_BRACK@[53; 54)
63 IDENT@[54; 55) "D"
64 R_BRACK@[55; 56)
65 WHITESPACE@[56; 57)
66 BLOCK_EXPR@[57; 59)
67 BLOCK@[57; 59)
68 L_CURLY@[57; 58)
69 R_CURLY@[58; 59)
70 WHITESPACE@[59; 64)
71 EXPR_STMT@[64; 79)
72 ATTR@[64; 68)
73 POUND@[64; 65)
74 TOKEN_TREE@[65; 68)
75 L_BRACK@[65; 66)
76 IDENT@[66; 67) "D"
77 R_BRACK@[67; 68)
78 WHITESPACE@[68; 69)
79 RETURN_EXPR@[69; 78)
80 RETURN_KW@[69; 75)
81 WHITESPACE@[75; 76)
82 TUPLE_EXPR@[76; 78)
83 L_PAREN@[76; 77)
84 R_PAREN@[77; 78)
85 SEMI@[78; 79)
86 WHITESPACE@[79; 80)
87 R_CURLY@[80; 81)
88 WHITESPACE@[81; 82)
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.rs b/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.rs
new file mode 100644
index 000000000..9c5c8eb36
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.rs
@@ -0,0 +1,4 @@
1fn foo() {
2 { #[A] bar!()? }
3 #[B] &()
4}
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.txt b/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.txt
new file mode 100644
index 000000000..4af64559c
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0127_attr_on_last_expr_in_block.txt
@@ -0,0 +1,54 @@
1SOURCE_FILE@[0; 47)
2 FN_DEF@[0; 46)
3 FN_KW@[0; 2)
4 WHITESPACE@[2; 3)
5 NAME@[3; 6)
6 IDENT@[3; 6) "foo"
7 PARAM_LIST@[6; 8)
8 L_PAREN@[6; 7)
9 R_PAREN@[7; 8)
10 WHITESPACE@[8; 9)
11 BLOCK@[9; 46)
12 L_CURLY@[9; 10)
13 WHITESPACE@[10; 15)
14 EXPR_STMT@[15; 31)
15 BLOCK_EXPR@[15; 31)
16 BLOCK@[15; 31)
17 L_CURLY@[15; 16)
18 WHITESPACE@[16; 17)
19 TRY_EXPR@[17; 29)
20 ATTR@[17; 21)
21 POUND@[17; 18)
22 TOKEN_TREE@[18; 21)
23 L_BRACK@[18; 19)
24 IDENT@[19; 20) "A"
25 R_BRACK@[20; 21)
26 WHITESPACE@[21; 22)
27 MACRO_CALL@[22; 28)
28 PATH@[22; 25)
29 PATH_SEGMENT@[22; 25)
30 NAME_REF@[22; 25)
31 IDENT@[22; 25) "bar"
32 EXCL@[25; 26)
33 TOKEN_TREE@[26; 28)
34 L_PAREN@[26; 27)
35 R_PAREN@[27; 28)
36 QUESTION@[28; 29)
37 WHITESPACE@[29; 30)
38 R_CURLY@[30; 31)
39 WHITESPACE@[31; 36)
40 REF_EXPR@[36; 44)
41 ATTR@[36; 40)
42 POUND@[36; 37)
43 TOKEN_TREE@[37; 40)
44 L_BRACK@[37; 38)
45 IDENT@[38; 39) "B"
46 R_BRACK@[39; 40)
47 WHITESPACE@[40; 41)
48 AMP@[41; 42)
49 TUPLE_EXPR@[42; 44)
50 L_PAREN@[42; 43)
51 R_PAREN@[43; 44)
52 WHITESPACE@[44; 45)
53 R_CURLY@[45; 46)
54 WHITESPACE@[46; 47)
diff --git a/docs/dev/README.md b/docs/dev/README.md
new file mode 100644
index 000000000..ac7f4fd71
--- /dev/null
+++ b/docs/dev/README.md
@@ -0,0 +1,124 @@
1# Contributing Quick Start
2
3Rust Analyzer is just a usual rust project, which is organized as a Cargo
4workspace, builds on stable and doesn't depend on C libraries. So, just
5
6```
7$ cargo test
8```
9
10should be enough to get you started!
11
12To learn more about how rust-analyzer works, see
13[./architecture.md](./architecture.md) document.
14
15Various organizational and process issues are discussed here.
16
17# Getting in Touch
18
19Rust Analyzer is a part of [RLS-2.0 working
20group](https://github.com/rust-lang/compiler-team/tree/6a769c13656c0a6959ebc09e7b1f7c09b86fb9c0/working-groups/rls-2.0).
21Discussion happens in this Zulip stream:
22
23https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0
24
25# Issue Labels
26
27* [good-first-issue](https://github.com/rust-analyzer/rust-analyzer/labels/good%20first%20issue)
28 are good issues to get into the project.
29* [E-mentor](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-mentor)
30 issues have links to the code in question and tests.
31* [E-easy](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy),
32 [E-medium](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-medium),
33 [E-hard](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-hard),
34 labels are *estimates* for how hard would be to write a fix.
35* [E-fun](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-fun)
36 is for cool, but probably hard stuff.
37
38# CI
39
40We use Travis for CI. Most of the things, including formatting, are checked by
41`cargo test` so, if `cargo test` passes locally, that's a good sign that CI will
42be green as well. We use bors-ng to enforce the [not rocket
43science](https://graydon2.dreamwidth.org/1597.html) rule.
44
45You can run `cargo format-hook` to install git-hook to run rustfmt on commit.
46
47# Code organization
48
49All Rust code lives in the `crates` top-level directory, and is organized as a
50single Cargo workspace. The `editors` top-level directory contains code for
51integrating with editors. Currently, it contains plugins for VS Code (in
52typescript) and Emacs (in elisp). The `docs` top-level directory contains both
53developer and user documentation.
54
55We have some automation infra in Rust in the `crates/tool` package. It contains
56stuff like formatting checking, code generation and powers `cargo install-code`.
57The latter syntax is achieved with the help of cargo aliases (see `.cargo`
58directory).
59
60# Launching rust-analyzer
61
62Debugging language server can be tricky: LSP is rather chatty, so driving it
63from the command line is not really feasible, driving it via VS Code requires
64interacting with two processes.
65
66For this reason, the best way to see how rust-analyzer works is to find a
67relevant test and execute it (VS Code includes an action for running a single
68test).
69
70However, launching a VS Code instance with locally build language server is
71possible. There's even a VS Code task for this, so just <kbd>F5</kbd> should
72work (thanks, [@andrew-w-ross](https://github.com/andrew-w-ross)!).
73
74I often just install development version with `cargo jinstall-lsp` and
75restart the host VS Code.
76
77See [./debugging.md](./debugging.md) for how to attach to rust-analyzer with
78debugger, and don't forget that rust-analyzer has useful `pd` snippet and `dbg`
79postfix completion for printf debugging :-)
80
81# Working With VS Code Extension
82
83To work on the VS Code extension, launch code inside `editors/code` and use `F5`
84to launch/debug. To automatically apply formatter and linter suggestions, use
85`npm run fix`.
86
87# Logging
88
89Logging is done by both rust-analyzer and VS Code, so it might be tricky to
90figure out where logs go.
91
92Inside rust-analyzer, we use the standard `log` crate for logging, and
93`flexi_logger` for logging frotend. By default, log goes to stderr (the same as
94with `env_logger`), but the stderr itself is processed by VS Code. To mirror
95logs to a `./log` directory, set `RA_INTERNAL_MODE=1` environmental variable.
96
97To see stderr in the running VS Code instance, go to the "Output" tab of the
98panel and select `rust-analyzer`. This shows `eprintln!` as well. Note that
99`stdout` is used for the actual protocol, so `println!` will break things.
100
101To log all communication between the server and the client, there are two choices:
102
103* you can log on the server side, by running something like
104 ```
105 env RUST_LOG=gen_lsp_server=trace code .
106 ```
107
108* you can log on the client side, by enabling `"rust-analyzer.trace.server":
109 "verbose"` workspace setting. These logs are shown in a separate tab in the
110 output and could be used with LSP inspector. Kudos to
111 [@DJMcNab](https://github.com/DJMcNab) for setting this awesome infra up!
112
113
114There's also two VS Code commands which might be of interest:
115
116* `Rust Analyzer: Status` shows some memory-usage statistics. To take full
117 advantage of it, you need to compile rust-analyzer with jemalloc support:
118 ```
119 $ cargo install --path crates/ra_lsp_server --force --features jemalloc
120 ```
121
122 There's an alias for this: `cargo jinstall-lsp`.
123
124* `Rust Analyzer: Syntax Tree` shows syntax tree of the current file/selection.
diff --git a/ARCHITECTURE.md b/docs/dev/architecture.md
index 57f76ebae..f990d5bf0 100644
--- a/ARCHITECTURE.md
+++ b/docs/dev/architecture.md
@@ -7,8 +7,10 @@ in the right place!
7See also the [guide](./guide.md), which walks through a particular snapshot of 7See also the [guide](./guide.md), which walks through a particular snapshot of
8rust-analyzer code base. 8rust-analyzer code base.
9 9
10For syntax-trees specifically, there's a [video walk 10Yet another resource is this playlist with videos about various parts of the
11through](https://youtu.be/DGAuLWdCCAI) as well. 11analyzer:
12
13https://www.youtube.com/playlist?list=PL85XCvVPmGQho7MZkdW-wtPtuJcFpzycE
12 14
13## The Big Picture 15## The Big Picture
14 16
@@ -61,7 +63,7 @@ processes. These are outlined below:
61 63
62## Code Walk-Through 64## Code Walk-Through
63 65
64### `crates/ra_syntax` 66### `crates/ra_syntax`, `crates/ra_parser`
65 67
66Rust syntax tree structure and parser. See 68Rust syntax tree structure and parser. See
67[RFC](https://github.com/rust-lang/rfcs/pull/2256) for some design notes. 69[RFC](https://github.com/rust-lang/rfcs/pull/2256) for some design notes.
@@ -145,12 +147,14 @@ throughout its modules.
145 147
146An LSP implementation which wraps `ra_ide_api` into a langauge server protocol. 148An LSP implementation which wraps `ra_ide_api` into a langauge server protocol.
147 149
148### `crates/ra_vfs` 150### `ra_vfs`
149 151
150Although `hir` and `ra_ide_api` don't do any IO, we need to be able to read 152Although `hir` and `ra_ide_api` don't do any IO, we need to be able to read
151files from disk at the end of the day. This is what `ra_vfs` does. It also 153files from disk at the end of the day. This is what `ra_vfs` does. It also
152manages overlays: "dirty" files in the editor, whose "true" contents is 154manages overlays: "dirty" files in the editor, whose "true" contents is
153different from data on disk. 155different from data on disk. This is more or less the single really
156platform-dependent component, so it lives in a separate repository and has an
157extensive cross-platform CI testing.
154 158
155### `crates/gen_lsp_server` 159### `crates/gen_lsp_server`
156 160
@@ -164,37 +168,32 @@ Run with `RUST_LOG=sync_lsp_server=debug` to see all the messages.
164 168
165A CLI interface to rust-analyzer. 169A CLI interface to rust-analyzer.
166 170
167### `crate/tools`
168
169Custom Cargo tasks used to develop rust-analyzer:
170
171- `cargo gen-syntax` -- generate `ast` and `syntax_kinds`
172- `cargo gen-tests` -- collect inline tests from grammar
173- `cargo install-code` -- build and install VS Code extension and server
174
175### `editors/code`
176
177VS Code plugin
178 171
172## Testing Infrastructure
179 173
180## Common workflows 174Rust Analyzer has three interesting [systems
175boundaries](https://www.tedinski.com/2018/04/10/making-tests-a-positive-influence-on-design.html)
176to concentrate tests on.
181 177
182To try out VS Code extensions, run `cargo install-code`. This installs both the 178The outermost boundary is the `ra_lsp_server` crate, which defines an LSP
183`ra_lsp_server` binary and the VS Code extension. To install only the binary, use 179interface in terms of stdio. We do integration testing of this component, by
184`cargo install-lsp` (shorthand for `cargo install --path crates/ra_lsp_server --force`) 180feeding it with a stream of LSP requests and checking responses. These tests are
181known as "heavy", because they interact with Cargo and read real files from
182disk. For this reason, we try to avoid writing too many tests on this boundary:
183in a statically typed language, it's hard to make an error in the protocol
184itself if messages are themselves typed.
185 185
186To see logs from the language server, set `RUST_LOG=info` env variable. To see 186The middle, and most important, boundary is `ra_ide_api`. Unlike
187all communication between the server and the client, use 187`ra_lsp_server`, which exposes API, `ide_api` uses Rust API and is intended to
188`RUST_LOG=gen_lsp_server=debug` (this will print quite a bit of stuff). 188use by various tools. Typical test creates an `AnalysisHost`, calls some
189`Analysis` functions and compares the results against expectation.
189 190
190There's `rust-analyzer: status` command which prints common high-level debug 191The innermost and most elaborate boundary is `hir`. It has a much richer
191info. In particular, it prints info about memory usage of various data 192vocabulary of types than `ide_api`, but the basic testing setup is the same: we
192structures, and, if compiled with jemalloc support (`cargo jinstall-lsp` or 193create a database, run some queries, assert result.
193`cargo install --path crates/ra_lsp_server --force --features jemalloc`), includes
194 statistic about the heap.
195 194
196To run tests, just `cargo test`. 195For comparisons, we use [insta](https://github.com/mitsuhiko/insta/) library for
196snapshot testing.
197 197
198To work on the VS Code extension, launch code inside `editors/code` and use `F5` to 198To test various analysis corner cases and avoid forgetting about old tests, we
199launch/debug. To automatically apply formatter and linter suggestions, use `npm 199use so-called marks. See the `marks` module in the `test_utils` crate for more.
200run fix`.
diff --git a/DEBUGGING.md b/docs/dev/debugging.md
index f868e6998..f868e6998 100644
--- a/DEBUGGING.md
+++ b/docs/dev/debugging.md
diff --git a/guide.md b/docs/dev/guide.md
index abbe4c154..abbe4c154 100644
--- a/guide.md
+++ b/docs/dev/guide.md
diff --git a/docs/dev/lsp-features.md b/docs/dev/lsp-features.md
new file mode 100644
index 000000000..212d132ee
--- /dev/null
+++ b/docs/dev/lsp-features.md
@@ -0,0 +1,74 @@
1# Supported LSP features
2
3This list documents LSP features, supported by rust-analyzer.
4
5## General
6- [x] [initialize](https://microsoft.github.io/language-server-protocol/specification#initialize)
7- [x] [initialized](https://microsoft.github.io/language-server-protocol/specification#initialized)
8- [x] [shutdown](https://microsoft.github.io/language-server-protocol/specification#shutdown)
9- [ ] [exit](https://microsoft.github.io/language-server-protocol/specification#exit)
10- [x] [$/cancelRequest](https://microsoft.github.io/language-server-protocol/specification#cancelRequest)
11
12## Workspace
13- [ ] [workspace/workspaceFolders](https://microsoft.github.io/language-server-protocol/specification#workspace_workspaceFolders)
14- [ ] [workspace/didChangeWorkspaceFolders](https://microsoft.github.io/language-server-protocol/specification#workspace_didChangeWorkspaceFolders)
15- [x] [workspace/didChangeConfiguration](https://microsoft.github.io/language-server-protocol/specification#workspace_didChangeConfiguration)
16- [ ] [workspace/configuration](https://microsoft.github.io/language-server-protocol/specification#workspace_configuration)
17- [x] [workspace/didChangeWatchedFiles](https://microsoft.github.io/language-server-protocol/specification#workspace_didChangeWatchedFiles)
18- [x] [workspace/symbol](https://microsoft.github.io/language-server-protocol/specification#workspace_symbol)
19- [x] [workspace/executeCommand](https://microsoft.github.io/language-server-protocol/specification#workspace_executeCommand)
20 - `apply_code_action`
21- [ ] [workspace/applyEdit](https://microsoft.github.io/language-server-protocol/specification#workspace_applyEdit)
22
23## Text Synchronization
24- [x] [textDocument/didOpen](https://microsoft.github.io/language-server-protocol/specification#textDocument_didOpen)
25- [x] [textDocument/didChange](https://microsoft.github.io/language-server-protocol/specification#textDocument_didChange)
26- [ ] [textDocument/willSave](https://microsoft.github.io/language-server-protocol/specification#textDocument_willSave)
27- [ ] [textDocument/willSaveWaitUntil](https://microsoft.github.io/language-server-protocol/specification#textDocument_willSaveWaitUntil)
28- [x] [textDocument/didSave](https://microsoft.github.io/language-server-protocol/specification#textDocument_didSave)
29- [x] [textDocument/didClose](https://microsoft.github.io/language-server-protocol/specification#textDocument_didClose)
30
31## Diagnostics
32- [x] [textDocument/publishDiagnostics](https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics)
33
34## Lanuguage Features
35- [x] [textDocument/completion](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion)
36 - open close: false
37 - change: Full
38 - will save: false
39 - will save wait until: false
40 - save: false
41- [x] [completionItem/resolve](https://microsoft.github.io/language-server-protocol/specification#completionItem_resolve)
42 - resolve provider: none
43 - trigger characters: `:`, `.`
44- [x] [textDocument/hover](https://microsoft.github.io/language-server-protocol/specification#textDocument_hover)
45- [x] [textDocument/signatureHelp](https://microsoft.github.io/language-server-protocol/specification#textDocument_signatureHelp)
46 - trigger characters: `(`, `,`, `)`
47- [ ] [textDocument/declaration](https://microsoft.github.io/language-server-protocol/specification#textDocument_declaration)
48- [x] [textDocument/definition](https://microsoft.github.io/language-server-protocol/specification#textDocument_definition)
49- [ ] [textDocument/typeDefinition](https://microsoft.github.io/language-server-protocol/specification#textDocument_typeDefinition)
50- [x] [textDocument/implementation](https://microsoft.github.io/language-server-protocol/specification#textDocument_implementation)
51- [x] [textDocument/references](https://microsoft.github.io/language-server-protocol/specification#textDocument_references)
52- [x] [textDocument/documentHighlight](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentHighlight)
53- [x] [textDocument/documentSymbol](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol)
54- [x] [textDocument/codeAction](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction)
55 - rust-analyzer.syntaxTree
56 - rust-analyzer.extendSelection
57 - rust-analyzer.matchingBrace
58 - rust-analyzer.parentModule
59 - rust-analyzer.joinLines
60 - rust-analyzer.run
61 - rust-analyzer.analyzerStatus
62- [x] [textDocument/codeLens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens)
63- [ ] [textDocument/documentLink](https://microsoft.github.io/language-server-protocol/specification#codeLens_resolve)
64- [ ] [documentLink/resolve](https://microsoft.github.io/language-server-protocol/specification#documentLink_resolve)
65- [ ] [textDocument/documentColor](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentColor)
66- [ ] [textDocument/colorPresentation](https://microsoft.github.io/language-server-protocol/specification#textDocument_colorPresentation)
67- [x] [textDocument/formatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting)
68- [ ] [textDocument/rangeFormatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_rangeFormatting)
69- [x] [textDocument/onTypeFormatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_onTypeFormatting)
70 - first trigger character: `=`
71 - more trigger character `.`
72- [x] [textDocument/rename](https://microsoft.github.io/language-server-protocol/specification#textDocument_rename)
73- [x] [textDocument/prepareRename](https://microsoft.github.io/language-server-protocol/specification#textDocument_prepareRename)
74- [x] [textDocument/foldingRange](https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange)
diff --git a/docs/user/README.md b/docs/user/README.md
new file mode 100644
index 000000000..439c4e6ae
--- /dev/null
+++ b/docs/user/README.md
@@ -0,0 +1,77 @@
1The main interface to rust-analyzer is the
2[LSP](https://microsoft.github.io/language-server-protocol/) implementation. To
3install lsp server, use `cargo install-lsp`, which is a shorthand for `cargo
4install --package ra_lsp_server`. The binary is named `ra_lsp_server`, you
5should be able to use it with any LSP-compatible editor. We use custom
6extensions to LSP, so special client-side support is required to take full
7advantage of rust-analyzer. This repository contains support code for VS Code
8and Emacs.
9
10Rust Analyzer needs sources of rust standard library to work, so you might need
11to execute
12
13```
14$ rustup component add rust-src
15```
16
17See [./features.md](./features.md) document for a list of features that are available.
18
19## VS Code
20
21Prerequisites:
22
23In order to build the VS Code plugin, you need to have node.js and npm with
24a minimum version of 10 installed. Please refer to
25[node.js and npm documentation](https://nodejs.org) for installation instructions.
26
27You will also need the most recent version of VS Code: we don't try to
28maintain compatibility with older versions yet.
29
30The experimental VS Code plugin can then be built and installed by executing the
31following commands:
32
33```
34$ git clone https://github.com/rust-analyzer/rust-analyzer.git --depth 1
35$ cd rust-analyzer
36$ cargo install-code
37```
38
39This will run `cargo install --package ra_lsp_server` to install the server
40binary into `~/.cargo/bin`, and then will build and install plugin from
41`editors/code`. See
42[this](https://github.com/rust-analyzer/rust-analyzer/blob/69ee5c9c5ef212f7911028c9ddf581559e6565c3/crates/tools/src/main.rs#L37-L56)
43for details. The installation is expected to *just work*, if it doesn't, report
44bugs!
45
46It's better to remove existing Rust plugins to avoid interference.
47
48Beyond basic LSP features, there are some extension commands which you can
49invoke via <kbd>Ctrl+Shift+P</kbd> or bind to a shortcut. See [./features.md](./features.md)
50for details.
51
52### Settings
53
54* `rust-analyzer.highlightingOn`: enables experimental syntax highlighting
55* `rust-analyzer.showWorkspaceLoadedNotification`: to ease troubleshooting, a
56 notification is shown by default when a workspace is loaded
57* `rust-analyzer.enableEnhancedTyping`: by default, rust-analyzer intercepts
58 `Enter` key to make it easier to continue comments
59* `rust-analyzer.raLspServerPath`: path to `ra_lsp_server` executable
60* `rust-analyzer.enableCargoWatchOnStartup`: prompt to install & enable `cargo
61 watch` for live error highlighting (note, this **does not** use rust-analyzer)
62* `rust-analyzer.trace.server`: enables internal logging
63
64
65## Emacs
66
67Prerequisites:
68
69`emacs-lsp`, `dash` and `ht` packages.
70
71Installation:
72
73* add
74[ra-emacs-lsp.el](https://github.com/rust-analyzer/rust-analyzer/blob/69ee5c9c5ef212f7911028c9ddf581559e6565c3/editors/emacs/ra-emacs-lsp.el)
75to load path and require it in `init.el`
76* run `lsp` in a rust buffer
77* (Optionally) bind commands like `rust-analyzer-join-lines` or `rust-analyzer-extend-selection` to keys
diff --git a/docs/user/features.md b/docs/user/features.md
new file mode 100644
index 000000000..b9d2aa84f
--- /dev/null
+++ b/docs/user/features.md
@@ -0,0 +1,359 @@
1This documents is an index of features that rust-analyzer language server
2provides. Shortcuts are for the default VS Code layout. If there's no shortcut,
3you can use <kbd>Ctrl+Shift+P</kbd> to search for the corresponding action.
4
5### Workspace Symbol <kbd>ctrl+t</kbd>
6
7Uses fuzzy-search to find types, modules and function by name across your
8project and dependencies. This **the** most useful feature, which improves code
9navigation tremendously. It mostly works on top of the built-in LSP
10functionality, however `#` and `*` symbols can be used to narrow down the
11search. Specifically,
12
13- `Foo` searches for `Foo` type in the current workspace
14- `foo#` searches for `foo` function in the current workspace
15- `Foo*` searches for `Foo` type among dependencies, excluding `stdlib`
16- `foo#*` searches for `foo` function among dependencies.
17
18That is, `#` switches from "types" to all symbols, `*` switches from the current
19workspace to dependencies.
20
21### Document Symbol <kbd>ctrl+shift+o</kbd>
22
23Provides a tree of the symbols defined in the file. Can be used to
24
25* fuzzy search symbol in a file (super useful)
26* draw breadcrumbs to describe the context around the cursor
27* draw outline of the file
28
29### On Typing Assists
30
31Some features trigger on typing certain characters:
32
33- typing `let =` tries to smartly add `;` if `=` is followed by an existing expression.
34- Enter inside comments automatically inserts `///`
35- typing `.` in a chain method call auto-indents
36
37### Commands <kbd>ctrl+shift+p</kbd>
38
39#### Extend Selection
40
41Extends the current selection to the encompassing syntactic construct
42(expression, statement, item, module, etc). It works with multiple cursors. Do
43bind this command to a key, it's super-useful! Expected to be upstreamed to LSP
44soonish: https://github.com/Microsoft/language-server-protocol/issues/613
45
46#### Run
47
48Shows popup suggesting to run a test/benchmark/binary **at the current cursor
49location**. Super useful for repeatedly running just a single test. Do bind this
50to a shortcut!
51
52#### Parent Module
53
54Navigates to the parent module of the current module.
55
56#### Matching Brace
57
58If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair,
59moves cursor to the matching brace. It uses the actual parser to determine
60braces, so it won't confuse generics with comparisons.
61
62#### Join Lines
63
64Join selected lines into one, smartly fixing up whitespace and trailing commas.
65
66#### Show Syntax Tree
67
68Shows the parse tree of the current file. It exists mostly for debugging
69rust-analyzer itself.
70
71#### Status
72
73Shows internal statistic about memory usage of rust-analyzer
74
75#### Run garbage collection
76
77Manually triggers GC
78
79### Code Actions (Assists)
80
81These are triggered in a particular context via light bulb. We use custom code on
82the VS Code side to be able to position cursor. `<|>` signifies cursor
83
84- Add `#[derive]`
85
86```rust
87// before:
88struct Foo {
89 <|>x: i32
90}
91// after:
92#[derive(<|>)]
93struct Foo {
94 x: i32
95}
96```
97
98- Add `impl`
99
100```rust
101// before:
102struct Foo<'a, T: Debug> {
103 <|>t: T
104}
105// after:
106struct Foo<'a, T: Debug> {
107 t: T
108}
109
110impl<'a, T: Debug> Foo<'a, T> {
111 <|>
112}
113```
114
115- Add missing `impl` members
116
117```rust
118// before:
119trait Foo {
120 fn foo(&self);
121 fn bar(&self);
122 fn baz(&self);
123}
124
125struct S;
126
127impl Foo for S {
128 fn bar(&self) {}
129 <|>
130}
131
132// after:
133trait Foo {
134 fn foo(&self);
135 fn bar(&self);
136 fn baz(&self);
137}
138
139struct S;
140
141impl Foo for S {
142 fn bar(&self) {}
143 fn foo(&self) { unimplemented!() }
144 fn baz(&self) { unimplemented!() }<|>
145}
146```
147
148- Import path
149
150```rust
151// before:
152impl std::fmt::Debug<|> for Foo {
153}
154
155// after:
156use std::fmt::Debug;
157
158impl Debug<|> for Foo {
159}
160```
161
162- Change Visibility
163
164```rust
165// before:
166<|>fn foo() {}
167
168// after:
169<|>pub(crate) fn foo() {}
170
171// after:
172<|>pub fn foo() {}
173```
174
175- Fill match arms
176
177```rust
178// before:
179enum A {
180 As,
181 Bs,
182 Cs(String),
183 Ds(String, String),
184 Es{x: usize, y: usize}
185}
186
187fn main() {
188 let a = A::As;
189 match a<|> {}
190}
191
192// after:
193enum A {
194 As,
195 Bs,
196 Cs(String),
197 Ds(String, String),
198 Es{x: usize, y: usize}
199}
200
201fn main() {
202 let a = A::As;
203 match <|>a {
204 A::As => (),
205 A::Bs => (),
206 A::Cs(_) => (),
207 A::Ds(_, _) => (),
208 A::Es{x, y} => (),
209 }
210}
211```
212
213-- Fill struct fields
214
215```rust
216// before:
217struct S<'a, D> {
218 a: u32,
219 b: String,
220 c: (i32, i32),
221 d: D,
222 r: &'a str,
223}
224
225fn main() {
226 let s = S<|> {}
227}
228
229// after:
230struct S<'a, D> {
231 a: u32,
232 b: String,
233 c: (i32, i32),
234 d: D,
235 r: &'a str,
236}
237
238fn main() {
239 let s = <|>S {
240 a: (),
241 b: (),
242 c: (),
243 d: (),
244 r: (),
245 }
246}
247```
248
249- Flip `,`
250
251```rust
252// before:
253fn foo(x: usize,<|> dim: (usize, usize)) {}
254// after:
255fn foo(dim: (usize, usize), x: usize) {}
256```
257
258- Introduce variable:
259
260```rust
261// before:
262fn foo() {
263 foo(<|>1 + 1<|>);
264}
265
266// after:
267fn foo() {
268 let var_name = 1 + 1;
269 foo(var_name);
270}
271```
272
273-- Remove `dbg!`
274
275```rust
276// before:
277fn foo(n: usize) {
278 if let Some(_) = dbg!(n.<|>checked_sub(4)) {
279 // ...
280 }
281}
282
283// after:
284fn foo(n: usize) {
285 if let Some(_) = n.<|>checked_sub(4) {
286 // ...
287 }
288}
289```
290
291- Replace if-let with match:
292
293```rust
294// before:
295impl VariantData {
296 pub fn is_struct(&self) -> bool {
297 if <|>let VariantData::Struct(..) = *self {
298 true
299 } else {
300 false
301 }
302 }
303}
304
305// after:
306impl VariantData {
307 pub fn is_struct(&self) -> bool {
308 <|>match *self {
309 VariantData::Struct(..) => true,
310 _ => false,
311 }
312 }
313}
314```
315
316- Split import
317
318```rust
319// before:
320use algo:<|>:visitor::{Visitor, visit};
321//after:
322use algo::{<|>visitor::{Visitor, visit}};
323```
324
325### Magic Completions
326
327In addition to usual reference completion, rust-analyzer provides some ✨magic✨
328completions as well:
329
330Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor
331is placed at the appropriate position. Even though `if` is easy to type, you
332still want to complete it, to get ` { }` for free! `return` is inserted with a
333space or `;` depending on the return type of the function.
334
335When completing a function call, `()` are automatically inserted. If function
336takes arguments, cursor is positioned inside the parenthesis.
337
338There are postifx completions, which can be triggerd by typing something like
339`foo().if`. The word after `.` determines postifx completion, possible variants are:
340
341- `expr.if` -> `if expr {}`
342- `expr.match` -> `match expr {}`
343- `expr.while` -> `while expr {}`
344- `expr.ref` -> `&expr`
345- `expr.refm` -> `&mut expr`
346- `expr.not` -> `!expr`
347- `expr.dbg` -> `dbg!(expr)`
348
349There also snippet completions:
350
351#### Inside Expressions
352
353- `pd` -> `println!("{:?}")`
354- `ppd` -> `println!("{:#?}")`
355
356#### Inside Modules
357
358- `tfn` -> `#[test] fn f(){}`
359
diff --git a/editors/README.md b/editors/README.md
deleted file mode 100644
index ddc6ee048..000000000
--- a/editors/README.md
+++ /dev/null
@@ -1,241 +0,0 @@
1
2Prerequisites:
3
4In order to build the VS Code plugin, you need to have node.js and npm with
5a minimum version of 10 installed. Please refer to
6[node.js and npm documentation](https://nodejs.org) for installation instructions.
7
8You will also need the most recent version of VS Code: we don't try to
9maintain compatibility with older versions yet.
10
11The experimental VS Code plugin can then be built and installed by executing the
12following commands:
13
14```
15$ git clone https://github.com/rust-analyzer/rust-analyzer.git --depth 1
16$ cd rust-analyzer
17$ cargo install-code
18
19# for stdlib support
20$ rustup component add rust-src
21```
22
23This will run `cargo install --package ra_lsp_server` to install the server
24binary into `~/.cargo/bin`, and then will build and install plugin from
25`editors/code`. See
26[this](https://github.com/rust-analyzer/rust-analyzer/blob/0199572a3d06ff66eeae85a2d2c9762996f0d2d8/crates/tools/src/main.rs#L150)
27for details. The installation is expected to *just work*, if it doesn't, report
28bugs!
29
30It's better to remove existing Rust plugins to avoid interference.
31
32## Rust Analyzer Specific Features
33
34These features are implemented as extensions to the language server protocol.
35They are more experimental in nature and work only with VS Code.
36
37### Syntax highlighting
38
39It overrides built-in highlighting, and works only with a specific theme
40(zenburn). `rust-analyzer.highlightingOn` setting can be used to disable it.
41
42### Go to symbol in workspace <kbd>ctrl+t</kbd>
43
44It mostly works on top of the built-in LSP functionality, however `#` and `*`
45symbols can be used to narrow down the search. Specifically,
46
47- `#Foo` searches for `Foo` type in the current workspace
48- `#foo#` searches for `foo` function in the current workspace
49- `#Foo*` searches for `Foo` type among dependencies, excluding `stdlib`
50- `#foo#*` searches for `foo` function among dependencies.
51
52That is, `#` switches from "types" to all symbols, `*` switches from the current
53workspace to dependencies.
54
55### Commands <kbd>ctrl+shift+p</kbd>
56
57#### Show Rust Syntax Tree
58
59Shows the parse tree of the current file. It exists mostly for debugging
60rust-analyzer itself.
61
62#### Extend Selection
63
64Extends the current selection to the encompassing syntactic construct
65(expression, statement, item, module, etc). It works with multiple cursors. Do
66bind this command to a key, its super-useful! Expected to be upstreamed to LSP soonish:
67https://github.com/Microsoft/language-server-protocol/issues/613
68
69#### Matching Brace
70
71If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair,
72moves cursor to the matching brace. It uses the actual parser to determine
73braces, so it won't confuse generics with comparisons.
74
75#### Parent Module
76
77Navigates to the parent module of the current module.
78
79#### Join Lines
80
81Join selected lines into one, smartly fixing up whitespace and trailing commas.
82
83#### Run
84
85Shows popup suggesting to run a test/benchmark/binary **at the current cursor
86location**. Super useful for repeatedly running just a single test. Do bind this
87to a shortcut!
88
89
90### On Typing Assists
91
92Some features trigger on typing certain characters:
93
94- typing `let =` tries to smartly add `;` if `=` is followed by an existing expression.
95- Enter inside comments automatically inserts `///`
96- typing `.` in a chain method call auto-indents
97
98
99### Code Actions (Assists)
100
101These are triggered in a particular context via light bulb. We use custom code on
102the VS Code side to be able to position cursor.
103
104
105- Flip `,`
106
107```rust
108// before:
109fn foo(x: usize,<|> dim: (usize, usize))
110// after:
111fn foo(dim: (usize, usize), x: usize)
112```
113
114- Add `#[derive]`
115
116```rust
117// before:
118struct Foo {
119 <|>x: i32
120}
121// after:
122#[derive(<|>)]
123struct Foo {
124 x: i32
125}
126```
127
128- Add `impl`
129
130```rust
131// before:
132struct Foo<'a, T: Debug> {
133 <|>t: T
134}
135// after:
136struct Foo<'a, T: Debug> {
137 t: T
138}
139
140impl<'a, T: Debug> Foo<'a, T> {
141 <|>
142}
143```
144
145- Change visibility
146
147```rust
148// before:
149fn<|> foo() {}
150
151// after
152pub(crate) fn foo() {}
153```
154
155- Introduce variable:
156
157```rust
158// before:
159fn foo() {
160 foo(<|>1 + 1<|>);
161}
162
163// after:
164fn foo() {
165 let var_name = 1 + 1;
166 foo(var_name);
167}
168```
169
170- Replace if-let with match:
171
172```rust
173// before:
174impl VariantData {
175 pub fn is_struct(&self) -> bool {
176 if <|>let VariantData::Struct(..) = *self {
177 true
178 } else {
179 false
180 }
181 }
182}
183
184// after:
185impl VariantData {
186 pub fn is_struct(&self) -> bool {
187 <|>match *self {
188 VariantData::Struct(..) => true,
189 _ => false,
190 }
191 }
192}
193```
194
195- Split import
196
197```rust
198// before:
199use algo:<|>:visitor::{Visitor, visit};
200//after:
201use algo::{<|>visitor::{Visitor, visit}};
202```
203
204## LSP features
205
206* **Go to definition**: works correctly for local variables and some paths,
207 falls back to heuristic name matching for other things for the time being.
208
209* **Completion**: completes paths, including dependencies and standard library.
210 Does not handle glob imports and macros. Completes fields and inherent
211 methods.
212
213* **Outline** <kbd>alt+shift+o</kbd>
214
215* **Signature Info**
216
217* **Format document**. Formats the current file with rustfmt. Rustfmt must be
218 installed separately with `rustup component add rustfmt`.
219
220* **Hover** shows types of expressions and docstings
221
222* **Rename** works for local variables
223
224* **Code Lens** for running tests
225
226* **Folding**
227
228* **Diagnostics**
229 - missing module for `mod foo;` with a fix to create `foo.rs`.
230 - struct field shorthand
231 - unnecessary braces in use item
232
233
234## Performance
235
236Rust Analyzer is expected to be pretty fast. Specifically, the initial analysis
237of the project (i.e, when you first invoke completion or symbols) typically
238takes dozen of seconds at most. After that, everything is supposed to be more or
239less instant. However currently all analysis results are kept in memory, so
240memory usage is pretty high. Working with `rust-lang/rust` repo, for example,
241needs about 5 gigabytes of ram.