aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--crates/flycheck/src/lib.rs92
-rw-r--r--crates/ra_assists/src/handlers/change_return_type_to_result.rs33
-rw-r--r--crates/ra_assists/src/handlers/extract_variable.rs (renamed from crates/ra_assists/src/handlers/introduce_variable.rs)106
-rw-r--r--crates/ra_assists/src/lib.rs4
-rw-r--r--crates/ra_assists/src/tests/generated.rs36
-rw-r--r--crates/ra_hir_def/src/body/lower.rs98
-rw-r--r--crates/ra_hir_def/src/body/scope.rs13
-rw-r--r--crates/ra_hir_def/src/item_scope.rs79
-rw-r--r--crates/ra_hir_def/src/nameres/collector.rs49
-rw-r--r--crates/ra_hir_def/src/nameres/tests/globs.rs90
-rw-r--r--crates/ra_hir_def/src/nameres/tests/mod_resolution.rs16
-rw-r--r--crates/ra_hir_ty/src/method_resolution.rs3
-rw-r--r--crates/ra_hir_ty/src/tests/simple.rs46
-rw-r--r--crates/ra_parser/src/parser.rs15
-rw-r--r--crates/ra_ssr/src/lib.rs18
-rw-r--r--crates/ra_ssr/src/matching.rs4
-rw-r--r--crates/ra_ssr/src/tests.rs61
-rw-r--r--crates/ra_toolchain/src/lib.rs25
-rw-r--r--crates/rust-analyzer/Cargo.toml1
-rw-r--r--crates/rust-analyzer/src/diagnostics.rs22
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs6
-rw-r--r--crates/rust-analyzer/src/global_state.rs64
-rw-r--r--crates/rust-analyzer/src/handlers.rs2
-rw-r--r--crates/rust-analyzer/src/lib.rs2
-rw-r--r--crates/rust-analyzer/src/lsp_utils.rs85
-rw-r--r--crates/rust-analyzer/src/main_loop.rs135
-rw-r--r--crates/rust-analyzer/src/reload.rs44
-rw-r--r--crates/vfs/src/file_set.rs2
-rw-r--r--crates/vfs/src/vfs_path.rs16
-rw-r--r--docs/dev/README.md11
-rw-r--r--editors/code/package.json8
-rw-r--r--editors/code/src/commands.ts4
-rw-r--r--editors/code/src/config.ts4
-rw-r--r--editors/code/src/main.ts2
-rw-r--r--editors/code/src/run.ts58
-rw-r--r--editors/code/src/tasks.ts111
37 files changed, 869 insertions, 497 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f22366a36..fe71b9971 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1458,6 +1458,7 @@ dependencies = [
1458 "ra_project_model", 1458 "ra_project_model",
1459 "ra_syntax", 1459 "ra_syntax",
1460 "ra_text_edit", 1460 "ra_text_edit",
1461 "ra_toolchain",
1461 "ra_tt", 1462 "ra_tt",
1462 "rand", 1463 "rand",
1463 "rustc-hash", 1464 "rustc-hash",
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs
index 4dcab7a61..92ec4f92e 100644
--- a/crates/flycheck/src/lib.rs
+++ b/crates/flycheck/src/lib.rs
@@ -7,7 +7,7 @@ use std::{
7 io::{self, BufReader}, 7 io::{self, BufReader},
8 path::PathBuf, 8 path::PathBuf,
9 process::{Command, Stdio}, 9 process::{Command, Stdio},
10 time::Instant, 10 time::Duration,
11}; 11};
12 12
13use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; 13use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
@@ -74,9 +74,6 @@ impl FlycheckHandle {
74 74
75#[derive(Debug)] 75#[derive(Debug)]
76pub enum Message { 76pub enum Message {
77 /// Request a clearing of all cached diagnostics from the check watcher
78 ClearDiagnostics,
79
80 /// Request adding a diagnostic with fixes included to a file 77 /// Request adding a diagnostic with fixes included to a file
81 AddDiagnostic { workspace_root: PathBuf, diagnostic: Diagnostic }, 78 AddDiagnostic { workspace_root: PathBuf, diagnostic: Diagnostic },
82 79
@@ -86,9 +83,10 @@ pub enum Message {
86 83
87#[derive(Debug)] 84#[derive(Debug)]
88pub enum Progress { 85pub enum Progress {
89 Being, 86 DidStart,
90 DidCheckCrate(String), 87 DidCheckCrate(String),
91 End, 88 DidFinish,
89 DidCancel,
92} 90}
93 91
94struct Restart; 92struct Restart;
@@ -97,19 +95,18 @@ struct FlycheckActor {
97 sender: Box<dyn Fn(Message) + Send>, 95 sender: Box<dyn Fn(Message) + Send>,
98 config: FlycheckConfig, 96 config: FlycheckConfig,
99 workspace_root: PathBuf, 97 workspace_root: PathBuf,
100 last_update_req: Option<Instant>,
101 /// WatchThread exists to wrap around the communication needed to be able to 98 /// WatchThread exists to wrap around the communication needed to be able to
102 /// run `cargo check` without blocking. Currently the Rust standard library 99 /// run `cargo check` without blocking. Currently the Rust standard library
103 /// doesn't provide a way to read sub-process output without blocking, so we 100 /// doesn't provide a way to read sub-process output without blocking, so we
104 /// have to wrap sub-processes output handling in a thread and pass messages 101 /// have to wrap sub-processes output handling in a thread and pass messages
105 /// back over a channel. 102 /// back over a channel.
106 // XXX: drop order is significant 103 // XXX: drop order is significant
107 check_process: Option<(Receiver<CheckEvent>, jod_thread::JoinHandle)>, 104 check_process: Option<(Receiver<cargo_metadata::Message>, jod_thread::JoinHandle)>,
108} 105}
109 106
110enum Event { 107enum Event {
111 Restart(Restart), 108 Restart(Restart),
112 CheckEvent(Option<CheckEvent>), 109 CheckEvent(Option<cargo_metadata::Message>),
113} 110}
114 111
115impl FlycheckActor { 112impl FlycheckActor {
@@ -118,7 +115,7 @@ impl FlycheckActor {
118 config: FlycheckConfig, 115 config: FlycheckConfig,
119 workspace_root: PathBuf, 116 workspace_root: PathBuf,
120 ) -> FlycheckActor { 117 ) -> FlycheckActor {
121 FlycheckActor { sender, config, workspace_root, last_update_req: None, check_process: None } 118 FlycheckActor { sender, config, workspace_root, check_process: None }
122 } 119 }
123 fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> { 120 fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> {
124 let check_chan = self.check_process.as_ref().map(|(chan, _thread)| chan); 121 let check_chan = self.check_process.as_ref().map(|(chan, _thread)| chan);
@@ -128,65 +125,48 @@ impl FlycheckActor {
128 } 125 }
129 } 126 }
130 fn run(&mut self, inbox: Receiver<Restart>) { 127 fn run(&mut self, inbox: Receiver<Restart>) {
131 // If we rerun the thread, we need to discard the previous check results first
132 self.send(Message::ClearDiagnostics);
133 self.send(Message::Progress(Progress::End));
134
135 while let Some(event) = self.next_event(&inbox) { 128 while let Some(event) = self.next_event(&inbox) {
136 match event { 129 match event {
137 Event::Restart(Restart) => self.last_update_req = Some(Instant::now()), 130 Event::Restart(Restart) => {
131 while let Ok(Restart) = inbox.recv_timeout(Duration::from_millis(50)) {}
132 self.cancel_check_process();
133 self.check_process = Some(self.start_check_process());
134 self.send(Message::Progress(Progress::DidStart));
135 }
138 Event::CheckEvent(None) => { 136 Event::CheckEvent(None) => {
139 // Watcher finished, replace it with a never channel to 137 // Watcher finished, replace it with a never channel to
140 // avoid busy-waiting. 138 // avoid busy-waiting.
141 self.check_process = None; 139 assert!(self.check_process.take().is_some());
140 self.send(Message::Progress(Progress::DidFinish));
142 } 141 }
143 Event::CheckEvent(Some(event)) => match event { 142 Event::CheckEvent(Some(message)) => match message {
144 CheckEvent::Begin => { 143 cargo_metadata::Message::CompilerArtifact(msg) => {
145 self.send(Message::Progress(Progress::Being));
146 }
147
148 CheckEvent::End => {
149 self.send(Message::Progress(Progress::End));
150 }
151
152 CheckEvent::Msg(cargo_metadata::Message::CompilerArtifact(msg)) => {
153 self.send(Message::Progress(Progress::DidCheckCrate(msg.target.name))); 144 self.send(Message::Progress(Progress::DidCheckCrate(msg.target.name)));
154 } 145 }
155 146
156 CheckEvent::Msg(cargo_metadata::Message::CompilerMessage(msg)) => { 147 cargo_metadata::Message::CompilerMessage(msg) => {
157 self.send(Message::AddDiagnostic { 148 self.send(Message::AddDiagnostic {
158 workspace_root: self.workspace_root.clone(), 149 workspace_root: self.workspace_root.clone(),
159 diagnostic: msg.message, 150 diagnostic: msg.message,
160 }); 151 });
161 } 152 }
162 153
163 CheckEvent::Msg(cargo_metadata::Message::BuildScriptExecuted(_)) 154 cargo_metadata::Message::BuildScriptExecuted(_)
164 | CheckEvent::Msg(cargo_metadata::Message::BuildFinished(_)) 155 | cargo_metadata::Message::BuildFinished(_)
165 | CheckEvent::Msg(cargo_metadata::Message::TextLine(_)) 156 | cargo_metadata::Message::TextLine(_)
166 | CheckEvent::Msg(cargo_metadata::Message::Unknown) => {} 157 | cargo_metadata::Message::Unknown => {}
167 }, 158 },
168 } 159 }
169 if self.should_recheck() {
170 self.last_update_req = None;
171 self.send(Message::ClearDiagnostics);
172 self.restart_check_process();
173 }
174 } 160 }
161 // If we rerun the thread, we need to discard the previous check results first
162 self.cancel_check_process();
175 } 163 }
176 fn should_recheck(&mut self) -> bool { 164 fn cancel_check_process(&mut self) {
177 if let Some(_last_update_req) = &self.last_update_req { 165 if self.check_process.take().is_some() {
178 // We currently only request an update on save, as we need up to 166 self.send(Message::Progress(Progress::DidCancel));
179 // date source on disk for cargo check to do it's magic, so we
180 // don't really need to debounce the requests at this point.
181 return true;
182 } 167 }
183 false
184 } 168 }
185 169 fn start_check_process(&self) -> (Receiver<cargo_metadata::Message>, jod_thread::JoinHandle) {
186 fn restart_check_process(&mut self) {
187 // First, clear and cancel the old thread
188 self.check_process = None;
189
190 let mut cmd = match &self.config { 170 let mut cmd = match &self.config {
191 FlycheckConfig::CargoCommand { 171 FlycheckConfig::CargoCommand {
192 command, 172 command,
@@ -223,8 +203,6 @@ impl FlycheckActor {
223 let thread = jod_thread::spawn(move || { 203 let thread = jod_thread::spawn(move || {
224 // If we trigger an error here, we will do so in the loop instead, 204 // If we trigger an error here, we will do so in the loop instead,
225 // which will break out of the loop, and continue the shutdown 205 // which will break out of the loop, and continue the shutdown
226 let _ = message_send.send(CheckEvent::Begin);
227
228 let res = run_cargo(cmd, &mut |message| { 206 let res = run_cargo(cmd, &mut |message| {
229 // Skip certain kinds of messages to only spend time on what's useful 207 // Skip certain kinds of messages to only spend time on what's useful
230 match &message { 208 match &message {
@@ -237,7 +215,7 @@ impl FlycheckActor {
237 } 215 }
238 216
239 // if the send channel was closed, we want to shutdown 217 // if the send channel was closed, we want to shutdown
240 message_send.send(CheckEvent::Msg(message)).is_ok() 218 message_send.send(message).is_ok()
241 }); 219 });
242 220
243 if let Err(err) = res { 221 if let Err(err) = res {
@@ -245,12 +223,8 @@ impl FlycheckActor {
245 // to display user-caused misconfiguration errors instead of just logging them here 223 // to display user-caused misconfiguration errors instead of just logging them here
246 log::error!("Cargo watcher failed {:?}", err); 224 log::error!("Cargo watcher failed {:?}", err);
247 } 225 }
248
249 // We can ignore any error here, as we are already in the progress
250 // of shutting down.
251 let _ = message_send.send(CheckEvent::End);
252 }); 226 });
253 self.check_process = Some((message_recv, thread)) 227 (message_recv, thread)
254 } 228 }
255 229
256 fn send(&self, check_task: Message) { 230 fn send(&self, check_task: Message) {
@@ -258,12 +232,6 @@ impl FlycheckActor {
258 } 232 }
259} 233}
260 234
261enum CheckEvent {
262 Begin,
263 Msg(cargo_metadata::Message),
264 End,
265}
266
267fn run_cargo( 235fn run_cargo(
268 mut command: Command, 236 mut command: Command,
269 on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool, 237 on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool,
diff --git a/crates/ra_assists/src/handlers/change_return_type_to_result.rs b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
index c6baa0a57..855baf187 100644
--- a/crates/ra_assists/src/handlers/change_return_type_to_result.rs
+++ b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
@@ -4,6 +4,7 @@ use ra_syntax::{
4}; 4};
5 5
6use crate::{AssistContext, AssistId, Assists}; 6use crate::{AssistContext, AssistId, Assists};
7use test_utils::mark;
7 8
8// Assist: change_return_type_to_result 9// Assist: change_return_type_to_result
9// 10//
@@ -22,8 +23,13 @@ pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContex
22 let fn_def = ret_type.syntax().parent().and_then(ast::FnDef::cast)?; 23 let fn_def = ret_type.syntax().parent().and_then(ast::FnDef::cast)?;
23 24
24 let type_ref = &ret_type.type_ref()?; 25 let type_ref = &ret_type.type_ref()?;
25 if type_ref.syntax().text().to_string().starts_with("Result<") { 26 let ret_type_str = type_ref.syntax().text().to_string();
26 return None; 27 let first_part_ret_type = ret_type_str.splitn(2, '<').next();
28 if let Some(ret_type_first_part) = first_part_ret_type {
29 if ret_type_first_part.ends_with("Result") {
30 mark::hit!(change_return_type_to_result_simple_return_type_already_result);
31 return None;
32 }
27 } 33 }
28 34
29 let block_expr = &fn_def.body()?; 35 let block_expr = &fn_def.body()?;
@@ -297,6 +303,29 @@ mod tests {
297 } 303 }
298 304
299 #[test] 305 #[test]
306 fn change_return_type_to_result_simple_return_type_already_result_std() {
307 check_assist_not_applicable(
308 change_return_type_to_result,
309 r#"fn foo() -> std::result::Result<i32<|>, String> {
310 let test = "test";
311 return 42i32;
312 }"#,
313 );
314 }
315
316 #[test]
317 fn change_return_type_to_result_simple_return_type_already_result() {
318 mark::check!(change_return_type_to_result_simple_return_type_already_result);
319 check_assist_not_applicable(
320 change_return_type_to_result,
321 r#"fn foo() -> Result<i32<|>, String> {
322 let test = "test";
323 return 42i32;
324 }"#,
325 );
326 }
327
328 #[test]
300 fn change_return_type_to_result_simple_with_cursor() { 329 fn change_return_type_to_result_simple_with_cursor() {
301 check_assist( 330 check_assist(
302 change_return_type_to_result, 331 change_return_type_to_result,
diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/extract_variable.rs
index 96affe49d..c4150d2bb 100644
--- a/crates/ra_assists/src/handlers/introduce_variable.rs
+++ b/crates/ra_assists/src/handlers/extract_variable.rs
@@ -11,7 +11,7 @@ use test_utils::mark;
11 11
12use crate::{AssistContext, AssistId, Assists}; 12use crate::{AssistContext, AssistId, Assists};
13 13
14// Assist: introduce_variable 14// Assist: extract_variable
15// 15//
16// Extracts subexpression into a variable. 16// Extracts subexpression into a variable.
17// 17//
@@ -27,13 +27,13 @@ use crate::{AssistContext, AssistId, Assists};
27// var_name * 4; 27// var_name * 4;
28// } 28// }
29// ``` 29// ```
30pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 30pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
31 if ctx.frange.range.is_empty() { 31 if ctx.frange.range.is_empty() {
32 return None; 32 return None;
33 } 33 }
34 let node = ctx.covering_element(); 34 let node = ctx.covering_element();
35 if node.kind() == COMMENT { 35 if node.kind() == COMMENT {
36 mark::hit!(introduce_var_in_comment_is_not_applicable); 36 mark::hit!(extract_var_in_comment_is_not_applicable);
37 return None; 37 return None;
38 } 38 }
39 let expr = node.ancestors().find_map(valid_target_expr)?; 39 let expr = node.ancestors().find_map(valid_target_expr)?;
@@ -43,7 +43,7 @@ pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Opti
43 return None; 43 return None;
44 } 44 }
45 let target = expr.syntax().text_range(); 45 let target = expr.syntax().text_range();
46 acc.add(AssistId("introduce_variable"), "Extract into variable", target, move |edit| { 46 acc.add(AssistId("extract_variable"), "Extract into variable", target, move |edit| {
47 let field_shorthand = match expr.syntax().parent().and_then(ast::RecordField::cast) { 47 let field_shorthand = match expr.syntax().parent().and_then(ast::RecordField::cast) {
48 Some(field) => field.name_ref(), 48 Some(field) => field.name_ref(),
49 None => None, 49 None => None,
@@ -74,7 +74,7 @@ pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Opti
74 false 74 false
75 }; 75 };
76 if is_full_stmt { 76 if is_full_stmt {
77 mark::hit!(test_introduce_var_expr_stmt); 77 mark::hit!(test_extract_var_expr_stmt);
78 if full_stmt.unwrap().semicolon_token().is_none() { 78 if full_stmt.unwrap().semicolon_token().is_none() {
79 buf.push_str(";"); 79 buf.push_str(";");
80 } 80 }
@@ -133,7 +133,7 @@ fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
133 } 133 }
134} 134}
135 135
136/// Returns the syntax node which will follow the freshly introduced var 136/// Returns the syntax node which will follow the freshly extractd var
137/// and a boolean indicating whether we have to wrap it within a { } block 137/// and a boolean indicating whether we have to wrap it within a { } block
138/// to produce correct code. 138/// to produce correct code.
139/// It can be a statement, the last in a block expression or a wanna be block 139/// It can be a statement, the last in a block expression or a wanna be block
@@ -142,7 +142,7 @@ fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> {
142 expr.syntax().ancestors().find_map(|node| { 142 expr.syntax().ancestors().find_map(|node| {
143 if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) { 143 if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) {
144 if expr.syntax() == &node { 144 if expr.syntax() == &node {
145 mark::hit!(test_introduce_var_last_expr); 145 mark::hit!(test_extract_var_last_expr);
146 return Some((node, false)); 146 return Some((node, false));
147 } 147 }
148 } 148 }
@@ -170,9 +170,9 @@ mod tests {
170 use super::*; 170 use super::*;
171 171
172 #[test] 172 #[test]
173 fn test_introduce_var_simple() { 173 fn test_extract_var_simple() {
174 check_assist( 174 check_assist(
175 introduce_variable, 175 extract_variable,
176 r#" 176 r#"
177fn foo() { 177fn foo() {
178 foo(<|>1 + 1<|>); 178 foo(<|>1 + 1<|>);
@@ -186,16 +186,16 @@ fn foo() {
186 } 186 }
187 187
188 #[test] 188 #[test]
189 fn introduce_var_in_comment_is_not_applicable() { 189 fn extract_var_in_comment_is_not_applicable() {
190 mark::check!(introduce_var_in_comment_is_not_applicable); 190 mark::check!(extract_var_in_comment_is_not_applicable);
191 check_assist_not_applicable(introduce_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }"); 191 check_assist_not_applicable(extract_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }");
192 } 192 }
193 193
194 #[test] 194 #[test]
195 fn test_introduce_var_expr_stmt() { 195 fn test_extract_var_expr_stmt() {
196 mark::check!(test_introduce_var_expr_stmt); 196 mark::check!(test_extract_var_expr_stmt);
197 check_assist( 197 check_assist(
198 introduce_variable, 198 extract_variable,
199 r#" 199 r#"
200fn foo() { 200fn foo() {
201 <|>1 + 1<|>; 201 <|>1 + 1<|>;
@@ -206,7 +206,7 @@ fn foo() {
206}"#, 206}"#,
207 ); 207 );
208 check_assist( 208 check_assist(
209 introduce_variable, 209 extract_variable,
210 " 210 "
211fn foo() { 211fn foo() {
212 <|>{ let x = 0; x }<|> 212 <|>{ let x = 0; x }<|>
@@ -221,9 +221,9 @@ fn foo() {
221 } 221 }
222 222
223 #[test] 223 #[test]
224 fn test_introduce_var_part_of_expr_stmt() { 224 fn test_extract_var_part_of_expr_stmt() {
225 check_assist( 225 check_assist(
226 introduce_variable, 226 extract_variable,
227 " 227 "
228fn foo() { 228fn foo() {
229 <|>1<|> + 1; 229 <|>1<|> + 1;
@@ -237,10 +237,10 @@ fn foo() {
237 } 237 }
238 238
239 #[test] 239 #[test]
240 fn test_introduce_var_last_expr() { 240 fn test_extract_var_last_expr() {
241 mark::check!(test_introduce_var_last_expr); 241 mark::check!(test_extract_var_last_expr);
242 check_assist( 242 check_assist(
243 introduce_variable, 243 extract_variable,
244 r#" 244 r#"
245fn foo() { 245fn foo() {
246 bar(<|>1 + 1<|>) 246 bar(<|>1 + 1<|>)
@@ -254,7 +254,7 @@ fn foo() {
254"#, 254"#,
255 ); 255 );
256 check_assist( 256 check_assist(
257 introduce_variable, 257 extract_variable,
258 r#" 258 r#"
259fn foo() { 259fn foo() {
260 <|>bar(1 + 1)<|> 260 <|>bar(1 + 1)<|>
@@ -270,9 +270,9 @@ fn foo() {
270 } 270 }
271 271
272 #[test] 272 #[test]
273 fn test_introduce_var_in_match_arm_no_block() { 273 fn test_extract_var_in_match_arm_no_block() {
274 check_assist( 274 check_assist(
275 introduce_variable, 275 extract_variable,
276 " 276 "
277fn main() { 277fn main() {
278 let x = true; 278 let x = true;
@@ -295,9 +295,9 @@ fn main() {
295 } 295 }
296 296
297 #[test] 297 #[test]
298 fn test_introduce_var_in_match_arm_with_block() { 298 fn test_extract_var_in_match_arm_with_block() {
299 check_assist( 299 check_assist(
300 introduce_variable, 300 extract_variable,
301 " 301 "
302fn main() { 302fn main() {
303 let x = true; 303 let x = true;
@@ -327,9 +327,9 @@ fn main() {
327 } 327 }
328 328
329 #[test] 329 #[test]
330 fn test_introduce_var_in_closure_no_block() { 330 fn test_extract_var_in_closure_no_block() {
331 check_assist( 331 check_assist(
332 introduce_variable, 332 extract_variable,
333 " 333 "
334fn main() { 334fn main() {
335 let lambda = |x: u32| <|>x * 2<|>; 335 let lambda = |x: u32| <|>x * 2<|>;
@@ -344,9 +344,9 @@ fn main() {
344 } 344 }
345 345
346 #[test] 346 #[test]
347 fn test_introduce_var_in_closure_with_block() { 347 fn test_extract_var_in_closure_with_block() {
348 check_assist( 348 check_assist(
349 introduce_variable, 349 extract_variable,
350 " 350 "
351fn main() { 351fn main() {
352 let lambda = |x: u32| { <|>x * 2<|> }; 352 let lambda = |x: u32| { <|>x * 2<|> };
@@ -361,9 +361,9 @@ fn main() {
361 } 361 }
362 362
363 #[test] 363 #[test]
364 fn test_introduce_var_path_simple() { 364 fn test_extract_var_path_simple() {
365 check_assist( 365 check_assist(
366 introduce_variable, 366 extract_variable,
367 " 367 "
368fn main() { 368fn main() {
369 let o = <|>Some(true)<|>; 369 let o = <|>Some(true)<|>;
@@ -379,9 +379,9 @@ fn main() {
379 } 379 }
380 380
381 #[test] 381 #[test]
382 fn test_introduce_var_path_method() { 382 fn test_extract_var_path_method() {
383 check_assist( 383 check_assist(
384 introduce_variable, 384 extract_variable,
385 " 385 "
386fn main() { 386fn main() {
387 let v = <|>bar.foo()<|>; 387 let v = <|>bar.foo()<|>;
@@ -397,9 +397,9 @@ fn main() {
397 } 397 }
398 398
399 #[test] 399 #[test]
400 fn test_introduce_var_return() { 400 fn test_extract_var_return() {
401 check_assist( 401 check_assist(
402 introduce_variable, 402 extract_variable,
403 " 403 "
404fn foo() -> u32 { 404fn foo() -> u32 {
405 <|>return 2 + 2<|>; 405 <|>return 2 + 2<|>;
@@ -415,9 +415,9 @@ fn foo() -> u32 {
415 } 415 }
416 416
417 #[test] 417 #[test]
418 fn test_introduce_var_does_not_add_extra_whitespace() { 418 fn test_extract_var_does_not_add_extra_whitespace() {
419 check_assist( 419 check_assist(
420 introduce_variable, 420 extract_variable,
421 " 421 "
422fn foo() -> u32 { 422fn foo() -> u32 {
423 423
@@ -436,7 +436,7 @@ fn foo() -> u32 {
436 ); 436 );
437 437
438 check_assist( 438 check_assist(
439 introduce_variable, 439 extract_variable,
440 " 440 "
441fn foo() -> u32 { 441fn foo() -> u32 {
442 442
@@ -453,7 +453,7 @@ fn foo() -> u32 {
453 ); 453 );
454 454
455 check_assist( 455 check_assist(
456 introduce_variable, 456 extract_variable,
457 " 457 "
458fn foo() -> u32 { 458fn foo() -> u32 {
459 let foo = 1; 459 let foo = 1;
@@ -479,9 +479,9 @@ fn foo() -> u32 {
479 } 479 }
480 480
481 #[test] 481 #[test]
482 fn test_introduce_var_break() { 482 fn test_extract_var_break() {
483 check_assist( 483 check_assist(
484 introduce_variable, 484 extract_variable,
485 " 485 "
486fn main() { 486fn main() {
487 let result = loop { 487 let result = loop {
@@ -501,9 +501,9 @@ fn main() {
501 } 501 }
502 502
503 #[test] 503 #[test]
504 fn test_introduce_var_for_cast() { 504 fn test_extract_var_for_cast() {
505 check_assist( 505 check_assist(
506 introduce_variable, 506 extract_variable,
507 " 507 "
508fn main() { 508fn main() {
509 let v = <|>0f32 as u32<|>; 509 let v = <|>0f32 as u32<|>;
@@ -519,9 +519,9 @@ fn main() {
519 } 519 }
520 520
521 #[test] 521 #[test]
522 fn introduce_var_field_shorthand() { 522 fn extract_var_field_shorthand() {
523 check_assist( 523 check_assist(
524 introduce_variable, 524 extract_variable,
525 r#" 525 r#"
526struct S { 526struct S {
527 foo: i32 527 foo: i32
@@ -545,22 +545,22 @@ fn main() {
545 } 545 }
546 546
547 #[test] 547 #[test]
548 fn test_introduce_var_for_return_not_applicable() { 548 fn test_extract_var_for_return_not_applicable() {
549 check_assist_not_applicable(introduce_variable, "fn foo() { <|>return<|>; } "); 549 check_assist_not_applicable(extract_variable, "fn foo() { <|>return<|>; } ");
550 } 550 }
551 551
552 #[test] 552 #[test]
553 fn test_introduce_var_for_break_not_applicable() { 553 fn test_extract_var_for_break_not_applicable() {
554 check_assist_not_applicable(introduce_variable, "fn main() { loop { <|>break<|>; }; }"); 554 check_assist_not_applicable(extract_variable, "fn main() { loop { <|>break<|>; }; }");
555 } 555 }
556 556
557 // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic 557 // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
558 #[test] 558 #[test]
559 fn introduce_var_target() { 559 fn extract_var_target() {
560 check_assist_target(introduce_variable, "fn foo() -> u32 { <|>return 2 + 2<|>; }", "2 + 2"); 560 check_assist_target(extract_variable, "fn foo() -> u32 { <|>return 2 + 2<|>; }", "2 + 2");
561 561
562 check_assist_target( 562 check_assist_target(
563 introduce_variable, 563 extract_variable,
564 " 564 "
565fn main() { 565fn main() {
566 let x = true; 566 let x = true;
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 185428bd5..1745f44a5 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -116,6 +116,7 @@ mod handlers {
116 mod change_visibility; 116 mod change_visibility;
117 mod early_return; 117 mod early_return;
118 mod extract_struct_from_enum_variant; 118 mod extract_struct_from_enum_variant;
119 mod extract_variable;
119 mod fill_match_arms; 120 mod fill_match_arms;
120 mod fix_visibility; 121 mod fix_visibility;
121 mod flip_binexpr; 122 mod flip_binexpr;
@@ -123,7 +124,6 @@ mod handlers {
123 mod flip_trait_bound; 124 mod flip_trait_bound;
124 mod inline_local_variable; 125 mod inline_local_variable;
125 mod introduce_named_lifetime; 126 mod introduce_named_lifetime;
126 mod introduce_variable;
127 mod invert_if; 127 mod invert_if;
128 mod merge_imports; 128 mod merge_imports;
129 mod merge_match_arms; 129 mod merge_match_arms;
@@ -157,6 +157,7 @@ mod handlers {
157 change_visibility::change_visibility, 157 change_visibility::change_visibility,
158 early_return::convert_to_guarded_return, 158 early_return::convert_to_guarded_return,
159 extract_struct_from_enum_variant::extract_struct_from_enum_variant, 159 extract_struct_from_enum_variant::extract_struct_from_enum_variant,
160 extract_variable::extract_variable,
160 fill_match_arms::fill_match_arms, 161 fill_match_arms::fill_match_arms,
161 fix_visibility::fix_visibility, 162 fix_visibility::fix_visibility,
162 flip_binexpr::flip_binexpr, 163 flip_binexpr::flip_binexpr,
@@ -164,7 +165,6 @@ mod handlers {
164 flip_trait_bound::flip_trait_bound, 165 flip_trait_bound::flip_trait_bound,
165 inline_local_variable::inline_local_variable, 166 inline_local_variable::inline_local_variable,
166 introduce_named_lifetime::introduce_named_lifetime, 167 introduce_named_lifetime::introduce_named_lifetime,
167 introduce_variable::introduce_variable,
168 invert_if::invert_if, 168 invert_if::invert_if,
169 merge_imports::merge_imports, 169 merge_imports::merge_imports,
170 merge_match_arms::merge_match_arms, 170 merge_match_arms::merge_match_arms,
diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs
index 40a223727..31ea888c5 100644
--- a/crates/ra_assists/src/tests/generated.rs
+++ b/crates/ra_assists/src/tests/generated.rs
@@ -353,6 +353,24 @@ enum A { One(One) }
353} 353}
354 354
355#[test] 355#[test]
356fn doctest_extract_variable() {
357 check_doc_test(
358 "extract_variable",
359 r#####"
360fn main() {
361 <|>(1 + 2)<|> * 4;
362}
363"#####,
364 r#####"
365fn main() {
366 let $0var_name = (1 + 2);
367 var_name * 4;
368}
369"#####,
370 )
371}
372
373#[test]
356fn doctest_fill_match_arms() { 374fn doctest_fill_match_arms() {
357 check_doc_test( 375 check_doc_test(
358 "fill_match_arms", 376 "fill_match_arms",
@@ -492,24 +510,6 @@ impl<'a> Cursor<'a> {
492} 510}
493 511
494#[test] 512#[test]
495fn doctest_introduce_variable() {
496 check_doc_test(
497 "introduce_variable",
498 r#####"
499fn main() {
500 <|>(1 + 2)<|> * 4;
501}
502"#####,
503 r#####"
504fn main() {
505 let $0var_name = (1 + 2);
506 var_name * 4;
507}
508"#####,
509 )
510}
511
512#[test]
513fn doctest_invert_if() { 513fn doctest_invert_if() {
514 check_doc_test( 514 check_doc_test(
515 "invert_if", 515 "invert_if",
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs
index 3ced648e5..a7e2e0982 100644
--- a/crates/ra_hir_def/src/body/lower.rs
+++ b/crates/ra_hir_def/src/body/lower.rs
@@ -5,7 +5,7 @@ use either::Either;
5use hir_expand::{ 5use hir_expand::{
6 hygiene::Hygiene, 6 hygiene::Hygiene,
7 name::{name, AsName, Name}, 7 name::{name, AsName, Name},
8 AstId, HirFileId, MacroDefId, MacroDefKind, 8 HirFileId, MacroDefId, MacroDefKind,
9}; 9};
10use ra_arena::Arena; 10use ra_arena::Arena;
11use ra_syntax::{ 11use ra_syntax::{
@@ -27,7 +27,7 @@ use crate::{
27 LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, 27 LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
28 }, 28 },
29 item_scope::BuiltinShadowMode, 29 item_scope::BuiltinShadowMode,
30 item_tree::{FileItemTreeId, ItemTree, ItemTreeNode}, 30 item_tree::{ItemTree, ItemTreeId, ItemTreeNode},
31 path::{GenericArgs, Path}, 31 path::{GenericArgs, Path},
32 type_ref::{Mutability, Rawness, TypeRef}, 32 type_ref::{Mutability, Rawness, TypeRef},
33 AdtId, ConstLoc, ContainerId, DefWithBodyId, EnumLoc, FunctionLoc, Intern, ModuleDefId, 33 AdtId, ConstLoc, ContainerId, DefWithBodyId, EnumLoc, FunctionLoc, Intern, ModuleDefId,
@@ -37,7 +37,7 @@ use crate::{
37use super::{ExprSource, PatSource}; 37use super::{ExprSource, PatSource};
38use ast::AstChildren; 38use ast::AstChildren;
39use rustc_hash::FxHashMap; 39use rustc_hash::FxHashMap;
40use std::sync::Arc; 40use std::{any::type_name, sync::Arc};
41 41
42pub(crate) struct LowerCtx { 42pub(crate) struct LowerCtx {
43 hygiene: Hygiene, 43 hygiene: Hygiene,
@@ -561,17 +561,30 @@ impl ExprCollector<'_> {
561 } 561 }
562 } 562 }
563 563
564 fn find_inner_item<S: ItemTreeNode>(&self, id: AstId<ast::ModuleItem>) -> FileItemTreeId<S> { 564 fn find_inner_item<N: ItemTreeNode>(&self, ast: &N::Source) -> Option<ItemTreeId<N>> {
565 let id = self.expander.ast_id(ast);
565 let tree = &self.item_trees[&id.file_id]; 566 let tree = &self.item_trees[&id.file_id];
566 567
567 // FIXME: This probably breaks with `use` items, since they produce multiple item tree nodes 568 // FIXME: This probably breaks with `use` items, since they produce multiple item tree nodes
568 569
569 // Root file (non-macro). 570 // Root file (non-macro).
570 tree.all_inner_items() 571 let item_tree_id = tree
572 .all_inner_items()
571 .chain(tree.top_level_items().iter().copied()) 573 .chain(tree.top_level_items().iter().copied())
572 .filter_map(|mod_item| mod_item.downcast::<S>()) 574 .filter_map(|mod_item| mod_item.downcast::<N>())
573 .find(|tree_id| tree[*tree_id].ast_id().upcast() == id.value) 575 .find(|tree_id| tree[*tree_id].ast_id().upcast() == id.value.upcast())
574 .unwrap_or_else(|| panic!("couldn't find inner item for {:?}", id)) 576 .or_else(|| {
577 log::debug!(
578 "couldn't find inner {} item for {:?} (AST: `{}` - {:?})",
579 type_name::<N>(),
580 id,
581 ast.syntax(),
582 ast.syntax(),
583 );
584 None
585 })?;
586
587 Some(ItemTreeId::new(id.file_id, item_tree_id))
575 } 588 }
576 589
577 fn collect_expr_opt(&mut self, expr: Option<ast::Expr>) -> ExprId { 590 fn collect_expr_opt(&mut self, expr: Option<ast::Expr>) -> ExprId {
@@ -611,82 +624,45 @@ impl ExprCollector<'_> {
611 .filter_map(|item| { 624 .filter_map(|item| {
612 let (def, name): (ModuleDefId, Option<ast::Name>) = match item { 625 let (def, name): (ModuleDefId, Option<ast::Name>) = match item {
613 ast::ModuleItem::FnDef(def) => { 626 ast::ModuleItem::FnDef(def) => {
614 let ast_id = self.expander.ast_id(&def); 627 let id = self.find_inner_item(&def)?;
615 let id = self.find_inner_item(ast_id.map(|id| id.upcast()));
616 ( 628 (
617 FunctionLoc { container: container.into(), id: ast_id.with_value(id) } 629 FunctionLoc { container: container.into(), id }.intern(self.db).into(),
618 .intern(self.db)
619 .into(),
620 def.name(), 630 def.name(),
621 ) 631 )
622 } 632 }
623 ast::ModuleItem::TypeAliasDef(def) => { 633 ast::ModuleItem::TypeAliasDef(def) => {
624 let ast_id = self.expander.ast_id(&def); 634 let id = self.find_inner_item(&def)?;
625 let id = self.find_inner_item(ast_id.map(|id| id.upcast()));
626 ( 635 (
627 TypeAliasLoc { container: container.into(), id: ast_id.with_value(id) } 636 TypeAliasLoc { container: container.into(), id }.intern(self.db).into(),
628 .intern(self.db)
629 .into(),
630 def.name(), 637 def.name(),
631 ) 638 )
632 } 639 }
633 ast::ModuleItem::ConstDef(def) => { 640 ast::ModuleItem::ConstDef(def) => {
634 let ast_id = self.expander.ast_id(&def); 641 let id = self.find_inner_item(&def)?;
635 let id = self.find_inner_item(ast_id.map(|id| id.upcast()));
636 ( 642 (
637 ConstLoc { container: container.into(), id: ast_id.with_value(id) } 643 ConstLoc { container: container.into(), id }.intern(self.db).into(),
638 .intern(self.db)
639 .into(),
640 def.name(), 644 def.name(),
641 ) 645 )
642 } 646 }
643 ast::ModuleItem::StaticDef(def) => { 647 ast::ModuleItem::StaticDef(def) => {
644 let ast_id = self.expander.ast_id(&def); 648 let id = self.find_inner_item(&def)?;
645 let id = self.find_inner_item(ast_id.map(|id| id.upcast())); 649 (StaticLoc { container, id }.intern(self.db).into(), def.name())
646 (
647 StaticLoc { container, id: ast_id.with_value(id) }
648 .intern(self.db)
649 .into(),
650 def.name(),
651 )
652 } 650 }
653 ast::ModuleItem::StructDef(def) => { 651 ast::ModuleItem::StructDef(def) => {
654 let ast_id = self.expander.ast_id(&def); 652 let id = self.find_inner_item(&def)?;
655 let id = self.find_inner_item(ast_id.map(|id| id.upcast())); 653 (StructLoc { container, id }.intern(self.db).into(), def.name())
656 (
657 StructLoc { container, id: ast_id.with_value(id) }
658 .intern(self.db)
659 .into(),
660 def.name(),
661 )
662 } 654 }
663 ast::ModuleItem::EnumDef(def) => { 655 ast::ModuleItem::EnumDef(def) => {
664 let ast_id = self.expander.ast_id(&def); 656 let id = self.find_inner_item(&def)?;
665 let id = self.find_inner_item(ast_id.map(|id| id.upcast())); 657 (EnumLoc { container, id }.intern(self.db).into(), def.name())
666 (
667 EnumLoc { container, id: ast_id.with_value(id) }.intern(self.db).into(),
668 def.name(),
669 )
670 } 658 }
671 ast::ModuleItem::UnionDef(def) => { 659 ast::ModuleItem::UnionDef(def) => {
672 let ast_id = self.expander.ast_id(&def); 660 let id = self.find_inner_item(&def)?;
673 let id = self.find_inner_item(ast_id.map(|id| id.upcast())); 661 (UnionLoc { container, id }.intern(self.db).into(), def.name())
674 (
675 UnionLoc { container, id: ast_id.with_value(id) }
676 .intern(self.db)
677 .into(),
678 def.name(),
679 )
680 } 662 }
681 ast::ModuleItem::TraitDef(def) => { 663 ast::ModuleItem::TraitDef(def) => {
682 let ast_id = self.expander.ast_id(&def); 664 let id = self.find_inner_item(&def)?;
683 let id = self.find_inner_item(ast_id.map(|id| id.upcast())); 665 (TraitLoc { container, id }.intern(self.db).into(), def.name())
684 (
685 TraitLoc { container, id: ast_id.with_value(id) }
686 .intern(self.db)
687 .into(),
688 def.name(),
689 )
690 } 666 }
691 ast::ModuleItem::ExternBlock(_) => return None, // FIXME: collect from extern blocks 667 ast::ModuleItem::ExternBlock(_) => return None, // FIXME: collect from extern blocks
692 ast::ModuleItem::ImplDef(_) 668 ast::ModuleItem::ImplDef(_)
diff --git a/crates/ra_hir_def/src/body/scope.rs b/crates/ra_hir_def/src/body/scope.rs
index 81397b063..99e876683 100644
--- a/crates/ra_hir_def/src/body/scope.rs
+++ b/crates/ra_hir_def/src/body/scope.rs
@@ -337,6 +337,19 @@ fn foo() {
337 ); 337 );
338 } 338 }
339 339
340 #[test]
341 fn broken_inner_item() {
342 do_check(
343 r"
344 fn foo() {
345 trait {}
346 <|>
347 }
348 ",
349 &[],
350 );
351 }
352
340 fn do_check_local_name(ra_fixture: &str, expected_offset: u32) { 353 fn do_check_local_name(ra_fixture: &str, expected_offset: u32) {
341 let (db, position) = TestDB::with_position(ra_fixture); 354 let (db, position) = TestDB::with_position(ra_fixture);
342 let file_id = position.file_id; 355 let file_id = position.file_id;
diff --git a/crates/ra_hir_def/src/item_scope.rs b/crates/ra_hir_def/src/item_scope.rs
index c81b966c3..4d446c707 100644
--- a/crates/ra_hir_def/src/item_scope.rs
+++ b/crates/ra_hir_def/src/item_scope.rs
@@ -4,14 +4,27 @@
4use hir_expand::name::Name; 4use hir_expand::name::Name;
5use once_cell::sync::Lazy; 5use once_cell::sync::Lazy;
6use ra_db::CrateId; 6use ra_db::CrateId;
7use rustc_hash::FxHashMap; 7use rustc_hash::{FxHashMap, FxHashSet};
8use test_utils::mark; 8use test_utils::mark;
9 9
10use crate::{ 10use crate::{
11 db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, HasModule, ImplId, 11 db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, HasModule, ImplId,
12 Lookup, MacroDefId, ModuleDefId, TraitId, 12 LocalModuleId, Lookup, MacroDefId, ModuleDefId, TraitId,
13}; 13};
14 14
15#[derive(Copy, Clone)]
16pub(crate) enum ImportType {
17 Glob,
18 Named,
19}
20
21#[derive(Debug, Default)]
22pub struct PerNsGlobImports {
23 types: FxHashSet<(LocalModuleId, Name)>,
24 values: FxHashSet<(LocalModuleId, Name)>,
25 macros: FxHashSet<(LocalModuleId, Name)>,
26}
27
15#[derive(Debug, Default, PartialEq, Eq)] 28#[derive(Debug, Default, PartialEq, Eq)]
16pub struct ItemScope { 29pub struct ItemScope {
17 visible: FxHashMap<Name, PerNs>, 30 visible: FxHashMap<Name, PerNs>,
@@ -127,16 +140,62 @@ impl ItemScope {
127 let mut changed = false; 140 let mut changed = false;
128 let existing = self.visible.entry(name).or_default(); 141 let existing = self.visible.entry(name).or_default();
129 142
143 if existing.types.is_none() && def.types.is_some() {
144 existing.types = def.types;
145 changed = true;
146 }
147
148 if existing.values.is_none() && def.values.is_some() {
149 existing.values = def.values;
150 changed = true;
151 }
152
153 if existing.macros.is_none() && def.macros.is_some() {
154 existing.macros = def.macros;
155 changed = true;
156 }
157
158 changed
159 }
160
161 pub(crate) fn push_res_with_import(
162 &mut self,
163 glob_imports: &mut PerNsGlobImports,
164 lookup: (LocalModuleId, Name),
165 def: PerNs,
166 def_import_type: ImportType,
167 ) -> bool {
168 let mut changed = false;
169 let existing = self.visible.entry(lookup.1.clone()).or_default();
170
130 macro_rules! check_changed { 171 macro_rules! check_changed {
131 ($changed:ident, $existing:expr, $def:expr) => { 172 (
132 match ($existing, $def) { 173 $changed:ident,
174 ( $existing:ident / $def:ident ) . $field:ident,
175 $glob_imports:ident [ $lookup:ident ],
176 $def_import_type:ident
177 ) => {
178 match ($existing.$field, $def.$field) {
133 (None, Some(_)) => { 179 (None, Some(_)) => {
134 $existing = $def; 180 match $def_import_type {
181 ImportType::Glob => {
182 $glob_imports.$field.insert($lookup.clone());
183 }
184 ImportType::Named => {
185 $glob_imports.$field.remove(&$lookup);
186 }
187 }
188
189 $existing.$field = $def.$field;
135 $changed = true; 190 $changed = true;
136 } 191 }
137 (Some(e), Some(d)) if e.0 != d.0 => { 192 (Some(_), Some(_))
193 if $glob_imports.$field.contains(&$lookup)
194 && matches!($def_import_type, ImportType::Named) =>
195 {
138 mark::hit!(import_shadowed); 196 mark::hit!(import_shadowed);
139 $existing = $def; 197 $glob_imports.$field.remove(&$lookup);
198 $existing.$field = $def.$field;
140 $changed = true; 199 $changed = true;
141 } 200 }
142 _ => {} 201 _ => {}
@@ -144,9 +203,9 @@ impl ItemScope {
144 }; 203 };
145 } 204 }
146 205
147 check_changed!(changed, existing.types, def.types); 206 check_changed!(changed, (existing / def).types, glob_imports[lookup], def_import_type);
148 check_changed!(changed, existing.values, def.values); 207 check_changed!(changed, (existing / def).values, glob_imports[lookup], def_import_type);
149 check_changed!(changed, existing.macros, def.macros); 208 check_changed!(changed, (existing / def).macros, glob_imports[lookup], def_import_type);
150 209
151 changed 210 changed
152 } 211 }
diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs
index 2ced4f66b..a35ac1024 100644
--- a/crates/ra_hir_def/src/nameres/collector.rs
+++ b/crates/ra_hir_def/src/nameres/collector.rs
@@ -20,6 +20,7 @@ use test_utils::mark;
20use crate::{ 20use crate::{
21 attr::Attrs, 21 attr::Attrs,
22 db::DefDatabase, 22 db::DefDatabase,
23 item_scope::{ImportType, PerNsGlobImports},
23 item_tree::{ 24 item_tree::{
24 self, FileItemTreeId, ItemTree, ItemTreeId, MacroCall, Mod, ModItem, ModKind, StructDefKind, 25 self, FileItemTreeId, ItemTree, ItemTreeId, MacroCall, Mod, ModItem, ModKind, StructDefKind,
25 }, 26 },
@@ -80,6 +81,7 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: CrateDefMap) -> Cr
80 mod_dirs: FxHashMap::default(), 81 mod_dirs: FxHashMap::default(),
81 cfg_options, 82 cfg_options,
82 proc_macros, 83 proc_macros,
84 from_glob_import: Default::default(),
83 }; 85 };
84 collector.collect(); 86 collector.collect();
85 collector.finish() 87 collector.finish()
@@ -186,6 +188,7 @@ struct DefCollector<'a> {
186 mod_dirs: FxHashMap<LocalModuleId, ModDir>, 188 mod_dirs: FxHashMap<LocalModuleId, ModDir>,
187 cfg_options: &'a CfgOptions, 189 cfg_options: &'a CfgOptions,
188 proc_macros: Vec<(Name, ProcMacroExpander)>, 190 proc_macros: Vec<(Name, ProcMacroExpander)>,
191 from_glob_import: PerNsGlobImports,
189} 192}
190 193
191impl DefCollector<'_> { 194impl DefCollector<'_> {
@@ -305,6 +308,7 @@ impl DefCollector<'_> {
305 self.def_map.root, 308 self.def_map.root,
306 &[(name, PerNs::macros(macro_, Visibility::Public))], 309 &[(name, PerNs::macros(macro_, Visibility::Public))],
307 Visibility::Public, 310 Visibility::Public,
311 ImportType::Named,
308 ); 312 );
309 } 313 }
310 } 314 }
@@ -330,6 +334,7 @@ impl DefCollector<'_> {
330 self.def_map.root, 334 self.def_map.root,
331 &[(name, PerNs::macros(macro_, Visibility::Public))], 335 &[(name, PerNs::macros(macro_, Visibility::Public))],
332 Visibility::Public, 336 Visibility::Public,
337 ImportType::Named,
333 ); 338 );
334 } 339 }
335 340
@@ -383,7 +388,6 @@ impl DefCollector<'_> {
383 let imports = std::mem::replace(&mut self.unresolved_imports, Vec::new()); 388 let imports = std::mem::replace(&mut self.unresolved_imports, Vec::new());
384 for mut directive in imports { 389 for mut directive in imports {
385 directive.status = self.resolve_import(directive.module_id, &directive.import); 390 directive.status = self.resolve_import(directive.module_id, &directive.import);
386
387 match directive.status { 391 match directive.status {
388 PartialResolvedImport::Indeterminate(_) => { 392 PartialResolvedImport::Indeterminate(_) => {
389 self.record_resolved_import(&directive); 393 self.record_resolved_import(&directive);
@@ -477,7 +481,7 @@ impl DefCollector<'_> {
477 .filter(|(_, res)| !res.is_none()) 481 .filter(|(_, res)| !res.is_none())
478 .collect::<Vec<_>>(); 482 .collect::<Vec<_>>();
479 483
480 self.update(module_id, &items, vis); 484 self.update(module_id, &items, vis, ImportType::Glob);
481 } else { 485 } else {
482 // glob import from same crate => we do an initial 486 // glob import from same crate => we do an initial
483 // import, and then need to propagate any further 487 // import, and then need to propagate any further
@@ -499,7 +503,7 @@ impl DefCollector<'_> {
499 .filter(|(_, res)| !res.is_none()) 503 .filter(|(_, res)| !res.is_none())
500 .collect::<Vec<_>>(); 504 .collect::<Vec<_>>();
501 505
502 self.update(module_id, &items, vis); 506 self.update(module_id, &items, vis, ImportType::Glob);
503 // record the glob import in case we add further items 507 // record the glob import in case we add further items
504 let glob = self.glob_imports.entry(m.local_id).or_default(); 508 let glob = self.glob_imports.entry(m.local_id).or_default();
505 if !glob.iter().any(|(mid, _)| *mid == module_id) { 509 if !glob.iter().any(|(mid, _)| *mid == module_id) {
@@ -529,7 +533,7 @@ impl DefCollector<'_> {
529 (name, res) 533 (name, res)
530 }) 534 })
531 .collect::<Vec<_>>(); 535 .collect::<Vec<_>>();
532 self.update(module_id, &resolutions, vis); 536 self.update(module_id, &resolutions, vis, ImportType::Glob);
533 } 537 }
534 Some(d) => { 538 Some(d) => {
535 log::debug!("glob import {:?} from non-module/enum {:?}", import, d); 539 log::debug!("glob import {:?} from non-module/enum {:?}", import, d);
@@ -555,15 +559,21 @@ impl DefCollector<'_> {
555 } 559 }
556 } 560 }
557 561
558 self.update(module_id, &[(name, def)], vis); 562 self.update(module_id, &[(name, def)], vis, ImportType::Named);
559 } 563 }
560 None => mark::hit!(bogus_paths), 564 None => mark::hit!(bogus_paths),
561 } 565 }
562 } 566 }
563 } 567 }
564 568
565 fn update(&mut self, module_id: LocalModuleId, resolutions: &[(Name, PerNs)], vis: Visibility) { 569 fn update(
566 self.update_recursive(module_id, resolutions, vis, 0) 570 &mut self,
571 module_id: LocalModuleId,
572 resolutions: &[(Name, PerNs)],
573 vis: Visibility,
574 import_type: ImportType,
575 ) {
576 self.update_recursive(module_id, resolutions, vis, import_type, 0)
567 } 577 }
568 578
569 fn update_recursive( 579 fn update_recursive(
@@ -573,6 +583,7 @@ impl DefCollector<'_> {
573 // All resolutions are imported with this visibility; the visibilies in 583 // All resolutions are imported with this visibility; the visibilies in
574 // the `PerNs` values are ignored and overwritten 584 // the `PerNs` values are ignored and overwritten
575 vis: Visibility, 585 vis: Visibility,
586 import_type: ImportType,
576 depth: usize, 587 depth: usize,
577 ) { 588 ) {
578 if depth > 100 { 589 if depth > 100 {
@@ -582,7 +593,12 @@ impl DefCollector<'_> {
582 let scope = &mut self.def_map.modules[module_id].scope; 593 let scope = &mut self.def_map.modules[module_id].scope;
583 let mut changed = false; 594 let mut changed = false;
584 for (name, res) in resolutions { 595 for (name, res) in resolutions {
585 changed |= scope.push_res(name.clone(), res.with_visibility(vis)); 596 changed |= scope.push_res_with_import(
597 &mut self.from_glob_import,
598 (module_id, name.clone()),
599 res.with_visibility(vis),
600 import_type,
601 );
586 } 602 }
587 603
588 if !changed { 604 if !changed {
@@ -601,7 +617,13 @@ impl DefCollector<'_> {
601 if !vis.is_visible_from_def_map(&self.def_map, glob_importing_module) { 617 if !vis.is_visible_from_def_map(&self.def_map, glob_importing_module) {
602 continue; 618 continue;
603 } 619 }
604 self.update_recursive(glob_importing_module, resolutions, glob_import_vis, depth + 1); 620 self.update_recursive(
621 glob_importing_module,
622 resolutions,
623 glob_import_vis,
624 ImportType::Glob,
625 depth + 1,
626 );
605 } 627 }
606 } 628 }
607 629
@@ -923,6 +945,7 @@ impl ModCollector<'_, '_> {
923 self.module_id, 945 self.module_id,
924 &[(name.clone(), PerNs::from_def(id, vis, has_constructor))], 946 &[(name.clone(), PerNs::from_def(id, vis, has_constructor))],
925 vis, 947 vis,
948 ImportType::Named,
926 ) 949 )
927 } 950 }
928 } 951 }
@@ -1025,7 +1048,12 @@ impl ModCollector<'_, '_> {
1025 let module = ModuleId { krate: self.def_collector.def_map.krate, local_id: res }; 1048 let module = ModuleId { krate: self.def_collector.def_map.krate, local_id: res };
1026 let def: ModuleDefId = module.into(); 1049 let def: ModuleDefId = module.into();
1027 self.def_collector.def_map.modules[self.module_id].scope.define_def(def); 1050 self.def_collector.def_map.modules[self.module_id].scope.define_def(def);
1028 self.def_collector.update(self.module_id, &[(name, PerNs::from_def(def, vis, false))], vis); 1051 self.def_collector.update(
1052 self.module_id,
1053 &[(name, PerNs::from_def(def, vis, false))],
1054 vis,
1055 ImportType::Named,
1056 );
1029 res 1057 res
1030 } 1058 }
1031 1059
@@ -1154,6 +1182,7 @@ mod tests {
1154 mod_dirs: FxHashMap::default(), 1182 mod_dirs: FxHashMap::default(),
1155 cfg_options: &CfgOptions::default(), 1183 cfg_options: &CfgOptions::default(),
1156 proc_macros: Default::default(), 1184 proc_macros: Default::default(),
1185 from_glob_import: Default::default(),
1157 }; 1186 };
1158 collector.collect(); 1187 collector.collect();
1159 collector.def_map 1188 collector.def_map
diff --git a/crates/ra_hir_def/src/nameres/tests/globs.rs b/crates/ra_hir_def/src/nameres/tests/globs.rs
index 2f440975a..7f3d7509c 100644
--- a/crates/ra_hir_def/src/nameres/tests/globs.rs
+++ b/crates/ra_hir_def/src/nameres/tests/globs.rs
@@ -276,3 +276,93 @@ fn glob_shadowed_def() {
276 "### 276 "###
277 ); 277 );
278} 278}
279
280#[test]
281fn glob_shadowed_def_reversed() {
282 let map = def_map(
283 r###"
284 //- /lib.rs
285 mod foo;
286 mod bar;
287
288 use bar::baz;
289 use foo::*;
290
291 use baz::Bar;
292
293 //- /foo.rs
294 pub mod baz {
295 pub struct Foo;
296 }
297
298 //- /bar.rs
299 pub mod baz {
300 pub struct Bar;
301 }
302 "###,
303 );
304 assert_snapshot!(map, @r###"
305 â‹®crate
306 â‹®Bar: t v
307 â‹®bar: t
308 â‹®baz: t
309 â‹®foo: t
310 â‹®
311 â‹®crate::bar
312 â‹®baz: t
313 â‹®
314 â‹®crate::bar::baz
315 â‹®Bar: t v
316 â‹®
317 â‹®crate::foo
318 â‹®baz: t
319 â‹®
320 â‹®crate::foo::baz
321 â‹®Foo: t v
322 "###
323 );
324}
325
326#[test]
327fn glob_shadowed_def_dependencies() {
328 let map = def_map(
329 r###"
330 //- /lib.rs
331 mod a { pub mod foo { pub struct X; } }
332 mod b { pub use super::a::foo; }
333 mod c { pub mod foo { pub struct Y; } }
334 mod d {
335 use super::c::foo;
336 use super::b::*;
337 use foo::Y;
338 }
339 "###,
340 );
341 assert_snapshot!(map, @r###"
342 â‹®crate
343 â‹®a: t
344 â‹®b: t
345 â‹®c: t
346 â‹®d: t
347 â‹®
348 â‹®crate::d
349 â‹®Y: t v
350 â‹®foo: t
351 â‹®
352 â‹®crate::c
353 â‹®foo: t
354 â‹®
355 â‹®crate::c::foo
356 â‹®Y: t v
357 â‹®
358 â‹®crate::b
359 â‹®foo: t
360 â‹®
361 â‹®crate::a
362 â‹®foo: t
363 â‹®
364 â‹®crate::a::foo
365 â‹®X: t v
366 "###
367 );
368}
diff --git a/crates/ra_hir_def/src/nameres/tests/mod_resolution.rs b/crates/ra_hir_def/src/nameres/tests/mod_resolution.rs
index e9a5e4cba..753684201 100644
--- a/crates/ra_hir_def/src/nameres/tests/mod_resolution.rs
+++ b/crates/ra_hir_def/src/nameres/tests/mod_resolution.rs
@@ -335,6 +335,22 @@ fn module_resolution_relative_path_2() {
335} 335}
336 336
337#[test] 337#[test]
338fn module_resolution_relative_path_outside_root() {
339 let map = def_map(
340 r###"
341 //- /main.rs
342
343 #[path="../../../../../outside.rs"]
344 mod foo;
345 "###,
346 );
347
348 assert_snapshot!(map, @r###"
349 â‹®crate
350 "###);
351}
352
353#[test]
338fn module_resolution_explicit_path_mod_rs_2() { 354fn module_resolution_explicit_path_mod_rs_2() {
339 let map = def_map( 355 let map = def_map(
340 r###" 356 r###"
diff --git a/crates/ra_hir_ty/src/method_resolution.rs b/crates/ra_hir_ty/src/method_resolution.rs
index 8b59a8bd6..c19519cf1 100644
--- a/crates/ra_hir_ty/src/method_resolution.rs
+++ b/crates/ra_hir_ty/src/method_resolution.rs
@@ -281,6 +281,7 @@ pub fn iterate_method_candidates<T>(
281 name, 281 name,
282 mode, 282 mode,
283 &mut |ty, item| { 283 &mut |ty, item| {
284 assert!(slot.is_none());
284 slot = callback(ty, item); 285 slot = callback(ty, item);
285 slot.is_some() 286 slot.is_some()
286 }, 287 },
@@ -288,7 +289,7 @@ pub fn iterate_method_candidates<T>(
288 slot 289 slot
289} 290}
290 291
291pub fn iterate_method_candidates_impl( 292fn iterate_method_candidates_impl(
292 ty: &Canonical<Ty>, 293 ty: &Canonical<Ty>,
293 db: &dyn HirDatabase, 294 db: &dyn HirDatabase,
294 env: Arc<TraitEnvironment>, 295 env: Arc<TraitEnvironment>,
diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs
index d7ef9add6..7d8197f8b 100644
--- a/crates/ra_hir_ty/src/tests/simple.rs
+++ b/crates/ra_hir_ty/src/tests/simple.rs
@@ -1739,6 +1739,52 @@ fn main() {
1739 assert_eq!(t, "u32"); 1739 assert_eq!(t, "u32");
1740} 1740}
1741 1741
1742// This test is actually testing the shadowing behavior within ra_hir_def. It
1743// lives here because the testing infrastructure in ra_hir_def isn't currently
1744// capable of asserting the necessary conditions.
1745#[test]
1746fn should_be_shadowing_imports() {
1747 let t = type_at(
1748 r#"
1749mod a {
1750 pub fn foo() -> i8 {0}
1751 pub struct foo { a: i8 }
1752}
1753mod b { pub fn foo () -> u8 {0} }
1754mod c { pub struct foo { a: u8 } }
1755mod d {
1756 pub use super::a::*;
1757 pub use super::c::foo;
1758 pub use super::b::foo;
1759}
1760
1761fn main() {
1762 d::foo()<|>;
1763}"#,
1764 );
1765 assert_eq!(t, "u8");
1766
1767 let t = type_at(
1768 r#"
1769mod a {
1770 pub fn foo() -> i8 {0}
1771 pub struct foo { a: i8 }
1772}
1773mod b { pub fn foo () -> u8 {0} }
1774mod c { pub struct foo { a: u8 } }
1775mod d {
1776 pub use super::a::*;
1777 pub use super::c::foo;
1778 pub use super::b::foo;
1779}
1780
1781fn main() {
1782 d::foo{a:0<|>};
1783}"#,
1784 );
1785 assert_eq!(t, "u8");
1786}
1787
1742#[test] 1788#[test]
1743fn closure_return() { 1789fn closure_return() {
1744 assert_snapshot!( 1790 assert_snapshot!(
diff --git a/crates/ra_parser/src/parser.rs b/crates/ra_parser/src/parser.rs
index 4f59b0a23..d797f2cc9 100644
--- a/crates/ra_parser/src/parser.rs
+++ b/crates/ra_parser/src/parser.rs
@@ -127,17 +127,24 @@ impl<'t> Parser<'t> {
127 127
128 fn at_composite2(&self, n: usize, k1: SyntaxKind, k2: SyntaxKind) -> bool { 128 fn at_composite2(&self, n: usize, k1: SyntaxKind, k2: SyntaxKind) -> bool {
129 let t1 = self.token_source.lookahead_nth(n); 129 let t1 = self.token_source.lookahead_nth(n);
130 if t1.kind != k1 || !t1.is_jointed_to_next {
131 return false;
132 }
130 let t2 = self.token_source.lookahead_nth(n + 1); 133 let t2 = self.token_source.lookahead_nth(n + 1);
131 t1.kind == k1 && t1.is_jointed_to_next && t2.kind == k2 134 t2.kind == k2
132 } 135 }
133 136
134 fn at_composite3(&self, n: usize, k1: SyntaxKind, k2: SyntaxKind, k3: SyntaxKind) -> bool { 137 fn at_composite3(&self, n: usize, k1: SyntaxKind, k2: SyntaxKind, k3: SyntaxKind) -> bool {
135 let t1 = self.token_source.lookahead_nth(n); 138 let t1 = self.token_source.lookahead_nth(n);
139 if t1.kind != k1 || !t1.is_jointed_to_next {
140 return false;
141 }
136 let t2 = self.token_source.lookahead_nth(n + 1); 142 let t2 = self.token_source.lookahead_nth(n + 1);
143 if t2.kind != k2 || !t2.is_jointed_to_next {
144 return false;
145 }
137 let t3 = self.token_source.lookahead_nth(n + 2); 146 let t3 = self.token_source.lookahead_nth(n + 2);
138 (t1.kind == k1 && t1.is_jointed_to_next) 147 t3.kind == k3
139 && (t2.kind == k2 && t2.is_jointed_to_next)
140 && t3.kind == k3
141 } 148 }
142 149
143 /// Checks if the current token is in `kinds`. 150 /// Checks if the current token is in `kinds`.
diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs
index da26ee669..8f149e3db 100644
--- a/crates/ra_ssr/src/lib.rs
+++ b/crates/ra_ssr/src/lib.rs
@@ -12,7 +12,7 @@ mod tests;
12use crate::matching::Match; 12use crate::matching::Match;
13use hir::Semantics; 13use hir::Semantics;
14use ra_db::{FileId, FileRange}; 14use ra_db::{FileId, FileRange};
15use ra_syntax::{AstNode, SmolStr, SyntaxNode}; 15use ra_syntax::{ast, AstNode, SmolStr, SyntaxNode};
16use ra_text_edit::TextEdit; 16use ra_text_edit::TextEdit;
17use rustc_hash::FxHashMap; 17use rustc_hash::FxHashMap;
18 18
@@ -107,6 +107,22 @@ impl<'db> MatchFinder<'db> {
107 return; 107 return;
108 } 108 }
109 } 109 }
110 // If we've got a macro call, we already tried matching it pre-expansion, which is the only
111 // way to match the whole macro, now try expanding it and matching the expansion.
112 if let Some(macro_call) = ast::MacroCall::cast(code.clone()) {
113 if let Some(expanded) = self.sema.expand(&macro_call) {
114 if let Some(tt) = macro_call.token_tree() {
115 // When matching within a macro expansion, we only want to allow matches of
116 // nodes that originated entirely from within the token tree of the macro call.
117 // i.e. we don't want to match something that came from the macro itself.
118 self.find_matches(
119 &expanded,
120 &Some(self.sema.original_range(tt.syntax())),
121 matches_out,
122 );
123 }
124 }
125 }
110 for child in code.children() { 126 for child in code.children() {
111 self.find_matches(&child, restrict_range, matches_out); 127 self.find_matches(&child, restrict_range, matches_out);
112 } 128 }
diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs
index bdaba9f1b..85420ed3c 100644
--- a/crates/ra_ssr/src/matching.rs
+++ b/crates/ra_ssr/src/matching.rs
@@ -343,7 +343,9 @@ impl<'db, 'sema> MatchState<'db, 'sema> {
343 } 343 }
344 344
345 /// Outside of token trees, a placeholder can only match a single AST node, whereas in a token 345 /// Outside of token trees, a placeholder can only match a single AST node, whereas in a token
346 /// tree it can match a sequence of tokens. 346 /// tree it can match a sequence of tokens. Note, that this code will only be used when the
347 /// pattern matches the macro invocation. For matches within the macro call, we'll already have
348 /// expanded the macro.
347 fn attempt_match_token_tree( 349 fn attempt_match_token_tree(
348 &mut self, 350 &mut self,
349 match_inputs: &MatchInputs, 351 match_inputs: &MatchInputs,
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs
index 3ee1e74e9..7a3141be8 100644
--- a/crates/ra_ssr/src/tests.rs
+++ b/crates/ra_ssr/src/tests.rs
@@ -209,6 +209,11 @@ fn assert_ssr_transform(rule: &str, input: &str, result: &str) {
209 assert_ssr_transforms(&[rule], input, result); 209 assert_ssr_transforms(&[rule], input, result);
210} 210}
211 211
212fn normalize_code(code: &str) -> String {
213 let (db, file_id) = single_file(code);
214 db.file_text(file_id).to_string()
215}
216
212fn assert_ssr_transforms(rules: &[&str], input: &str, result: &str) { 217fn assert_ssr_transforms(rules: &[&str], input: &str, result: &str) {
213 let (db, file_id) = single_file(input); 218 let (db, file_id) = single_file(input);
214 let mut match_finder = MatchFinder::new(&db); 219 let mut match_finder = MatchFinder::new(&db);
@@ -217,8 +222,13 @@ fn assert_ssr_transforms(rules: &[&str], input: &str, result: &str) {
217 match_finder.add_rule(rule); 222 match_finder.add_rule(rule);
218 } 223 }
219 if let Some(edits) = match_finder.edits_for_file(file_id) { 224 if let Some(edits) = match_finder.edits_for_file(file_id) {
220 let mut after = input.to_string(); 225 // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters
226 // stuff.
227 let mut after = db.file_text(file_id).to_string();
221 edits.apply(&mut after); 228 edits.apply(&mut after);
229 // Likewise, we need to make sure that whatever transformations fixture parsing applies,
230 // also get appplied to our expected result.
231 let result = normalize_code(result);
222 assert_eq!(after, result); 232 assert_eq!(after, result);
223 } else { 233 } else {
224 panic!("No edits were made"); 234 panic!("No edits were made");
@@ -355,6 +365,18 @@ fn match_nested_method_calls() {
355 ); 365 );
356} 366}
357 367
368// Make sure that our node matching semantics don't differ within macro calls.
369#[test]
370fn match_nested_method_calls_with_macro_call() {
371 assert_matches(
372 "$a.z().z().z()",
373 r#"
374 macro_rules! m1 { ($a:expr) => {$a}; }
375 fn f() {m1!(h().i().j().z().z().z().d().e())}"#,
376 &["h().i().j().z().z().z()"],
377 );
378}
379
358#[test] 380#[test]
359fn match_complex_expr() { 381fn match_complex_expr() {
360 let code = "fn f() -> i32 {foo(bar(40, 2), 42)}"; 382 let code = "fn f() -> i32 {foo(bar(40, 2), 42)}";
@@ -547,3 +569,40 @@ fn multiple_rules() {
547 "fn f() -> i32 {add_one(add(3, 2))}", 569 "fn f() -> i32 {add_one(add(3, 2))}",
548 ) 570 )
549} 571}
572
573#[test]
574fn match_within_macro_invocation() {
575 let code = r#"
576 macro_rules! foo {
577 ($a:stmt; $b:expr) => {
578 $b
579 };
580 }
581 struct A {}
582 impl A {
583 fn bar() {}
584 }
585 fn f1() {
586 let aaa = A {};
587 foo!(macro_ignores_this(); aaa.bar());
588 }
589 "#;
590 assert_matches("$a.bar()", code, &["aaa.bar()"]);
591}
592
593#[test]
594fn replace_within_macro_expansion() {
595 assert_ssr_transform(
596 "$a.foo() ==>> bar($a)",
597 r#"
598 macro_rules! macro1 {
599 ($a:expr) => {$a}
600 }
601 fn f() {macro1!(5.x().foo().o2())}"#,
602 r#"
603 macro_rules! macro1 {
604 ($a:expr) => {$a}
605 }
606 fn f() {macro1!(bar(5.x()).o2())}"#,
607 )
608}
diff --git a/crates/ra_toolchain/src/lib.rs b/crates/ra_toolchain/src/lib.rs
index 3d2865e09..9916e52c4 100644
--- a/crates/ra_toolchain/src/lib.rs
+++ b/crates/ra_toolchain/src/lib.rs
@@ -15,6 +15,10 @@ pub fn rustup() -> PathBuf {
15 get_path_for_executable("rustup") 15 get_path_for_executable("rustup")
16} 16}
17 17
18pub fn rustfmt() -> PathBuf {
19 get_path_for_executable("rustfmt")
20}
21
18/// Return a `PathBuf` to use for the given executable. 22/// Return a `PathBuf` to use for the given executable.
19/// 23///
20/// E.g., `get_path_for_executable("cargo")` may return just `cargo` if that 24/// E.g., `get_path_for_executable("cargo")` may return just `cargo` if that
@@ -42,22 +46,23 @@ fn get_path_for_executable(executable_name: &'static str) -> PathBuf {
42 path.push(".cargo"); 46 path.push(".cargo");
43 path.push("bin"); 47 path.push("bin");
44 path.push(executable_name); 48 path.push(executable_name);
45 if path.is_file() { 49 if let Some(path) = probe(path) {
46 return path; 50 return path;
47 } 51 }
48 } 52 }
53
49 executable_name.into() 54 executable_name.into()
50} 55}
51 56
52fn lookup_in_path(exec: &str) -> bool { 57fn lookup_in_path(exec: &str) -> bool {
53 let paths = env::var_os("PATH").unwrap_or_default(); 58 let paths = env::var_os("PATH").unwrap_or_default();
54 let mut candidates = env::split_paths(&paths).flat_map(|path| { 59 env::split_paths(&paths).map(|path| path.join(exec)).find_map(probe).is_some()
55 let candidate = path.join(&exec); 60}
56 let with_exe = match env::consts::EXE_EXTENSION { 61
57 "" => None, 62fn probe(path: PathBuf) -> Option<PathBuf> {
58 it => Some(candidate.with_extension(it)), 63 let with_extension = match env::consts::EXE_EXTENSION {
59 }; 64 "" => None,
60 iter::once(candidate).chain(with_exe) 65 it => Some(path.with_extension(it)),
61 }); 66 };
62 candidates.any(|it| it.is_file()) 67 iter::once(path).chain(with_extension).find(|it| it.is_file())
63} 68}
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 08c67ddd0..122a1605f 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -41,6 +41,7 @@ ra_text_edit = { path = "../ra_text_edit" }
41vfs = { path = "../vfs" } 41vfs = { path = "../vfs" }
42vfs-notify = { path = "../vfs-notify" } 42vfs-notify = { path = "../vfs-notify" }
43ra_cfg = { path = "../ra_cfg"} 43ra_cfg = { path = "../ra_cfg"}
44ra_toolchain = { path = "../ra_toolchain" }
44 45
45# This should only be used in CLI 46# This should only be used in CLI
46ra_db = { path = "../ra_db" } 47ra_db = { path = "../ra_db" }
diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs
index f3cdb842b..1cf50b677 100644
--- a/crates/rust-analyzer/src/diagnostics.rs
+++ b/crates/rust-analyzer/src/diagnostics.rs
@@ -3,7 +3,6 @@ pub(crate) mod to_proto;
3 3
4use std::{collections::HashMap, mem, sync::Arc}; 4use std::{collections::HashMap, mem, sync::Arc};
5 5
6use lsp_types::{Diagnostic, Range};
7use ra_ide::FileId; 6use ra_ide::FileId;
8use rustc_hash::FxHashSet; 7use rustc_hash::FxHashSet;
9 8
@@ -19,15 +18,15 @@ pub struct DiagnosticsConfig {
19 18
20#[derive(Debug, Default, Clone)] 19#[derive(Debug, Default, Clone)]
21pub(crate) struct DiagnosticCollection { 20pub(crate) struct DiagnosticCollection {
22 pub(crate) native: HashMap<FileId, Vec<Diagnostic>>, 21 pub(crate) native: HashMap<FileId, Vec<lsp_types::Diagnostic>>,
23 pub(crate) check: HashMap<FileId, Vec<Diagnostic>>, 22 pub(crate) check: HashMap<FileId, Vec<lsp_types::Diagnostic>>,
24 pub(crate) check_fixes: CheckFixes, 23 pub(crate) check_fixes: CheckFixes,
25 changes: FxHashSet<FileId>, 24 changes: FxHashSet<FileId>,
26} 25}
27 26
28#[derive(Debug, Clone)] 27#[derive(Debug, Clone)]
29pub(crate) struct Fix { 28pub(crate) struct Fix {
30 pub(crate) range: Range, 29 pub(crate) range: lsp_types::Range,
31 pub(crate) action: lsp_ext::CodeAction, 30 pub(crate) action: lsp_ext::CodeAction,
32} 31}
33 32
@@ -40,7 +39,7 @@ impl DiagnosticCollection {
40 pub(crate) fn add_check_diagnostic( 39 pub(crate) fn add_check_diagnostic(
41 &mut self, 40 &mut self,
42 file_id: FileId, 41 file_id: FileId,
43 diagnostic: Diagnostic, 42 diagnostic: lsp_types::Diagnostic,
44 fixes: Vec<lsp_ext::CodeAction>, 43 fixes: Vec<lsp_ext::CodeAction>,
45 ) { 44 ) {
46 let diagnostics = self.check.entry(file_id).or_default(); 45 let diagnostics = self.check.entry(file_id).or_default();
@@ -59,12 +58,19 @@ impl DiagnosticCollection {
59 self.changes.insert(file_id); 58 self.changes.insert(file_id);
60 } 59 }
61 60
62 pub(crate) fn set_native_diagnostics(&mut self, file_id: FileId, diagnostics: Vec<Diagnostic>) { 61 pub(crate) fn set_native_diagnostics(
62 &mut self,
63 file_id: FileId,
64 diagnostics: Vec<lsp_types::Diagnostic>,
65 ) {
63 self.native.insert(file_id, diagnostics); 66 self.native.insert(file_id, diagnostics);
64 self.changes.insert(file_id); 67 self.changes.insert(file_id);
65 } 68 }
66 69
67 pub(crate) fn diagnostics_for(&self, file_id: FileId) -> impl Iterator<Item = &Diagnostic> { 70 pub(crate) fn diagnostics_for(
71 &self,
72 file_id: FileId,
73 ) -> impl Iterator<Item = &lsp_types::Diagnostic> {
68 let native = self.native.get(&file_id).into_iter().flatten(); 74 let native = self.native.get(&file_id).into_iter().flatten();
69 let check = self.check.get(&file_id).into_iter().flatten(); 75 let check = self.check.get(&file_id).into_iter().flatten();
70 native.chain(check) 76 native.chain(check)
@@ -78,7 +84,7 @@ impl DiagnosticCollection {
78 } 84 }
79} 85}
80 86
81fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool { 87fn are_diagnostics_equal(left: &lsp_types::Diagnostic, right: &lsp_types::Diagnostic) -> bool {
82 left.source == right.source 88 left.source == right.source
83 && left.severity == right.severity 89 && left.severity == right.severity
84 && left.range == right.range 90 && left.range == right.range
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index f379f5ed0..3eed118a9 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -167,9 +167,9 @@ fn map_rust_child_diagnostic(
167 167
168#[derive(Debug)] 168#[derive(Debug)]
169pub(crate) struct MappedRustDiagnostic { 169pub(crate) struct MappedRustDiagnostic {
170 pub location: Location, 170 pub(crate) location: Location,
171 pub diagnostic: Diagnostic, 171 pub(crate) diagnostic: Diagnostic,
172 pub fixes: Vec<lsp_ext::CodeAction>, 172 pub(crate) fixes: Vec<lsp_ext::CodeAction>,
173} 173}
174 174
175/// Converts a Rust root diagnostic to LSP form 175/// Converts a Rust root diagnostic to LSP form
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 7533bb319..b8aa1e5b5 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -12,6 +12,7 @@ use parking_lot::RwLock;
12use ra_db::{CrateId, VfsPath}; 12use ra_db::{CrateId, VfsPath};
13use ra_ide::{Analysis, AnalysisChange, AnalysisHost, FileId}; 13use ra_ide::{Analysis, AnalysisChange, AnalysisHost, FileId};
14use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target}; 14use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target};
15use rustc_hash::{FxHashMap, FxHashSet};
15 16
16use crate::{ 17use crate::{
17 config::Config, 18 config::Config,
@@ -21,12 +22,10 @@ use crate::{
21 main_loop::Task, 22 main_loop::Task,
22 reload::SourceRootConfig, 23 reload::SourceRootConfig,
23 request_metrics::{LatestRequests, RequestMetrics}, 24 request_metrics::{LatestRequests, RequestMetrics},
24 show_message,
25 thread_pool::TaskPool, 25 thread_pool::TaskPool,
26 to_proto::url_from_abs_path, 26 to_proto::url_from_abs_path,
27 Result, 27 Result,
28}; 28};
29use rustc_hash::{FxHashMap, FxHashSet};
30 29
31#[derive(Eq, PartialEq)] 30#[derive(Eq, PartialEq)]
32pub(crate) enum Status { 31pub(crate) enum Status {
@@ -58,6 +57,7 @@ pub(crate) type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>;
58/// Note that this struct has more than on impl in various modules! 57/// Note that this struct has more than on impl in various modules!
59pub(crate) struct GlobalState { 58pub(crate) struct GlobalState {
60 sender: Sender<lsp_server::Message>, 59 sender: Sender<lsp_server::Message>,
60 req_queue: ReqQueue,
61 pub(crate) task_pool: Handle<TaskPool<Task>, Receiver<Task>>, 61 pub(crate) task_pool: Handle<TaskPool<Task>, Receiver<Task>>,
62 pub(crate) loader: Handle<Box<dyn vfs::loader::Handle>, Receiver<vfs::loader::Message>>, 62 pub(crate) loader: Handle<Box<dyn vfs::loader::Handle>, Receiver<vfs::loader::Message>>,
63 pub(crate) flycheck: Option<Handle<FlycheckHandle, Receiver<flycheck::Message>>>, 63 pub(crate) flycheck: Option<Handle<FlycheckHandle, Receiver<flycheck::Message>>>,
@@ -67,7 +67,6 @@ pub(crate) struct GlobalState {
67 pub(crate) mem_docs: FxHashSet<VfsPath>, 67 pub(crate) mem_docs: FxHashSet<VfsPath>,
68 pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, 68 pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
69 pub(crate) status: Status, 69 pub(crate) status: Status,
70 pub(crate) req_queue: ReqQueue,
71 pub(crate) source_root_config: SourceRootConfig, 70 pub(crate) source_root_config: SourceRootConfig,
72 pub(crate) proc_macro_client: ProcMacroClient, 71 pub(crate) proc_macro_client: ProcMacroClient,
73 pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>, 72 pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
@@ -103,16 +102,16 @@ impl GlobalState {
103 let analysis_host = AnalysisHost::new(config.lru_capacity); 102 let analysis_host = AnalysisHost::new(config.lru_capacity);
104 GlobalState { 103 GlobalState {
105 sender, 104 sender,
105 req_queue: ReqQueue::default(),
106 task_pool, 106 task_pool,
107 loader, 107 loader,
108 flycheck: None,
108 config, 109 config,
109 analysis_host, 110 analysis_host,
110 flycheck: None,
111 diagnostics: Default::default(), 111 diagnostics: Default::default(),
112 mem_docs: FxHashSet::default(), 112 mem_docs: FxHashSet::default(),
113 vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))), 113 vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))),
114 status: Status::default(), 114 status: Status::default(),
115 req_queue: ReqQueue::default(),
116 source_root_config: SourceRootConfig::default(), 115 source_root_config: SourceRootConfig::default(),
117 proc_macro_client: ProcMacroClient::dummy(), 116 proc_macro_client: ProcMacroClient::dummy(),
118 workspaces: Arc::new(Vec::new()), 117 workspaces: Arc::new(Vec::new()),
@@ -169,8 +168,35 @@ impl GlobalState {
169 } 168 }
170 } 169 }
171 170
172 pub(crate) fn send(&mut self, message: lsp_server::Message) { 171 pub(crate) fn send_request<R: lsp_types::request::Request>(
173 self.sender.send(message).unwrap() 172 &mut self,
173 params: R::Params,
174 handler: ReqHandler,
175 ) {
176 let request = self.req_queue.outgoing.register(R::METHOD.to_string(), params, handler);
177 self.send(request.into());
178 }
179 pub(crate) fn complete_request(&mut self, response: lsp_server::Response) {
180 let handler = self.req_queue.outgoing.complete(response.id.clone());
181 handler(self, response)
182 }
183
184 pub(crate) fn send_notification<N: lsp_types::notification::Notification>(
185 &mut self,
186 params: N::Params,
187 ) {
188 let not = lsp_server::Notification::new(N::METHOD.to_string(), params);
189 self.send(not.into());
190 }
191
192 pub(crate) fn register_request(
193 &mut self,
194 request: &lsp_server::Request,
195 request_received: Instant,
196 ) {
197 self.req_queue
198 .incoming
199 .register(request.id.clone(), (request.method.clone(), request_received));
174 } 200 }
175 pub(crate) fn respond(&mut self, response: lsp_server::Response) { 201 pub(crate) fn respond(&mut self, response: lsp_server::Response) {
176 if let Some((method, start)) = self.req_queue.incoming.complete(response.id.clone()) { 202 if let Some((method, start)) = self.req_queue.incoming.complete(response.id.clone()) {
@@ -182,8 +208,14 @@ impl GlobalState {
182 self.send(response.into()); 208 self.send(response.into());
183 } 209 }
184 } 210 }
185 pub(crate) fn show_message(&self, typ: lsp_types::MessageType, message: String) { 211 pub(crate) fn cancel(&mut self, request_id: lsp_server::RequestId) {
186 show_message(typ, message, &self.sender) 212 if let Some(response) = self.req_queue.incoming.cancel(request_id) {
213 self.send(response.into());
214 }
215 }
216
217 fn send(&mut self, message: lsp_server::Message) {
218 self.sender.send(message).unwrap()
187 } 219 }
188} 220}
189 221
@@ -195,11 +227,7 @@ impl Drop for GlobalState {
195 227
196impl GlobalStateSnapshot { 228impl GlobalStateSnapshot {
197 pub(crate) fn url_to_file_id(&self, url: &Url) -> Result<FileId> { 229 pub(crate) fn url_to_file_id(&self, url: &Url) -> Result<FileId> {
198 let path = from_proto::abs_path(url)?; 230 url_to_file_id(&self.vfs.read().0, url)
199 let path = path.into();
200 let res =
201 self.vfs.read().0.file_id(&path).ok_or_else(|| format!("file not found: {}", path))?;
202 Ok(res)
203 } 231 }
204 232
205 pub(crate) fn file_id_to_url(&self, id: FileId) -> Url { 233 pub(crate) fn file_id_to_url(&self, id: FileId) -> Url {
@@ -213,7 +241,7 @@ impl GlobalStateSnapshot {
213 pub(crate) fn anchored_path(&self, file_id: FileId, path: &str) -> Url { 241 pub(crate) fn anchored_path(&self, file_id: FileId, path: &str) -> Url {
214 let mut base = self.vfs.read().0.file_path(file_id); 242 let mut base = self.vfs.read().0.file_path(file_id);
215 base.pop(); 243 base.pop();
216 let path = base.join(path); 244 let path = base.join(path).unwrap();
217 let path = path.as_path().unwrap(); 245 let path = path.as_path().unwrap();
218 url_from_abs_path(&path) 246 url_from_abs_path(&path)
219 } 247 }
@@ -239,3 +267,9 @@ pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url {
239 let path = path.as_path().unwrap(); 267 let path = path.as_path().unwrap();
240 url_from_abs_path(&path) 268 url_from_abs_path(&path)
241} 269}
270
271pub(crate) fn url_to_file_id(vfs: &vfs::Vfs, url: &Url) -> Result<FileId> {
272 let path = from_proto::vfs_path(url)?;
273 let res = vfs.file_id(&path).ok_or_else(|| format!("file not found: {}", path))?;
274 Ok(res)
275}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 6c21f25fe..38e3c3324 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -650,7 +650,7 @@ pub(crate) fn handle_formatting(
650 650
651 let mut rustfmt = match &snap.config.rustfmt { 651 let mut rustfmt = match &snap.config.rustfmt {
652 RustfmtConfig::Rustfmt { extra_args } => { 652 RustfmtConfig::Rustfmt { extra_args } => {
653 let mut cmd = process::Command::new("rustfmt"); 653 let mut cmd = process::Command::new(ra_toolchain::rustfmt());
654 cmd.args(extra_args); 654 cmd.args(extra_args);
655 if let Some(&crate_id) = crate_ids.first() { 655 if let Some(&crate_id) = crate_ids.first() {
656 // Assume all crates are in the same edition 656 // Assume all crates are in the same edition
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs
index a24dfe58c..407944d85 100644
--- a/crates/rust-analyzer/src/lib.rs
+++ b/crates/rust-analyzer/src/lib.rs
@@ -39,7 +39,7 @@ pub mod config;
39use serde::de::DeserializeOwned; 39use serde::de::DeserializeOwned;
40 40
41pub type Result<T, E = Box<dyn std::error::Error + Send + Sync>> = std::result::Result<T, E>; 41pub type Result<T, E = Box<dyn std::error::Error + Send + Sync>> = std::result::Result<T, E>;
42pub use crate::{caps::server_capabilities, lsp_utils::show_message, main_loop::main_loop}; 42pub use crate::{caps::server_capabilities, main_loop::main_loop};
43use std::fmt; 43use std::fmt;
44 44
45pub fn from_json<T: DeserializeOwned>(what: &'static str, json: serde_json::Value) -> Result<T> { 45pub fn from_json<T: DeserializeOwned>(what: &'static str, json: serde_json::Value) -> Result<T> {
diff --git a/crates/rust-analyzer/src/lsp_utils.rs b/crates/rust-analyzer/src/lsp_utils.rs
index 35917030c..0bc3ff115 100644
--- a/crates/rust-analyzer/src/lsp_utils.rs
+++ b/crates/rust-analyzer/src/lsp_utils.rs
@@ -1,24 +1,11 @@
1//! Utilities for LSP-related boilerplate code. 1//! Utilities for LSP-related boilerplate code.
2use std::{error::Error, ops::Range}; 2use std::{error::Error, ops::Range};
3 3
4use crossbeam_channel::Sender; 4use lsp_server::Notification;
5use lsp_server::{Message, Notification};
6use ra_db::Canceled; 5use ra_db::Canceled;
7use ra_ide::LineIndex; 6use ra_ide::LineIndex;
8use serde::Serialize;
9 7
10use crate::from_proto; 8use crate::{from_proto, global_state::GlobalState};
11
12pub fn show_message(
13 typ: lsp_types::MessageType,
14 message: impl Into<String>,
15 sender: &Sender<Message>,
16) {
17 let message = message.into();
18 let params = lsp_types::ShowMessageParams { typ, message };
19 let not = notification_new::<lsp_types::notification::ShowMessage>(params);
20 sender.send(not.into()).unwrap();
21}
22 9
23pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool { 10pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool {
24 e.downcast_ref::<Canceled>().is_some() 11 e.downcast_ref::<Canceled>().is_some()
@@ -30,12 +17,68 @@ pub(crate) fn notification_is<N: lsp_types::notification::Notification>(
30 notification.method == N::METHOD 17 notification.method == N::METHOD
31} 18}
32 19
33pub(crate) fn notification_new<N>(params: N::Params) -> Notification 20#[derive(Debug, Eq, PartialEq)]
34where 21pub(crate) enum Progress {
35 N: lsp_types::notification::Notification, 22 Begin,
36 N::Params: Serialize, 23 Report,
37{ 24 End,
38 Notification::new(N::METHOD.to_string(), params) 25}
26
27impl Progress {
28 pub(crate) fn percentage(done: usize, total: usize) -> f64 {
29 (done as f64 / total.max(1) as f64) * 100.0
30 }
31}
32
33impl GlobalState {
34 pub(crate) fn show_message(&mut self, typ: lsp_types::MessageType, message: String) {
35 let message = message.into();
36 self.send_notification::<lsp_types::notification::ShowMessage>(
37 lsp_types::ShowMessageParams { typ, message },
38 )
39 }
40
41 pub(crate) fn report_progress(
42 &mut self,
43 title: &str,
44 state: Progress,
45 message: Option<String>,
46 percentage: Option<f64>,
47 ) {
48 if !self.config.client_caps.work_done_progress {
49 return;
50 }
51 let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title));
52 let work_done_progress = match state {
53 Progress::Begin => {
54 self.send_request::<lsp_types::request::WorkDoneProgressCreate>(
55 lsp_types::WorkDoneProgressCreateParams { token: token.clone() },
56 |_, _| (),
57 );
58
59 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
60 title: title.into(),
61 cancellable: None,
62 message,
63 percentage,
64 })
65 }
66 Progress::Report => {
67 lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
68 cancellable: None,
69 message,
70 percentage,
71 })
72 }
73 Progress::End => {
74 lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message })
75 }
76 };
77 self.send_notification::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
78 token,
79 value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress),
80 });
81 }
39} 82}
40 83
41pub(crate) fn apply_document_changes( 84pub(crate) fn apply_document_changes(
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 162c0057e..e5194fe41 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -7,7 +7,7 @@ use std::{
7 7
8use crossbeam_channel::{never, select, Receiver}; 8use crossbeam_channel::{never, select, Receiver};
9use lsp_server::{Connection, Notification, Request, Response}; 9use lsp_server::{Connection, Notification, Request, Response};
10use lsp_types::{notification::Notification as _, request::Request as _}; 10use lsp_types::notification::Notification as _;
11use ra_db::VfsPath; 11use ra_db::VfsPath;
12use ra_ide::{Canceled, FileId}; 12use ra_ide::{Canceled, FileId};
13use ra_prof::profile; 13use ra_prof::profile;
@@ -16,9 +16,9 @@ use crate::{
16 config::Config, 16 config::Config,
17 dispatch::{NotificationDispatcher, RequestDispatcher}, 17 dispatch::{NotificationDispatcher, RequestDispatcher},
18 from_proto, 18 from_proto,
19 global_state::{file_id_to_url, GlobalState, Status}, 19 global_state::{file_id_to_url, url_to_file_id, GlobalState, Status},
20 handlers, lsp_ext, 20 handlers, lsp_ext,
21 lsp_utils::{apply_document_changes, is_canceled, notification_is, notification_new}, 21 lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress},
22 Result, 22 Result,
23}; 23};
24 24
@@ -143,10 +143,7 @@ impl GlobalState {
143 lsp_server::Message::Notification(not) => { 143 lsp_server::Message::Notification(not) => {
144 self.on_notification(not)?; 144 self.on_notification(not)?;
145 } 145 }
146 lsp_server::Message::Response(resp) => { 146 lsp_server::Message::Response(resp) => self.complete_request(resp),
147 let handler = self.req_queue.outgoing.complete(resp.id.clone());
148 handler(self, resp)
149 }
150 }, 147 },
151 Event::Task(task) => { 148 Event::Task(task) => {
152 match task { 149 match task {
@@ -181,18 +178,15 @@ impl GlobalState {
181 became_ready = true; 178 became_ready = true;
182 Progress::End 179 Progress::End
183 }; 180 };
184 report_progress( 181 self.report_progress(
185 self,
186 "roots scanned", 182 "roots scanned",
187 state, 183 state,
188 Some(format!("{}/{}", n_done, n_total)), 184 Some(format!("{}/{}", n_done, n_total)),
189 Some(percentage(n_done, n_total)), 185 Some(Progress::percentage(n_done, n_total)),
190 ) 186 )
191 } 187 }
192 }, 188 },
193 Event::Flycheck(task) => match task { 189 Event::Flycheck(task) => match task {
194 flycheck::Message::ClearDiagnostics => self.diagnostics.clear_check(),
195
196 flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => { 190 flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => {
197 let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( 191 let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
198 &self.config.diagnostics, 192 &self.config.diagnostics,
@@ -200,31 +194,34 @@ impl GlobalState {
200 &workspace_root, 194 &workspace_root,
201 ); 195 );
202 for diag in diagnostics { 196 for diag in diagnostics {
203 let path = from_proto::vfs_path(&diag.location.uri)?; 197 match url_to_file_id(&self.vfs.read().0, &diag.location.uri) {
204 let file_id = match self.vfs.read().0.file_id(&path) { 198 Ok(file_id) => self.diagnostics.add_check_diagnostic(
205 Some(file) => FileId(file.0), 199 file_id,
206 None => { 200 diag.diagnostic,
207 log::error!( 201 diag.fixes,
208 "File with cargo diagnostic not found in VFS: {}", 202 ),
209 path 203 Err(err) => {
210 ); 204 log::error!("File with cargo diagnostic not found in VFS: {}", err);
211 return Ok(());
212 } 205 }
213 }; 206 };
214 self.diagnostics.add_check_diagnostic(file_id, diag.diagnostic, diag.fixes)
215 } 207 }
216 } 208 }
217 209
218 flycheck::Message::Progress(status) => { 210 flycheck::Message::Progress(status) => {
219 let (state, message) = match status { 211 let (state, message) = match status {
220 flycheck::Progress::Being => (Progress::Begin, None), 212 flycheck::Progress::DidStart => {
213 self.diagnostics.clear_check();
214 (Progress::Begin, None)
215 }
221 flycheck::Progress::DidCheckCrate(target) => { 216 flycheck::Progress::DidCheckCrate(target) => {
222 (Progress::Report, Some(target)) 217 (Progress::Report, Some(target))
223 } 218 }
224 flycheck::Progress::End => (Progress::End, None), 219 flycheck::Progress::DidFinish | flycheck::Progress::DidCancel => {
220 (Progress::End, None)
221 }
225 }; 222 };
226 223
227 report_progress(self, "cargo check", state, message, None); 224 self.report_progress("cargo check", state, message, None);
228 } 225 }
229 }, 226 },
230 } 227 }
@@ -250,10 +247,9 @@ impl GlobalState {
250 for file_id in diagnostic_changes { 247 for file_id in diagnostic_changes {
251 let url = file_id_to_url(&self.vfs.read().0, file_id); 248 let url = file_id_to_url(&self.vfs.read().0, file_id);
252 let diagnostics = self.diagnostics.diagnostics_for(file_id).cloned().collect(); 249 let diagnostics = self.diagnostics.diagnostics_for(file_id).cloned().collect();
253 let params = 250 self.send_notification::<lsp_types::notification::PublishDiagnostics>(
254 lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version: None }; 251 lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version: None },
255 let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params); 252 );
256 self.send(not.into());
257 } 253 }
258 } 254 }
259 255
@@ -271,7 +267,7 @@ impl GlobalState {
271 } 267 }
272 268
273 fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()> { 269 fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()> {
274 self.req_queue.incoming.register(req.id.clone(), (req.method.clone(), request_received)); 270 self.register_request(&req, request_received);
275 271
276 RequestDispatcher { req: Some(req), global_state: self } 272 RequestDispatcher { req: Some(req), global_state: self }
277 .on_sync::<lsp_ext::CollectGarbage>(|s, ()| Ok(s.analysis_host.collect_garbage()))? 273 .on_sync::<lsp_ext::CollectGarbage>(|s, ()| Ok(s.analysis_host.collect_garbage()))?
@@ -335,9 +331,7 @@ impl GlobalState {
335 lsp_types::NumberOrString::Number(id) => id.into(), 331 lsp_types::NumberOrString::Number(id) => id.into(),
336 lsp_types::NumberOrString::String(id) => id.into(), 332 lsp_types::NumberOrString::String(id) => id.into(),
337 }; 333 };
338 if let Some(response) = this.req_queue.incoming.cancel(id) { 334 this.cancel(id);
339 this.send(response.into());
340 }
341 Ok(()) 335 Ok(())
342 })? 336 })?
343 .on::<lsp_types::notification::DidOpenTextDocument>(|this, params| { 337 .on::<lsp_types::notification::DidOpenTextDocument>(|this, params| {
@@ -372,13 +366,13 @@ impl GlobalState {
372 this.loader.handle.invalidate(path.to_path_buf()); 366 this.loader.handle.invalidate(path.to_path_buf());
373 } 367 }
374 } 368 }
375 let params = lsp_types::PublishDiagnosticsParams { 369 this.send_notification::<lsp_types::notification::PublishDiagnostics>(
376 uri: params.text_document.uri, 370 lsp_types::PublishDiagnosticsParams {
377 diagnostics: Vec::new(), 371 uri: params.text_document.uri,
378 version: None, 372 diagnostics: Vec::new(),
379 }; 373 version: None,
380 let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params); 374 },
381 this.send(not.into()); 375 );
382 Ok(()) 376 Ok(())
383 })? 377 })?
384 .on::<lsp_types::notification::DidSaveTextDocument>(|this, _params| { 378 .on::<lsp_types::notification::DidSaveTextDocument>(|this, _params| {
@@ -390,8 +384,7 @@ impl GlobalState {
390 .on::<lsp_types::notification::DidChangeConfiguration>(|this, _params| { 384 .on::<lsp_types::notification::DidChangeConfiguration>(|this, _params| {
391 // As stated in https://github.com/microsoft/language-server-protocol/issues/676, 385 // As stated in https://github.com/microsoft/language-server-protocol/issues/676,
392 // this notification's parameters should be ignored and the actual config queried separately. 386 // this notification's parameters should be ignored and the actual config queried separately.
393 let request = this.req_queue.outgoing.register( 387 this.send_request::<lsp_types::request::WorkspaceConfiguration>(
394 lsp_types::request::WorkspaceConfiguration::METHOD.to_string(),
395 lsp_types::ConfigurationParams { 388 lsp_types::ConfigurationParams {
396 items: vec![lsp_types::ConfigurationItem { 389 items: vec![lsp_types::ConfigurationItem {
397 scope_uri: None, 390 scope_uri: None,
@@ -419,7 +412,6 @@ impl GlobalState {
419 } 412 }
420 }, 413 },
421 ); 414 );
422 this.send(request.into());
423 415
424 return Ok(()); 416 return Ok(());
425 })? 417 })?
@@ -467,60 +459,3 @@ impl GlobalState {
467 }); 459 });
468 } 460 }
469} 461}
470
471#[derive(Eq, PartialEq)]
472enum Progress {
473 Begin,
474 Report,
475 End,
476}
477
478fn percentage(done: usize, total: usize) -> f64 {
479 (done as f64 / total.max(1) as f64) * 100.0
480}
481
482fn report_progress(
483 global_state: &mut GlobalState,
484 title: &str,
485 state: Progress,
486 message: Option<String>,
487 percentage: Option<f64>,
488) {
489 if !global_state.config.client_caps.work_done_progress {
490 return;
491 }
492 let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title));
493 let work_done_progress = match state {
494 Progress::Begin => {
495 let work_done_progress_create = global_state.req_queue.outgoing.register(
496 lsp_types::request::WorkDoneProgressCreate::METHOD.to_string(),
497 lsp_types::WorkDoneProgressCreateParams { token: token.clone() },
498 |_, _| (),
499 );
500 global_state.send(work_done_progress_create.into());
501
502 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
503 title: title.into(),
504 cancellable: None,
505 message,
506 percentage,
507 })
508 }
509 Progress::Report => {
510 lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
511 cancellable: None,
512 message,
513 percentage,
514 })
515 }
516 Progress::End => {
517 lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message })
518 }
519 };
520 let notification =
521 notification_new::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
522 token,
523 value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress),
524 });
525 global_state.send(notification.into());
526}
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index a22d3e262..ec71f8b29 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -1,9 +1,8 @@
1//! Project loading & configuration updates 1//! Project loading & configuration updates
2use std::sync::Arc; 2use std::{mem, sync::Arc};
3 3
4use crossbeam_channel::unbounded; 4use crossbeam_channel::unbounded;
5use flycheck::FlycheckHandle; 5use flycheck::FlycheckHandle;
6use lsp_types::request::Request;
7use ra_db::{CrateGraph, SourceRoot, VfsPath}; 6use ra_db::{CrateGraph, SourceRoot, VfsPath};
8use ra_ide::AnalysisChange; 7use ra_ide::AnalysisChange;
9use ra_project_model::{PackageRoot, ProcMacroClient, ProjectWorkspace}; 8use ra_project_model::{PackageRoot, ProcMacroClient, ProjectWorkspace};
@@ -15,12 +14,14 @@ use crate::{
15}; 14};
16 15
17impl GlobalState { 16impl GlobalState {
18 pub(crate) fn update_configuration(&mut self, new_config: Config) { 17 pub(crate) fn update_configuration(&mut self, config: Config) {
19 self.analysis_host.update_lru_capacity(new_config.lru_capacity); 18 let old_config = mem::replace(&mut self.config, config);
20 if new_config.flycheck != self.config.flycheck { 19 if self.config.lru_capacity != old_config.lru_capacity {
20 self.analysis_host.update_lru_capacity(old_config.lru_capacity);
21 }
22 if self.config.flycheck != old_config.flycheck {
21 self.reload_flycheck(); 23 self.reload_flycheck();
22 } 24 }
23 self.config = new_config;
24 } 25 }
25 pub(crate) fn reload(&mut self) { 26 pub(crate) fn reload(&mut self) {
26 let workspaces = { 27 let workspaces = {
@@ -36,27 +37,31 @@ impl GlobalState {
36 self.config 37 self.config
37 .linked_projects 38 .linked_projects
38 .iter() 39 .iter()
39 .filter_map(|project| match project { 40 .map(|project| match project {
40 LinkedProject::ProjectManifest(manifest) => { 41 LinkedProject::ProjectManifest(manifest) => {
41 ra_project_model::ProjectWorkspace::load( 42 ra_project_model::ProjectWorkspace::load(
42 manifest.clone(), 43 manifest.clone(),
43 &self.config.cargo, 44 &self.config.cargo,
44 self.config.with_sysroot, 45 self.config.with_sysroot,
45 ) 46 )
46 .map_err(|err| {
47 log::error!("failed to load workspace: {:#}", err);
48 self.show_message(
49 lsp_types::MessageType::Error,
50 format!("rust-analyzer failed to load workspace: {:#}", err),
51 );
52 })
53 .ok()
54 } 47 }
55 LinkedProject::InlineJsonProject(it) => { 48 LinkedProject::InlineJsonProject(it) => {
56 Some(ra_project_model::ProjectWorkspace::Json { project: it.clone() }) 49 Ok(ra_project_model::ProjectWorkspace::Json { project: it.clone() })
57 } 50 }
58 }) 51 })
59 .collect::<Vec<_>>() 52 .collect::<Vec<_>>()
53 .into_iter()
54 .filter_map(|res| {
55 res.map_err(|err| {
56 log::error!("failed to load workspace: {:#}", err);
57 self.show_message(
58 lsp_types::MessageType::Error,
59 format!("rust-analyzer failed to load workspace: {:#}", err),
60 );
61 })
62 .ok()
63 })
64 .collect::<Vec<_>>()
60 }; 65 };
61 66
62 if let FilesWatcher::Client = self.config.files.watcher { 67 if let FilesWatcher::Client = self.config.files.watcher {
@@ -74,13 +79,10 @@ impl GlobalState {
74 method: "workspace/didChangeWatchedFiles".to_string(), 79 method: "workspace/didChangeWatchedFiles".to_string(),
75 register_options: Some(serde_json::to_value(registration_options).unwrap()), 80 register_options: Some(serde_json::to_value(registration_options).unwrap()),
76 }; 81 };
77 let params = lsp_types::RegistrationParams { registrations: vec![registration] }; 82 self.send_request::<lsp_types::request::RegisterCapability>(
78 let request = self.req_queue.outgoing.register( 83 lsp_types::RegistrationParams { registrations: vec![registration] },
79 lsp_types::request::RegisterCapability::METHOD.to_string(),
80 params,
81 |_, _| (), 84 |_, _| (),
82 ); 85 );
83 self.send(request.into());
84 } 86 }
85 87
86 let mut change = AnalysisChange::new(); 88 let mut change = AnalysisChange::new();
diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs
index 0173f7464..d0ddeafe7 100644
--- a/crates/vfs/src/file_set.rs
+++ b/crates/vfs/src/file_set.rs
@@ -18,7 +18,7 @@ impl FileSet {
18 pub fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> { 18 pub fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> {
19 let mut base = self.paths[&anchor].clone(); 19 let mut base = self.paths[&anchor].clone();
20 base.pop(); 20 base.pop();
21 let path = base.join(path); 21 let path = base.join(path)?;
22 let res = self.files.get(&path).copied(); 22 let res = self.files.get(&path).copied();
23 res 23 res
24 } 24 }
diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs
index 940f91d0e..dc3031ada 100644
--- a/crates/vfs/src/vfs_path.rs
+++ b/crates/vfs/src/vfs_path.rs
@@ -22,15 +22,15 @@ impl VfsPath {
22 VfsPathRepr::VirtualPath(_) => None, 22 VfsPathRepr::VirtualPath(_) => None,
23 } 23 }
24 } 24 }
25 pub fn join(&self, path: &str) -> VfsPath { 25 pub fn join(&self, path: &str) -> Option<VfsPath> {
26 match &self.0 { 26 match &self.0 {
27 VfsPathRepr::PathBuf(it) => { 27 VfsPathRepr::PathBuf(it) => {
28 let res = it.join(path).normalize(); 28 let res = it.join(path).normalize();
29 VfsPath(VfsPathRepr::PathBuf(res)) 29 Some(VfsPath(VfsPathRepr::PathBuf(res)))
30 } 30 }
31 VfsPathRepr::VirtualPath(it) => { 31 VfsPathRepr::VirtualPath(it) => {
32 let res = it.join(path); 32 let res = it.join(path)?;
33 VfsPath(VfsPathRepr::VirtualPath(res)) 33 Some(VfsPath(VfsPathRepr::VirtualPath(res)))
34 } 34 }
35 } 35 }
36 } 36 }
@@ -101,13 +101,15 @@ impl VirtualPath {
101 self.0 = self.0[..pos].to_string(); 101 self.0 = self.0[..pos].to_string();
102 true 102 true
103 } 103 }
104 fn join(&self, mut path: &str) -> VirtualPath { 104 fn join(&self, mut path: &str) -> Option<VirtualPath> {
105 let mut res = self.clone(); 105 let mut res = self.clone();
106 while path.starts_with("../") { 106 while path.starts_with("../") {
107 assert!(res.pop()); 107 if !res.pop() {
108 return None;
109 }
108 path = &path["../".len()..] 110 path = &path["../".len()..]
109 } 111 }
110 res.0 = format!("{}/{}", res.0, path); 112 res.0 = format!("{}/{}", res.0, path);
111 res 113 Some(res)
112 } 114 }
113} 115}
diff --git a/docs/dev/README.md b/docs/dev/README.md
index 76e1da6cf..11dc5261b 100644
--- a/docs/dev/README.md
+++ b/docs/dev/README.md
@@ -166,6 +166,17 @@ That said, adding an innocent-looking `pub use` is a very simple way to break en
166Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate 166Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate
167https://www.tedinski.com/2018/02/06/system-boundaries.html 167https://www.tedinski.com/2018/02/06/system-boundaries.html
168 168
169## Minimal Tests
170
171Most tests in rust-analyzer start with a snippet of Rust code.
172This snippets should be minimal -- if you copy-paste a snippet of real code into the tests, make sure to remove everything which could be removed.
173There are many benefits to this:
174
175* less to read or to scroll past
176* easier to understand what exactly is tested
177* less stuff printed during printf-debugging
178* less time to run test
179
169## Order of Imports 180## Order of Imports
170 181
171We separate import groups with blank lines 182We separate import groups with blank lines
diff --git a/editors/code/package.json b/editors/code/package.json
index 68484a370..f542a490a 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -336,6 +336,14 @@
336 "default": null, 336 "default": null,
337 "description": "List of features to activate. Defaults to `rust-analyzer.cargo.features`." 337 "description": "List of features to activate. Defaults to `rust-analyzer.cargo.features`."
338 }, 338 },
339 "rust-analyzer.cargoRunner": {
340 "type": [
341 "null",
342 "string"
343 ],
344 "default": null,
345 "description": "Custom cargo runner extension ID."
346 },
339 "rust-analyzer.inlayHints.enable": { 347 "rust-analyzer.inlayHints.enable": {
340 "type": "boolean", 348 "type": "boolean",
341 "default": true, 349 "default": true,
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 48a25495f..8c9d7802f 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -394,7 +394,7 @@ export function run(ctx: Ctx): Cmd {
394 394
395 item.detail = 'rerun'; 395 item.detail = 'rerun';
396 prevRunnable = item; 396 prevRunnable = item;
397 const task = createTask(item.runnable); 397 const task = await createTask(item.runnable, ctx.config);
398 return await vscode.tasks.executeTask(task); 398 return await vscode.tasks.executeTask(task);
399 }; 399 };
400} 400}
@@ -404,7 +404,7 @@ export function runSingle(ctx: Ctx): Cmd {
404 const editor = ctx.activeRustEditor; 404 const editor = ctx.activeRustEditor;
405 if (!editor) return; 405 if (!editor) return;
406 406
407 const task = createTask(runnable); 407 const task = await createTask(runnable, ctx.config);
408 task.group = vscode.TaskGroup.Build; 408 task.group = vscode.TaskGroup.Build;
409 task.presentationOptions = { 409 task.presentationOptions = {
410 reveal: vscode.TaskRevealKind.Always, 410 reveal: vscode.TaskRevealKind.Always,
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 9591d4fe3..fc95a7de6 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -110,6 +110,10 @@ export class Config {
110 }; 110 };
111 } 111 }
112 112
113 get cargoRunner() {
114 return this.get<string | undefined>("cargoRunner");
115 }
116
113 get debug() { 117 get debug() {
114 // "/rustc/<id>" used by suggestions only. 118 // "/rustc/<id>" used by suggestions only.
115 const { ["/rustc/<id>"]: _, ...sourceFileMap } = this.get<Record<string, string>>("debug.sourceFileMap"); 119 const { ["/rustc/<id>"]: _, ...sourceFileMap } = this.get<Record<string, string>>("debug.sourceFileMap");
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index cdb63b46f..5ceab8b44 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -114,7 +114,7 @@ export async function activate(context: vscode.ExtensionContext) {
114 ctx.registerCommand('applyActionGroup', commands.applyActionGroup); 114 ctx.registerCommand('applyActionGroup', commands.applyActionGroup);
115 ctx.registerCommand('gotoLocation', commands.gotoLocation); 115 ctx.registerCommand('gotoLocation', commands.gotoLocation);
116 116
117 ctx.pushCleanup(activateTaskProvider(workspaceFolder)); 117 ctx.pushCleanup(activateTaskProvider(workspaceFolder, ctx.config));
118 118
119 activateInlayHints(ctx); 119 activateInlayHints(ctx);
120 120
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts
index bb060cfe1..766b05112 100644
--- a/editors/code/src/run.ts
+++ b/editors/code/src/run.ts
@@ -1,10 +1,11 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3import * as ra from './lsp_ext'; 3import * as ra from './lsp_ext';
4import * as toolchain from "./toolchain"; 4import * as tasks from './tasks';
5 5
6import { Ctx } from './ctx'; 6import { Ctx } from './ctx';
7import { makeDebugConfig } from './debug'; 7import { makeDebugConfig } from './debug';
8import { Config } from './config';
8 9
9const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; 10const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }];
10 11
@@ -95,52 +96,29 @@ export class RunnableQuickPick implements vscode.QuickPickItem {
95 } 96 }
96} 97}
97 98
98interface CargoTaskDefinition extends vscode.TaskDefinition { 99export async function createTask(runnable: ra.Runnable, config: Config): Promise<vscode.Task> {
99 type: 'cargo'; 100 if (runnable.kind !== "cargo") {
100 label: string; 101 // rust-analyzer supports only one kind, "cargo"
101 command: string; 102 // do not use tasks.TASK_TYPE here, these are completely different meanings.
102 args: string[];
103 env?: { [key: string]: string };
104}
105
106export function createTask(runnable: ra.Runnable): vscode.Task {
107 const TASK_SOURCE = 'Rust';
108 103
109 let command; 104 throw `Unexpected runnable kind: ${runnable.kind}`;
110 switch (runnable.kind) {
111 case "cargo": command = toolchain.getPathForExecutable("cargo");
112 } 105 }
106
113 const args = [...runnable.args.cargoArgs]; // should be a copy! 107 const args = [...runnable.args.cargoArgs]; // should be a copy!
114 if (runnable.args.executableArgs.length > 0) { 108 if (runnable.args.executableArgs.length > 0) {
115 args.push('--', ...runnable.args.executableArgs); 109 args.push('--', ...runnable.args.executableArgs);
116 } 110 }
117 const definition: CargoTaskDefinition = { 111 const definition: tasks.CargoTaskDefinition = {
118 type: 'cargo', 112 type: tasks.TASK_TYPE,
119 label: runnable.label, 113 command: args[0], // run, test, etc...
120 command, 114 args: args.slice(1),
121 args, 115 cwd: runnable.args.workspaceRoot,
122 env: Object.assign({}, process.env as { [key: string]: string }, { "RUST_BACKTRACE": "short" }), 116 env: Object.assign({}, process.env as { [key: string]: string }, { "RUST_BACKTRACE": "short" }),
123 }; 117 };
124 118
125 const execOption: vscode.ShellExecutionOptions = { 119 const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate()
126 cwd: runnable.args.workspaceRoot || '.', 120 const cargoTask = await tasks.buildCargoTask(target, definition, runnable.label, args, config.cargoRunner, true);
127 env: definition.env, 121 cargoTask.presentationOptions.clear = true;
128 }; 122
129 const exec = new vscode.ShellExecution( 123 return cargoTask;
130 definition.command,
131 definition.args,
132 execOption,
133 );
134
135 const f = vscode.workspace.workspaceFolders![0];
136 const t = new vscode.Task(
137 definition,
138 f,
139 definition.label,
140 TASK_SOURCE,
141 exec,
142 ['$rustc'],
143 );
144 t.presentationOptions.clear = true;
145 return t;
146} 124}
diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts
index 9748824df..14abbd5b7 100644
--- a/editors/code/src/tasks.ts
+++ b/editors/code/src/tasks.ts
@@ -1,11 +1,14 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as toolchain from "./toolchain"; 2import * as toolchain from "./toolchain";
3import { Config } from './config';
4import { log } from './util';
3 5
4// This ends up as the `type` key in tasks.json. RLS also uses `cargo` and 6// This ends up as the `type` key in tasks.json. RLS also uses `cargo` and
5// our configuration should be compatible with it so use the same key. 7// our configuration should be compatible with it so use the same key.
6const TASK_TYPE = 'cargo'; 8export const TASK_TYPE = 'cargo';
9export const TASK_SOURCE = 'rust';
7 10
8interface CargoTaskDefinition extends vscode.TaskDefinition { 11export interface CargoTaskDefinition extends vscode.TaskDefinition {
9 command?: string; 12 command?: string;
10 args?: string[]; 13 args?: string[];
11 cwd?: string; 14 cwd?: string;
@@ -14,73 +17,101 @@ interface CargoTaskDefinition extends vscode.TaskDefinition {
14 17
15class CargoTaskProvider implements vscode.TaskProvider { 18class CargoTaskProvider implements vscode.TaskProvider {
16 private readonly target: vscode.WorkspaceFolder; 19 private readonly target: vscode.WorkspaceFolder;
20 private readonly config: Config;
17 21
18 constructor(target: vscode.WorkspaceFolder) { 22 constructor(target: vscode.WorkspaceFolder, config: Config) {
19 this.target = target; 23 this.target = target;
24 this.config = config;
20 } 25 }
21 26
22 provideTasks(): vscode.Task[] { 27 async provideTasks(): Promise<vscode.Task[]> {
23 // Detect Rust tasks. Currently we do not do any actual detection 28 // Detect Rust tasks. Currently we do not do any actual detection
24 // of tasks (e.g. aliases in .cargo/config) and just return a fixed 29 // of tasks (e.g. aliases in .cargo/config) and just return a fixed
25 // set of tasks that always exist. These tasks cannot be removed in 30 // set of tasks that always exist. These tasks cannot be removed in
26 // tasks.json - only tweaked. 31 // tasks.json - only tweaked.
27 32
28 const cargoPath = toolchain.cargoPath(); 33 const defs = [
29
30 return [
31 { command: 'build', group: vscode.TaskGroup.Build }, 34 { command: 'build', group: vscode.TaskGroup.Build },
32 { command: 'check', group: vscode.TaskGroup.Build }, 35 { command: 'check', group: vscode.TaskGroup.Build },
33 { command: 'test', group: vscode.TaskGroup.Test }, 36 { command: 'test', group: vscode.TaskGroup.Test },
34 { command: 'clean', group: vscode.TaskGroup.Clean }, 37 { command: 'clean', group: vscode.TaskGroup.Clean },
35 { command: 'run', group: undefined }, 38 { command: 'run', group: undefined },
36 ] 39 ];
37 .map(({ command, group }) => { 40
38 const vscodeTask = new vscode.Task( 41 const tasks: vscode.Task[] = [];
39 // The contents of this object end up in the tasks.json entries. 42 for (const def of defs) {
40 { 43 const vscodeTask = await buildCargoTask(this.target, { type: TASK_TYPE, command: def.command }, `cargo ${def.command}`, [def.command], this.config.cargoRunner);
41 type: TASK_TYPE, 44 vscodeTask.group = def.group;
42 command, 45 tasks.push(vscodeTask);
43 }, 46 }
44 // The scope of the task - workspace or specific folder (global 47
45 // is not supported). 48 return tasks;
46 this.target,
47 // The task name, and task source. These are shown in the UI as
48 // `${source}: ${name}`, e.g. `rust: cargo build`.
49 `cargo ${command}`,
50 'rust',
51 // What to do when this command is executed.
52 new vscode.ShellExecution(cargoPath, [command]),
53 // Problem matchers.
54 ['$rustc'],
55 );
56 vscodeTask.group = group;
57 return vscodeTask;
58 });
59 } 49 }
60 50
61 resolveTask(task: vscode.Task): vscode.Task | undefined { 51 async resolveTask(task: vscode.Task): Promise<vscode.Task | undefined> {
62 // VSCode calls this for every cargo task in the user's tasks.json, 52 // VSCode calls this for every cargo task in the user's tasks.json,
63 // we need to inform VSCode how to execute that command by creating 53 // we need to inform VSCode how to execute that command by creating
64 // a ShellExecution for it. 54 // a ShellExecution for it.
65 55
66 const definition = task.definition as CargoTaskDefinition; 56 const definition = task.definition as CargoTaskDefinition;
67 57
68 if (definition.type === 'cargo' && definition.command) { 58 if (definition.type === TASK_TYPE && definition.command) {
69 const args = [definition.command].concat(definition.args ?? []); 59 const args = [definition.command].concat(definition.args ?? []);
70 60
71 return new vscode.Task( 61 return await buildCargoTask(this.target, definition, task.name, args, this.config.cargoRunner);
72 definition,
73 task.name,
74 'rust',
75 new vscode.ShellExecution('cargo', args, definition),
76 );
77 } 62 }
78 63
79 return undefined; 64 return undefined;
80 } 65 }
81} 66}
82 67
83export function activateTaskProvider(target: vscode.WorkspaceFolder): vscode.Disposable { 68export async function buildCargoTask(
84 const provider = new CargoTaskProvider(target); 69 target: vscode.WorkspaceFolder,
70 definition: CargoTaskDefinition,
71 name: string,
72 args: string[],
73 customRunner?: string,
74 throwOnError: boolean = false
75): Promise<vscode.Task> {
76
77 let exec: vscode.ShellExecution | undefined = undefined;
78
79 if (customRunner) {
80 const runnerCommand = `${customRunner}.buildShellExecution`;
81 try {
82 const runnerArgs = { kind: TASK_TYPE, args, cwd: definition.cwd, env: definition.env };
83 const customExec = await vscode.commands.executeCommand(runnerCommand, runnerArgs);
84 if (customExec) {
85 if (customExec instanceof vscode.ShellExecution) {
86 exec = customExec;
87 } else {
88 log.debug("Invalid cargo ShellExecution", customExec);
89 throw "Invalid cargo ShellExecution.";
90 }
91 }
92 // fallback to default processing
93
94 } catch (e) {
95 if (throwOnError) throw `Cargo runner '${customRunner}' failed! ${e}`;
96 // fallback to default processing
97 }
98 }
99
100 if (!exec) {
101 exec = new vscode.ShellExecution(toolchain.cargoPath(), args, definition);
102 }
103
104 return new vscode.Task(
105 definition,
106 target,
107 name,
108 TASK_SOURCE,
109 exec,
110 ['$rustc']
111 );
112}
113
114export function activateTaskProvider(target: vscode.WorkspaceFolder, config: Config): vscode.Disposable {
115 const provider = new CargoTaskProvider(target, config);
85 return vscode.tasks.registerTaskProvider(TASK_TYPE, provider); 116 return vscode.tasks.registerTaskProvider(TASK_TYPE, provider);
86} 117}