diff options
22 files changed, 839 insertions, 239 deletions
diff --git a/.travis.yml b/.travis.yml index 940435172..c6e22d765 100644 --- a/.travis.yml +++ b/.travis.yml | |||
@@ -1,12 +1,13 @@ | |||
1 | cache: cargo | 1 | cache: cargo |
2 | before_cache: | 2 | before_cache: |
3 | - find ./target/debug -type f -maxdepth 1 -delete | 3 | - find ./target/debug -maxdepth 1 -type f -delete |
4 | - rm -fr ./target/debug/{deps,.fingerprint}/{*ra_*,*test*,*tools*,*gen_lsp*,*thread_worker*} | 4 | - rm -fr ./target/debug/{deps,.fingerprint}/{*ra_*,*test*,*tools*,*gen_lsp*,*thread_worker*} |
5 | - rm -f ./target/.rustc_info.json | 5 | - rm -f ./target/.rustc_info.json |
6 | 6 | ||
7 | matrix: | 7 | matrix: |
8 | include: | 8 | include: |
9 | - os: linux | 9 | - os: linux |
10 | dist: xenial | ||
10 | language: rust | 11 | language: rust |
11 | rust: stable | 12 | rust: stable |
12 | script: | 13 | script: |
diff --git a/Cargo.lock b/Cargo.lock index 5aa4ff5de..9502fed45 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -491,6 +491,7 @@ version = "0.2.0" | |||
491 | dependencies = [ | 491 | dependencies = [ |
492 | "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", | 492 | "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", |
493 | "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", | 493 | "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", |
494 | "flexi_logger 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||
494 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", | 495 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", |
495 | "lsp-types 0.57.1 (registry+https://github.com/rust-lang/crates.io-index)", | 496 | "lsp-types 0.57.1 (registry+https://github.com/rust-lang/crates.io-index)", |
496 | "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", | 497 | "serde 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", |
@@ -1089,6 +1090,7 @@ version = "0.1.0" | |||
1089 | dependencies = [ | 1090 | dependencies = [ |
1090 | "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", | 1091 | "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", |
1091 | "ra_arena 0.1.0", | 1092 | "ra_arena 0.1.0", |
1093 | "ra_prof 0.1.0", | ||
1092 | "ra_syntax 0.1.0", | 1094 | "ra_syntax 0.1.0", |
1093 | "relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1095 | "relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1094 | "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | 1096 | "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", |
diff --git a/crates/gen_lsp_server/Cargo.toml b/crates/gen_lsp_server/Cargo.toml index ba8bfdbd3..fa2fefea5 100644 --- a/crates/gen_lsp_server/Cargo.toml +++ b/crates/gen_lsp_server/Cargo.toml | |||
@@ -14,3 +14,6 @@ failure = "0.1.4" | |||
14 | serde_json = "1.0.34" | 14 | serde_json = "1.0.34" |
15 | serde = { version = "1.0.83", features = ["derive"] } | 15 | serde = { version = "1.0.83", features = ["derive"] } |
16 | crossbeam-channel = "0.3.5" | 16 | crossbeam-channel = "0.3.5" |
17 | |||
18 | [dev-dependencies] | ||
19 | flexi_logger = "0.11.0" | ||
diff --git a/crates/gen_lsp_server/examples/01_gen_lsp_server.rs b/crates/gen_lsp_server/examples/01_gen_lsp_server.rs new file mode 100644 index 000000000..60c581075 --- /dev/null +++ b/crates/gen_lsp_server/examples/01_gen_lsp_server.rs | |||
@@ -0,0 +1,45 @@ | |||
1 | use crossbeam_channel::{Sender, Receiver}; | ||
2 | use lsp_types::{ | ||
3 | ServerCapabilities, InitializeParams, | ||
4 | request::{GotoDefinition, GotoDefinitionResponse}, | ||
5 | }; | ||
6 | use gen_lsp_server::{run_server, stdio_transport, handle_shutdown, RawMessage, RawResponse}; | ||
7 | |||
8 | fn main() -> Result<(), failure::Error> { | ||
9 | let (receiver, sender, io_threads) = stdio_transport(); | ||
10 | run_server(ServerCapabilities::default(), receiver, sender, main_loop)?; | ||
11 | io_threads.join()?; | ||
12 | Ok(()) | ||
13 | } | ||
14 | |||
15 | fn main_loop( | ||
16 | _params: InitializeParams, | ||
17 | receiver: &Receiver<RawMessage>, | ||
18 | sender: &Sender<RawMessage>, | ||
19 | ) -> Result<(), failure::Error> { | ||
20 | for msg in receiver { | ||
21 | match msg { | ||
22 | RawMessage::Request(req) => { | ||
23 | let req = match handle_shutdown(req, sender) { | ||
24 | None => return Ok(()), | ||
25 | Some(req) => req, | ||
26 | }; | ||
27 | match req.cast::<GotoDefinition>() { | ||
28 | Ok((id, _params)) => { | ||
29 | let resp = RawResponse::ok::<GotoDefinition>( | ||
30 | id, | ||
31 | &Some(GotoDefinitionResponse::Array(Vec::new())), | ||
32 | ); | ||
33 | sender.send(RawMessage::Response(resp))?; | ||
34 | continue; | ||
35 | } | ||
36 | Err(req) => req, | ||
37 | }; | ||
38 | // ... | ||
39 | } | ||
40 | RawMessage::Response(_resp) => (), | ||
41 | RawMessage::Notification(_not) => (), | ||
42 | } | ||
43 | } | ||
44 | Ok(()) | ||
45 | } | ||
diff --git a/crates/gen_lsp_server/examples/02_gen_lsp_server_with_logging.rs b/crates/gen_lsp_server/examples/02_gen_lsp_server_with_logging.rs new file mode 100644 index 000000000..27e4f1cbc --- /dev/null +++ b/crates/gen_lsp_server/examples/02_gen_lsp_server_with_logging.rs | |||
@@ -0,0 +1,118 @@ | |||
1 | //! A minimal example LSP server that can only respond to the `gotoDefinition` request. To use | ||
2 | //! this example, execute it and then send an `initialize` request. | ||
3 | //! | ||
4 | //! ```no_run | ||
5 | //! Content-Length: 85 | ||
6 | //! | ||
7 | //! {"jsonrpc": "2.0", "method": "initialize", "id": 1, "params": {"capabilities": {}}} | ||
8 | //! ``` | ||
9 | //! | ||
10 | //! This will respond with a server respose. Then send it a `initialized` notification which will | ||
11 | //! have no response. | ||
12 | //! | ||
13 | //! ```no_run | ||
14 | //! Content-Length: 59 | ||
15 | //! | ||
16 | //! {"jsonrpc": "2.0", "method": "initialized", "params": {}} | ||
17 | //! ``` | ||
18 | //! | ||
19 | //! Once these two are sent, then we enter the main loop of the server. The only request this | ||
20 | //! example can handle is `gotoDefinition`: | ||
21 | //! | ||
22 | //! ```no_run | ||
23 | //! Content-Length: 159 | ||
24 | //! | ||
25 | //! {"jsonrpc": "2.0", "method": "textDocument/definition", "id": 2, "params": {"textDocument": {"uri": "file://temp"}, "position": {"line": 1, "character": 1}}} | ||
26 | //! ``` | ||
27 | //! | ||
28 | //! To finish up without errors, send a shutdown request: | ||
29 | //! | ||
30 | //! ```no_run | ||
31 | //! Content-Length: 67 | ||
32 | //! | ||
33 | //! {"jsonrpc": "2.0", "method": "shutdown", "id": 3, "params": null} | ||
34 | //! ``` | ||
35 | //! | ||
36 | //! The server will exit the main loop and finally we send a `shutdown` notification to stop | ||
37 | //! the server. | ||
38 | //! | ||
39 | //! ``` | ||
40 | //! Content-Length: 54 | ||
41 | //! | ||
42 | //! {"jsonrpc": "2.0", "method": "exit", "params": null} | ||
43 | //! ``` | ||
44 | |||
45 | use crossbeam_channel::{Sender, Receiver}; | ||
46 | use lsp_types::{ | ||
47 | ServerCapabilities, InitializeParams, | ||
48 | request::{GotoDefinition, GotoDefinitionResponse}, | ||
49 | }; | ||
50 | use log::info; | ||
51 | use gen_lsp_server::{ | ||
52 | run_server, stdio_transport, handle_shutdown, RawMessage, RawResponse, RawRequest, | ||
53 | }; | ||
54 | |||
55 | fn main() -> Result<(), failure::Error> { | ||
56 | // Set up logging. Because `stdio_transport` gets a lock on stdout and stdin, we must have | ||
57 | // our logging only write out to stderr. | ||
58 | flexi_logger::Logger::with_str("info").start().unwrap(); | ||
59 | info!("starting generic LSP server"); | ||
60 | |||
61 | // Create the transport. Includes the stdio (stdin and stdout) versions but this could | ||
62 | // also be implemented to use sockets or HTTP. | ||
63 | let (receiver, sender, io_threads) = stdio_transport(); | ||
64 | |||
65 | // Run the server and wait for the two threads to end (typically by trigger LSP Exit event). | ||
66 | run_server(ServerCapabilities::default(), receiver, sender, main_loop)?; | ||
67 | io_threads.join()?; | ||
68 | |||
69 | // Shut down gracefully. | ||
70 | info!("shutting down server"); | ||
71 | Ok(()) | ||
72 | } | ||
73 | |||
74 | fn main_loop( | ||
75 | _params: InitializeParams, | ||
76 | receiver: &Receiver<RawMessage>, | ||
77 | sender: &Sender<RawMessage>, | ||
78 | ) -> Result<(), failure::Error> { | ||
79 | info!("starting example main loop"); | ||
80 | for msg in receiver { | ||
81 | info!("got msg: {:?}", msg); | ||
82 | match msg { | ||
83 | RawMessage::Request(req) => { | ||
84 | let req = match log_handle_shutdown(req, sender) { | ||
85 | None => return Ok(()), | ||
86 | Some(req) => req, | ||
87 | }; | ||
88 | info!("got request: {:?}", req); | ||
89 | match req.cast::<GotoDefinition>() { | ||
90 | Ok((id, params)) => { | ||
91 | info!("got gotoDefinition request #{}: {:?}", id, params); | ||
92 | let resp = RawResponse::ok::<GotoDefinition>( | ||
93 | id, | ||
94 | &Some(GotoDefinitionResponse::Array(Vec::new())), | ||
95 | ); | ||
96 | info!("sending gotoDefinition response: {:?}", resp); | ||
97 | sender.send(RawMessage::Response(resp))?; | ||
98 | continue; | ||
99 | } | ||
100 | Err(req) => req, | ||
101 | }; | ||
102 | // ... | ||
103 | } | ||
104 | RawMessage::Response(resp) => { | ||
105 | info!("got response: {:?}", resp); | ||
106 | } | ||
107 | RawMessage::Notification(not) => { | ||
108 | info!("got notification: {:?}", not); | ||
109 | } | ||
110 | } | ||
111 | } | ||
112 | Ok(()) | ||
113 | } | ||
114 | |||
115 | pub fn log_handle_shutdown(req: RawRequest, sender: &Sender<RawMessage>) -> Option<RawRequest> { | ||
116 | info!("handle_shutdown: {:?}", req); | ||
117 | handle_shutdown(req, sender) | ||
118 | } | ||
diff --git a/crates/gen_lsp_server/src/lib.rs b/crates/gen_lsp_server/src/lib.rs index edbdda6c8..1cd5a3a7c 100644 --- a/crates/gen_lsp_server/src/lib.rs +++ b/crates/gen_lsp_server/src/lib.rs | |||
@@ -2,21 +2,16 @@ | |||
2 | //! This crate handles protocol handshaking and parsing messages, while you | 2 | //! This crate handles protocol handshaking and parsing messages, while you |
3 | //! control the message dispatch loop yourself. | 3 | //! control the message dispatch loop yourself. |
4 | //! | 4 | //! |
5 | //! Run with `RUST_LOG=sync_lsp_server=debug` to see all the messages. | 5 | //! Run with `RUST_LOG=gen_lsp_server=debug` to see all the messages. |
6 | //! | 6 | //! |
7 | //! ```no_run | 7 | //! ```no_run |
8 | //! extern crate gen_lsp_server; | ||
9 | //! extern crate lsp_types; | ||
10 | //! extern crate failure; | ||
11 | //! extern crate crossbeam_channel; | ||
12 | //! | ||
13 | //! use crossbeam_channel::{Sender, Receiver}; | 8 | //! use crossbeam_channel::{Sender, Receiver}; |
14 | //! use lsp_types::{ServerCapabilities, InitializeParams, request::{GotoDefinition, GotoDefinitionResponse}}; | 9 | //! use lsp_types::{ServerCapabilities, InitializeParams, request::{GotoDefinition, GotoDefinitionResponse}}; |
15 | //! use gen_lsp_server::{run_server, stdio_transport, handle_shutdown, RawMessage, RawResponse}; | 10 | //! use gen_lsp_server::{run_server, stdio_transport, handle_shutdown, RawMessage, RawResponse}; |
16 | //! | 11 | //! |
17 | //! fn main() -> Result<(), failure::Error> { | 12 | //! fn main() -> Result<(), failure::Error> { |
18 | //! let (receiver, sender, io_threads) = stdio_transport(); | 13 | //! let (receiver, sender, io_threads) = stdio_transport(); |
19 | //! gen_lsp_server::run_server( | 14 | //! run_server( |
20 | //! ServerCapabilities::default(), | 15 | //! ServerCapabilities::default(), |
21 | //! receiver, | 16 | //! receiver, |
22 | //! sender, | 17 | //! sender, |
@@ -38,13 +33,13 @@ | |||
38 | //! None => return Ok(()), | 33 | //! None => return Ok(()), |
39 | //! Some(req) => req, | 34 | //! Some(req) => req, |
40 | //! }; | 35 | //! }; |
41 | //! let req = match req.cast::<GotoDefinition>() { | 36 | //! match req.cast::<GotoDefinition>() { |
42 | //! Ok((id, _params)) => { | 37 | //! Ok((id, _params)) => { |
43 | //! let resp = RawResponse::ok::<GotoDefinition>( | 38 | //! let resp = RawResponse::ok::<GotoDefinition>( |
44 | //! id, | 39 | //! id, |
45 | //! &Some(GotoDefinitionResponse::Array(Vec::new())), | 40 | //! &Some(GotoDefinitionResponse::Array(Vec::new())), |
46 | //! ); | 41 | //! ); |
47 | //! sender.send(RawMessage::Response(resp)); | 42 | //! sender.send(RawMessage::Response(resp))?; |
48 | //! continue; | 43 | //! continue; |
49 | //! }, | 44 | //! }, |
50 | //! Err(req) => req, | 45 | //! Err(req) => req, |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index ae97a1ab5..28eb0226b 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -100,7 +100,7 @@ mod split_import; | |||
100 | mod remove_dbg; | 100 | mod remove_dbg; |
101 | pub mod auto_import; | 101 | pub mod auto_import; |
102 | mod add_missing_impl_members; | 102 | mod add_missing_impl_members; |
103 | mod move_guard_to_arm_body; | 103 | mod move_guard; |
104 | 104 | ||
105 | fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { | 105 | fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { |
106 | &[ | 106 | &[ |
@@ -119,7 +119,8 @@ fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assis | |||
119 | add_missing_impl_members::add_missing_impl_members, | 119 | add_missing_impl_members::add_missing_impl_members, |
120 | add_missing_impl_members::add_missing_default_members, | 120 | add_missing_impl_members::add_missing_default_members, |
121 | inline_local_variable::inline_local_varialbe, | 121 | inline_local_variable::inline_local_varialbe, |
122 | move_guard_to_arm_body::move_guard_to_arm_body, | 122 | move_guard::move_guard_to_arm_body, |
123 | move_guard::move_arm_cond_to_match_guard, | ||
123 | ] | 124 | ] |
124 | } | 125 | } |
125 | 126 | ||
diff --git a/crates/ra_assists/src/move_guard.rs b/crates/ra_assists/src/move_guard.rs new file mode 100644 index 000000000..22ba91fb7 --- /dev/null +++ b/crates/ra_assists/src/move_guard.rs | |||
@@ -0,0 +1,260 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | ||
3 | TextUnit, | ||
4 | SyntaxElement, | ||
5 | ast::{MatchArm, AstNode, AstToken, IfExpr}, | ||
6 | ast, | ||
7 | }; | ||
8 | |||
9 | use crate::{AssistCtx, Assist, AssistId}; | ||
10 | |||
11 | pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
12 | let match_arm = ctx.node_at_offset::<MatchArm>()?; | ||
13 | let guard = match_arm.guard()?; | ||
14 | let space_before_guard = guard.syntax().prev_sibling_or_token(); | ||
15 | |||
16 | let guard_conditions = guard.expr()?; | ||
17 | let arm_expr = match_arm.expr()?; | ||
18 | let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); | ||
19 | |||
20 | ctx.add_action(AssistId("move_guard_to_arm_body"), "move guard to arm body", |edit| { | ||
21 | edit.target(guard.syntax().range()); | ||
22 | let offseting_amount = match space_before_guard { | ||
23 | Some(SyntaxElement::Token(tok)) => { | ||
24 | if let Some(_) = ast::Whitespace::cast(tok) { | ||
25 | let ele = space_before_guard.unwrap().range(); | ||
26 | edit.delete(ele); | ||
27 | ele.len() | ||
28 | } else { | ||
29 | TextUnit::from(0) | ||
30 | } | ||
31 | } | ||
32 | _ => TextUnit::from(0), | ||
33 | }; | ||
34 | |||
35 | edit.delete(guard.syntax().range()); | ||
36 | edit.replace_node_and_indent(arm_expr.syntax(), buf); | ||
37 | edit.set_cursor(arm_expr.syntax().range().start() + TextUnit::from(3) - offseting_amount); | ||
38 | }); | ||
39 | ctx.build() | ||
40 | } | ||
41 | |||
42 | pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
43 | let match_arm: &MatchArm = ctx.node_at_offset::<MatchArm>()?; | ||
44 | let last_match_pat = match_arm.pats().last()?; | ||
45 | |||
46 | let arm_body = match_arm.expr()?; | ||
47 | let if_expr: &IfExpr = IfExpr::cast(arm_body.syntax())?; | ||
48 | let cond = if_expr.condition()?; | ||
49 | let then_block = if_expr.then_branch()?; | ||
50 | |||
51 | // Not support if with else branch | ||
52 | if let Some(_) = if_expr.else_branch() { | ||
53 | return None; | ||
54 | } | ||
55 | // Not support moving if let to arm guard | ||
56 | if let Some(_) = cond.pat() { | ||
57 | return None; | ||
58 | } | ||
59 | |||
60 | let buf = format!(" if {}", cond.syntax().text()); | ||
61 | |||
62 | ctx.add_action( | ||
63 | AssistId("move_arm_cond_to_match_guard"), | ||
64 | "move condition to match guard", | ||
65 | |edit| { | ||
66 | edit.target(if_expr.syntax().range()); | ||
67 | let then_only_expr = then_block.statements().next().is_none(); | ||
68 | |||
69 | match then_block.expr() { | ||
70 | Some(then_expr) if then_only_expr => { | ||
71 | edit.replace(if_expr.syntax().range(), then_expr.syntax().text()) | ||
72 | } | ||
73 | _ => edit.replace(if_expr.syntax().range(), then_block.syntax().text()), | ||
74 | } | ||
75 | |||
76 | edit.insert(last_match_pat.syntax().range().end(), buf); | ||
77 | edit.set_cursor(last_match_pat.syntax().range().end() + TextUnit::from(1)); | ||
78 | }, | ||
79 | ); | ||
80 | ctx.build() | ||
81 | } | ||
82 | |||
83 | #[cfg(test)] | ||
84 | mod tests { | ||
85 | use super::*; | ||
86 | |||
87 | use crate::helpers::{ check_assist, check_assist_target, check_assist_not_applicable }; | ||
88 | |||
89 | #[test] | ||
90 | fn move_guard_to_arm_body_target() { | ||
91 | check_assist_target( | ||
92 | move_guard_to_arm_body, | ||
93 | r#" | ||
94 | fn f() { | ||
95 | let t = 'a'; | ||
96 | let chars = "abcd"; | ||
97 | match t { | ||
98 | '\r' <|>if chars.clone().next() == Some('\n') => false, | ||
99 | _ => true | ||
100 | } | ||
101 | } | ||
102 | "#, | ||
103 | r#"if chars.clone().next() == Some('\n')"#, | ||
104 | ); | ||
105 | } | ||
106 | |||
107 | #[test] | ||
108 | fn move_guard_to_arm_body_works() { | ||
109 | check_assist( | ||
110 | move_guard_to_arm_body, | ||
111 | r#" | ||
112 | fn f() { | ||
113 | let t = 'a'; | ||
114 | let chars = "abcd"; | ||
115 | match t { | ||
116 | '\r' <|>if chars.clone().next() == Some('\n') => false, | ||
117 | _ => true | ||
118 | } | ||
119 | } | ||
120 | "#, | ||
121 | r#" | ||
122 | fn f() { | ||
123 | let t = 'a'; | ||
124 | let chars = "abcd"; | ||
125 | match t { | ||
126 | '\r' => if chars.clone().next() == Some('\n') { <|>false }, | ||
127 | _ => true | ||
128 | } | ||
129 | } | ||
130 | "#, | ||
131 | ); | ||
132 | } | ||
133 | |||
134 | #[test] | ||
135 | fn move_guard_to_arm_body_works_complex_match() { | ||
136 | check_assist( | ||
137 | move_guard_to_arm_body, | ||
138 | r#" | ||
139 | fn f() { | ||
140 | match x { | ||
141 | <|>y @ 4 | y @ 5 if y > 5 => true, | ||
142 | _ => false | ||
143 | } | ||
144 | } | ||
145 | "#, | ||
146 | r#" | ||
147 | fn f() { | ||
148 | match x { | ||
149 | y @ 4 | y @ 5 => if y > 5 { <|>true }, | ||
150 | _ => false | ||
151 | } | ||
152 | } | ||
153 | "#, | ||
154 | ); | ||
155 | } | ||
156 | |||
157 | #[test] | ||
158 | fn move_arm_cond_to_match_guard_works() { | ||
159 | check_assist( | ||
160 | move_arm_cond_to_match_guard, | ||
161 | r#" | ||
162 | fn f() { | ||
163 | let t = 'a'; | ||
164 | let chars = "abcd"; | ||
165 | match t { | ||
166 | '\r' => if chars.clone().next() == Some('\n') { <|>false }, | ||
167 | _ => true | ||
168 | } | ||
169 | } | ||
170 | "#, | ||
171 | r#" | ||
172 | fn f() { | ||
173 | let t = 'a'; | ||
174 | let chars = "abcd"; | ||
175 | match t { | ||
176 | '\r' <|>if chars.clone().next() == Some('\n') => false, | ||
177 | _ => true | ||
178 | } | ||
179 | } | ||
180 | "#, | ||
181 | ); | ||
182 | } | ||
183 | |||
184 | #[test] | ||
185 | fn move_arm_cond_to_match_guard_if_let_not_works() { | ||
186 | check_assist_not_applicable( | ||
187 | move_arm_cond_to_match_guard, | ||
188 | r#" | ||
189 | fn f() { | ||
190 | let t = 'a'; | ||
191 | let chars = "abcd"; | ||
192 | match t { | ||
193 | '\r' => if let Some(_) = chars.clone().next() { <|>false }, | ||
194 | _ => true | ||
195 | } | ||
196 | } | ||
197 | "#, | ||
198 | ); | ||
199 | } | ||
200 | |||
201 | #[test] | ||
202 | fn move_arm_cond_to_match_guard_if_empty_body_works() { | ||
203 | check_assist( | ||
204 | move_arm_cond_to_match_guard, | ||
205 | r#" | ||
206 | fn f() { | ||
207 | let t = 'a'; | ||
208 | let chars = "abcd"; | ||
209 | match t { | ||
210 | '\r' => if chars.clone().next().is_some() { <|> }, | ||
211 | _ => true | ||
212 | } | ||
213 | } | ||
214 | "#, | ||
215 | r#" | ||
216 | fn f() { | ||
217 | let t = 'a'; | ||
218 | let chars = "abcd"; | ||
219 | match t { | ||
220 | '\r' <|>if chars.clone().next().is_some() => { }, | ||
221 | _ => true | ||
222 | } | ||
223 | } | ||
224 | "#, | ||
225 | ); | ||
226 | } | ||
227 | |||
228 | #[test] | ||
229 | fn move_arm_cond_to_match_guard_if_multiline_body_works() { | ||
230 | check_assist( | ||
231 | move_arm_cond_to_match_guard, | ||
232 | r#" | ||
233 | fn f() { | ||
234 | let mut t = 'a'; | ||
235 | let chars = "abcd"; | ||
236 | match t { | ||
237 | '\r' => if chars.clone().next().is_some() { | ||
238 | t = 'e';<|> | ||
239 | false | ||
240 | }, | ||
241 | _ => true | ||
242 | } | ||
243 | } | ||
244 | "#, | ||
245 | r#" | ||
246 | fn f() { | ||
247 | let mut t = 'a'; | ||
248 | let chars = "abcd"; | ||
249 | match t { | ||
250 | '\r' <|>if chars.clone().next().is_some() => { | ||
251 | t = 'e'; | ||
252 | false | ||
253 | }, | ||
254 | _ => true | ||
255 | } | ||
256 | } | ||
257 | "#, | ||
258 | ); | ||
259 | } | ||
260 | } | ||
diff --git a/crates/ra_assists/src/move_guard_to_arm_body.rs b/crates/ra_assists/src/move_guard_to_arm_body.rs deleted file mode 100644 index a8ca19f5d..000000000 --- a/crates/ra_assists/src/move_guard_to_arm_body.rs +++ /dev/null | |||
@@ -1,115 +0,0 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | ||
3 | TextUnit, | ||
4 | SyntaxElement, | ||
5 | ast::{MatchArm, AstNode, AstToken}, | ||
6 | ast, | ||
7 | }; | ||
8 | |||
9 | use crate::{AssistCtx, Assist, AssistId}; | ||
10 | |||
11 | pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
12 | let match_arm = ctx.node_at_offset::<MatchArm>()?; | ||
13 | let guard = match_arm.guard()?; | ||
14 | let space_before_guard = guard.syntax().prev_sibling_or_token(); | ||
15 | |||
16 | let guard_conditions = guard.expr()?; | ||
17 | let arm_expr = match_arm.expr()?; | ||
18 | let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); | ||
19 | |||
20 | ctx.add_action(AssistId("move_guard_to_arm_body"), "move guard to arm body", |edit| { | ||
21 | edit.target(guard.syntax().range()); | ||
22 | let offseting_amount = match space_before_guard { | ||
23 | Some(SyntaxElement::Token(tok)) => { | ||
24 | if let Some(_) = ast::Whitespace::cast(tok) { | ||
25 | let ele = space_before_guard.unwrap().range(); | ||
26 | edit.delete(ele); | ||
27 | ele.len() | ||
28 | } else { | ||
29 | TextUnit::from(0) | ||
30 | } | ||
31 | } | ||
32 | _ => TextUnit::from(0), | ||
33 | }; | ||
34 | |||
35 | edit.delete(guard.syntax().range()); | ||
36 | edit.replace_node_and_indent(arm_expr.syntax(), buf); | ||
37 | edit.set_cursor(arm_expr.syntax().range().start() + TextUnit::from(3) - offseting_amount); | ||
38 | }); | ||
39 | ctx.build() | ||
40 | } | ||
41 | |||
42 | #[cfg(test)] | ||
43 | mod tests { | ||
44 | use super::*; | ||
45 | |||
46 | use crate::helpers::{ check_assist, check_assist_target }; | ||
47 | |||
48 | #[test] | ||
49 | fn move_guard_to_arm_body_target() { | ||
50 | check_assist_target( | ||
51 | move_guard_to_arm_body, | ||
52 | r#" | ||
53 | fn f() { | ||
54 | let t = 'a'; | ||
55 | let chars = "abcd"; | ||
56 | match t { | ||
57 | '\r' <|>if chars.clone().next() == Some('\n') => false, | ||
58 | _ => true | ||
59 | } | ||
60 | } | ||
61 | "#, | ||
62 | r#"if chars.clone().next() == Some('\n')"#, | ||
63 | ); | ||
64 | } | ||
65 | |||
66 | #[test] | ||
67 | fn move_guard_to_arm_body_works() { | ||
68 | check_assist( | ||
69 | move_guard_to_arm_body, | ||
70 | r#" | ||
71 | fn f() { | ||
72 | let t = 'a'; | ||
73 | let chars = "abcd"; | ||
74 | match t { | ||
75 | '\r' <|>if chars.clone().next() == Some('\n') => false, | ||
76 | _ => true | ||
77 | } | ||
78 | } | ||
79 | "#, | ||
80 | r#" | ||
81 | fn f() { | ||
82 | let t = 'a'; | ||
83 | let chars = "abcd"; | ||
84 | match t { | ||
85 | '\r' => if chars.clone().next() == Some('\n') { <|>false }, | ||
86 | _ => true | ||
87 | } | ||
88 | } | ||
89 | "#, | ||
90 | ); | ||
91 | } | ||
92 | |||
93 | #[test] | ||
94 | fn move_guard_to_arm_body_works_complex_match() { | ||
95 | check_assist( | ||
96 | move_guard_to_arm_body, | ||
97 | r#" | ||
98 | fn f() { | ||
99 | match x { | ||
100 | <|>y @ 4 | y @ 5 if y > 5 => true, | ||
101 | _ => false | ||
102 | } | ||
103 | } | ||
104 | "#, | ||
105 | r#" | ||
106 | fn f() { | ||
107 | match x { | ||
108 | y @ 4 | y @ 5 => if y > 5 { <|>true }, | ||
109 | _ => false | ||
110 | } | ||
111 | } | ||
112 | "#, | ||
113 | ); | ||
114 | } | ||
115 | } | ||
diff --git a/crates/ra_db/Cargo.toml b/crates/ra_db/Cargo.toml index 08aef9bf5..5328303d2 100644 --- a/crates/ra_db/Cargo.toml +++ b/crates/ra_db/Cargo.toml | |||
@@ -12,4 +12,5 @@ parking_lot = "0.7.0" | |||
12 | 12 | ||
13 | ra_arena = { path = "../ra_arena" } | 13 | ra_arena = { path = "../ra_arena" } |
14 | ra_syntax = { path = "../ra_syntax" } | 14 | ra_syntax = { path = "../ra_syntax" } |
15 | ra_prof = { path = "../ra_prof" } | ||
15 | test_utils = { path = "../test_utils" } | 16 | test_utils = { path = "../test_utils" } |
diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs index bf567721a..68b9a7143 100644 --- a/crates/ra_db/src/lib.rs +++ b/crates/ra_db/src/lib.rs | |||
@@ -6,6 +6,7 @@ use std::{panic, sync::Arc}; | |||
6 | 6 | ||
7 | use ra_syntax::{TextUnit, TextRange, SourceFile, TreeArc}; | 7 | use ra_syntax::{TextUnit, TextRange, SourceFile, TreeArc}; |
8 | use relative_path::RelativePathBuf; | 8 | use relative_path::RelativePathBuf; |
9 | use ra_prof::profile; | ||
9 | 10 | ||
10 | pub use ::salsa as salsa; | 11 | pub use ::salsa as salsa; |
11 | pub use crate::{ | 12 | pub use crate::{ |
@@ -72,6 +73,7 @@ pub trait SourceDatabase: CheckCanceled + std::fmt::Debug { | |||
72 | #[salsa::input] | 73 | #[salsa::input] |
73 | fn file_text(&self, file_id: FileId) -> Arc<String>; | 74 | fn file_text(&self, file_id: FileId) -> Arc<String>; |
74 | // Parses the file into the syntax tree. | 75 | // Parses the file into the syntax tree. |
76 | #[salsa::invoke(parse_query)] | ||
75 | fn parse(&self, file_id: FileId) -> TreeArc<SourceFile>; | 77 | fn parse(&self, file_id: FileId) -> TreeArc<SourceFile>; |
76 | /// Path to a file, relative to the root of its source root. | 78 | /// Path to a file, relative to the root of its source root. |
77 | #[salsa::input] | 79 | #[salsa::input] |
@@ -96,7 +98,8 @@ fn source_root_crates(db: &impl SourceDatabase, id: SourceRootId) -> Arc<Vec<Cra | |||
96 | Arc::new(res) | 98 | Arc::new(res) |
97 | } | 99 | } |
98 | 100 | ||
99 | fn parse(db: &impl SourceDatabase, file_id: FileId) -> TreeArc<SourceFile> { | 101 | fn parse_query(db: &impl SourceDatabase, file_id: FileId) -> TreeArc<SourceFile> { |
102 | let _p = profile("parse_query"); | ||
100 | let text = db.file_text(file_id); | 103 | let text = db.file_text(file_id); |
101 | SourceFile::parse(&*text) | 104 | SourceFile::parse(&*text) |
102 | } | 105 | } |
diff --git a/crates/ra_hir/src/ids.rs b/crates/ra_hir/src/ids.rs index f901a7432..2eb7f0da0 100644 --- a/crates/ra_hir/src/ids.rs +++ b/crates/ra_hir/src/ids.rs | |||
@@ -5,6 +5,7 @@ use std::{ | |||
5 | 5 | ||
6 | use ra_db::{FileId, salsa}; | 6 | use ra_db::{FileId, salsa}; |
7 | use ra_syntax::{TreeArc, AstNode, ast, SyntaxNode}; | 7 | use ra_syntax::{TreeArc, AstNode, ast, SyntaxNode}; |
8 | use ra_prof::profile; | ||
8 | use mbe::MacroRules; | 9 | use mbe::MacroRules; |
9 | 10 | ||
10 | use crate::{ | 11 | use crate::{ |
@@ -60,6 +61,7 @@ impl HirFileId { | |||
60 | db: &impl DefDatabase, | 61 | db: &impl DefDatabase, |
61 | file_id: HirFileId, | 62 | file_id: HirFileId, |
62 | ) -> Option<TreeArc<SyntaxNode>> { | 63 | ) -> Option<TreeArc<SyntaxNode>> { |
64 | let _p = profile("parse_or_expand_query"); | ||
63 | match file_id.0 { | 65 | match file_id.0 { |
64 | HirFileIdRepr::File(file_id) => Some(db.parse(file_id).syntax().to_owned()), | 66 | HirFileIdRepr::File(file_id) => Some(db.parse(file_id).syntax().to_owned()), |
65 | HirFileIdRepr::Macro(macro_file) => { | 67 | HirFileIdRepr::Macro(macro_file) => { |
diff --git a/crates/ra_ide_api/src/display/navigation_target.rs b/crates/ra_ide_api/src/display/navigation_target.rs index 7ea336c50..1c694cbc9 100644 --- a/crates/ra_ide_api/src/display/navigation_target.rs +++ b/crates/ra_ide_api/src/display/navigation_target.rs | |||
@@ -5,7 +5,7 @@ use ra_syntax::{ | |||
5 | ast::{self, NameOwner, VisibilityOwner, TypeAscriptionOwner}, | 5 | ast::{self, NameOwner, VisibilityOwner, TypeAscriptionOwner}, |
6 | algo::visit::{visitor, Visitor}, | 6 | algo::visit::{visitor, Visitor}, |
7 | }; | 7 | }; |
8 | use hir::{ModuleSource, FieldSource, ImplItem, Either}; | 8 | use hir::{ModuleSource, FieldSource, ImplItem}; |
9 | 9 | ||
10 | use crate::{FileSymbol, db::RootDatabase}; | 10 | use crate::{FileSymbol, db::RootDatabase}; |
11 | 11 | ||
@@ -77,17 +77,12 @@ impl NavigationTarget { | |||
77 | pub(crate) fn from_pat( | 77 | pub(crate) fn from_pat( |
78 | db: &RootDatabase, | 78 | db: &RootDatabase, |
79 | file_id: FileId, | 79 | file_id: FileId, |
80 | pat: Either<AstPtr<ast::Pat>, AstPtr<ast::SelfParam>>, | 80 | pat: AstPtr<ast::Pat>, |
81 | ) -> NavigationTarget { | 81 | ) -> NavigationTarget { |
82 | let file = db.parse(file_id); | 82 | let file = db.parse(file_id); |
83 | let (name, full_range) = match pat { | 83 | let (name, full_range) = match pat.to_node(file.syntax()).kind() { |
84 | Either::A(pat) => match pat.to_node(file.syntax()).kind() { | 84 | ast::PatKind::BindPat(pat) => return NavigationTarget::from_bind_pat(file_id, &pat), |
85 | ast::PatKind::BindPat(pat) => { | 85 | _ => ("_".into(), pat.syntax_node_ptr().range()), |
86 | return NavigationTarget::from_bind_pat(file_id, &pat) | ||
87 | } | ||
88 | _ => ("_".into(), pat.syntax_node_ptr().range()), | ||
89 | }, | ||
90 | Either::B(slf) => ("self".into(), slf.syntax_node_ptr().range()), | ||
91 | }; | 86 | }; |
92 | NavigationTarget { | 87 | NavigationTarget { |
93 | file_id, | 88 | file_id, |
@@ -99,6 +94,21 @@ impl NavigationTarget { | |||
99 | } | 94 | } |
100 | } | 95 | } |
101 | 96 | ||
97 | pub(crate) fn from_self_param( | ||
98 | file_id: FileId, | ||
99 | par: AstPtr<ast::SelfParam>, | ||
100 | ) -> NavigationTarget { | ||
101 | let (name, full_range) = ("self".into(), par.syntax_node_ptr().range()); | ||
102 | NavigationTarget { | ||
103 | file_id, | ||
104 | name, | ||
105 | full_range, | ||
106 | focus_range: None, | ||
107 | kind: NAME, | ||
108 | container_name: None, | ||
109 | } | ||
110 | } | ||
111 | |||
102 | pub(crate) fn from_module(db: &RootDatabase, module: hir::Module) -> NavigationTarget { | 112 | pub(crate) fn from_module(db: &RootDatabase, module: hir::Module) -> NavigationTarget { |
103 | let (file_id, source) = module.definition_source(db); | 113 | let (file_id, source) = module.definition_source(db); |
104 | let file_id = file_id.as_original_file(); | 114 | let file_id = file_id.as_original_file(); |
diff --git a/crates/ra_ide_api/src/goto_definition.rs b/crates/ra_ide_api/src/goto_definition.rs index adae29e9c..9c56f17f2 100644 --- a/crates/ra_ide_api/src/goto_definition.rs +++ b/crates/ra_ide_api/src/goto_definition.rs | |||
@@ -1,12 +1,19 @@ | |||
1 | use ra_db::{FileId, SourceDatabase}; | 1 | use ra_db::{FileId, SourceDatabase}; |
2 | use ra_syntax::{ | 2 | use ra_syntax::{ |
3 | AstNode, ast, | 3 | AstNode, ast, |
4 | algo::{find_node_at_offset, visit::{visitor, Visitor}}, | 4 | algo::{ |
5 | find_node_at_offset, | ||
6 | visit::{visitor, Visitor}, | ||
7 | }, | ||
5 | SyntaxNode, | 8 | SyntaxNode, |
6 | }; | 9 | }; |
7 | use test_utils::tested_by; | ||
8 | 10 | ||
9 | use crate::{FilePosition, NavigationTarget, db::RootDatabase, RangeInfo}; | 11 | use crate::{ |
12 | FilePosition, NavigationTarget, | ||
13 | db::RootDatabase, | ||
14 | RangeInfo, | ||
15 | name_ref_kind::{NameRefKind::*, classify_name_ref}, | ||
16 | }; | ||
10 | 17 | ||
11 | pub(crate) fn goto_definition( | 18 | pub(crate) fn goto_definition( |
12 | db: &RootDatabase, | 19 | db: &RootDatabase, |
@@ -50,85 +57,24 @@ pub(crate) fn reference_definition( | |||
50 | 57 | ||
51 | let analyzer = hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); | 58 | let analyzer = hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); |
52 | 59 | ||
53 | // Special cases: | 60 | match classify_name_ref(db, &analyzer, name_ref) { |
54 | 61 | Some(Method(func)) => return Exact(NavigationTarget::from_function(db, func)), | |
55 | // Check if it is a method | 62 | Some(Macro(mac)) => return Exact(NavigationTarget::from_macro_def(db, mac)), |
56 | if let Some(method_call) = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast) { | 63 | Some(FieldAccess(field)) => return Exact(NavigationTarget::from_field(db, field)), |
57 | tested_by!(goto_definition_works_for_methods); | 64 | Some(AssocItem(assoc)) => return Exact(NavigationTarget::from_impl_item(db, assoc)), |
58 | if let Some(func) = analyzer.resolve_method_call(method_call) { | 65 | Some(Def(def)) => return Exact(NavigationTarget::from_def(db, def)), |
59 | return Exact(NavigationTarget::from_function(db, func)); | 66 | Some(SelfType(ty)) => { |
60 | } | 67 | if let Some((def_id, _)) = ty.as_adt() { |
61 | } | 68 | return Exact(NavigationTarget::from_adt_def(db, def_id)); |
62 | |||
63 | //it could be a macro call | ||
64 | if let Some(macro_call) = name_ref | ||
65 | .syntax() | ||
66 | .parent() | ||
67 | .and_then(|node| node.parent()) | ||
68 | .and_then(|node| node.parent()) | ||
69 | .and_then(ast::MacroCall::cast) | ||
70 | { | ||
71 | tested_by!(goto_definition_works_for_macros); | ||
72 | if let Some(macro_call) = analyzer.resolve_macro_call(macro_call) { | ||
73 | return Exact(NavigationTarget::from_macro_def(db, macro_call)); | ||
74 | } | ||
75 | } | ||
76 | |||
77 | // It could also be a field access | ||
78 | if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::FieldExpr::cast) { | ||
79 | tested_by!(goto_definition_works_for_fields); | ||
80 | if let Some(field) = analyzer.resolve_field(field_expr) { | ||
81 | return Exact(NavigationTarget::from_field(db, field)); | ||
82 | }; | ||
83 | } | ||
84 | |||
85 | // It could also be a named field | ||
86 | if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::NamedField::cast) { | ||
87 | tested_by!(goto_definition_works_for_named_fields); | ||
88 | |||
89 | let struct_lit = field_expr.syntax().ancestors().find_map(ast::StructLit::cast); | ||
90 | |||
91 | if let Some(ty) = struct_lit.and_then(|lit| analyzer.type_of(db, lit.into())) { | ||
92 | if let Some((hir::AdtDef::Struct(s), _)) = ty.as_adt() { | ||
93 | let hir_path = hir::Path::from_name_ref(name_ref); | ||
94 | let hir_name = hir_path.as_ident().unwrap(); | ||
95 | |||
96 | if let Some(field) = s.field(db, hir_name) { | ||
97 | return Exact(NavigationTarget::from_field(db, field)); | ||
98 | } | ||
99 | } | 69 | } |
100 | } | 70 | } |
101 | } | 71 | Some(Pat(pat)) => return Exact(NavigationTarget::from_pat(db, file_id, pat)), |
102 | 72 | Some(SelfParam(par)) => return Exact(NavigationTarget::from_self_param(file_id, par)), | |
103 | // General case, a path or a local: | 73 | Some(GenericParam(_)) => { |
104 | if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) { | 74 | // FIXME: go to the generic param def |
105 | if let Some(resolved) = analyzer.resolve_path(db, path) { | ||
106 | match resolved { | ||
107 | hir::PathResolution::Def(def) => return Exact(NavigationTarget::from_def(db, def)), | ||
108 | hir::PathResolution::LocalBinding(pat) => { | ||
109 | let nav = NavigationTarget::from_pat(db, file_id, pat); | ||
110 | return Exact(nav); | ||
111 | } | ||
112 | hir::PathResolution::GenericParam(..) => { | ||
113 | // FIXME: go to the generic param def | ||
114 | } | ||
115 | hir::PathResolution::Macro(def) => { | ||
116 | let nav = NavigationTarget::from_macro_def(db, def); | ||
117 | return Exact(nav); | ||
118 | } | ||
119 | hir::PathResolution::SelfType(impl_block) => { | ||
120 | let ty = impl_block.target_ty(db); | ||
121 | |||
122 | if let Some((def_id, _)) = ty.as_adt() { | ||
123 | return Exact(NavigationTarget::from_adt_def(db, def_id)); | ||
124 | } | ||
125 | } | ||
126 | hir::PathResolution::AssocItem(assoc) => { | ||
127 | return Exact(NavigationTarget::from_impl_item(db, assoc)); | ||
128 | } | ||
129 | } | ||
130 | } | 75 | } |
131 | } | 76 | None => {} |
77 | }; | ||
132 | 78 | ||
133 | // Fallback index based approach: | 79 | // Fallback index based approach: |
134 | let navs = crate::symbol_index::index_resolve(db, name_ref) | 80 | let navs = crate::symbol_index::index_resolve(db, name_ref) |
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index d4be8bd6c..f78348f74 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs | |||
@@ -18,6 +18,7 @@ mod change; | |||
18 | mod status; | 18 | mod status; |
19 | mod completion; | 19 | mod completion; |
20 | mod runnables; | 20 | mod runnables; |
21 | mod name_ref_kind; | ||
21 | mod goto_definition; | 22 | mod goto_definition; |
22 | mod goto_type_definition; | 23 | mod goto_type_definition; |
23 | mod extend_selection; | 24 | mod extend_selection; |
@@ -53,10 +54,7 @@ use ra_db::{ | |||
53 | }; | 54 | }; |
54 | use relative_path::RelativePathBuf; | 55 | use relative_path::RelativePathBuf; |
55 | 56 | ||
56 | use crate::{ | 57 | use crate::{symbol_index::FileSymbol, db::LineIndexDatabase}; |
57 | symbol_index::FileSymbol, | ||
58 | db::LineIndexDatabase, | ||
59 | }; | ||
60 | 58 | ||
61 | pub use crate::{ | 59 | pub use crate::{ |
62 | change::{AnalysisChange, LibraryData}, | 60 | change::{AnalysisChange, LibraryData}, |
@@ -73,10 +71,7 @@ pub use crate::{ | |||
73 | display::{FunctionSignature, NavigationTarget, StructureNode, file_structure}, | 71 | display::{FunctionSignature, NavigationTarget, StructureNode, file_structure}, |
74 | }; | 72 | }; |
75 | 73 | ||
76 | pub use ra_db::{ | 74 | pub use ra_db::{Canceled, CrateGraph, CrateId, FileId, FilePosition, FileRange, SourceRootId, Edition}; |
77 | Canceled, CrateGraph, CrateId, FileId, FilePosition, FileRange, SourceRootId, | ||
78 | Edition | ||
79 | }; | ||
80 | pub use hir::Documentation; | 75 | pub use hir::Documentation; |
81 | 76 | ||
82 | // We use jemalloc mainly to get heap usage statistics, actual performance | 77 | // We use jemalloc mainly to get heap usage statistics, actual performance |
diff --git a/crates/ra_ide_api/src/name_ref_kind.rs b/crates/ra_ide_api/src/name_ref_kind.rs new file mode 100644 index 000000000..b498fe495 --- /dev/null +++ b/crates/ra_ide_api/src/name_ref_kind.rs | |||
@@ -0,0 +1,95 @@ | |||
1 | use ra_syntax::{AstNode, AstPtr, ast}; | ||
2 | use hir::Either; | ||
3 | use crate::db::RootDatabase; | ||
4 | use test_utils::tested_by; | ||
5 | |||
6 | pub enum NameRefKind { | ||
7 | Method(hir::Function), | ||
8 | Macro(hir::MacroByExampleDef), | ||
9 | FieldAccess(hir::StructField), | ||
10 | AssocItem(hir::ImplItem), | ||
11 | Def(hir::ModuleDef), | ||
12 | SelfType(hir::Ty), | ||
13 | Pat(AstPtr<ast::Pat>), | ||
14 | SelfParam(AstPtr<ast::SelfParam>), | ||
15 | GenericParam(u32), | ||
16 | } | ||
17 | |||
18 | pub(crate) fn classify_name_ref( | ||
19 | db: &RootDatabase, | ||
20 | analyzer: &hir::SourceAnalyzer, | ||
21 | name_ref: &ast::NameRef, | ||
22 | ) -> Option<NameRefKind> { | ||
23 | use NameRefKind::*; | ||
24 | |||
25 | // Check if it is a method | ||
26 | if let Some(method_call) = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast) { | ||
27 | tested_by!(goto_definition_works_for_methods); | ||
28 | if let Some(func) = analyzer.resolve_method_call(method_call) { | ||
29 | return Some(Method(func)); | ||
30 | } | ||
31 | } | ||
32 | |||
33 | // It could be a macro call | ||
34 | if let Some(macro_call) = name_ref | ||
35 | .syntax() | ||
36 | .parent() | ||
37 | .and_then(|node| node.parent()) | ||
38 | .and_then(|node| node.parent()) | ||
39 | .and_then(ast::MacroCall::cast) | ||
40 | { | ||
41 | tested_by!(goto_definition_works_for_macros); | ||
42 | if let Some(mac) = analyzer.resolve_macro_call(macro_call) { | ||
43 | return Some(Macro(mac)); | ||
44 | } | ||
45 | } | ||
46 | |||
47 | // It could also be a field access | ||
48 | if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::FieldExpr::cast) { | ||
49 | tested_by!(goto_definition_works_for_fields); | ||
50 | if let Some(field) = analyzer.resolve_field(field_expr) { | ||
51 | return Some(FieldAccess(field)); | ||
52 | }; | ||
53 | } | ||
54 | |||
55 | // It could also be a named field | ||
56 | if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::NamedField::cast) { | ||
57 | tested_by!(goto_definition_works_for_named_fields); | ||
58 | |||
59 | let struct_lit = field_expr.syntax().ancestors().find_map(ast::StructLit::cast); | ||
60 | |||
61 | if let Some(ty) = struct_lit.and_then(|lit| analyzer.type_of(db, lit.into())) { | ||
62 | if let Some((hir::AdtDef::Struct(s), _)) = ty.as_adt() { | ||
63 | let hir_path = hir::Path::from_name_ref(name_ref); | ||
64 | let hir_name = hir_path.as_ident().unwrap(); | ||
65 | |||
66 | if let Some(field) = s.field(db, hir_name) { | ||
67 | return Some(FieldAccess(field)); | ||
68 | } | ||
69 | } | ||
70 | } | ||
71 | } | ||
72 | |||
73 | // General case, a path or a local: | ||
74 | if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) { | ||
75 | if let Some(resolved) = analyzer.resolve_path(db, path) { | ||
76 | return match resolved { | ||
77 | hir::PathResolution::Def(def) => Some(Def(def)), | ||
78 | hir::PathResolution::LocalBinding(Either::A(pat)) => Some(Pat(pat)), | ||
79 | hir::PathResolution::LocalBinding(Either::B(par)) => Some(SelfParam(par)), | ||
80 | hir::PathResolution::GenericParam(par) => { | ||
81 | // FIXME: get generic param def | ||
82 | Some(GenericParam(par)) | ||
83 | } | ||
84 | hir::PathResolution::Macro(def) => Some(Macro(def)), | ||
85 | hir::PathResolution::SelfType(impl_block) => { | ||
86 | let ty = impl_block.target_ty(db); | ||
87 | Some(SelfType(ty)) | ||
88 | } | ||
89 | hir::PathResolution::AssocItem(assoc) => Some(AssocItem(assoc)), | ||
90 | }; | ||
91 | } | ||
92 | } | ||
93 | |||
94 | None | ||
95 | } | ||
diff --git a/crates/ra_ide_api/src/snapshots/tests__highlighting.snap b/crates/ra_ide_api/src/snapshots/tests__highlighting.snap index 72029e0ed..9d4c04db3 100644 --- a/crates/ra_ide_api/src/snapshots/tests__highlighting.snap +++ b/crates/ra_ide_api/src/snapshots/tests__highlighting.snap | |||
@@ -1,33 +1,145 @@ | |||
1 | --- | 1 | --- |
2 | created: "2019-03-23T16:20:31.394314144Z" | 2 | created: "2019-05-23T12:10:32.628883358Z" |
3 | creator: insta@0.7.1 | 3 | creator: insta@0.8.1 |
4 | source: crates/ra_ide_api/src/syntax_highlighting.rs | 4 | source: crates/ra_ide_api/src/syntax_highlighting.rs |
5 | expression: result | 5 | expression: result |
6 | --- | 6 | --- |
7 | Ok( | 7 | Ok( |
8 | [ | 8 | [ |
9 | HighlightedRange { | 9 | HighlightedRange { |
10 | range: [1; 11), | 10 | range: [1; 24), |
11 | tag: "attribute" | ||
12 | }, | ||
13 | HighlightedRange { | ||
14 | range: [25; 31), | ||
15 | tag: "keyword" | ||
16 | }, | ||
17 | HighlightedRange { | ||
18 | range: [32; 35), | ||
19 | tag: "function" | ||
20 | }, | ||
21 | HighlightedRange { | ||
22 | range: [42; 45), | ||
23 | tag: "keyword" | ||
24 | }, | ||
25 | HighlightedRange { | ||
26 | range: [46; 47), | ||
27 | tag: "function" | ||
28 | }, | ||
29 | HighlightedRange { | ||
30 | range: [49; 52), | ||
31 | tag: "text" | ||
32 | }, | ||
33 | HighlightedRange { | ||
34 | range: [58; 61), | ||
35 | tag: "keyword" | ||
36 | }, | ||
37 | HighlightedRange { | ||
38 | range: [62; 63), | ||
39 | tag: "function" | ||
40 | }, | ||
41 | HighlightedRange { | ||
42 | range: [65; 68), | ||
43 | tag: "text" | ||
44 | }, | ||
45 | HighlightedRange { | ||
46 | range: [73; 75), | ||
47 | tag: "keyword" | ||
48 | }, | ||
49 | HighlightedRange { | ||
50 | range: [76; 79), | ||
51 | tag: "function" | ||
52 | }, | ||
53 | HighlightedRange { | ||
54 | range: [80; 81), | ||
55 | tag: "type" | ||
56 | }, | ||
57 | HighlightedRange { | ||
58 | range: [80; 81), | ||
59 | tag: "function" | ||
60 | }, | ||
61 | HighlightedRange { | ||
62 | range: [88; 89), | ||
63 | tag: "type" | ||
64 | }, | ||
65 | HighlightedRange { | ||
66 | range: [96; 110), | ||
67 | tag: "macro" | ||
68 | }, | ||
69 | HighlightedRange { | ||
70 | range: [117; 127), | ||
11 | tag: "comment" | 71 | tag: "comment" |
12 | }, | 72 | }, |
13 | HighlightedRange { | 73 | HighlightedRange { |
14 | range: [12; 14), | 74 | range: [128; 130), |
15 | tag: "keyword" | 75 | tag: "keyword" |
16 | }, | 76 | }, |
17 | HighlightedRange { | 77 | HighlightedRange { |
18 | range: [15; 19), | 78 | range: [131; 135), |
19 | tag: "function" | 79 | tag: "function" |
20 | }, | 80 | }, |
21 | HighlightedRange { | 81 | HighlightedRange { |
22 | range: [29; 37), | 82 | range: [145; 153), |
23 | tag: "macro" | 83 | tag: "macro" |
24 | }, | 84 | }, |
25 | HighlightedRange { | 85 | HighlightedRange { |
26 | range: [38; 50), | 86 | range: [154; 166), |
27 | tag: "string" | 87 | tag: "string" |
28 | }, | 88 | }, |
29 | HighlightedRange { | 89 | HighlightedRange { |
30 | range: [52; 54), | 90 | range: [168; 170), |
91 | tag: "literal" | ||
92 | }, | ||
93 | HighlightedRange { | ||
94 | range: [178; 181), | ||
95 | tag: "keyword" | ||
96 | }, | ||
97 | HighlightedRange { | ||
98 | range: [182; 185), | ||
99 | tag: "keyword" | ||
100 | }, | ||
101 | HighlightedRange { | ||
102 | range: [186; 189), | ||
103 | tag: "macro" | ||
104 | }, | ||
105 | HighlightedRange { | ||
106 | range: [197; 200), | ||
107 | tag: "macro" | ||
108 | }, | ||
109 | HighlightedRange { | ||
110 | range: [192; 195), | ||
111 | tag: "text" | ||
112 | }, | ||
113 | HighlightedRange { | ||
114 | range: [208; 211), | ||
115 | tag: "macro" | ||
116 | }, | ||
117 | HighlightedRange { | ||
118 | range: [212; 216), | ||
119 | tag: "macro" | ||
120 | }, | ||
121 | HighlightedRange { | ||
122 | range: [226; 227), | ||
123 | tag: "literal" | ||
124 | }, | ||
125 | HighlightedRange { | ||
126 | range: [232; 233), | ||
127 | tag: "literal" | ||
128 | }, | ||
129 | HighlightedRange { | ||
130 | range: [242; 248), | ||
131 | tag: "keyword.unsafe" | ||
132 | }, | ||
133 | HighlightedRange { | ||
134 | range: [251; 254), | ||
135 | tag: "text" | ||
136 | }, | ||
137 | HighlightedRange { | ||
138 | range: [255; 262), | ||
139 | tag: "text" | ||
140 | }, | ||
141 | HighlightedRange { | ||
142 | range: [263; 264), | ||
31 | tag: "literal" | 143 | tag: "literal" |
32 | } | 144 | } |
33 | ] | 145 | ] |
diff --git a/crates/ra_ide_api/src/syntax_highlighting.rs b/crates/ra_ide_api/src/syntax_highlighting.rs index 2158291dc..89f20260f 100644 --- a/crates/ra_ide_api/src/syntax_highlighting.rs +++ b/crates/ra_ide_api/src/syntax_highlighting.rs | |||
@@ -40,8 +40,41 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa | |||
40 | COMMENT => "comment", | 40 | COMMENT => "comment", |
41 | STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => "string", | 41 | STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => "string", |
42 | ATTR => "attribute", | 42 | ATTR => "attribute", |
43 | NAME_REF => "text", | 43 | NAME_REF => { |
44 | if let Some(name_ref) = node.as_node().and_then(|n| ast::NameRef::cast(n)) { | ||
45 | use crate::name_ref_kind::{classify_name_ref, NameRefKind::*}; | ||
46 | use hir::{ModuleDef, ImplItem}; | ||
47 | |||
48 | // FIXME: try to reuse the SourceAnalyzers | ||
49 | let analyzer = hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); | ||
50 | match classify_name_ref(db, &analyzer, name_ref) { | ||
51 | Some(Method(_)) => "function", | ||
52 | Some(Macro(_)) => "macro", | ||
53 | Some(FieldAccess(_)) => "field", | ||
54 | Some(AssocItem(ImplItem::Method(_))) => "function", | ||
55 | Some(AssocItem(ImplItem::Const(_))) => "constant", | ||
56 | Some(AssocItem(ImplItem::TypeAlias(_))) => "type", | ||
57 | Some(Def(ModuleDef::Module(_))) => "module", | ||
58 | Some(Def(ModuleDef::Function(_))) => "function", | ||
59 | Some(Def(ModuleDef::Struct(_))) => "type", | ||
60 | Some(Def(ModuleDef::Enum(_))) => "type", | ||
61 | Some(Def(ModuleDef::EnumVariant(_))) => "constant", | ||
62 | Some(Def(ModuleDef::Const(_))) => "constant", | ||
63 | Some(Def(ModuleDef::Static(_))) => "constant", | ||
64 | Some(Def(ModuleDef::Trait(_))) => "type", | ||
65 | Some(Def(ModuleDef::TypeAlias(_))) => "type", | ||
66 | Some(SelfType(_)) => "type", | ||
67 | Some(Pat(_)) => "text", | ||
68 | Some(SelfParam(_)) => "type", | ||
69 | Some(GenericParam(_)) => "type", | ||
70 | None => "text", | ||
71 | } | ||
72 | } else { | ||
73 | "text" | ||
74 | } | ||
75 | } | ||
44 | NAME => "function", | 76 | NAME => "function", |
77 | TYPE_ALIAS_DEF | TYPE_ARG | TYPE_PARAM => "type", | ||
45 | INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal", | 78 | INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal", |
46 | LIFETIME => "parameter", | 79 | LIFETIME => "parameter", |
47 | T![unsafe] => "keyword.unsafe", | 80 | T![unsafe] => "keyword.unsafe", |
@@ -87,9 +120,23 @@ mod tests { | |||
87 | fn test_highlighting() { | 120 | fn test_highlighting() { |
88 | let (analysis, file_id) = single_file( | 121 | let (analysis, file_id) = single_file( |
89 | r#" | 122 | r#" |
123 | #[derive(Clone, Debug)] | ||
124 | struct Foo { | ||
125 | pub x: i32, | ||
126 | pub y: i32, | ||
127 | } | ||
128 | |||
129 | fn foo<T>() -> T { | ||
130 | unimplemented!(); | ||
131 | } | ||
132 | |||
90 | // comment | 133 | // comment |
91 | fn main() {} | 134 | fn main() {} |
92 | println!("Hello, {}!", 92); | 135 | println!("Hello, {}!", 92); |
136 | |||
137 | let mut vec = Vec::new(); | ||
138 | vec.push(Foo { x: 0, y: 1 }); | ||
139 | unsafe { vec.set_len(0); } | ||
93 | "#, | 140 | "#, |
94 | ); | 141 | ); |
95 | let result = analysis.highlight(file_id); | 142 | let result = analysis.highlight(file_id); |
diff --git a/docs/user/README.md b/docs/user/README.md index 1b4d13d0f..affb96939 100644 --- a/docs/user/README.md +++ b/docs/user/README.md | |||
@@ -64,7 +64,7 @@ for details. | |||
64 | * `rust-analyzer.showWorkspaceLoadedNotification`: to ease troubleshooting, a | 64 | * `rust-analyzer.showWorkspaceLoadedNotification`: to ease troubleshooting, a |
65 | notification is shown by default when a workspace is loaded | 65 | notification is shown by default when a workspace is loaded |
66 | * `rust-analyzer.enableEnhancedTyping`: by default, rust-analyzer intercepts | 66 | * `rust-analyzer.enableEnhancedTyping`: by default, rust-analyzer intercepts |
67 | `Enter` key to make it easier to continue comments | 67 | `Enter` key to make it easier to continue comments. Note that it may conflict with VIM emulation plugin. |
68 | * `rust-analyzer.raLspServerPath`: path to `ra_lsp_server` executable | 68 | * `rust-analyzer.raLspServerPath`: path to `ra_lsp_server` executable |
69 | * `rust-analyzer.enableCargoWatchOnStartup`: prompt to install & enable `cargo | 69 | * `rust-analyzer.enableCargoWatchOnStartup`: prompt to install & enable `cargo |
70 | watch` for live error highlighting (note, this **does not** use rust-analyzer) | 70 | watch` for live error highlighting (note, this **does not** use rust-analyzer) |
diff --git a/docs/user/features.md b/docs/user/features.md index a714574fb..22470bc56 100644 --- a/docs/user/features.md +++ b/docs/user/features.md | |||
@@ -390,14 +390,14 @@ fn foo() { | |||
390 | 390 | ||
391 | - Move guard expression to match arm body | 391 | - Move guard expression to match arm body |
392 | ```rust | 392 | ```rust |
393 | //before: | 393 | // before: |
394 | fn f() { | 394 | fn f() { |
395 | match x { | 395 | match x { |
396 | <|>y @ 4 | y @ 5 if y > 5 => true, | 396 | <|>y @ 4 | y @ 5 if y > 5 => true, |
397 | _ => false | 397 | _ => false |
398 | } | 398 | } |
399 | } | 399 | } |
400 | //after: | 400 | // after: |
401 | fn f() { | 401 | fn f() { |
402 | match x { | 402 | match x { |
403 | y @ 4 | y @ 5 => if y > 5 { <|>true }, | 403 | y @ 4 | y @ 5 => if y > 5 { <|>true }, |
@@ -406,6 +406,35 @@ fn f() { | |||
406 | } | 406 | } |
407 | ``` | 407 | ``` |
408 | 408 | ||
409 | - Move if condition to match arm guard | ||
410 | ```rust | ||
411 | // before: | ||
412 | fn f() { | ||
413 | let mut t = 'a'; | ||
414 | let chars = "abcd"; | ||
415 | match t { | ||
416 | '\r' => if chars.clone().next().is_some() { | ||
417 | t = 'e';<|> | ||
418 | false | ||
419 | }, | ||
420 | _ => true | ||
421 | } | ||
422 | } | ||
423 | |||
424 | // after: | ||
425 | fn f() { | ||
426 | let mut t = 'a'; | ||
427 | let chars = "abcd"; | ||
428 | match t { | ||
429 | '\r' <|>if chars.clone().next().is_some() => { | ||
430 | t = 'e'; | ||
431 | false | ||
432 | }, | ||
433 | _ => true | ||
434 | } | ||
435 | } | ||
436 | ``` | ||
437 | |||
409 | ### Magic Completions | 438 | ### Magic Completions |
410 | 439 | ||
411 | In addition to usual reference completion, rust-analyzer provides some ✨magic✨ | 440 | In addition to usual reference completion, rust-analyzer provides some ✨magic✨ |
diff --git a/editors/code/package.json b/editors/code/package.json index 750c97bb1..cde5fbcb8 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -371,12 +371,57 @@ | |||
371 | }, | 371 | }, |
372 | { | 372 | { |
373 | "id": "ralsp.macro", | 373 | "id": "ralsp.macro", |
374 | "description": "Color for DFAF8F", | 374 | "description": "Color for macros", |
375 | "defaults": { | 375 | "defaults": { |
376 | "dark": "#BFEBBF", | 376 | "dark": "#BFEBBF", |
377 | "light": "#DD6718", | 377 | "light": "#DD6718", |
378 | "highContrast": "#ED7718" | 378 | "highContrast": "#ED7718" |
379 | } | 379 | } |
380 | }, | ||
381 | { | ||
382 | "id": "ralsp.constant", | ||
383 | "description": "Color for constants", | ||
384 | "defaults": { | ||
385 | "dark": "#569cd6", | ||
386 | "light": "#267cb6", | ||
387 | "highContrast": "#569cd6" | ||
388 | } | ||
389 | }, | ||
390 | { | ||
391 | "id": "ralsp.type", | ||
392 | "description": "Color for types", | ||
393 | "defaults": { | ||
394 | "dark": "#4EC9B0", | ||
395 | "light": "#267F99", | ||
396 | "highContrast": "#4EC9B0" | ||
397 | } | ||
398 | }, | ||
399 | { | ||
400 | "id": "ralsp.field", | ||
401 | "description": "Color for fields", | ||
402 | "defaults": { | ||
403 | "dark": "#4EC9B0", | ||
404 | "light": "#267F99", | ||
405 | "highContrast": "#4EC9B0" | ||
406 | } | ||
407 | }, | ||
408 | { | ||
409 | "id": "ralsp.variable", | ||
410 | "description": "Color for variables", | ||
411 | "defaults": { | ||
412 | "dark": "#4EC9B0", | ||
413 | "light": "#267F99", | ||
414 | "highContrast": "#4EC9B0" | ||
415 | } | ||
416 | }, | ||
417 | { | ||
418 | "id": "ralsp.module", | ||
419 | "description": "Color for modules", | ||
420 | "defaults": { | ||
421 | "dark": "#D4D4D4", | ||
422 | "light": "#000000", | ||
423 | "highContrast": "#FFFFFF" | ||
424 | } | ||
380 | } | 425 | } |
381 | ] | 426 | ] |
382 | } | 427 | } |
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index e1a68544a..8389d94b8 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts | |||
@@ -33,11 +33,16 @@ export class Highlighter { | |||
33 | colorContrib('keyword.unsafe'), | 33 | colorContrib('keyword.unsafe'), |
34 | colorContrib('function'), | 34 | colorContrib('function'), |
35 | colorContrib('parameter'), | 35 | colorContrib('parameter'), |
36 | colorContrib('constant'), | ||
37 | colorContrib('type'), | ||
36 | colorContrib('builtin'), | 38 | colorContrib('builtin'), |
37 | colorContrib('text'), | 39 | colorContrib('text'), |
38 | colorContrib('attribute'), | 40 | colorContrib('attribute'), |
39 | colorContrib('literal'), | 41 | colorContrib('literal'), |
40 | colorContrib('macro') | 42 | colorContrib('macro'), |
43 | colorContrib('variable'), | ||
44 | colorContrib('field'), | ||
45 | colorContrib('module') | ||
41 | ]; | 46 | ]; |
42 | 47 | ||
43 | return new Map<string, vscode.TextEditorDecorationType>(decorations); | 48 | return new Map<string, vscode.TextEditorDecorationType>(decorations); |