diff options
Diffstat (limited to 'crates')
69 files changed, 2642 insertions, 1681 deletions
diff --git a/crates/ra_arena/src/map.rs b/crates/ra_arena/src/map.rs index 5e764113d..0f33907c0 100644 --- a/crates/ra_arena/src/map.rs +++ b/crates/ra_arena/src/map.rs | |||
@@ -14,14 +14,8 @@ pub struct ArenaMap<ID, V> { | |||
14 | impl<T, V> ArenaMap<Idx<T>, V> { | 14 | impl<T, V> ArenaMap<Idx<T>, V> { |
15 | pub fn insert(&mut self, id: Idx<T>, t: V) { | 15 | pub fn insert(&mut self, id: Idx<T>, t: V) { |
16 | let idx = Self::to_idx(id); | 16 | let idx = Self::to_idx(id); |
17 | if self.v.capacity() <= idx { | 17 | |
18 | self.v.reserve(idx + 1 - self.v.capacity()); | 18 | self.v.resize_with((idx + 1).max(self.v.len()), || None); |
19 | } | ||
20 | if self.v.len() <= idx { | ||
21 | while self.v.len() <= idx { | ||
22 | self.v.push(None); | ||
23 | } | ||
24 | } | ||
25 | self.v[idx] = Some(t); | 19 | self.v[idx] = Some(t); |
26 | } | 20 | } |
27 | 21 | ||
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs index add82e5b1..8d1af9933 100644 --- a/crates/ra_assists/src/handlers/fill_match_arms.rs +++ b/crates/ra_assists/src/handlers/fill_match_arms.rs | |||
@@ -1,15 +1,11 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use std::iter; | 1 | use std::iter; |
4 | 2 | ||
5 | use hir::{Adt, HasSource, ModuleDef, Semantics}; | 3 | use hir::{Adt, HasSource, ModuleDef, Semantics}; |
6 | use itertools::Itertools; | 4 | use itertools::Itertools; |
7 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; | ||
8 | 7 | ||
9 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{Assist, AssistCtx, AssistId}; |
10 | use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner}; | ||
11 | |||
12 | use ast::{MatchArm, Pat}; | ||
13 | 9 | ||
14 | // Assist: fill_match_arms | 10 | // Assist: fill_match_arms |
15 | // | 11 | // |
@@ -97,10 +93,7 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
97 | } | 93 | } |
98 | 94 | ||
99 | ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| { | 95 | ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| { |
100 | arms.extend(missing_arms); | 96 | let new_arm_list = match_arm_list.remove_placeholder().append_arms(missing_arms); |
101 | |||
102 | let indent_level = IndentLevel::from_node(match_arm_list.syntax()); | ||
103 | let new_arm_list = indent_level.increase_indent(make::match_arm_list(arms)); | ||
104 | 97 | ||
105 | edit.target(match_expr.syntax().text_range()); | 98 | edit.target(match_expr.syntax().text_range()); |
106 | edit.set_cursor(expr.syntax().text_range().start()); | 99 | edit.set_cursor(expr.syntax().text_range().start()); |
@@ -655,4 +648,93 @@ mod tests { | |||
655 | "#, | 648 | "#, |
656 | ); | 649 | ); |
657 | } | 650 | } |
651 | |||
652 | #[test] | ||
653 | fn fill_match_arms_preserves_comments() { | ||
654 | check_assist( | ||
655 | fill_match_arms, | ||
656 | r#" | ||
657 | enum A { | ||
658 | One, | ||
659 | Two, | ||
660 | } | ||
661 | fn foo(a: A) { | ||
662 | match a { | ||
663 | // foo bar baz<|> | ||
664 | A::One => {} | ||
665 | // This is where the rest should be | ||
666 | } | ||
667 | } | ||
668 | "#, | ||
669 | r#" | ||
670 | enum A { | ||
671 | One, | ||
672 | Two, | ||
673 | } | ||
674 | fn foo(a: A) { | ||
675 | match <|>a { | ||
676 | // foo bar baz | ||
677 | A::One => {} | ||
678 | // This is where the rest should be | ||
679 | A::Two => {} | ||
680 | } | ||
681 | } | ||
682 | "#, | ||
683 | ); | ||
684 | } | ||
685 | |||
686 | #[test] | ||
687 | fn fill_match_arms_preserves_comments_empty() { | ||
688 | check_assist( | ||
689 | fill_match_arms, | ||
690 | r#" | ||
691 | enum A { | ||
692 | One, | ||
693 | Two, | ||
694 | } | ||
695 | fn foo(a: A) { | ||
696 | match a { | ||
697 | // foo bar baz<|> | ||
698 | } | ||
699 | } | ||
700 | "#, | ||
701 | r#" | ||
702 | enum A { | ||
703 | One, | ||
704 | Two, | ||
705 | } | ||
706 | fn foo(a: A) { | ||
707 | match <|>a { | ||
708 | // foo bar baz | ||
709 | A::One => {} | ||
710 | A::Two => {} | ||
711 | } | ||
712 | } | ||
713 | "#, | ||
714 | ); | ||
715 | } | ||
716 | |||
717 | #[test] | ||
718 | fn fill_match_arms_placeholder() { | ||
719 | check_assist( | ||
720 | fill_match_arms, | ||
721 | r#" | ||
722 | enum A { One, Two, } | ||
723 | fn foo(a: A) { | ||
724 | match a<|> { | ||
725 | _ => (), | ||
726 | } | ||
727 | } | ||
728 | "#, | ||
729 | r#" | ||
730 | enum A { One, Two, } | ||
731 | fn foo(a: A) { | ||
732 | match <|>a { | ||
733 | A::One => {} | ||
734 | A::Two => {} | ||
735 | } | ||
736 | } | ||
737 | "#, | ||
738 | ); | ||
739 | } | ||
658 | } | 740 | } |
diff --git a/crates/ra_cargo_watch/src/lib.rs b/crates/ra_cargo_watch/src/lib.rs deleted file mode 100644 index 7c525c430..000000000 --- a/crates/ra_cargo_watch/src/lib.rs +++ /dev/null | |||
@@ -1,405 +0,0 @@ | |||
1 | //! cargo_check provides the functionality needed to run `cargo check` or | ||
2 | //! another compatible command (f.x. clippy) in a background thread and provide | ||
3 | //! LSP diagnostics based on the output of the command. | ||
4 | use cargo_metadata::Message; | ||
5 | use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; | ||
6 | use lsp_types::{ | ||
7 | CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, | ||
8 | WorkDoneProgressEnd, WorkDoneProgressReport, | ||
9 | }; | ||
10 | use std::{ | ||
11 | error, fmt, | ||
12 | io::{BufRead, BufReader}, | ||
13 | path::{Path, PathBuf}, | ||
14 | process::{Command, Stdio}, | ||
15 | thread::JoinHandle, | ||
16 | time::Instant, | ||
17 | }; | ||
18 | |||
19 | mod conv; | ||
20 | |||
21 | use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic}; | ||
22 | |||
23 | pub use crate::conv::url_from_path_with_drive_lowercasing; | ||
24 | |||
25 | #[derive(Clone, Debug)] | ||
26 | pub struct CheckOptions { | ||
27 | pub enable: bool, | ||
28 | pub args: Vec<String>, | ||
29 | pub command: String, | ||
30 | pub all_targets: bool, | ||
31 | } | ||
32 | |||
33 | /// CheckWatcher wraps the shared state and communication machinery used for | ||
34 | /// running `cargo check` (or other compatible command) and providing | ||
35 | /// diagnostics based on the output. | ||
36 | /// The spawned thread is shut down when this struct is dropped. | ||
37 | #[derive(Debug)] | ||
38 | pub struct CheckWatcher { | ||
39 | pub task_recv: Receiver<CheckTask>, | ||
40 | cmd_send: Option<Sender<CheckCommand>>, | ||
41 | handle: Option<JoinHandle<()>>, | ||
42 | } | ||
43 | |||
44 | impl CheckWatcher { | ||
45 | pub fn new(options: &CheckOptions, workspace_root: PathBuf) -> CheckWatcher { | ||
46 | let options = options.clone(); | ||
47 | |||
48 | let (task_send, task_recv) = unbounded::<CheckTask>(); | ||
49 | let (cmd_send, cmd_recv) = unbounded::<CheckCommand>(); | ||
50 | let handle = std::thread::spawn(move || { | ||
51 | let mut check = CheckWatcherThread::new(options, workspace_root); | ||
52 | check.run(&task_send, &cmd_recv); | ||
53 | }); | ||
54 | CheckWatcher { task_recv, cmd_send: Some(cmd_send), handle: Some(handle) } | ||
55 | } | ||
56 | |||
57 | /// Returns a CheckWatcher that doesn't actually do anything | ||
58 | pub fn dummy() -> CheckWatcher { | ||
59 | CheckWatcher { task_recv: never(), cmd_send: None, handle: None } | ||
60 | } | ||
61 | |||
62 | /// Schedule a re-start of the cargo check worker. | ||
63 | pub fn update(&self) { | ||
64 | if let Some(cmd_send) = &self.cmd_send { | ||
65 | cmd_send.send(CheckCommand::Update).unwrap(); | ||
66 | } | ||
67 | } | ||
68 | } | ||
69 | |||
70 | impl std::ops::Drop for CheckWatcher { | ||
71 | fn drop(&mut self) { | ||
72 | if let Some(handle) = self.handle.take() { | ||
73 | // Take the sender out of the option | ||
74 | let cmd_send = self.cmd_send.take(); | ||
75 | |||
76 | // Dropping the sender finishes the thread loop | ||
77 | drop(cmd_send); | ||
78 | |||
79 | // Join the thread, it should finish shortly. We don't really care | ||
80 | // whether it panicked, so it is safe to ignore the result | ||
81 | let _ = handle.join(); | ||
82 | } | ||
83 | } | ||
84 | } | ||
85 | |||
86 | #[derive(Debug)] | ||
87 | pub enum CheckTask { | ||
88 | /// Request a clearing of all cached diagnostics from the check watcher | ||
89 | ClearDiagnostics, | ||
90 | |||
91 | /// Request adding a diagnostic with fixes included to a file | ||
92 | AddDiagnostic { url: Url, diagnostic: Diagnostic, fixes: Vec<CodeActionOrCommand> }, | ||
93 | |||
94 | /// Request check progress notification to client | ||
95 | Status(WorkDoneProgress), | ||
96 | } | ||
97 | |||
98 | pub enum CheckCommand { | ||
99 | /// Request re-start of check thread | ||
100 | Update, | ||
101 | } | ||
102 | |||
103 | struct CheckWatcherThread { | ||
104 | options: CheckOptions, | ||
105 | workspace_root: PathBuf, | ||
106 | watcher: WatchThread, | ||
107 | last_update_req: Option<Instant>, | ||
108 | } | ||
109 | |||
110 | impl CheckWatcherThread { | ||
111 | fn new(options: CheckOptions, workspace_root: PathBuf) -> CheckWatcherThread { | ||
112 | CheckWatcherThread { | ||
113 | options, | ||
114 | workspace_root, | ||
115 | watcher: WatchThread::dummy(), | ||
116 | last_update_req: None, | ||
117 | } | ||
118 | } | ||
119 | |||
120 | fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) { | ||
121 | loop { | ||
122 | select! { | ||
123 | recv(&cmd_recv) -> cmd => match cmd { | ||
124 | Ok(cmd) => self.handle_command(cmd), | ||
125 | Err(RecvError) => { | ||
126 | // Command channel has closed, so shut down | ||
127 | break; | ||
128 | }, | ||
129 | }, | ||
130 | recv(self.watcher.message_recv) -> msg => match msg { | ||
131 | Ok(msg) => self.handle_message(msg, task_send), | ||
132 | Err(RecvError) => { | ||
133 | // Watcher finished, replace it with a never channel to | ||
134 | // avoid busy-waiting. | ||
135 | std::mem::replace(&mut self.watcher.message_recv, never()); | ||
136 | }, | ||
137 | } | ||
138 | }; | ||
139 | |||
140 | if self.should_recheck() { | ||
141 | self.last_update_req.take(); | ||
142 | task_send.send(CheckTask::ClearDiagnostics).unwrap(); | ||
143 | |||
144 | // Replace with a dummy watcher first so we drop the original and wait for completion | ||
145 | std::mem::replace(&mut self.watcher, WatchThread::dummy()); | ||
146 | |||
147 | // Then create the actual new watcher | ||
148 | self.watcher = WatchThread::new(&self.options, &self.workspace_root); | ||
149 | } | ||
150 | } | ||
151 | } | ||
152 | |||
153 | fn should_recheck(&mut self) -> bool { | ||
154 | if let Some(_last_update_req) = &self.last_update_req { | ||
155 | // We currently only request an update on save, as we need up to | ||
156 | // date source on disk for cargo check to do it's magic, so we | ||
157 | // don't really need to debounce the requests at this point. | ||
158 | return true; | ||
159 | } | ||
160 | false | ||
161 | } | ||
162 | |||
163 | fn handle_command(&mut self, cmd: CheckCommand) { | ||
164 | match cmd { | ||
165 | CheckCommand::Update => self.last_update_req = Some(Instant::now()), | ||
166 | } | ||
167 | } | ||
168 | |||
169 | fn handle_message(&self, msg: CheckEvent, task_send: &Sender<CheckTask>) { | ||
170 | match msg { | ||
171 | CheckEvent::Begin => { | ||
172 | task_send | ||
173 | .send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin { | ||
174 | title: "Running 'cargo check'".to_string(), | ||
175 | cancellable: Some(false), | ||
176 | message: None, | ||
177 | percentage: None, | ||
178 | }))) | ||
179 | .unwrap(); | ||
180 | } | ||
181 | |||
182 | CheckEvent::End => { | ||
183 | task_send | ||
184 | .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd { | ||
185 | message: None, | ||
186 | }))) | ||
187 | .unwrap(); | ||
188 | } | ||
189 | |||
190 | CheckEvent::Msg(Message::CompilerArtifact(msg)) => { | ||
191 | task_send | ||
192 | .send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport { | ||
193 | cancellable: Some(false), | ||
194 | message: Some(msg.target.name), | ||
195 | percentage: None, | ||
196 | }))) | ||
197 | .unwrap(); | ||
198 | } | ||
199 | |||
200 | CheckEvent::Msg(Message::CompilerMessage(msg)) => { | ||
201 | let map_result = map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root); | ||
202 | if map_result.is_empty() { | ||
203 | return; | ||
204 | } | ||
205 | |||
206 | for MappedRustDiagnostic { location, diagnostic, fixes } in map_result { | ||
207 | let fixes = fixes | ||
208 | .into_iter() | ||
209 | .map(|fix| { | ||
210 | CodeAction { diagnostics: Some(vec![diagnostic.clone()]), ..fix }.into() | ||
211 | }) | ||
212 | .collect(); | ||
213 | |||
214 | task_send | ||
215 | .send(CheckTask::AddDiagnostic { url: location.uri, diagnostic, fixes }) | ||
216 | .unwrap(); | ||
217 | } | ||
218 | } | ||
219 | |||
220 | CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {} | ||
221 | CheckEvent::Msg(Message::Unknown) => {} | ||
222 | } | ||
223 | } | ||
224 | } | ||
225 | |||
226 | #[derive(Debug)] | ||
227 | pub struct DiagnosticWithFixes { | ||
228 | diagnostic: Diagnostic, | ||
229 | fixes: Vec<CodeAction>, | ||
230 | } | ||
231 | |||
232 | /// WatchThread exists to wrap around the communication needed to be able to | ||
233 | /// run `cargo check` without blocking. Currently the Rust standard library | ||
234 | /// doesn't provide a way to read sub-process output without blocking, so we | ||
235 | /// have to wrap sub-processes output handling in a thread and pass messages | ||
236 | /// back over a channel. | ||
237 | /// The correct way to dispose of the thread is to drop it, on which the | ||
238 | /// sub-process will be killed, and the thread will be joined. | ||
239 | struct WatchThread { | ||
240 | handle: Option<JoinHandle<()>>, | ||
241 | message_recv: Receiver<CheckEvent>, | ||
242 | } | ||
243 | |||
244 | enum CheckEvent { | ||
245 | Begin, | ||
246 | Msg(cargo_metadata::Message), | ||
247 | End, | ||
248 | } | ||
249 | |||
250 | #[derive(Debug)] | ||
251 | pub struct CargoError(String); | ||
252 | |||
253 | impl fmt::Display for CargoError { | ||
254 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
255 | write!(f, "Cargo failed: {}", self.0) | ||
256 | } | ||
257 | } | ||
258 | impl error::Error for CargoError {} | ||
259 | |||
260 | pub fn run_cargo( | ||
261 | args: &[String], | ||
262 | current_dir: Option<&Path>, | ||
263 | on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool, | ||
264 | ) -> Result<(), CargoError> { | ||
265 | let mut command = Command::new("cargo"); | ||
266 | if let Some(current_dir) = current_dir { | ||
267 | command.current_dir(current_dir); | ||
268 | } | ||
269 | |||
270 | let mut child = command | ||
271 | .args(args) | ||
272 | .stdout(Stdio::piped()) | ||
273 | .stderr(Stdio::null()) | ||
274 | .stdin(Stdio::null()) | ||
275 | .spawn() | ||
276 | .expect("couldn't launch cargo"); | ||
277 | |||
278 | // We manually read a line at a time, instead of using serde's | ||
279 | // stream deserializers, because the deserializer cannot recover | ||
280 | // from an error, resulting in it getting stuck, because we try to | ||
281 | // be resillient against failures. | ||
282 | // | ||
283 | // Because cargo only outputs one JSON object per line, we can | ||
284 | // simply skip a line if it doesn't parse, which just ignores any | ||
285 | // erroneus output. | ||
286 | let stdout = BufReader::new(child.stdout.take().unwrap()); | ||
287 | let mut read_at_least_one_message = false; | ||
288 | |||
289 | for line in stdout.lines() { | ||
290 | let line = match line { | ||
291 | Ok(line) => line, | ||
292 | Err(err) => { | ||
293 | log::error!("Couldn't read line from cargo: {}", err); | ||
294 | continue; | ||
295 | } | ||
296 | }; | ||
297 | |||
298 | let message = serde_json::from_str::<cargo_metadata::Message>(&line); | ||
299 | let message = match message { | ||
300 | Ok(message) => message, | ||
301 | Err(err) => { | ||
302 | log::error!("Invalid json from cargo check, ignoring ({}): {:?} ", err, line); | ||
303 | continue; | ||
304 | } | ||
305 | }; | ||
306 | |||
307 | read_at_least_one_message = true; | ||
308 | |||
309 | if !on_message(message) { | ||
310 | break; | ||
311 | } | ||
312 | } | ||
313 | |||
314 | // It is okay to ignore the result, as it only errors if the process is already dead | ||
315 | let _ = child.kill(); | ||
316 | |||
317 | let err_msg = match child.wait() { | ||
318 | Ok(exit_code) if !exit_code.success() && !read_at_least_one_message => { | ||
319 | // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment: | ||
320 | // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298 | ||
321 | format!( | ||
322 | "the command produced no valid metadata (exit code: {:?}): cargo {}", | ||
323 | exit_code, | ||
324 | args.join(" ") | ||
325 | ) | ||
326 | } | ||
327 | Err(err) => format!("io error: {:?}", err), | ||
328 | Ok(_) => return Ok(()), | ||
329 | }; | ||
330 | |||
331 | Err(CargoError(err_msg)) | ||
332 | } | ||
333 | |||
334 | impl WatchThread { | ||
335 | fn dummy() -> WatchThread { | ||
336 | WatchThread { handle: None, message_recv: never() } | ||
337 | } | ||
338 | |||
339 | fn new(options: &CheckOptions, workspace_root: &Path) -> WatchThread { | ||
340 | let mut args: Vec<String> = vec![ | ||
341 | options.command.clone(), | ||
342 | "--workspace".to_string(), | ||
343 | "--message-format=json".to_string(), | ||
344 | "--manifest-path".to_string(), | ||
345 | format!("{}/Cargo.toml", workspace_root.display()), | ||
346 | ]; | ||
347 | if options.all_targets { | ||
348 | args.push("--all-targets".to_string()); | ||
349 | } | ||
350 | args.extend(options.args.iter().cloned()); | ||
351 | |||
352 | let (message_send, message_recv) = unbounded(); | ||
353 | let workspace_root = workspace_root.to_owned(); | ||
354 | let handle = if options.enable { | ||
355 | Some(std::thread::spawn(move || { | ||
356 | // If we trigger an error here, we will do so in the loop instead, | ||
357 | // which will break out of the loop, and continue the shutdown | ||
358 | let _ = message_send.send(CheckEvent::Begin); | ||
359 | |||
360 | let res = run_cargo(&args, Some(&workspace_root), &mut |message| { | ||
361 | // Skip certain kinds of messages to only spend time on what's useful | ||
362 | match &message { | ||
363 | Message::CompilerArtifact(artifact) if artifact.fresh => return true, | ||
364 | Message::BuildScriptExecuted(_) => return true, | ||
365 | Message::Unknown => return true, | ||
366 | _ => {} | ||
367 | } | ||
368 | |||
369 | // if the send channel was closed, we want to shutdown | ||
370 | message_send.send(CheckEvent::Msg(message)).is_ok() | ||
371 | }); | ||
372 | |||
373 | if let Err(err) = res { | ||
374 | // FIXME: make the `message_send` to be `Sender<Result<CheckEvent, CargoError>>` | ||
375 | // to display user-caused misconfiguration errors instead of just logging them here | ||
376 | log::error!("Cargo watcher failed {:?}", err); | ||
377 | } | ||
378 | |||
379 | // We can ignore any error here, as we are already in the progress | ||
380 | // of shutting down. | ||
381 | let _ = message_send.send(CheckEvent::End); | ||
382 | })) | ||
383 | } else { | ||
384 | None | ||
385 | }; | ||
386 | WatchThread { handle, message_recv } | ||
387 | } | ||
388 | } | ||
389 | |||
390 | impl std::ops::Drop for WatchThread { | ||
391 | fn drop(&mut self) { | ||
392 | if let Some(handle) = self.handle.take() { | ||
393 | // Replace our reciever with dummy one, so we can drop and close the | ||
394 | // one actually communicating with the thread | ||
395 | let recv = std::mem::replace(&mut self.message_recv, never()); | ||
396 | |||
397 | // Dropping the original reciever initiates thread sub-process shutdown | ||
398 | drop(recv); | ||
399 | |||
400 | // Join the thread, it should finish shortly. We don't really care | ||
401 | // whether it panicked, so it is safe to ignore the result | ||
402 | let _ = handle.join(); | ||
403 | } | ||
404 | } | ||
405 | } | ||
diff --git a/crates/ra_cargo_watch/Cargo.toml b/crates/ra_flycheck/Cargo.toml index 741345a21..c9a9ddc12 100644 --- a/crates/ra_cargo_watch/Cargo.toml +++ b/crates/ra_flycheck/Cargo.toml | |||
@@ -1,6 +1,6 @@ | |||
1 | [package] | 1 | [package] |
2 | edition = "2018" | 2 | edition = "2018" |
3 | name = "ra_cargo_watch" | 3 | name = "ra_flycheck" |
4 | version = "0.1.0" | 4 | version = "0.1.0" |
5 | authors = ["rust-analyzer developers"] | 5 | authors = ["rust-analyzer developers"] |
6 | 6 | ||
@@ -10,6 +10,7 @@ lsp-types = { version = "0.73.0", features = ["proposed"] } | |||
10 | log = "0.4.8" | 10 | log = "0.4.8" |
11 | cargo_metadata = "0.9.1" | 11 | cargo_metadata = "0.9.1" |
12 | serde_json = "1.0.48" | 12 | serde_json = "1.0.48" |
13 | jod-thread = "0.1.1" | ||
13 | 14 | ||
14 | [dev-dependencies] | 15 | [dev-dependencies] |
15 | insta = "0.15.0" | 16 | insta = "0.15.0" |
diff --git a/crates/ra_cargo_watch/src/conv.rs b/crates/ra_flycheck/src/conv.rs index 817543deb..817543deb 100644 --- a/crates/ra_cargo_watch/src/conv.rs +++ b/crates/ra_flycheck/src/conv.rs | |||
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_clippy_pass_by_ref.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_clippy_pass_by_ref.snap index a59fa84fa..4c9db0385 100644 --- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_clippy_pass_by_ref.snap +++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_clippy_pass_by_ref.snap | |||
@@ -1,5 +1,5 @@ | |||
1 | --- | 1 | --- |
2 | source: crates/ra_cargo_watch/src/conv/test.rs | 2 | source: crates/ra_flycheck/src/conv/test.rs |
3 | expression: diag | 3 | expression: diag |
4 | --- | 4 | --- |
5 | [ | 5 | [ |
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_handles_macro_location.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_handles_macro_location.snap index 61ae0c9ae..7cde4d867 100644 --- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_handles_macro_location.snap +++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_handles_macro_location.snap | |||
@@ -1,5 +1,5 @@ | |||
1 | --- | 1 | --- |
2 | source: crates/ra_cargo_watch/src/conv/test.rs | 2 | source: crates/ra_flycheck/src/conv/test.rs |
3 | expression: diag | 3 | expression: diag |
4 | --- | 4 | --- |
5 | [ | 5 | [ |
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_macro_compiler_error.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_macro_compiler_error.snap index 641da1a58..1cc37e087 100644 --- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_macro_compiler_error.snap +++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_macro_compiler_error.snap | |||
@@ -1,5 +1,5 @@ | |||
1 | --- | 1 | --- |
2 | source: crates/ra_cargo_watch/src/conv/test.rs | 2 | source: crates/ra_flycheck/src/conv/test.rs |
3 | expression: diag | 3 | expression: diag |
4 | --- | 4 | --- |
5 | [ | 5 | [ |
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_multi_line_fix.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_multi_line_fix.snap index 0557a2e79..615ed8378 100644 --- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_multi_line_fix.snap +++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_multi_line_fix.snap | |||
@@ -1,5 +1,5 @@ | |||
1 | --- | 1 | --- |
2 | source: crates/ra_cargo_watch/src/conv/test.rs | 2 | source: crates/ra_flycheck/src/conv/test.rs |
3 | expression: diag | 3 | expression: diag |
4 | --- | 4 | --- |
5 | [ | 5 | [ |
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_incompatible_type_for_trait.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_incompatible_type_for_trait.snap index 754bc33a4..0df0fce18 100644 --- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_incompatible_type_for_trait.snap +++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_incompatible_type_for_trait.snap | |||
@@ -1,5 +1,5 @@ | |||
1 | --- | 1 | --- |
2 | source: crates/ra_cargo_watch/src/conv/test.rs | 2 | source: crates/ra_flycheck/src/conv/test.rs |
3 | expression: diag | 3 | expression: diag |
4 | --- | 4 | --- |
5 | [ | 5 | [ |
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_mismatched_type.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_mismatched_type.snap index 78b7f7cc8..28ebcb3b3 100644 --- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_mismatched_type.snap +++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_mismatched_type.snap | |||
@@ -1,5 +1,5 @@ | |||
1 | --- | 1 | --- |
2 | source: crates/ra_cargo_watch/src/conv/test.rs | 2 | source: crates/ra_flycheck/src/conv/test.rs |
3 | expression: diag | 3 | expression: diag |
4 | --- | 4 | --- |
5 | [ | 5 | [ |
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_unused_variable.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_unused_variable.snap index 5989ed202..5e0873281 100644 --- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_unused_variable.snap +++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_unused_variable.snap | |||
@@ -1,5 +1,5 @@ | |||
1 | --- | 1 | --- |
2 | source: crates/ra_cargo_watch/src/conv/test.rs | 2 | source: crates/ra_flycheck/src/conv/test.rs |
3 | expression: diag | 3 | expression: diag |
4 | --- | 4 | --- |
5 | [ | 5 | [ |
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_wrong_number_of_parameters.snap b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_wrong_number_of_parameters.snap index e34b546dc..e500d3cd6 100644 --- a/crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_rustc_wrong_number_of_parameters.snap +++ b/crates/ra_flycheck/src/conv/snapshots/ra_flycheck__conv__test__snap_rustc_wrong_number_of_parameters.snap | |||
@@ -1,5 +1,5 @@ | |||
1 | --- | 1 | --- |
2 | source: crates/ra_cargo_watch/src/conv/test.rs | 2 | source: crates/ra_flycheck/src/conv/test.rs |
3 | expression: diag | 3 | expression: diag |
4 | --- | 4 | --- |
5 | [ | 5 | [ |
diff --git a/crates/ra_cargo_watch/src/conv/test.rs b/crates/ra_flycheck/src/conv/test.rs index 4e81455ca..4e81455ca 100644 --- a/crates/ra_cargo_watch/src/conv/test.rs +++ b/crates/ra_flycheck/src/conv/test.rs | |||
diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs new file mode 100644 index 000000000..b54a30ab8 --- /dev/null +++ b/crates/ra_flycheck/src/lib.rs | |||
@@ -0,0 +1,340 @@ | |||
1 | //! cargo_check provides the functionality needed to run `cargo check` or | ||
2 | //! another compatible command (f.x. clippy) in a background thread and provide | ||
3 | //! LSP diagnostics based on the output of the command. | ||
4 | mod conv; | ||
5 | |||
6 | use std::{ | ||
7 | env, | ||
8 | io::{self, BufRead, BufReader}, | ||
9 | path::PathBuf, | ||
10 | process::{Command, Stdio}, | ||
11 | time::Instant, | ||
12 | }; | ||
13 | |||
14 | use cargo_metadata::Message; | ||
15 | use crossbeam_channel::{never, select, unbounded, Receiver, RecvError, Sender}; | ||
16 | use lsp_types::{ | ||
17 | CodeAction, CodeActionOrCommand, Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, | ||
18 | WorkDoneProgressEnd, WorkDoneProgressReport, | ||
19 | }; | ||
20 | |||
21 | use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic}; | ||
22 | |||
23 | pub use crate::conv::url_from_path_with_drive_lowercasing; | ||
24 | |||
25 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
26 | pub enum FlycheckConfig { | ||
27 | CargoCommand { command: String, all_targets: bool, extra_args: Vec<String> }, | ||
28 | CustomCommand { command: String, args: Vec<String> }, | ||
29 | } | ||
30 | |||
31 | /// Flycheck wraps the shared state and communication machinery used for | ||
32 | /// running `cargo check` (or other compatible command) and providing | ||
33 | /// diagnostics based on the output. | ||
34 | /// The spawned thread is shut down when this struct is dropped. | ||
35 | #[derive(Debug)] | ||
36 | pub struct Flycheck { | ||
37 | // XXX: drop order is significant | ||
38 | cmd_send: Sender<CheckCommand>, | ||
39 | handle: jod_thread::JoinHandle<()>, | ||
40 | pub task_recv: Receiver<CheckTask>, | ||
41 | } | ||
42 | |||
43 | impl Flycheck { | ||
44 | pub fn new(config: FlycheckConfig, workspace_root: PathBuf) -> Flycheck { | ||
45 | let (task_send, task_recv) = unbounded::<CheckTask>(); | ||
46 | let (cmd_send, cmd_recv) = unbounded::<CheckCommand>(); | ||
47 | let handle = jod_thread::spawn(move || { | ||
48 | FlycheckThread::new(config, workspace_root).run(&task_send, &cmd_recv); | ||
49 | }); | ||
50 | Flycheck { task_recv, cmd_send, handle } | ||
51 | } | ||
52 | |||
53 | /// Schedule a re-start of the cargo check worker. | ||
54 | pub fn update(&self) { | ||
55 | self.cmd_send.send(CheckCommand::Update).unwrap(); | ||
56 | } | ||
57 | } | ||
58 | |||
59 | #[derive(Debug)] | ||
60 | pub enum CheckTask { | ||
61 | /// Request a clearing of all cached diagnostics from the check watcher | ||
62 | ClearDiagnostics, | ||
63 | |||
64 | /// Request adding a diagnostic with fixes included to a file | ||
65 | AddDiagnostic { url: Url, diagnostic: Diagnostic, fixes: Vec<CodeActionOrCommand> }, | ||
66 | |||
67 | /// Request check progress notification to client | ||
68 | Status(WorkDoneProgress), | ||
69 | } | ||
70 | |||
71 | pub enum CheckCommand { | ||
72 | /// Request re-start of check thread | ||
73 | Update, | ||
74 | } | ||
75 | |||
76 | struct FlycheckThread { | ||
77 | config: FlycheckConfig, | ||
78 | workspace_root: PathBuf, | ||
79 | last_update_req: Option<Instant>, | ||
80 | // XXX: drop order is significant | ||
81 | message_recv: Receiver<CheckEvent>, | ||
82 | /// WatchThread exists to wrap around the communication needed to be able to | ||
83 | /// run `cargo check` without blocking. Currently the Rust standard library | ||
84 | /// doesn't provide a way to read sub-process output without blocking, so we | ||
85 | /// have to wrap sub-processes output handling in a thread and pass messages | ||
86 | /// back over a channel. | ||
87 | check_process: Option<jod_thread::JoinHandle<()>>, | ||
88 | } | ||
89 | |||
90 | impl FlycheckThread { | ||
91 | fn new(config: FlycheckConfig, workspace_root: PathBuf) -> FlycheckThread { | ||
92 | FlycheckThread { | ||
93 | config, | ||
94 | workspace_root, | ||
95 | last_update_req: None, | ||
96 | message_recv: never(), | ||
97 | check_process: None, | ||
98 | } | ||
99 | } | ||
100 | |||
101 | fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) { | ||
102 | // If we rerun the thread, we need to discard the previous check results first | ||
103 | self.clean_previous_results(task_send); | ||
104 | |||
105 | loop { | ||
106 | select! { | ||
107 | recv(&cmd_recv) -> cmd => match cmd { | ||
108 | Ok(cmd) => self.handle_command(cmd), | ||
109 | Err(RecvError) => { | ||
110 | // Command channel has closed, so shut down | ||
111 | break; | ||
112 | }, | ||
113 | }, | ||
114 | recv(self.message_recv) -> msg => match msg { | ||
115 | Ok(msg) => self.handle_message(msg, task_send), | ||
116 | Err(RecvError) => { | ||
117 | // Watcher finished, replace it with a never channel to | ||
118 | // avoid busy-waiting. | ||
119 | self.message_recv = never(); | ||
120 | self.check_process = None; | ||
121 | }, | ||
122 | } | ||
123 | }; | ||
124 | |||
125 | if self.should_recheck() { | ||
126 | self.last_update_req = None; | ||
127 | task_send.send(CheckTask::ClearDiagnostics).unwrap(); | ||
128 | self.restart_check_process(); | ||
129 | } | ||
130 | } | ||
131 | } | ||
132 | |||
133 | fn clean_previous_results(&self, task_send: &Sender<CheckTask>) { | ||
134 | task_send.send(CheckTask::ClearDiagnostics).unwrap(); | ||
135 | task_send | ||
136 | .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd { message: None }))) | ||
137 | .unwrap(); | ||
138 | } | ||
139 | |||
140 | fn should_recheck(&mut self) -> bool { | ||
141 | if let Some(_last_update_req) = &self.last_update_req { | ||
142 | // We currently only request an update on save, as we need up to | ||
143 | // date source on disk for cargo check to do it's magic, so we | ||
144 | // don't really need to debounce the requests at this point. | ||
145 | return true; | ||
146 | } | ||
147 | false | ||
148 | } | ||
149 | |||
150 | fn handle_command(&mut self, cmd: CheckCommand) { | ||
151 | match cmd { | ||
152 | CheckCommand::Update => self.last_update_req = Some(Instant::now()), | ||
153 | } | ||
154 | } | ||
155 | |||
156 | fn handle_message(&self, msg: CheckEvent, task_send: &Sender<CheckTask>) { | ||
157 | match msg { | ||
158 | CheckEvent::Begin => { | ||
159 | task_send | ||
160 | .send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin { | ||
161 | title: "Running 'cargo check'".to_string(), | ||
162 | cancellable: Some(false), | ||
163 | message: None, | ||
164 | percentage: None, | ||
165 | }))) | ||
166 | .unwrap(); | ||
167 | } | ||
168 | |||
169 | CheckEvent::End => { | ||
170 | task_send | ||
171 | .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd { | ||
172 | message: None, | ||
173 | }))) | ||
174 | .unwrap(); | ||
175 | } | ||
176 | |||
177 | CheckEvent::Msg(Message::CompilerArtifact(msg)) => { | ||
178 | task_send | ||
179 | .send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport { | ||
180 | cancellable: Some(false), | ||
181 | message: Some(msg.target.name), | ||
182 | percentage: None, | ||
183 | }))) | ||
184 | .unwrap(); | ||
185 | } | ||
186 | |||
187 | CheckEvent::Msg(Message::CompilerMessage(msg)) => { | ||
188 | let map_result = map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root); | ||
189 | if map_result.is_empty() { | ||
190 | return; | ||
191 | } | ||
192 | |||
193 | for MappedRustDiagnostic { location, diagnostic, fixes } in map_result { | ||
194 | let fixes = fixes | ||
195 | .into_iter() | ||
196 | .map(|fix| { | ||
197 | CodeAction { diagnostics: Some(vec![diagnostic.clone()]), ..fix }.into() | ||
198 | }) | ||
199 | .collect(); | ||
200 | |||
201 | task_send | ||
202 | .send(CheckTask::AddDiagnostic { url: location.uri, diagnostic, fixes }) | ||
203 | .unwrap(); | ||
204 | } | ||
205 | } | ||
206 | |||
207 | CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {} | ||
208 | CheckEvent::Msg(Message::Unknown) => {} | ||
209 | } | ||
210 | } | ||
211 | |||
212 | fn restart_check_process(&mut self) { | ||
213 | // First, clear and cancel the old thread | ||
214 | self.message_recv = never(); | ||
215 | self.check_process = None; | ||
216 | |||
217 | let mut cmd = match &self.config { | ||
218 | FlycheckConfig::CargoCommand { command, all_targets, extra_args } => { | ||
219 | let mut cmd = Command::new(cargo_binary()); | ||
220 | cmd.arg(command); | ||
221 | cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]); | ||
222 | cmd.arg(self.workspace_root.join("Cargo.toml")); | ||
223 | if *all_targets { | ||
224 | cmd.arg("--all-targets"); | ||
225 | } | ||
226 | cmd.args(extra_args); | ||
227 | cmd | ||
228 | } | ||
229 | FlycheckConfig::CustomCommand { command, args } => { | ||
230 | let mut cmd = Command::new(command); | ||
231 | cmd.args(args); | ||
232 | cmd | ||
233 | } | ||
234 | }; | ||
235 | cmd.current_dir(&self.workspace_root); | ||
236 | |||
237 | let (message_send, message_recv) = unbounded(); | ||
238 | self.message_recv = message_recv; | ||
239 | self.check_process = Some(jod_thread::spawn(move || { | ||
240 | // If we trigger an error here, we will do so in the loop instead, | ||
241 | // which will break out of the loop, and continue the shutdown | ||
242 | let _ = message_send.send(CheckEvent::Begin); | ||
243 | |||
244 | let res = run_cargo(cmd, &mut |message| { | ||
245 | // Skip certain kinds of messages to only spend time on what's useful | ||
246 | match &message { | ||
247 | Message::CompilerArtifact(artifact) if artifact.fresh => return true, | ||
248 | Message::BuildScriptExecuted(_) => return true, | ||
249 | Message::Unknown => return true, | ||
250 | _ => {} | ||
251 | } | ||
252 | |||
253 | // if the send channel was closed, we want to shutdown | ||
254 | message_send.send(CheckEvent::Msg(message)).is_ok() | ||
255 | }); | ||
256 | |||
257 | if let Err(err) = res { | ||
258 | // FIXME: make the `message_send` to be `Sender<Result<CheckEvent, CargoError>>` | ||
259 | // to display user-caused misconfiguration errors instead of just logging them here | ||
260 | log::error!("Cargo watcher failed {:?}", err); | ||
261 | } | ||
262 | |||
263 | // We can ignore any error here, as we are already in the progress | ||
264 | // of shutting down. | ||
265 | let _ = message_send.send(CheckEvent::End); | ||
266 | })) | ||
267 | } | ||
268 | } | ||
269 | |||
270 | #[derive(Debug)] | ||
271 | pub struct DiagnosticWithFixes { | ||
272 | diagnostic: Diagnostic, | ||
273 | fixes: Vec<CodeAction>, | ||
274 | } | ||
275 | |||
276 | enum CheckEvent { | ||
277 | Begin, | ||
278 | Msg(cargo_metadata::Message), | ||
279 | End, | ||
280 | } | ||
281 | |||
282 | fn run_cargo( | ||
283 | mut command: Command, | ||
284 | on_message: &mut dyn FnMut(cargo_metadata::Message) -> bool, | ||
285 | ) -> io::Result<()> { | ||
286 | let mut child = | ||
287 | command.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null()).spawn()?; | ||
288 | |||
289 | // We manually read a line at a time, instead of using serde's | ||
290 | // stream deserializers, because the deserializer cannot recover | ||
291 | // from an error, resulting in it getting stuck, because we try to | ||
292 | // be resillient against failures. | ||
293 | // | ||
294 | // Because cargo only outputs one JSON object per line, we can | ||
295 | // simply skip a line if it doesn't parse, which just ignores any | ||
296 | // erroneus output. | ||
297 | let stdout = BufReader::new(child.stdout.take().unwrap()); | ||
298 | let mut read_at_least_one_message = false; | ||
299 | |||
300 | for line in stdout.lines() { | ||
301 | let line = line?; | ||
302 | |||
303 | let message = serde_json::from_str::<cargo_metadata::Message>(&line); | ||
304 | let message = match message { | ||
305 | Ok(message) => message, | ||
306 | Err(err) => { | ||
307 | log::error!("Invalid json from cargo check, ignoring ({}): {:?} ", err, line); | ||
308 | continue; | ||
309 | } | ||
310 | }; | ||
311 | |||
312 | read_at_least_one_message = true; | ||
313 | |||
314 | if !on_message(message) { | ||
315 | break; | ||
316 | } | ||
317 | } | ||
318 | |||
319 | // It is okay to ignore the result, as it only errors if the process is already dead | ||
320 | let _ = child.kill(); | ||
321 | |||
322 | let exit_status = child.wait()?; | ||
323 | if !exit_status.success() && !read_at_least_one_message { | ||
324 | // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment: | ||
325 | // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298 | ||
326 | return Err(io::Error::new( | ||
327 | io::ErrorKind::Other, | ||
328 | format!( | ||
329 | "the command produced no valid metadata (exit code: {:?}): {:?}", | ||
330 | exit_status, command | ||
331 | ), | ||
332 | )); | ||
333 | } | ||
334 | |||
335 | Ok(()) | ||
336 | } | ||
337 | |||
338 | fn cargo_binary() -> String { | ||
339 | env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()) | ||
340 | } | ||
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs index e8443dde8..28c570c76 100644 --- a/crates/ra_hir_def/src/body/lower.rs +++ b/crates/ra_hir_def/src/body/lower.rs | |||
@@ -417,26 +417,7 @@ impl ExprCollector<'_> { | |||
417 | } | 417 | } |
418 | } | 418 | } |
419 | 419 | ||
420 | ast::Expr::Literal(e) => { | 420 | ast::Expr::Literal(e) => self.alloc_expr(Expr::Literal(e.kind().into()), syntax_ptr), |
421 | let lit = match e.kind() { | ||
422 | LiteralKind::IntNumber { suffix } => { | ||
423 | let known_name = suffix.and_then(|it| BuiltinInt::from_suffix(&it)); | ||
424 | |||
425 | Literal::Int(Default::default(), known_name) | ||
426 | } | ||
427 | LiteralKind::FloatNumber { suffix } => { | ||
428 | let known_name = suffix.and_then(|it| BuiltinFloat::from_suffix(&it)); | ||
429 | |||
430 | Literal::Float(Default::default(), known_name) | ||
431 | } | ||
432 | LiteralKind::ByteString => Literal::ByteString(Default::default()), | ||
433 | LiteralKind::String => Literal::String(Default::default()), | ||
434 | LiteralKind::Byte => Literal::Int(Default::default(), Some(BuiltinInt::U8)), | ||
435 | LiteralKind::Bool => Literal::Bool(Default::default()), | ||
436 | LiteralKind::Char => Literal::Char(Default::default()), | ||
437 | }; | ||
438 | self.alloc_expr(Expr::Literal(lit), syntax_ptr) | ||
439 | } | ||
440 | ast::Expr::IndexExpr(e) => { | 421 | ast::Expr::IndexExpr(e) => { |
441 | let base = self.collect_expr_opt(e.base()); | 422 | let base = self.collect_expr_opt(e.base()); |
442 | let index = self.collect_expr_opt(e.index()); | 423 | let index = self.collect_expr_opt(e.index()); |
@@ -679,10 +660,19 @@ impl ExprCollector<'_> { | |||
679 | suffix: suffix.into_iter().map(|p| self.collect_pat(p)).collect(), | 660 | suffix: suffix.into_iter().map(|p| self.collect_pat(p)).collect(), |
680 | } | 661 | } |
681 | } | 662 | } |
663 | ast::Pat::LiteralPat(lit) => { | ||
664 | if let Some(ast_lit) = lit.literal() { | ||
665 | let expr = Expr::Literal(ast_lit.kind().into()); | ||
666 | let expr_ptr = AstPtr::new(&ast::Expr::Literal(ast_lit)); | ||
667 | let expr_id = self.alloc_expr(expr, expr_ptr); | ||
668 | Pat::Lit(expr_id) | ||
669 | } else { | ||
670 | Pat::Missing | ||
671 | } | ||
672 | } | ||
682 | 673 | ||
683 | // FIXME: implement | 674 | // FIXME: implement |
684 | ast::Pat::BoxPat(_) => Pat::Missing, | 675 | ast::Pat::BoxPat(_) => Pat::Missing, |
685 | ast::Pat::LiteralPat(_) => Pat::Missing, | ||
686 | ast::Pat::RangePat(_) => Pat::Missing, | 676 | ast::Pat::RangePat(_) => Pat::Missing, |
687 | }; | 677 | }; |
688 | let ptr = AstPtr::new(&pat); | 678 | let ptr = AstPtr::new(&pat); |
@@ -741,3 +731,25 @@ impl From<ast::BinOp> for BinaryOp { | |||
741 | } | 731 | } |
742 | } | 732 | } |
743 | } | 733 | } |
734 | |||
735 | impl From<ast::LiteralKind> for Literal { | ||
736 | fn from(ast_lit_kind: ast::LiteralKind) -> Self { | ||
737 | match ast_lit_kind { | ||
738 | LiteralKind::IntNumber { suffix } => { | ||
739 | let known_name = suffix.and_then(|it| BuiltinInt::from_suffix(&it)); | ||
740 | |||
741 | Literal::Int(Default::default(), known_name) | ||
742 | } | ||
743 | LiteralKind::FloatNumber { suffix } => { | ||
744 | let known_name = suffix.and_then(|it| BuiltinFloat::from_suffix(&it)); | ||
745 | |||
746 | Literal::Float(Default::default(), known_name) | ||
747 | } | ||
748 | LiteralKind::ByteString => Literal::ByteString(Default::default()), | ||
749 | LiteralKind::String => Literal::String(Default::default()), | ||
750 | LiteralKind::Byte => Literal::Int(Default::default(), Some(BuiltinInt::U8)), | ||
751 | LiteralKind::Bool(val) => Literal::Bool(val), | ||
752 | LiteralKind::Char => Literal::Char(Default::default()), | ||
753 | } | ||
754 | } | ||
755 | } | ||
diff --git a/crates/ra_hir_expand/src/proc_macro.rs b/crates/ra_hir_expand/src/proc_macro.rs index 4d270e0de..97d1208ec 100644 --- a/crates/ra_hir_expand/src/proc_macro.rs +++ b/crates/ra_hir_expand/src/proc_macro.rs | |||
@@ -9,6 +9,15 @@ pub struct ProcMacroExpander { | |||
9 | proc_macro_id: ProcMacroId, | 9 | proc_macro_id: ProcMacroId, |
10 | } | 10 | } |
11 | 11 | ||
12 | macro_rules! err { | ||
13 | ($fmt:literal, $($tt:tt),*) => { | ||
14 | mbe::ExpandError::ProcMacroError(tt::ExpansionError::Unknown(format!($fmt, $($tt),*))) | ||
15 | }; | ||
16 | ($fmt:literal) => { | ||
17 | mbe::ExpandError::ProcMacroError(tt::ExpansionError::Unknown($fmt.to_string())) | ||
18 | } | ||
19 | } | ||
20 | |||
12 | impl ProcMacroExpander { | 21 | impl ProcMacroExpander { |
13 | pub fn new(krate: CrateId, proc_macro_id: ProcMacroId) -> ProcMacroExpander { | 22 | pub fn new(krate: CrateId, proc_macro_id: ProcMacroId) -> ProcMacroExpander { |
14 | ProcMacroExpander { krate, proc_macro_id } | 23 | ProcMacroExpander { krate, proc_macro_id } |
@@ -25,8 +34,24 @@ impl ProcMacroExpander { | |||
25 | .proc_macro | 34 | .proc_macro |
26 | .get(self.proc_macro_id.0 as usize) | 35 | .get(self.proc_macro_id.0 as usize) |
27 | .clone() | 36 | .clone() |
28 | .ok_or_else(|| mbe::ExpandError::ConversionError)?; | 37 | .ok_or_else(|| err!("No derive macro found."))?; |
38 | |||
39 | let tt = remove_derive_atr(tt, &proc_macro.name) | ||
40 | .ok_or_else(|| err!("Fail to remove derive for custom derive"))?; | ||
29 | 41 | ||
30 | proc_macro.expander.expand(&tt, None).map_err(mbe::ExpandError::from) | 42 | proc_macro.expander.expand(&tt, None).map_err(mbe::ExpandError::from) |
31 | } | 43 | } |
32 | } | 44 | } |
45 | |||
46 | fn remove_derive_atr(tt: &tt::Subtree, _name: &str) -> Option<tt::Subtree> { | ||
47 | // FIXME: proper handle the remove derive | ||
48 | // We assume the first 2 tokens are #[derive(name)] | ||
49 | if tt.token_trees.len() > 2 { | ||
50 | let mut tt = tt.clone(); | ||
51 | tt.token_trees.remove(0); | ||
52 | tt.token_trees.remove(0); | ||
53 | return Some(tt); | ||
54 | } | ||
55 | |||
56 | None | ||
57 | } | ||
diff --git a/crates/ra_hir_ty/src/display.rs b/crates/ra_hir_ty/src/display.rs index a6ef44a31..13ecd537a 100644 --- a/crates/ra_hir_ty/src/display.rs +++ b/crates/ra_hir_ty/src/display.rs | |||
@@ -159,20 +159,13 @@ impl HirDisplay for ApplicationTy { | |||
159 | } | 159 | } |
160 | TypeCtor::FnDef(def) => { | 160 | TypeCtor::FnDef(def) => { |
161 | let sig = f.db.callable_item_signature(def).subst(&self.parameters); | 161 | let sig = f.db.callable_item_signature(def).subst(&self.parameters); |
162 | let name = match def { | 162 | match def { |
163 | CallableDef::FunctionId(ff) => f.db.function_data(ff).name.clone(), | 163 | CallableDef::FunctionId(ff) => write!(f, "fn {}", f.db.function_data(ff).name)?, |
164 | CallableDef::StructId(s) => f.db.struct_data(s).name.clone(), | 164 | CallableDef::StructId(s) => write!(f, "{}", f.db.struct_data(s).name)?, |
165 | CallableDef::EnumVariantId(e) => { | 165 | CallableDef::EnumVariantId(e) => { |
166 | let enum_data = f.db.enum_data(e.parent); | 166 | write!(f, "{}", f.db.enum_data(e.parent).variants[e.local_id].name)? |
167 | enum_data.variants[e.local_id].name.clone() | ||
168 | } | 167 | } |
169 | }; | 168 | }; |
170 | match def { | ||
171 | CallableDef::FunctionId(_) => write!(f, "fn {}", name)?, | ||
172 | CallableDef::StructId(_) | CallableDef::EnumVariantId(_) => { | ||
173 | write!(f, "{}", name)? | ||
174 | } | ||
175 | } | ||
176 | if self.parameters.len() > 0 { | 169 | if self.parameters.len() > 0 { |
177 | let generics = generics(f.db.upcast(), def.into()); | 170 | let generics = generics(f.db.upcast(), def.into()); |
178 | let (parent_params, self_param, type_params, _impl_trait_params) = | 171 | let (parent_params, self_param, type_params, _impl_trait_params) = |
@@ -197,8 +190,6 @@ impl HirDisplay for ApplicationTy { | |||
197 | }; | 190 | }; |
198 | write!(f, "{}", name)?; | 191 | write!(f, "{}", name)?; |
199 | if self.parameters.len() > 0 { | 192 | if self.parameters.len() > 0 { |
200 | write!(f, "<")?; | ||
201 | |||
202 | let mut non_default_parameters = Vec::with_capacity(self.parameters.len()); | 193 | let mut non_default_parameters = Vec::with_capacity(self.parameters.len()); |
203 | let parameters_to_write = if f.omit_verbose_types() { | 194 | let parameters_to_write = if f.omit_verbose_types() { |
204 | match self | 195 | match self |
@@ -207,8 +198,8 @@ impl HirDisplay for ApplicationTy { | |||
207 | .map(|generic_def_id| f.db.generic_defaults(generic_def_id)) | 198 | .map(|generic_def_id| f.db.generic_defaults(generic_def_id)) |
208 | .filter(|defaults| !defaults.is_empty()) | 199 | .filter(|defaults| !defaults.is_empty()) |
209 | { | 200 | { |
210 | Option::None => self.parameters.0.as_ref(), | 201 | None => self.parameters.0.as_ref(), |
211 | Option::Some(default_parameters) => { | 202 | Some(default_parameters) => { |
212 | for (i, parameter) in self.parameters.iter().enumerate() { | 203 | for (i, parameter) in self.parameters.iter().enumerate() { |
213 | match (parameter, default_parameters.get(i)) { | 204 | match (parameter, default_parameters.get(i)) { |
214 | (&Ty::Unknown, _) | (_, None) => { | 205 | (&Ty::Unknown, _) | (_, None) => { |
@@ -228,7 +219,7 @@ impl HirDisplay for ApplicationTy { | |||
228 | } else { | 219 | } else { |
229 | self.parameters.0.as_ref() | 220 | self.parameters.0.as_ref() |
230 | }; | 221 | }; |
231 | 222 | write!(f, "<")?; | |
232 | f.write_joined(parameters_to_write, ", ")?; | 223 | f.write_joined(parameters_to_write, ", ")?; |
233 | write!(f, ">")?; | 224 | write!(f, ">")?; |
234 | } | 225 | } |
@@ -238,9 +229,9 @@ impl HirDisplay for ApplicationTy { | |||
238 | AssocContainerId::TraitId(it) => it, | 229 | AssocContainerId::TraitId(it) => it, |
239 | _ => panic!("not an associated type"), | 230 | _ => panic!("not an associated type"), |
240 | }; | 231 | }; |
241 | let trait_name = f.db.trait_data(trait_).name.clone(); | 232 | let trait_ = f.db.trait_data(trait_); |
242 | let name = f.db.type_alias_data(type_alias).name.clone(); | 233 | let type_alias = f.db.type_alias_data(type_alias); |
243 | write!(f, "{}::{}", trait_name, name)?; | 234 | write!(f, "{}::{}", trait_.name, type_alias.name)?; |
244 | if self.parameters.len() > 0 { | 235 | if self.parameters.len() > 0 { |
245 | write!(f, "<")?; | 236 | write!(f, "<")?; |
246 | f.write_joined(&*self.parameters.0, ", ")?; | 237 | f.write_joined(&*self.parameters.0, ", ")?; |
@@ -273,8 +264,8 @@ impl HirDisplay for ProjectionTy { | |||
273 | return write!(f, "{}", TYPE_HINT_TRUNCATION); | 264 | return write!(f, "{}", TYPE_HINT_TRUNCATION); |
274 | } | 265 | } |
275 | 266 | ||
276 | let trait_name = f.db.trait_data(self.trait_(f.db)).name.clone(); | 267 | let trait_ = f.db.trait_data(self.trait_(f.db)); |
277 | write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_name,)?; | 268 | write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_.name)?; |
278 | if self.parameters.len() > 1 { | 269 | if self.parameters.len() > 1 { |
279 | write!(f, "<")?; | 270 | write!(f, "<")?; |
280 | f.write_joined(&self.parameters[1..], ", ")?; | 271 | f.write_joined(&self.parameters[1..], ", ")?; |
@@ -319,7 +310,7 @@ impl HirDisplay for Ty { | |||
319 | Ty::Opaque(_) => write!(f, "impl ")?, | 310 | Ty::Opaque(_) => write!(f, "impl ")?, |
320 | _ => unreachable!(), | 311 | _ => unreachable!(), |
321 | }; | 312 | }; |
322 | write_bounds_like_dyn_trait(&predicates, f)?; | 313 | write_bounds_like_dyn_trait(predicates, f)?; |
323 | } | 314 | } |
324 | Ty::Unknown => write!(f, "{{unknown}}")?, | 315 | Ty::Unknown => write!(f, "{{unknown}}")?, |
325 | Ty::Infer(..) => write!(f, "_")?, | 316 | Ty::Infer(..) => write!(f, "_")?, |
@@ -352,7 +343,7 @@ fn write_bounds_like_dyn_trait( | |||
352 | // We assume that the self type is $0 (i.e. the | 343 | // We assume that the self type is $0 (i.e. the |
353 | // existential) here, which is the only thing that's | 344 | // existential) here, which is the only thing that's |
354 | // possible in actual Rust, and hence don't print it | 345 | // possible in actual Rust, and hence don't print it |
355 | write!(f, "{}", f.db.trait_data(trait_ref.trait_).name.clone())?; | 346 | write!(f, "{}", f.db.trait_data(trait_ref.trait_).name)?; |
356 | if trait_ref.substs.len() > 1 { | 347 | if trait_ref.substs.len() > 1 { |
357 | write!(f, "<")?; | 348 | write!(f, "<")?; |
358 | f.write_joined(&trait_ref.substs[1..], ", ")?; | 349 | f.write_joined(&trait_ref.substs[1..], ", ")?; |
@@ -369,9 +360,8 @@ fn write_bounds_like_dyn_trait( | |||
369 | write!(f, "<")?; | 360 | write!(f, "<")?; |
370 | angle_open = true; | 361 | angle_open = true; |
371 | } | 362 | } |
372 | let name = | 363 | let type_alias = f.db.type_alias_data(projection_pred.projection_ty.associated_ty); |
373 | f.db.type_alias_data(projection_pred.projection_ty.associated_ty).name.clone(); | 364 | write!(f, "{} = ", type_alias.name)?; |
374 | write!(f, "{} = ", name)?; | ||
375 | projection_pred.ty.hir_fmt(f)?; | 365 | projection_pred.ty.hir_fmt(f)?; |
376 | } | 366 | } |
377 | GenericPredicate::Error => { | 367 | GenericPredicate::Error => { |
@@ -405,7 +395,7 @@ impl TraitRef { | |||
405 | } else { | 395 | } else { |
406 | write!(f, ": ")?; | 396 | write!(f, ": ")?; |
407 | } | 397 | } |
408 | write!(f, "{}", f.db.trait_data(self.trait_).name.clone())?; | 398 | write!(f, "{}", f.db.trait_data(self.trait_).name)?; |
409 | if self.substs.len() > 1 { | 399 | if self.substs.len() > 1 { |
410 | write!(f, "<")?; | 400 | write!(f, "<")?; |
411 | f.write_joined(&self.substs[1..], ", ")?; | 401 | f.write_joined(&self.substs[1..], ", ")?; |
diff --git a/crates/ra_hir_ty/src/infer/pat.rs b/crates/ra_hir_ty/src/infer/pat.rs index baed6225b..86acd27f8 100644 --- a/crates/ra_hir_ty/src/infer/pat.rs +++ b/crates/ra_hir_ty/src/infer/pat.rs | |||
@@ -11,7 +11,7 @@ use hir_def::{ | |||
11 | use hir_expand::name::Name; | 11 | use hir_expand::name::Name; |
12 | use test_utils::tested_by; | 12 | use test_utils::tested_by; |
13 | 13 | ||
14 | use super::{BindingMode, InferenceContext}; | 14 | use super::{BindingMode, Expectation, InferenceContext}; |
15 | use crate::{utils::variant_data, Substs, Ty, TypeCtor}; | 15 | use crate::{utils::variant_data, Substs, Ty, TypeCtor}; |
16 | 16 | ||
17 | impl<'a> InferenceContext<'a> { | 17 | impl<'a> InferenceContext<'a> { |
@@ -198,7 +198,14 @@ impl<'a> InferenceContext<'a> { | |||
198 | 198 | ||
199 | Ty::apply_one(container_ty, elem_ty) | 199 | Ty::apply_one(container_ty, elem_ty) |
200 | } | 200 | } |
201 | _ => Ty::Unknown, | 201 | Pat::Wild => expected.clone(), |
202 | Pat::Range { start, end } => { | ||
203 | let start_ty = self.infer_expr(*start, &Expectation::has_type(expected.clone())); | ||
204 | let end_ty = self.infer_expr(*end, &Expectation::has_type(start_ty)); | ||
205 | end_ty | ||
206 | } | ||
207 | Pat::Lit(expr) => self.infer_expr(*expr, &Expectation::has_type(expected.clone())), | ||
208 | Pat::Missing => Ty::Unknown, | ||
202 | }; | 209 | }; |
203 | // use a new type variable if we got Ty::Unknown here | 210 | // use a new type variable if we got Ty::Unknown here |
204 | let ty = self.insert_type_vars_shallow(ty); | 211 | let ty = self.insert_type_vars_shallow(ty); |
diff --git a/crates/ra_hir_ty/src/tests/coercion.rs b/crates/ra_hir_ty/src/tests/coercion.rs index 1e303f5ce..3e3d55c04 100644 --- a/crates/ra_hir_ty/src/tests/coercion.rs +++ b/crates/ra_hir_ty/src/tests/coercion.rs | |||
@@ -275,12 +275,14 @@ fn test(i: i32) { | |||
275 | [70; 147) 'match ... }': &[i32] | 275 | [70; 147) 'match ... }': &[i32] |
276 | [76; 77) 'i': i32 | 276 | [76; 77) 'i': i32 |
277 | [88; 89) '2': i32 | 277 | [88; 89) '2': i32 |
278 | [88; 89) '2': i32 | ||
278 | [93; 96) 'foo': fn foo<i32>(&[i32]) -> &[i32] | 279 | [93; 96) 'foo': fn foo<i32>(&[i32]) -> &[i32] |
279 | [93; 102) 'foo(&[2])': &[i32] | 280 | [93; 102) 'foo(&[2])': &[i32] |
280 | [97; 101) '&[2]': &[i32; _] | 281 | [97; 101) '&[2]': &[i32; _] |
281 | [98; 101) '[2]': [i32; _] | 282 | [98; 101) '[2]': [i32; _] |
282 | [99; 100) '2': i32 | 283 | [99; 100) '2': i32 |
283 | [112; 113) '1': i32 | 284 | [112; 113) '1': i32 |
285 | [112; 113) '1': i32 | ||
284 | [117; 121) '&[1]': &[i32; _] | 286 | [117; 121) '&[1]': &[i32; _] |
285 | [118; 121) '[1]': [i32; _] | 287 | [118; 121) '[1]': [i32; _] |
286 | [119; 120) '1': i32 | 288 | [119; 120) '1': i32 |
@@ -316,10 +318,12 @@ fn test(i: i32) { | |||
316 | [70; 147) 'match ... }': &[i32] | 318 | [70; 147) 'match ... }': &[i32] |
317 | [76; 77) 'i': i32 | 319 | [76; 77) 'i': i32 |
318 | [88; 89) '1': i32 | 320 | [88; 89) '1': i32 |
321 | [88; 89) '1': i32 | ||
319 | [93; 97) '&[1]': &[i32; _] | 322 | [93; 97) '&[1]': &[i32; _] |
320 | [94; 97) '[1]': [i32; _] | 323 | [94; 97) '[1]': [i32; _] |
321 | [95; 96) '1': i32 | 324 | [95; 96) '1': i32 |
322 | [107; 108) '2': i32 | 325 | [107; 108) '2': i32 |
326 | [107; 108) '2': i32 | ||
323 | [112; 115) 'foo': fn foo<i32>(&[i32]) -> &[i32] | 327 | [112; 115) 'foo': fn foo<i32>(&[i32]) -> &[i32] |
324 | [112; 121) 'foo(&[2])': &[i32] | 328 | [112; 121) 'foo(&[2])': &[i32] |
325 | [116; 120) '&[2]': &[i32; _] | 329 | [116; 120) '&[2]': &[i32; _] |
@@ -357,9 +361,11 @@ fn test() { | |||
357 | [45; 142) 'match ... }': *const i32 | 361 | [45; 142) 'match ... }': *const i32 |
358 | [51; 52) '1': i32 | 362 | [51; 52) '1': i32 |
359 | [63; 64) '1': i32 | 363 | [63; 64) '1': i32 |
364 | [63; 64) '1': i32 | ||
360 | [68; 69) 't': &mut i32 | 365 | [68; 69) 't': &mut i32 |
361 | [68; 81) 't as *mut i32': *mut i32 | 366 | [68; 81) 't as *mut i32': *mut i32 |
362 | [91; 92) '2': i32 | 367 | [91; 92) '2': i32 |
368 | [91; 92) '2': i32 | ||
363 | [96; 97) 't': &mut i32 | 369 | [96; 97) 't': &mut i32 |
364 | [96; 105) 't as &i32': &i32 | 370 | [96; 105) 't as &i32': &i32 |
365 | [115; 116) '_': i32 | 371 | [115; 116) '_': i32 |
diff --git a/crates/ra_hir_ty/src/tests/patterns.rs b/crates/ra_hir_ty/src/tests/patterns.rs index 76aa32024..6e5d2247c 100644 --- a/crates/ra_hir_ty/src/tests/patterns.rs +++ b/crates/ra_hir_ty/src/tests/patterns.rs | |||
@@ -82,6 +82,90 @@ fn test(x: &i32) { | |||
82 | } | 82 | } |
83 | 83 | ||
84 | #[test] | 84 | #[test] |
85 | fn infer_literal_pattern() { | ||
86 | assert_snapshot!( | ||
87 | infer_with_mismatches(r#" | ||
88 | fn any<T>() -> T { loop {} } | ||
89 | fn test(x: &i32) { | ||
90 | if let "foo" = any() {} | ||
91 | if let 1 = any() {} | ||
92 | if let 1u32 = any() {} | ||
93 | if let 1f32 = any() {} | ||
94 | if let 1.0 = any() {} | ||
95 | if let true = any() {} | ||
96 | } | ||
97 | "#, true), | ||
98 | @r###" | ||
99 | [18; 29) '{ loop {} }': T | ||
100 | [20; 27) 'loop {}': ! | ||
101 | [25; 27) '{}': () | ||
102 | [38; 39) 'x': &i32 | ||
103 | [47; 209) '{ ...) {} }': () | ||
104 | [53; 76) 'if let...y() {}': () | ||
105 | [60; 65) '"foo"': &str | ||
106 | [60; 65) '"foo"': &str | ||
107 | [68; 71) 'any': fn any<&str>() -> &str | ||
108 | [68; 73) 'any()': &str | ||
109 | [74; 76) '{}': () | ||
110 | [81; 100) 'if let...y() {}': () | ||
111 | [88; 89) '1': i32 | ||
112 | [88; 89) '1': i32 | ||
113 | [92; 95) 'any': fn any<i32>() -> i32 | ||
114 | [92; 97) 'any()': i32 | ||
115 | [98; 100) '{}': () | ||
116 | [105; 127) 'if let...y() {}': () | ||
117 | [112; 116) '1u32': u32 | ||
118 | [112; 116) '1u32': u32 | ||
119 | [119; 122) 'any': fn any<u32>() -> u32 | ||
120 | [119; 124) 'any()': u32 | ||
121 | [125; 127) '{}': () | ||
122 | [132; 154) 'if let...y() {}': () | ||
123 | [139; 143) '1f32': f32 | ||
124 | [139; 143) '1f32': f32 | ||
125 | [146; 149) 'any': fn any<f32>() -> f32 | ||
126 | [146; 151) 'any()': f32 | ||
127 | [152; 154) '{}': () | ||
128 | [159; 180) 'if let...y() {}': () | ||
129 | [166; 169) '1.0': f64 | ||
130 | [166; 169) '1.0': f64 | ||
131 | [172; 175) 'any': fn any<f64>() -> f64 | ||
132 | [172; 177) 'any()': f64 | ||
133 | [178; 180) '{}': () | ||
134 | [185; 207) 'if let...y() {}': () | ||
135 | [192; 196) 'true': bool | ||
136 | [192; 196) 'true': bool | ||
137 | [199; 202) 'any': fn any<bool>() -> bool | ||
138 | [199; 204) 'any()': bool | ||
139 | [205; 207) '{}': () | ||
140 | "### | ||
141 | ); | ||
142 | } | ||
143 | |||
144 | #[test] | ||
145 | fn infer_range_pattern() { | ||
146 | assert_snapshot!( | ||
147 | infer_with_mismatches(r#" | ||
148 | fn test(x: &i32) { | ||
149 | if let 1..76 = 2u32 {} | ||
150 | if let 1..=76 = 2u32 {} | ||
151 | } | ||
152 | "#, true), | ||
153 | @r###" | ||
154 | [9; 10) 'x': &i32 | ||
155 | [18; 76) '{ ...2 {} }': () | ||
156 | [24; 46) 'if let...u32 {}': () | ||
157 | [31; 36) '1..76': u32 | ||
158 | [39; 43) '2u32': u32 | ||
159 | [44; 46) '{}': () | ||
160 | [51; 74) 'if let...u32 {}': () | ||
161 | [58; 64) '1..=76': u32 | ||
162 | [67; 71) '2u32': u32 | ||
163 | [72; 74) '{}': () | ||
164 | "### | ||
165 | ); | ||
166 | } | ||
167 | |||
168 | #[test] | ||
85 | fn infer_pattern_match_ergonomics() { | 169 | fn infer_pattern_match_ergonomics() { |
86 | assert_snapshot!( | 170 | assert_snapshot!( |
87 | infer(r#" | 171 | infer(r#" |
@@ -212,6 +296,7 @@ fn test() { | |||
212 | [59; 62) 'arr': [f64; _] | 296 | [59; 62) 'arr': [f64; _] |
213 | [73; 81) '[1.0, a]': [f64; _] | 297 | [73; 81) '[1.0, a]': [f64; _] |
214 | [74; 77) '1.0': f64 | 298 | [74; 77) '1.0': f64 |
299 | [74; 77) '1.0': f64 | ||
215 | [79; 80) 'a': f64 | 300 | [79; 80) 'a': f64 |
216 | [85; 111) '{ ... }': () | 301 | [85; 111) '{ ... }': () |
217 | [99; 100) 'a': f64 | 302 | [99; 100) 'a': f64 |
diff --git a/crates/ra_hir_ty/src/tests/regression.rs b/crates/ra_hir_ty/src/tests/regression.rs index a02e3ee05..2ee9b8f10 100644 --- a/crates/ra_hir_ty/src/tests/regression.rs +++ b/crates/ra_hir_ty/src/tests/regression.rs | |||
@@ -206,7 +206,8 @@ pub fn compute() { | |||
206 | [24; 106) 'match ... }': () | 206 | [24; 106) 'match ... }': () |
207 | [30; 37) 'nope!()': {unknown} | 207 | [30; 37) 'nope!()': {unknown} |
208 | [48; 94) 'SizeSk...tail }': {unknown} | 208 | [48; 94) 'SizeSk...tail }': {unknown} |
209 | [82; 86) 'true': {unknown} | 209 | [82; 86) 'true': bool |
210 | [82; 86) 'true': bool | ||
210 | [88; 92) 'tail': {unknown} | 211 | [88; 92) 'tail': {unknown} |
211 | [98; 100) '{}': () | 212 | [98; 100) '{}': () |
212 | "### | 213 | "### |
diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs index c140bd513..a600b947d 100644 --- a/crates/ra_hir_ty/src/tests/simple.rs +++ b/crates/ra_hir_ty/src/tests/simple.rs | |||
@@ -948,6 +948,7 @@ fn foo() { | |||
948 | [165; 247) 'match ... }': i32 | 948 | [165; 247) 'match ... }': i32 |
949 | [171; 175) 'true': bool | 949 | [171; 175) 'true': bool |
950 | [186; 190) 'true': bool | 950 | [186; 190) 'true': bool |
951 | [186; 190) 'true': bool | ||
951 | [194; 195) '3': i32 | 952 | [194; 195) '3': i32 |
952 | [205; 206) '_': bool | 953 | [205; 206) '_': bool |
953 | [210; 241) '{ ... }': ! | 954 | [210; 241) '{ ... }': ! |
@@ -956,6 +957,7 @@ fn foo() { | |||
956 | [263; 320) 'match ... }': i32 | 957 | [263; 320) 'match ... }': i32 |
957 | [269; 273) 'true': bool | 958 | [269; 273) 'true': bool |
958 | [284; 288) 'true': bool | 959 | [284; 288) 'true': bool |
960 | [284; 288) 'true': bool | ||
959 | [292; 293) '4': i32 | 961 | [292; 293) '4': i32 |
960 | [303; 304) '_': bool | 962 | [303; 304) '_': bool |
961 | [308; 314) 'return': ! | 963 | [308; 314) 'return': ! |
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index cd0757be5..93157bbba 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs | |||
@@ -5,8 +5,7 @@ mod completion_context; | |||
5 | mod presentation; | 5 | mod presentation; |
6 | 6 | ||
7 | mod complete_dot; | 7 | mod complete_dot; |
8 | mod complete_record_literal; | 8 | mod complete_record; |
9 | mod complete_record_pattern; | ||
10 | mod complete_pattern; | 9 | mod complete_pattern; |
11 | mod complete_fn_param; | 10 | mod complete_fn_param; |
12 | mod complete_keyword; | 11 | mod complete_keyword; |
@@ -34,15 +33,15 @@ pub use crate::completion::completion_item::{ | |||
34 | }; | 33 | }; |
35 | 34 | ||
36 | #[derive(Clone, Debug, PartialEq, Eq)] | 35 | #[derive(Clone, Debug, PartialEq, Eq)] |
37 | pub struct CompletionOptions { | 36 | pub struct CompletionConfig { |
38 | pub enable_postfix_completions: bool, | 37 | pub enable_postfix_completions: bool, |
39 | pub add_call_parenthesis: bool, | 38 | pub add_call_parenthesis: bool, |
40 | pub add_call_argument_snippets: bool, | 39 | pub add_call_argument_snippets: bool, |
41 | } | 40 | } |
42 | 41 | ||
43 | impl Default for CompletionOptions { | 42 | impl Default for CompletionConfig { |
44 | fn default() -> Self { | 43 | fn default() -> Self { |
45 | CompletionOptions { | 44 | CompletionConfig { |
46 | enable_postfix_completions: true, | 45 | enable_postfix_completions: true, |
47 | add_call_parenthesis: true, | 46 | add_call_parenthesis: true, |
48 | add_call_argument_snippets: true, | 47 | add_call_argument_snippets: true, |
@@ -75,9 +74,9 @@ impl Default for CompletionOptions { | |||
75 | pub(crate) fn completions( | 74 | pub(crate) fn completions( |
76 | db: &RootDatabase, | 75 | db: &RootDatabase, |
77 | position: FilePosition, | 76 | position: FilePosition, |
78 | options: &CompletionOptions, | 77 | config: &CompletionConfig, |
79 | ) -> Option<Completions> { | 78 | ) -> Option<Completions> { |
80 | let ctx = CompletionContext::new(db, position, options)?; | 79 | let ctx = CompletionContext::new(db, position, config)?; |
81 | 80 | ||
82 | let mut acc = Completions::default(); | 81 | let mut acc = Completions::default(); |
83 | 82 | ||
@@ -89,8 +88,7 @@ pub(crate) fn completions( | |||
89 | complete_path::complete_path(&mut acc, &ctx); | 88 | complete_path::complete_path(&mut acc, &ctx); |
90 | complete_scope::complete_scope(&mut acc, &ctx); | 89 | complete_scope::complete_scope(&mut acc, &ctx); |
91 | complete_dot::complete_dot(&mut acc, &ctx); | 90 | complete_dot::complete_dot(&mut acc, &ctx); |
92 | complete_record_literal::complete_record_literal(&mut acc, &ctx); | 91 | complete_record::complete_record(&mut acc, &ctx); |
93 | complete_record_pattern::complete_record_pattern(&mut acc, &ctx); | ||
94 | complete_pattern::complete_pattern(&mut acc, &ctx); | 92 | complete_pattern::complete_pattern(&mut acc, &ctx); |
95 | complete_postfix::complete_postfix(&mut acc, &ctx); | 93 | complete_postfix::complete_postfix(&mut acc, &ctx); |
96 | complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); | 94 | complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); |
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index 0a00054b2..29c2881c6 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs | |||
@@ -15,7 +15,7 @@ use crate::{ | |||
15 | }; | 15 | }; |
16 | 16 | ||
17 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | 17 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { |
18 | if !ctx.options.enable_postfix_completions { | 18 | if !ctx.config.enable_postfix_completions { |
19 | return; | 19 | return; |
20 | } | 20 | } |
21 | 21 | ||
diff --git a/crates/ra_ide/src/completion/complete_record.rs b/crates/ra_ide/src/completion/complete_record.rs new file mode 100644 index 000000000..01dd8c6db --- /dev/null +++ b/crates/ra_ide/src/completion/complete_record.rs | |||
@@ -0,0 +1,411 @@ | |||
1 | //! Complete fields in record literals and patterns. | ||
2 | use crate::completion::{CompletionContext, Completions}; | ||
3 | use ra_syntax::{ast, ast::NameOwner, SmolStr}; | ||
4 | |||
5 | pub(super) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | ||
6 | let (ty, variant, already_present_fields) = | ||
7 | match (ctx.record_lit_pat.as_ref(), ctx.record_lit_syntax.as_ref()) { | ||
8 | (None, None) => return None, | ||
9 | (Some(_), Some(_)) => panic!("A record cannot be both a literal and a pattern"), | ||
10 | (Some(record_pat), _) => ( | ||
11 | ctx.sema.type_of_pat(&record_pat.clone().into())?, | ||
12 | ctx.sema.resolve_record_pattern(record_pat)?, | ||
13 | pattern_ascribed_fields(record_pat), | ||
14 | ), | ||
15 | (_, Some(record_lit)) => ( | ||
16 | ctx.sema.type_of_expr(&record_lit.clone().into())?, | ||
17 | ctx.sema.resolve_record_literal(record_lit)?, | ||
18 | literal_ascribed_fields(record_lit), | ||
19 | ), | ||
20 | }; | ||
21 | |||
22 | for (field, field_ty) in ty.variant_fields(ctx.db, variant).into_iter().filter(|(field, _)| { | ||
23 | // FIXME: already_present_names better be `Vec<hir::Name>` | ||
24 | !already_present_fields.contains(&SmolStr::from(field.name(ctx.db).to_string())) | ||
25 | }) { | ||
26 | acc.add_field(ctx, field, &field_ty); | ||
27 | } | ||
28 | Some(()) | ||
29 | } | ||
30 | |||
31 | fn literal_ascribed_fields(record_lit: &ast::RecordLit) -> Vec<SmolStr> { | ||
32 | record_lit | ||
33 | .record_field_list() | ||
34 | .map(|field_list| field_list.fields()) | ||
35 | .map(|fields| { | ||
36 | fields | ||
37 | .into_iter() | ||
38 | .filter_map(|field| field.name_ref()) | ||
39 | .map(|name_ref| name_ref.text().clone()) | ||
40 | .collect() | ||
41 | }) | ||
42 | .unwrap_or_default() | ||
43 | } | ||
44 | |||
45 | fn pattern_ascribed_fields(record_pat: &ast::RecordPat) -> Vec<SmolStr> { | ||
46 | record_pat | ||
47 | .record_field_pat_list() | ||
48 | .map(|pat_list| { | ||
49 | pat_list | ||
50 | .record_field_pats() | ||
51 | .filter_map(|fild_pat| fild_pat.name()) | ||
52 | .chain(pat_list.bind_pats().filter_map(|bind_pat| bind_pat.name())) | ||
53 | .map(|name| name.text().clone()) | ||
54 | .collect() | ||
55 | }) | ||
56 | .unwrap_or_default() | ||
57 | } | ||
58 | |||
59 | #[cfg(test)] | ||
60 | mod tests { | ||
61 | mod record_lit_tests { | ||
62 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; | ||
63 | use insta::assert_debug_snapshot; | ||
64 | |||
65 | fn complete(code: &str) -> Vec<CompletionItem> { | ||
66 | do_completion(code, CompletionKind::Reference) | ||
67 | } | ||
68 | |||
69 | #[test] | ||
70 | fn test_record_pattern_field() { | ||
71 | let completions = complete( | ||
72 | r" | ||
73 | struct S { foo: u32 } | ||
74 | |||
75 | fn process(f: S) { | ||
76 | match f { | ||
77 | S { f<|>: 92 } => (), | ||
78 | } | ||
79 | } | ||
80 | ", | ||
81 | ); | ||
82 | assert_debug_snapshot!(completions, @r###" | ||
83 | [ | ||
84 | CompletionItem { | ||
85 | label: "foo", | ||
86 | source_range: [117; 118), | ||
87 | delete: [117; 118), | ||
88 | insert: "foo", | ||
89 | kind: Field, | ||
90 | detail: "u32", | ||
91 | }, | ||
92 | ] | ||
93 | "###); | ||
94 | } | ||
95 | |||
96 | #[test] | ||
97 | fn test_record_pattern_enum_variant() { | ||
98 | let completions = complete( | ||
99 | r" | ||
100 | enum E { | ||
101 | S { foo: u32, bar: () } | ||
102 | } | ||
103 | |||
104 | fn process(e: E) { | ||
105 | match e { | ||
106 | E::S { <|> } => (), | ||
107 | } | ||
108 | } | ||
109 | ", | ||
110 | ); | ||
111 | assert_debug_snapshot!(completions, @r###" | ||
112 | [ | ||
113 | CompletionItem { | ||
114 | label: "bar", | ||
115 | source_range: [161; 161), | ||
116 | delete: [161; 161), | ||
117 | insert: "bar", | ||
118 | kind: Field, | ||
119 | detail: "()", | ||
120 | }, | ||
121 | CompletionItem { | ||
122 | label: "foo", | ||
123 | source_range: [161; 161), | ||
124 | delete: [161; 161), | ||
125 | insert: "foo", | ||
126 | kind: Field, | ||
127 | detail: "u32", | ||
128 | }, | ||
129 | ] | ||
130 | "###); | ||
131 | } | ||
132 | |||
133 | #[test] | ||
134 | fn test_record_pattern_field_in_simple_macro() { | ||
135 | let completions = complete( | ||
136 | r" | ||
137 | macro_rules! m { ($e:expr) => { $e } } | ||
138 | struct S { foo: u32 } | ||
139 | |||
140 | fn process(f: S) { | ||
141 | m!(match f { | ||
142 | S { f<|>: 92 } => (), | ||
143 | }) | ||
144 | } | ||
145 | ", | ||
146 | ); | ||
147 | assert_debug_snapshot!(completions, @r###" | ||
148 | [ | ||
149 | CompletionItem { | ||
150 | label: "foo", | ||
151 | source_range: [171; 172), | ||
152 | delete: [171; 172), | ||
153 | insert: "foo", | ||
154 | kind: Field, | ||
155 | detail: "u32", | ||
156 | }, | ||
157 | ] | ||
158 | "###); | ||
159 | } | ||
160 | |||
161 | #[test] | ||
162 | fn only_missing_fields_are_completed_in_destruct_pats() { | ||
163 | let completions = complete( | ||
164 | r" | ||
165 | struct S { | ||
166 | foo1: u32, | ||
167 | foo2: u32, | ||
168 | bar: u32, | ||
169 | baz: u32, | ||
170 | } | ||
171 | |||
172 | fn main() { | ||
173 | let s = S { | ||
174 | foo1: 1, | ||
175 | foo2: 2, | ||
176 | bar: 3, | ||
177 | baz: 4, | ||
178 | }; | ||
179 | if let S { foo1, foo2: a, <|> } = s {} | ||
180 | } | ||
181 | ", | ||
182 | ); | ||
183 | assert_debug_snapshot!(completions, @r###" | ||
184 | [ | ||
185 | CompletionItem { | ||
186 | label: "bar", | ||
187 | source_range: [372; 372), | ||
188 | delete: [372; 372), | ||
189 | insert: "bar", | ||
190 | kind: Field, | ||
191 | detail: "u32", | ||
192 | }, | ||
193 | CompletionItem { | ||
194 | label: "baz", | ||
195 | source_range: [372; 372), | ||
196 | delete: [372; 372), | ||
197 | insert: "baz", | ||
198 | kind: Field, | ||
199 | detail: "u32", | ||
200 | }, | ||
201 | ] | ||
202 | "###); | ||
203 | } | ||
204 | } | ||
205 | |||
206 | mod record_pat_tests { | ||
207 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; | ||
208 | use insta::assert_debug_snapshot; | ||
209 | |||
210 | fn complete(code: &str) -> Vec<CompletionItem> { | ||
211 | do_completion(code, CompletionKind::Reference) | ||
212 | } | ||
213 | |||
214 | #[test] | ||
215 | fn test_record_literal_deprecated_field() { | ||
216 | let completions = complete( | ||
217 | r" | ||
218 | struct A { | ||
219 | #[deprecated] | ||
220 | the_field: u32, | ||
221 | } | ||
222 | fn foo() { | ||
223 | A { the<|> } | ||
224 | } | ||
225 | ", | ||
226 | ); | ||
227 | assert_debug_snapshot!(completions, @r###" | ||
228 | [ | ||
229 | CompletionItem { | ||
230 | label: "the_field", | ||
231 | source_range: [142; 145), | ||
232 | delete: [142; 145), | ||
233 | insert: "the_field", | ||
234 | kind: Field, | ||
235 | detail: "u32", | ||
236 | deprecated: true, | ||
237 | }, | ||
238 | ] | ||
239 | "###); | ||
240 | } | ||
241 | |||
242 | #[test] | ||
243 | fn test_record_literal_field() { | ||
244 | let completions = complete( | ||
245 | r" | ||
246 | struct A { the_field: u32 } | ||
247 | fn foo() { | ||
248 | A { the<|> } | ||
249 | } | ||
250 | ", | ||
251 | ); | ||
252 | assert_debug_snapshot!(completions, @r###" | ||
253 | [ | ||
254 | CompletionItem { | ||
255 | label: "the_field", | ||
256 | source_range: [83; 86), | ||
257 | delete: [83; 86), | ||
258 | insert: "the_field", | ||
259 | kind: Field, | ||
260 | detail: "u32", | ||
261 | }, | ||
262 | ] | ||
263 | "###); | ||
264 | } | ||
265 | |||
266 | #[test] | ||
267 | fn test_record_literal_enum_variant() { | ||
268 | let completions = complete( | ||
269 | r" | ||
270 | enum E { | ||
271 | A { a: u32 } | ||
272 | } | ||
273 | fn foo() { | ||
274 | let _ = E::A { <|> } | ||
275 | } | ||
276 | ", | ||
277 | ); | ||
278 | assert_debug_snapshot!(completions, @r###" | ||
279 | [ | ||
280 | CompletionItem { | ||
281 | label: "a", | ||
282 | source_range: [119; 119), | ||
283 | delete: [119; 119), | ||
284 | insert: "a", | ||
285 | kind: Field, | ||
286 | detail: "u32", | ||
287 | }, | ||
288 | ] | ||
289 | "###); | ||
290 | } | ||
291 | |||
292 | #[test] | ||
293 | fn test_record_literal_two_structs() { | ||
294 | let completions = complete( | ||
295 | r" | ||
296 | struct A { a: u32 } | ||
297 | struct B { b: u32 } | ||
298 | |||
299 | fn foo() { | ||
300 | let _: A = B { <|> } | ||
301 | } | ||
302 | ", | ||
303 | ); | ||
304 | assert_debug_snapshot!(completions, @r###" | ||
305 | [ | ||
306 | CompletionItem { | ||
307 | label: "b", | ||
308 | source_range: [119; 119), | ||
309 | delete: [119; 119), | ||
310 | insert: "b", | ||
311 | kind: Field, | ||
312 | detail: "u32", | ||
313 | }, | ||
314 | ] | ||
315 | "###); | ||
316 | } | ||
317 | |||
318 | #[test] | ||
319 | fn test_record_literal_generic_struct() { | ||
320 | let completions = complete( | ||
321 | r" | ||
322 | struct A<T> { a: T } | ||
323 | |||
324 | fn foo() { | ||
325 | let _: A<u32> = A { <|> } | ||
326 | } | ||
327 | ", | ||
328 | ); | ||
329 | assert_debug_snapshot!(completions, @r###" | ||
330 | [ | ||
331 | CompletionItem { | ||
332 | label: "a", | ||
333 | source_range: [93; 93), | ||
334 | delete: [93; 93), | ||
335 | insert: "a", | ||
336 | kind: Field, | ||
337 | detail: "u32", | ||
338 | }, | ||
339 | ] | ||
340 | "###); | ||
341 | } | ||
342 | |||
343 | #[test] | ||
344 | fn test_record_literal_field_in_simple_macro() { | ||
345 | let completions = complete( | ||
346 | r" | ||
347 | macro_rules! m { ($e:expr) => { $e } } | ||
348 | struct A { the_field: u32 } | ||
349 | fn foo() { | ||
350 | m!(A { the<|> }) | ||
351 | } | ||
352 | ", | ||
353 | ); | ||
354 | assert_debug_snapshot!(completions, @r###" | ||
355 | [ | ||
356 | CompletionItem { | ||
357 | label: "the_field", | ||
358 | source_range: [137; 140), | ||
359 | delete: [137; 140), | ||
360 | insert: "the_field", | ||
361 | kind: Field, | ||
362 | detail: "u32", | ||
363 | }, | ||
364 | ] | ||
365 | "###); | ||
366 | } | ||
367 | |||
368 | #[test] | ||
369 | fn only_missing_fields_are_completed() { | ||
370 | let completions = complete( | ||
371 | r" | ||
372 | struct S { | ||
373 | foo1: u32, | ||
374 | foo2: u32, | ||
375 | bar: u32, | ||
376 | baz: u32, | ||
377 | } | ||
378 | |||
379 | fn main() { | ||
380 | let foo1 = 1; | ||
381 | let s = S { | ||
382 | foo1, | ||
383 | foo2: 5, | ||
384 | <|> | ||
385 | } | ||
386 | } | ||
387 | ", | ||
388 | ); | ||
389 | assert_debug_snapshot!(completions, @r###" | ||
390 | [ | ||
391 | CompletionItem { | ||
392 | label: "bar", | ||
393 | source_range: [302; 302), | ||
394 | delete: [302; 302), | ||
395 | insert: "bar", | ||
396 | kind: Field, | ||
397 | detail: "u32", | ||
398 | }, | ||
399 | CompletionItem { | ||
400 | label: "baz", | ||
401 | source_range: [302; 302), | ||
402 | delete: [302; 302), | ||
403 | insert: "baz", | ||
404 | kind: Field, | ||
405 | detail: "u32", | ||
406 | }, | ||
407 | ] | ||
408 | "###); | ||
409 | } | ||
410 | } | ||
411 | } | ||
diff --git a/crates/ra_ide/src/completion/complete_record_literal.rs b/crates/ra_ide/src/completion/complete_record_literal.rs deleted file mode 100644 index e4e764f58..000000000 --- a/crates/ra_ide/src/completion/complete_record_literal.rs +++ /dev/null | |||
@@ -1,241 +0,0 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::completion::{CompletionContext, Completions}; | ||
4 | use ra_syntax::SmolStr; | ||
5 | |||
6 | /// Complete fields in fields literals. | ||
7 | pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionContext) { | ||
8 | let (ty, variant) = match ctx.record_lit_syntax.as_ref().and_then(|it| { | ||
9 | Some((ctx.sema.type_of_expr(&it.clone().into())?, ctx.sema.resolve_record_literal(it)?)) | ||
10 | }) { | ||
11 | Some(it) => it, | ||
12 | _ => return, | ||
13 | }; | ||
14 | |||
15 | let already_present_names: Vec<SmolStr> = ctx | ||
16 | .record_lit_syntax | ||
17 | .as_ref() | ||
18 | .and_then(|record_literal| record_literal.record_field_list()) | ||
19 | .map(|field_list| field_list.fields()) | ||
20 | .map(|fields| { | ||
21 | fields | ||
22 | .into_iter() | ||
23 | .filter_map(|field| field.name_ref()) | ||
24 | .map(|name_ref| name_ref.text().clone()) | ||
25 | .collect() | ||
26 | }) | ||
27 | .unwrap_or_default(); | ||
28 | |||
29 | for (field, field_ty) in ty.variant_fields(ctx.db, variant) { | ||
30 | if !already_present_names.contains(&SmolStr::from(field.name(ctx.db).to_string())) { | ||
31 | acc.add_field(ctx, field, &field_ty); | ||
32 | } | ||
33 | } | ||
34 | } | ||
35 | |||
36 | #[cfg(test)] | ||
37 | mod tests { | ||
38 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; | ||
39 | use insta::assert_debug_snapshot; | ||
40 | |||
41 | fn complete(code: &str) -> Vec<CompletionItem> { | ||
42 | do_completion(code, CompletionKind::Reference) | ||
43 | } | ||
44 | |||
45 | #[test] | ||
46 | fn test_record_literal_deprecated_field() { | ||
47 | let completions = complete( | ||
48 | r" | ||
49 | struct A { | ||
50 | #[deprecated] | ||
51 | the_field: u32, | ||
52 | } | ||
53 | fn foo() { | ||
54 | A { the<|> } | ||
55 | } | ||
56 | ", | ||
57 | ); | ||
58 | assert_debug_snapshot!(completions, @r###" | ||
59 | [ | ||
60 | CompletionItem { | ||
61 | label: "the_field", | ||
62 | source_range: [142; 145), | ||
63 | delete: [142; 145), | ||
64 | insert: "the_field", | ||
65 | kind: Field, | ||
66 | detail: "u32", | ||
67 | deprecated: true, | ||
68 | }, | ||
69 | ] | ||
70 | "###); | ||
71 | } | ||
72 | |||
73 | #[test] | ||
74 | fn test_record_literal_field() { | ||
75 | let completions = complete( | ||
76 | r" | ||
77 | struct A { the_field: u32 } | ||
78 | fn foo() { | ||
79 | A { the<|> } | ||
80 | } | ||
81 | ", | ||
82 | ); | ||
83 | assert_debug_snapshot!(completions, @r###" | ||
84 | [ | ||
85 | CompletionItem { | ||
86 | label: "the_field", | ||
87 | source_range: [83; 86), | ||
88 | delete: [83; 86), | ||
89 | insert: "the_field", | ||
90 | kind: Field, | ||
91 | detail: "u32", | ||
92 | }, | ||
93 | ] | ||
94 | "###); | ||
95 | } | ||
96 | |||
97 | #[test] | ||
98 | fn test_record_literal_enum_variant() { | ||
99 | let completions = complete( | ||
100 | r" | ||
101 | enum E { | ||
102 | A { a: u32 } | ||
103 | } | ||
104 | fn foo() { | ||
105 | let _ = E::A { <|> } | ||
106 | } | ||
107 | ", | ||
108 | ); | ||
109 | assert_debug_snapshot!(completions, @r###" | ||
110 | [ | ||
111 | CompletionItem { | ||
112 | label: "a", | ||
113 | source_range: [119; 119), | ||
114 | delete: [119; 119), | ||
115 | insert: "a", | ||
116 | kind: Field, | ||
117 | detail: "u32", | ||
118 | }, | ||
119 | ] | ||
120 | "###); | ||
121 | } | ||
122 | |||
123 | #[test] | ||
124 | fn test_record_literal_two_structs() { | ||
125 | let completions = complete( | ||
126 | r" | ||
127 | struct A { a: u32 } | ||
128 | struct B { b: u32 } | ||
129 | |||
130 | fn foo() { | ||
131 | let _: A = B { <|> } | ||
132 | } | ||
133 | ", | ||
134 | ); | ||
135 | assert_debug_snapshot!(completions, @r###" | ||
136 | [ | ||
137 | CompletionItem { | ||
138 | label: "b", | ||
139 | source_range: [119; 119), | ||
140 | delete: [119; 119), | ||
141 | insert: "b", | ||
142 | kind: Field, | ||
143 | detail: "u32", | ||
144 | }, | ||
145 | ] | ||
146 | "###); | ||
147 | } | ||
148 | |||
149 | #[test] | ||
150 | fn test_record_literal_generic_struct() { | ||
151 | let completions = complete( | ||
152 | r" | ||
153 | struct A<T> { a: T } | ||
154 | |||
155 | fn foo() { | ||
156 | let _: A<u32> = A { <|> } | ||
157 | } | ||
158 | ", | ||
159 | ); | ||
160 | assert_debug_snapshot!(completions, @r###" | ||
161 | [ | ||
162 | CompletionItem { | ||
163 | label: "a", | ||
164 | source_range: [93; 93), | ||
165 | delete: [93; 93), | ||
166 | insert: "a", | ||
167 | kind: Field, | ||
168 | detail: "u32", | ||
169 | }, | ||
170 | ] | ||
171 | "###); | ||
172 | } | ||
173 | |||
174 | #[test] | ||
175 | fn test_record_literal_field_in_simple_macro() { | ||
176 | let completions = complete( | ||
177 | r" | ||
178 | macro_rules! m { ($e:expr) => { $e } } | ||
179 | struct A { the_field: u32 } | ||
180 | fn foo() { | ||
181 | m!(A { the<|> }) | ||
182 | } | ||
183 | ", | ||
184 | ); | ||
185 | assert_debug_snapshot!(completions, @r###" | ||
186 | [ | ||
187 | CompletionItem { | ||
188 | label: "the_field", | ||
189 | source_range: [137; 140), | ||
190 | delete: [137; 140), | ||
191 | insert: "the_field", | ||
192 | kind: Field, | ||
193 | detail: "u32", | ||
194 | }, | ||
195 | ] | ||
196 | "###); | ||
197 | } | ||
198 | |||
199 | #[test] | ||
200 | fn only_missing_fields_are_completed() { | ||
201 | let completions = complete( | ||
202 | r" | ||
203 | struct S { | ||
204 | foo1: u32, | ||
205 | foo2: u32, | ||
206 | bar: u32, | ||
207 | baz: u32, | ||
208 | } | ||
209 | |||
210 | fn main() { | ||
211 | let foo1 = 1; | ||
212 | let s = S { | ||
213 | foo1, | ||
214 | foo2: 5, | ||
215 | <|> | ||
216 | } | ||
217 | } | ||
218 | ", | ||
219 | ); | ||
220 | assert_debug_snapshot!(completions, @r###" | ||
221 | [ | ||
222 | CompletionItem { | ||
223 | label: "bar", | ||
224 | source_range: [302; 302), | ||
225 | delete: [302; 302), | ||
226 | insert: "bar", | ||
227 | kind: Field, | ||
228 | detail: "u32", | ||
229 | }, | ||
230 | CompletionItem { | ||
231 | label: "baz", | ||
232 | source_range: [302; 302), | ||
233 | delete: [302; 302), | ||
234 | insert: "baz", | ||
235 | kind: Field, | ||
236 | detail: "u32", | ||
237 | }, | ||
238 | ] | ||
239 | "###); | ||
240 | } | ||
241 | } | ||
diff --git a/crates/ra_ide/src/completion/complete_record_pattern.rs b/crates/ra_ide/src/completion/complete_record_pattern.rs deleted file mode 100644 index 962376428..000000000 --- a/crates/ra_ide/src/completion/complete_record_pattern.rs +++ /dev/null | |||
@@ -1,118 +0,0 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::completion::{CompletionContext, Completions}; | ||
4 | |||
5 | pub(super) fn complete_record_pattern(acc: &mut Completions, ctx: &CompletionContext) { | ||
6 | let (ty, variant) = match ctx.record_lit_pat.as_ref().and_then(|it| { | ||
7 | Some((ctx.sema.type_of_pat(&it.clone().into())?, ctx.sema.resolve_record_pattern(it)?)) | ||
8 | }) { | ||
9 | Some(it) => it, | ||
10 | _ => return, | ||
11 | }; | ||
12 | |||
13 | for (field, field_ty) in ty.variant_fields(ctx.db, variant) { | ||
14 | acc.add_field(ctx, field, &field_ty); | ||
15 | } | ||
16 | } | ||
17 | |||
18 | #[cfg(test)] | ||
19 | mod tests { | ||
20 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; | ||
21 | use insta::assert_debug_snapshot; | ||
22 | |||
23 | fn complete(code: &str) -> Vec<CompletionItem> { | ||
24 | do_completion(code, CompletionKind::Reference) | ||
25 | } | ||
26 | |||
27 | #[test] | ||
28 | fn test_record_pattern_field() { | ||
29 | let completions = complete( | ||
30 | r" | ||
31 | struct S { foo: u32 } | ||
32 | |||
33 | fn process(f: S) { | ||
34 | match f { | ||
35 | S { f<|>: 92 } => (), | ||
36 | } | ||
37 | } | ||
38 | ", | ||
39 | ); | ||
40 | assert_debug_snapshot!(completions, @r###" | ||
41 | [ | ||
42 | CompletionItem { | ||
43 | label: "foo", | ||
44 | source_range: [117; 118), | ||
45 | delete: [117; 118), | ||
46 | insert: "foo", | ||
47 | kind: Field, | ||
48 | detail: "u32", | ||
49 | }, | ||
50 | ] | ||
51 | "###); | ||
52 | } | ||
53 | |||
54 | #[test] | ||
55 | fn test_record_pattern_enum_variant() { | ||
56 | let completions = complete( | ||
57 | r" | ||
58 | enum E { | ||
59 | S { foo: u32, bar: () } | ||
60 | } | ||
61 | |||
62 | fn process(e: E) { | ||
63 | match e { | ||
64 | E::S { <|> } => (), | ||
65 | } | ||
66 | } | ||
67 | ", | ||
68 | ); | ||
69 | assert_debug_snapshot!(completions, @r###" | ||
70 | [ | ||
71 | CompletionItem { | ||
72 | label: "bar", | ||
73 | source_range: [161; 161), | ||
74 | delete: [161; 161), | ||
75 | insert: "bar", | ||
76 | kind: Field, | ||
77 | detail: "()", | ||
78 | }, | ||
79 | CompletionItem { | ||
80 | label: "foo", | ||
81 | source_range: [161; 161), | ||
82 | delete: [161; 161), | ||
83 | insert: "foo", | ||
84 | kind: Field, | ||
85 | detail: "u32", | ||
86 | }, | ||
87 | ] | ||
88 | "###); | ||
89 | } | ||
90 | |||
91 | #[test] | ||
92 | fn test_record_pattern_field_in_simple_macro() { | ||
93 | let completions = complete( | ||
94 | r" | ||
95 | macro_rules! m { ($e:expr) => { $e } } | ||
96 | struct S { foo: u32 } | ||
97 | |||
98 | fn process(f: S) { | ||
99 | m!(match f { | ||
100 | S { f<|>: 92 } => (), | ||
101 | }) | ||
102 | } | ||
103 | ", | ||
104 | ); | ||
105 | assert_debug_snapshot!(completions, @r###" | ||
106 | [ | ||
107 | CompletionItem { | ||
108 | label: "foo", | ||
109 | source_range: [171; 172), | ||
110 | delete: [171; 172), | ||
111 | insert: "foo", | ||
112 | kind: Field, | ||
113 | detail: "u32", | ||
114 | }, | ||
115 | ] | ||
116 | "###); | ||
117 | } | ||
118 | } | ||
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index 319e33b61..fdc0da2c5 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
@@ -11,7 +11,7 @@ use ra_syntax::{ | |||
11 | }; | 11 | }; |
12 | use ra_text_edit::AtomTextEdit; | 12 | use ra_text_edit::AtomTextEdit; |
13 | 13 | ||
14 | use crate::{completion::CompletionOptions, FilePosition}; | 14 | use crate::{completion::CompletionConfig, FilePosition}; |
15 | 15 | ||
16 | /// `CompletionContext` is created early during completion to figure out, where | 16 | /// `CompletionContext` is created early during completion to figure out, where |
17 | /// exactly is the cursor, syntax-wise. | 17 | /// exactly is the cursor, syntax-wise. |
@@ -19,7 +19,7 @@ use crate::{completion::CompletionOptions, FilePosition}; | |||
19 | pub(crate) struct CompletionContext<'a> { | 19 | pub(crate) struct CompletionContext<'a> { |
20 | pub(super) sema: Semantics<'a, RootDatabase>, | 20 | pub(super) sema: Semantics<'a, RootDatabase>, |
21 | pub(super) db: &'a RootDatabase, | 21 | pub(super) db: &'a RootDatabase, |
22 | pub(super) options: &'a CompletionOptions, | 22 | pub(super) config: &'a CompletionConfig, |
23 | pub(super) offset: TextUnit, | 23 | pub(super) offset: TextUnit, |
24 | /// The token before the cursor, in the original file. | 24 | /// The token before the cursor, in the original file. |
25 | pub(super) original_token: SyntaxToken, | 25 | pub(super) original_token: SyntaxToken, |
@@ -61,7 +61,7 @@ impl<'a> CompletionContext<'a> { | |||
61 | pub(super) fn new( | 61 | pub(super) fn new( |
62 | db: &'a RootDatabase, | 62 | db: &'a RootDatabase, |
63 | position: FilePosition, | 63 | position: FilePosition, |
64 | options: &'a CompletionOptions, | 64 | config: &'a CompletionConfig, |
65 | ) -> Option<CompletionContext<'a>> { | 65 | ) -> Option<CompletionContext<'a>> { |
66 | let sema = Semantics::new(db); | 66 | let sema = Semantics::new(db); |
67 | 67 | ||
@@ -85,7 +85,7 @@ impl<'a> CompletionContext<'a> { | |||
85 | let mut ctx = CompletionContext { | 85 | let mut ctx = CompletionContext { |
86 | sema, | 86 | sema, |
87 | db, | 87 | db, |
88 | options, | 88 | config, |
89 | original_token, | 89 | original_token, |
90 | token, | 90 | token, |
91 | offset: position.offset, | 91 | offset: position.offset, |
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs index 60f1b83f3..1c7c0924d 100644 --- a/crates/ra_ide/src/completion/presentation.rs +++ b/crates/ra_ide/src/completion/presentation.rs | |||
@@ -106,7 +106,7 @@ impl Completions { | |||
106 | }; | 106 | }; |
107 | 107 | ||
108 | // Add `<>` for generic types | 108 | // Add `<>` for generic types |
109 | if ctx.is_path_type && !ctx.has_type_args && ctx.options.add_call_parenthesis { | 109 | if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis { |
110 | let has_non_default_type_params = match resolution { | 110 | let has_non_default_type_params = match resolution { |
111 | ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db), | 111 | ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db), |
112 | ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db), | 112 | ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db), |
@@ -211,14 +211,14 @@ impl Completions { | |||
211 | .detail(function_signature.to_string()); | 211 | .detail(function_signature.to_string()); |
212 | 212 | ||
213 | // If not an import, add parenthesis automatically. | 213 | // If not an import, add parenthesis automatically. |
214 | if ctx.use_item_syntax.is_none() && !ctx.is_call && ctx.options.add_call_parenthesis { | 214 | if ctx.use_item_syntax.is_none() && !ctx.is_call && ctx.config.add_call_parenthesis { |
215 | tested_by!(inserts_parens_for_function_calls); | 215 | tested_by!(inserts_parens_for_function_calls); |
216 | 216 | ||
217 | let (snippet, label) = if params.is_empty() || has_self_param && params.len() == 1 { | 217 | let (snippet, label) = if params.is_empty() || has_self_param && params.len() == 1 { |
218 | (format!("{}()$0", name), format!("{}()", name)) | 218 | (format!("{}()$0", name), format!("{}()", name)) |
219 | } else { | 219 | } else { |
220 | builder = builder.trigger_call_info(); | 220 | builder = builder.trigger_call_info(); |
221 | let snippet = if ctx.options.add_call_argument_snippets { | 221 | let snippet = if ctx.config.add_call_argument_snippets { |
222 | let to_skip = if has_self_param { 1 } else { 0 }; | 222 | let to_skip = if has_self_param { 1 } else { 0 }; |
223 | let function_params_snippet = function_signature | 223 | let function_params_snippet = function_signature |
224 | .parameter_names | 224 | .parameter_names |
@@ -311,7 +311,7 @@ mod tests { | |||
311 | 311 | ||
312 | use crate::completion::{ | 312 | use crate::completion::{ |
313 | test_utils::{do_completion, do_completion_with_options}, | 313 | test_utils::{do_completion, do_completion_with_options}, |
314 | CompletionItem, CompletionKind, CompletionOptions, | 314 | CompletionConfig, CompletionItem, CompletionKind, |
315 | }; | 315 | }; |
316 | 316 | ||
317 | fn do_reference_completion(ra_fixture: &str) -> Vec<CompletionItem> { | 317 | fn do_reference_completion(ra_fixture: &str) -> Vec<CompletionItem> { |
@@ -320,7 +320,7 @@ mod tests { | |||
320 | 320 | ||
321 | fn do_reference_completion_with_options( | 321 | fn do_reference_completion_with_options( |
322 | ra_fixture: &str, | 322 | ra_fixture: &str, |
323 | options: CompletionOptions, | 323 | options: CompletionConfig, |
324 | ) -> Vec<CompletionItem> { | 324 | ) -> Vec<CompletionItem> { |
325 | do_completion_with_options(ra_fixture, CompletionKind::Reference, &options) | 325 | do_completion_with_options(ra_fixture, CompletionKind::Reference, &options) |
326 | } | 326 | } |
@@ -589,7 +589,7 @@ mod tests { | |||
589 | s.f<|> | 589 | s.f<|> |
590 | } | 590 | } |
591 | ", | 591 | ", |
592 | CompletionOptions { | 592 | CompletionConfig { |
593 | add_call_argument_snippets: false, | 593 | add_call_argument_snippets: false, |
594 | .. Default::default() | 594 | .. Default::default() |
595 | } | 595 | } |
diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs index 136857315..eb90b5279 100644 --- a/crates/ra_ide/src/completion/test_utils.rs +++ b/crates/ra_ide/src/completion/test_utils.rs | |||
@@ -1,19 +1,19 @@ | |||
1 | //! Runs completion for testing purposes. | 1 | //! Runs completion for testing purposes. |
2 | 2 | ||
3 | use crate::{ | 3 | use crate::{ |
4 | completion::{completion_item::CompletionKind, CompletionOptions}, | 4 | completion::{completion_item::CompletionKind, CompletionConfig}, |
5 | mock_analysis::{analysis_and_position, single_file_with_position}, | 5 | mock_analysis::{analysis_and_position, single_file_with_position}, |
6 | CompletionItem, | 6 | CompletionItem, |
7 | }; | 7 | }; |
8 | 8 | ||
9 | pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { | 9 | pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { |
10 | do_completion_with_options(code, kind, &CompletionOptions::default()) | 10 | do_completion_with_options(code, kind, &CompletionConfig::default()) |
11 | } | 11 | } |
12 | 12 | ||
13 | pub(crate) fn do_completion_with_options( | 13 | pub(crate) fn do_completion_with_options( |
14 | code: &str, | 14 | code: &str, |
15 | kind: CompletionKind, | 15 | kind: CompletionKind, |
16 | options: &CompletionOptions, | 16 | options: &CompletionConfig, |
17 | ) -> Vec<CompletionItem> { | 17 | ) -> Vec<CompletionItem> { |
18 | let (analysis, position) = if code.contains("//-") { | 18 | let (analysis, position) = if code.contains("//-") { |
19 | analysis_and_position(code) | 19 | analysis_and_position(code) |
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index f4f0751c0..4b133b19b 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs | |||
@@ -11,14 +11,14 @@ use ra_syntax::{ | |||
11 | use crate::{FileId, FunctionSignature}; | 11 | use crate::{FileId, FunctionSignature}; |
12 | 12 | ||
13 | #[derive(Clone, Debug, PartialEq, Eq)] | 13 | #[derive(Clone, Debug, PartialEq, Eq)] |
14 | pub struct InlayHintsOptions { | 14 | pub struct InlayHintsConfig { |
15 | pub type_hints: bool, | 15 | pub type_hints: bool, |
16 | pub parameter_hints: bool, | 16 | pub parameter_hints: bool, |
17 | pub chaining_hints: bool, | 17 | pub chaining_hints: bool, |
18 | pub max_length: Option<usize>, | 18 | pub max_length: Option<usize>, |
19 | } | 19 | } |
20 | 20 | ||
21 | impl Default for InlayHintsOptions { | 21 | impl Default for InlayHintsConfig { |
22 | fn default() -> Self { | 22 | fn default() -> Self { |
23 | Self { type_hints: true, parameter_hints: true, chaining_hints: true, max_length: None } | 23 | Self { type_hints: true, parameter_hints: true, chaining_hints: true, max_length: None } |
24 | } | 24 | } |
@@ -41,7 +41,7 @@ pub struct InlayHint { | |||
41 | pub(crate) fn inlay_hints( | 41 | pub(crate) fn inlay_hints( |
42 | db: &RootDatabase, | 42 | db: &RootDatabase, |
43 | file_id: FileId, | 43 | file_id: FileId, |
44 | options: &InlayHintsOptions, | 44 | config: &InlayHintsConfig, |
45 | ) -> Vec<InlayHint> { | 45 | ) -> Vec<InlayHint> { |
46 | let _p = profile("inlay_hints"); | 46 | let _p = profile("inlay_hints"); |
47 | let sema = Semantics::new(db); | 47 | let sema = Semantics::new(db); |
@@ -50,14 +50,14 @@ pub(crate) fn inlay_hints( | |||
50 | let mut res = Vec::new(); | 50 | let mut res = Vec::new(); |
51 | for node in file.syntax().descendants() { | 51 | for node in file.syntax().descendants() { |
52 | if let Some(expr) = ast::Expr::cast(node.clone()) { | 52 | if let Some(expr) = ast::Expr::cast(node.clone()) { |
53 | get_chaining_hints(&mut res, &sema, options, expr); | 53 | get_chaining_hints(&mut res, &sema, config, expr); |
54 | } | 54 | } |
55 | 55 | ||
56 | match_ast! { | 56 | match_ast! { |
57 | match node { | 57 | match node { |
58 | ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, options, ast::Expr::from(it)); }, | 58 | ast::CallExpr(it) => { get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it)); }, |
59 | ast::MethodCallExpr(it) => { get_param_name_hints(&mut res, &sema, options, ast::Expr::from(it)); }, | 59 | ast::MethodCallExpr(it) => { get_param_name_hints(&mut res, &sema, config, ast::Expr::from(it)); }, |
60 | ast::BindPat(it) => { get_bind_pat_hints(&mut res, &sema, options, it); }, | 60 | ast::BindPat(it) => { get_bind_pat_hints(&mut res, &sema, config, it); }, |
61 | _ => (), | 61 | _ => (), |
62 | } | 62 | } |
63 | } | 63 | } |
@@ -68,15 +68,14 @@ pub(crate) fn inlay_hints( | |||
68 | fn get_chaining_hints( | 68 | fn get_chaining_hints( |
69 | acc: &mut Vec<InlayHint>, | 69 | acc: &mut Vec<InlayHint>, |
70 | sema: &Semantics<RootDatabase>, | 70 | sema: &Semantics<RootDatabase>, |
71 | options: &InlayHintsOptions, | 71 | config: &InlayHintsConfig, |
72 | expr: ast::Expr, | 72 | expr: ast::Expr, |
73 | ) -> Option<()> { | 73 | ) -> Option<()> { |
74 | if !options.chaining_hints { | 74 | if !config.chaining_hints { |
75 | return None; | 75 | return None; |
76 | } | 76 | } |
77 | 77 | ||
78 | let ty = sema.type_of_expr(&expr)?; | 78 | if matches!(expr, ast::Expr::RecordLit(_)) { |
79 | if ty.is_unknown() { | ||
80 | return None; | 79 | return None; |
81 | } | 80 | } |
82 | 81 | ||
@@ -95,7 +94,18 @@ fn get_chaining_hints( | |||
95 | let next = tokens.next()?.kind(); | 94 | let next = tokens.next()?.kind(); |
96 | let next_next = tokens.next()?.kind(); | 95 | let next_next = tokens.next()?.kind(); |
97 | if next == SyntaxKind::WHITESPACE && next_next == SyntaxKind::DOT { | 96 | if next == SyntaxKind::WHITESPACE && next_next == SyntaxKind::DOT { |
98 | let label = ty.display_truncated(sema.db, options.max_length).to_string(); | 97 | let ty = sema.type_of_expr(&expr)?; |
98 | if ty.is_unknown() { | ||
99 | return None; | ||
100 | } | ||
101 | if matches!(expr, ast::Expr::PathExpr(_)) { | ||
102 | if let Some(Adt::Struct(st)) = ty.as_adt() { | ||
103 | if st.fields(sema.db).is_empty() { | ||
104 | return None; | ||
105 | } | ||
106 | } | ||
107 | } | ||
108 | let label = ty.display_truncated(sema.db, config.max_length).to_string(); | ||
99 | acc.push(InlayHint { | 109 | acc.push(InlayHint { |
100 | range: expr.syntax().text_range(), | 110 | range: expr.syntax().text_range(), |
101 | kind: InlayKind::ChainingHint, | 111 | kind: InlayKind::ChainingHint, |
@@ -108,10 +118,10 @@ fn get_chaining_hints( | |||
108 | fn get_param_name_hints( | 118 | fn get_param_name_hints( |
109 | acc: &mut Vec<InlayHint>, | 119 | acc: &mut Vec<InlayHint>, |
110 | sema: &Semantics<RootDatabase>, | 120 | sema: &Semantics<RootDatabase>, |
111 | options: &InlayHintsOptions, | 121 | config: &InlayHintsConfig, |
112 | expr: ast::Expr, | 122 | expr: ast::Expr, |
113 | ) -> Option<()> { | 123 | ) -> Option<()> { |
114 | if !options.parameter_hints { | 124 | if !config.parameter_hints { |
115 | return None; | 125 | return None; |
116 | } | 126 | } |
117 | 127 | ||
@@ -148,10 +158,10 @@ fn get_param_name_hints( | |||
148 | fn get_bind_pat_hints( | 158 | fn get_bind_pat_hints( |
149 | acc: &mut Vec<InlayHint>, | 159 | acc: &mut Vec<InlayHint>, |
150 | sema: &Semantics<RootDatabase>, | 160 | sema: &Semantics<RootDatabase>, |
151 | options: &InlayHintsOptions, | 161 | config: &InlayHintsConfig, |
152 | pat: ast::BindPat, | 162 | pat: ast::BindPat, |
153 | ) -> Option<()> { | 163 | ) -> Option<()> { |
154 | if !options.type_hints { | 164 | if !config.type_hints { |
155 | return None; | 165 | return None; |
156 | } | 166 | } |
157 | 167 | ||
@@ -164,7 +174,7 @@ fn get_bind_pat_hints( | |||
164 | acc.push(InlayHint { | 174 | acc.push(InlayHint { |
165 | range: pat.syntax().text_range(), | 175 | range: pat.syntax().text_range(), |
166 | kind: InlayKind::TypeHint, | 176 | kind: InlayKind::TypeHint, |
167 | label: ty.display_truncated(sema.db, options.max_length).to_string().into(), | 177 | label: ty.display_truncated(sema.db, config.max_length).to_string().into(), |
168 | }); | 178 | }); |
169 | Some(()) | 179 | Some(()) |
170 | } | 180 | } |
@@ -270,7 +280,7 @@ fn get_fn_signature(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option< | |||
270 | 280 | ||
271 | #[cfg(test)] | 281 | #[cfg(test)] |
272 | mod tests { | 282 | mod tests { |
273 | use crate::inlay_hints::InlayHintsOptions; | 283 | use crate::inlay_hints::InlayHintsConfig; |
274 | use insta::assert_debug_snapshot; | 284 | use insta::assert_debug_snapshot; |
275 | 285 | ||
276 | use crate::mock_analysis::single_file; | 286 | use crate::mock_analysis::single_file; |
@@ -284,7 +294,7 @@ mod tests { | |||
284 | let _x = foo(4, 4); | 294 | let _x = foo(4, 4); |
285 | }"#, | 295 | }"#, |
286 | ); | 296 | ); |
287 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: true, type_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###" | 297 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ parameter_hints: true, type_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###" |
288 | [ | 298 | [ |
289 | InlayHint { | 299 | InlayHint { |
290 | range: [106; 107), | 300 | range: [106; 107), |
@@ -308,7 +318,7 @@ mod tests { | |||
308 | let _x = foo(4, 4); | 318 | let _x = foo(4, 4); |
309 | }"#, | 319 | }"#, |
310 | ); | 320 | ); |
311 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: false, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"[]"###); | 321 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ type_hints: false, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###"[]"###); |
312 | } | 322 | } |
313 | 323 | ||
314 | #[test] | 324 | #[test] |
@@ -320,7 +330,7 @@ mod tests { | |||
320 | let _x = foo(4, 4); | 330 | let _x = foo(4, 4); |
321 | }"#, | 331 | }"#, |
322 | ); | 332 | ); |
323 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ type_hints: true, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###" | 333 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ type_hints: true, parameter_hints: false, chaining_hints: false, max_length: None}).unwrap(), @r###" |
324 | [ | 334 | [ |
325 | InlayHint { | 335 | InlayHint { |
326 | range: [97; 99), | 336 | range: [97; 99), |
@@ -344,7 +354,7 @@ fn main() { | |||
344 | }"#, | 354 | }"#, |
345 | ); | 355 | ); |
346 | 356 | ||
347 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" | 357 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###" |
348 | [ | 358 | [ |
349 | InlayHint { | 359 | InlayHint { |
350 | range: [69; 71), | 360 | range: [69; 71), |
@@ -401,7 +411,7 @@ fn main() { | |||
401 | }"#, | 411 | }"#, |
402 | ); | 412 | ); |
403 | 413 | ||
404 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" | 414 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###" |
405 | [ | 415 | [ |
406 | InlayHint { | 416 | InlayHint { |
407 | range: [193; 197), | 417 | range: [193; 197), |
@@ -481,7 +491,7 @@ fn main() { | |||
481 | }"#, | 491 | }"#, |
482 | ); | 492 | ); |
483 | 493 | ||
484 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" | 494 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###" |
485 | [ | 495 | [ |
486 | InlayHint { | 496 | InlayHint { |
487 | range: [21; 30), | 497 | range: [21; 30), |
@@ -545,7 +555,7 @@ fn main() { | |||
545 | }"#, | 555 | }"#, |
546 | ); | 556 | ); |
547 | 557 | ||
548 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" | 558 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###" |
549 | [ | 559 | [ |
550 | InlayHint { | 560 | InlayHint { |
551 | range: [21; 30), | 561 | range: [21; 30), |
@@ -595,7 +605,7 @@ fn main() { | |||
595 | }"#, | 605 | }"#, |
596 | ); | 606 | ); |
597 | 607 | ||
598 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" | 608 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###" |
599 | [ | 609 | [ |
600 | InlayHint { | 610 | InlayHint { |
601 | range: [188; 192), | 611 | range: [188; 192), |
@@ -690,7 +700,7 @@ fn main() { | |||
690 | }"#, | 700 | }"#, |
691 | ); | 701 | ); |
692 | 702 | ||
693 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" | 703 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###" |
694 | [ | 704 | [ |
695 | InlayHint { | 705 | InlayHint { |
696 | range: [188; 192), | 706 | range: [188; 192), |
@@ -785,7 +795,7 @@ fn main() { | |||
785 | }"#, | 795 | }"#, |
786 | ); | 796 | ); |
787 | 797 | ||
788 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" | 798 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###" |
789 | [ | 799 | [ |
790 | InlayHint { | 800 | InlayHint { |
791 | range: [252; 256), | 801 | range: [252; 256), |
@@ -857,7 +867,7 @@ fn main() { | |||
857 | }"#, | 867 | }"#, |
858 | ); | 868 | ); |
859 | 869 | ||
860 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions { max_length: Some(8), ..Default::default() }).unwrap(), @r###" | 870 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig { max_length: Some(8), ..Default::default() }).unwrap(), @r###" |
861 | [ | 871 | [ |
862 | InlayHint { | 872 | InlayHint { |
863 | range: [74; 75), | 873 | range: [74; 75), |
@@ -945,7 +955,7 @@ fn main() { | |||
945 | }"#, | 955 | }"#, |
946 | ); | 956 | ); |
947 | 957 | ||
948 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions::default()).unwrap(), @r###" | 958 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig::default()).unwrap(), @r###" |
949 | [ | 959 | [ |
950 | InlayHint { | 960 | InlayHint { |
951 | range: [798; 809), | 961 | range: [798; 809), |
@@ -1067,7 +1077,7 @@ fn main() { | |||
1067 | }"#, | 1077 | }"#, |
1068 | ); | 1078 | ); |
1069 | 1079 | ||
1070 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions { max_length: Some(8), ..Default::default() }).unwrap(), @r###" | 1080 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig { max_length: Some(8), ..Default::default() }).unwrap(), @r###" |
1071 | [] | 1081 | [] |
1072 | "### | 1082 | "### |
1073 | ); | 1083 | ); |
@@ -1093,7 +1103,7 @@ fn main() { | |||
1093 | }"#, | 1103 | }"#, |
1094 | ); | 1104 | ); |
1095 | 1105 | ||
1096 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions { max_length: Some(8), ..Default::default() }).unwrap(), @r###" | 1106 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig { max_length: Some(8), ..Default::default() }).unwrap(), @r###" |
1097 | [] | 1107 | [] |
1098 | "### | 1108 | "### |
1099 | ); | 1109 | ); |
@@ -1115,7 +1125,7 @@ fn main() { | |||
1115 | .into_c(); | 1125 | .into_c(); |
1116 | }"#, | 1126 | }"#, |
1117 | ); | 1127 | ); |
1118 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###" | 1128 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###" |
1119 | [ | 1129 | [ |
1120 | InlayHint { | 1130 | InlayHint { |
1121 | range: [232; 269), | 1131 | range: [232; 269), |
@@ -1144,7 +1154,7 @@ fn main() { | |||
1144 | let c = A(B(C)).into_b().into_c(); | 1154 | let c = A(B(C)).into_b().into_c(); |
1145 | }"#, | 1155 | }"#, |
1146 | ); | 1156 | ); |
1147 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"[]"###); | 1157 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###"[]"###); |
1148 | } | 1158 | } |
1149 | 1159 | ||
1150 | #[test] | 1160 | #[test] |
@@ -1154,32 +1164,35 @@ fn main() { | |||
1154 | struct A { pub b: B } | 1164 | struct A { pub b: B } |
1155 | struct B { pub c: C } | 1165 | struct B { pub c: C } |
1156 | struct C(pub bool); | 1166 | struct C(pub bool); |
1167 | struct D; | ||
1168 | |||
1169 | impl D { | ||
1170 | fn foo(&self) -> i32 { 42 } | ||
1171 | } | ||
1157 | 1172 | ||
1158 | fn main() { | 1173 | fn main() { |
1159 | let x = A { b: B { c: C(true) } } | 1174 | let x = A { b: B { c: C(true) } } |
1160 | .b | 1175 | .b |
1161 | .c | 1176 | .c |
1162 | .0; | 1177 | .0; |
1178 | let x = D | ||
1179 | .foo(); | ||
1163 | }"#, | 1180 | }"#, |
1164 | ); | 1181 | ); |
1165 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###" | 1182 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###" |
1166 | [ | 1183 | [ |
1167 | InlayHint { | 1184 | InlayHint { |
1168 | range: [150; 221), | 1185 | range: [252; 323), |
1169 | kind: ChainingHint, | 1186 | kind: ChainingHint, |
1170 | label: "C", | 1187 | label: "C", |
1171 | }, | 1188 | }, |
1172 | InlayHint { | 1189 | InlayHint { |
1173 | range: [150; 198), | 1190 | range: [252; 300), |
1174 | kind: ChainingHint, | 1191 | kind: ChainingHint, |
1175 | label: "B", | 1192 | label: "B", |
1176 | }, | 1193 | }, |
1177 | InlayHint { | 1194 | ] |
1178 | range: [150; 175), | 1195 | "###); |
1179 | kind: ChainingHint, | ||
1180 | label: "A", | ||
1181 | }, | ||
1182 | ]"###); | ||
1183 | } | 1196 | } |
1184 | 1197 | ||
1185 | #[test] | 1198 | #[test] |
@@ -1204,7 +1217,7 @@ fn main() { | |||
1204 | .into_c(); | 1217 | .into_c(); |
1205 | }"#, | 1218 | }"#, |
1206 | ); | 1219 | ); |
1207 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsOptions{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###" | 1220 | assert_debug_snapshot!(analysis.inlay_hints(file_id, &InlayHintsConfig{ parameter_hints: false, type_hints: false, chaining_hints: true, max_length: None}).unwrap(), @r###" |
1208 | [ | 1221 | [ |
1209 | InlayHint { | 1222 | InlayHint { |
1210 | range: [403; 452), | 1223 | range: [403; 452), |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index e43414985..285381086 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -62,13 +62,13 @@ use crate::display::ToNav; | |||
62 | pub use crate::{ | 62 | pub use crate::{ |
63 | assists::{Assist, AssistId}, | 63 | assists::{Assist, AssistId}, |
64 | call_hierarchy::CallItem, | 64 | call_hierarchy::CallItem, |
65 | completion::{CompletionItem, CompletionItemKind, CompletionOptions, InsertTextFormat}, | 65 | completion::{CompletionConfig, CompletionItem, CompletionItemKind, InsertTextFormat}, |
66 | diagnostics::Severity, | 66 | diagnostics::Severity, |
67 | display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, | 67 | display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, |
68 | expand_macro::ExpandedMacro, | 68 | expand_macro::ExpandedMacro, |
69 | folding_ranges::{Fold, FoldKind}, | 69 | folding_ranges::{Fold, FoldKind}, |
70 | hover::HoverResult, | 70 | hover::HoverResult, |
71 | inlay_hints::{InlayHint, InlayHintsOptions, InlayKind}, | 71 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, |
72 | references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, | 72 | references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, |
73 | runnables::{Runnable, RunnableKind, TestId}, | 73 | runnables::{Runnable, RunnableKind, TestId}, |
74 | source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, | 74 | source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, |
@@ -138,6 +138,11 @@ impl AnalysisHost { | |||
138 | pub fn new(lru_capacity: Option<usize>) -> AnalysisHost { | 138 | pub fn new(lru_capacity: Option<usize>) -> AnalysisHost { |
139 | AnalysisHost { db: RootDatabase::new(lru_capacity) } | 139 | AnalysisHost { db: RootDatabase::new(lru_capacity) } |
140 | } | 140 | } |
141 | |||
142 | pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) { | ||
143 | self.db.update_lru_capacity(lru_capacity); | ||
144 | } | ||
145 | |||
141 | /// Returns a snapshot of the current state, which you can query for | 146 | /// Returns a snapshot of the current state, which you can query for |
142 | /// semantic information. | 147 | /// semantic information. |
143 | pub fn analysis(&self) -> Analysis { | 148 | pub fn analysis(&self) -> Analysis { |
@@ -320,9 +325,9 @@ impl Analysis { | |||
320 | pub fn inlay_hints( | 325 | pub fn inlay_hints( |
321 | &self, | 326 | &self, |
322 | file_id: FileId, | 327 | file_id: FileId, |
323 | inlay_hint_opts: &InlayHintsOptions, | 328 | config: &InlayHintsConfig, |
324 | ) -> Cancelable<Vec<InlayHint>> { | 329 | ) -> Cancelable<Vec<InlayHint>> { |
325 | self.with_db(|db| inlay_hints::inlay_hints(db, file_id, inlay_hint_opts)) | 330 | self.with_db(|db| inlay_hints::inlay_hints(db, file_id, config)) |
326 | } | 331 | } |
327 | 332 | ||
328 | /// Returns the set of folding ranges. | 333 | /// Returns the set of folding ranges. |
@@ -445,9 +450,9 @@ impl Analysis { | |||
445 | pub fn completions( | 450 | pub fn completions( |
446 | &self, | 451 | &self, |
447 | position: FilePosition, | 452 | position: FilePosition, |
448 | options: &CompletionOptions, | 453 | config: &CompletionConfig, |
449 | ) -> Cancelable<Option<Vec<CompletionItem>>> { | 454 | ) -> Cancelable<Option<Vec<CompletionItem>>> { |
450 | self.with_db(|db| completion::completions(db, position, options).map(Into::into)) | 455 | self.with_db(|db| completion::completions(db, position, config).map(Into::into)) |
451 | } | 456 | } |
452 | 457 | ||
453 | /// Computes assists (aka code actions aka intentions) for the given | 458 | /// Computes assists (aka code actions aka intentions) for the given |
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index 1c9710a5d..1abb891c1 100644 --- a/crates/ra_ide/src/ssr.rs +++ b/crates/ra_ide/src/ssr.rs | |||
@@ -5,7 +5,7 @@ use ra_db::{SourceDatabase, SourceDatabaseExt}; | |||
5 | use ra_ide_db::symbol_index::SymbolsDatabase; | 5 | use ra_ide_db::symbol_index::SymbolsDatabase; |
6 | use ra_ide_db::RootDatabase; | 6 | use ra_ide_db::RootDatabase; |
7 | use ra_syntax::ast::make::try_expr_from_text; | 7 | use ra_syntax::ast::make::try_expr_from_text; |
8 | use ra_syntax::ast::{AstToken, Comment}; | 8 | use ra_syntax::ast::{AstToken, Comment, RecordField, RecordLit}; |
9 | use ra_syntax::{AstNode, SyntaxElement, SyntaxNode}; | 9 | use ra_syntax::{AstNode, SyntaxElement, SyntaxNode}; |
10 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 10 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
11 | use rustc_hash::FxHashMap; | 11 | use rustc_hash::FxHashMap; |
@@ -186,47 +186,102 @@ fn create_name<'a>(name: &str, vars: &'a mut Vec<Var>) -> Result<&'a str, SsrErr | |||
186 | } | 186 | } |
187 | 187 | ||
188 | fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches { | 188 | fn find(pattern: &SsrPattern, code: &SyntaxNode) -> SsrMatches { |
189 | fn check_record_lit( | ||
190 | pattern: RecordLit, | ||
191 | code: RecordLit, | ||
192 | placeholders: &[Var], | ||
193 | match_: Match, | ||
194 | ) -> Option<Match> { | ||
195 | let match_ = check_opt_nodes(pattern.path(), code.path(), placeholders, match_)?; | ||
196 | |||
197 | let mut pattern_fields = | ||
198 | pattern.record_field_list().map(|x| x.fields().collect()).unwrap_or(vec![]); | ||
199 | let mut code_fields = | ||
200 | code.record_field_list().map(|x| x.fields().collect()).unwrap_or(vec![]); | ||
201 | |||
202 | if pattern_fields.len() != code_fields.len() { | ||
203 | return None; | ||
204 | } | ||
205 | |||
206 | let by_name = |a: &RecordField, b: &RecordField| { | ||
207 | a.name_ref() | ||
208 | .map(|x| x.syntax().text().to_string()) | ||
209 | .cmp(&b.name_ref().map(|x| x.syntax().text().to_string())) | ||
210 | }; | ||
211 | pattern_fields.sort_by(by_name); | ||
212 | code_fields.sort_by(by_name); | ||
213 | |||
214 | pattern_fields.into_iter().zip(code_fields.into_iter()).fold( | ||
215 | Some(match_), | ||
216 | |accum, (a, b)| { | ||
217 | accum.and_then(|match_| check_opt_nodes(Some(a), Some(b), placeholders, match_)) | ||
218 | }, | ||
219 | ) | ||
220 | } | ||
221 | |||
222 | fn check_opt_nodes( | ||
223 | pattern: Option<impl AstNode>, | ||
224 | code: Option<impl AstNode>, | ||
225 | placeholders: &[Var], | ||
226 | match_: Match, | ||
227 | ) -> Option<Match> { | ||
228 | match (pattern, code) { | ||
229 | (Some(pattern), Some(code)) => check( | ||
230 | &SyntaxElement::from(pattern.syntax().clone()), | ||
231 | &SyntaxElement::from(code.syntax().clone()), | ||
232 | placeholders, | ||
233 | match_, | ||
234 | ), | ||
235 | (None, None) => Some(match_), | ||
236 | _ => None, | ||
237 | } | ||
238 | } | ||
239 | |||
189 | fn check( | 240 | fn check( |
190 | pattern: &SyntaxElement, | 241 | pattern: &SyntaxElement, |
191 | code: &SyntaxElement, | 242 | code: &SyntaxElement, |
192 | placeholders: &[Var], | 243 | placeholders: &[Var], |
193 | mut match_: Match, | 244 | mut match_: Match, |
194 | ) -> Option<Match> { | 245 | ) -> Option<Match> { |
195 | match (pattern, code) { | 246 | match (&pattern, &code) { |
196 | (SyntaxElement::Token(ref pattern), SyntaxElement::Token(ref code)) => { | 247 | (SyntaxElement::Token(pattern), SyntaxElement::Token(code)) => { |
197 | if pattern.text() == code.text() { | 248 | if pattern.text() == code.text() { |
198 | Some(match_) | 249 | Some(match_) |
199 | } else { | 250 | } else { |
200 | None | 251 | None |
201 | } | 252 | } |
202 | } | 253 | } |
203 | (SyntaxElement::Node(ref pattern), SyntaxElement::Node(ref code)) => { | 254 | (SyntaxElement::Node(pattern), SyntaxElement::Node(code)) => { |
204 | if placeholders.iter().any(|n| n.0.as_str() == pattern.text()) { | 255 | if placeholders.iter().any(|n| n.0.as_str() == pattern.text()) { |
205 | match_.binding.insert(Var(pattern.text().to_string()), code.clone()); | 256 | match_.binding.insert(Var(pattern.text().to_string()), code.clone()); |
206 | Some(match_) | 257 | Some(match_) |
207 | } else { | 258 | } else { |
208 | let mut pattern_children = pattern | 259 | if let (Some(pattern), Some(code)) = |
209 | .children_with_tokens() | 260 | (RecordLit::cast(pattern.clone()), RecordLit::cast(code.clone())) |
210 | .filter(|element| !element.kind().is_trivia()); | 261 | { |
211 | let mut code_children = | 262 | check_record_lit(pattern, code, placeholders, match_) |
212 | code.children_with_tokens().filter(|element| !element.kind().is_trivia()); | 263 | } else { |
213 | let new_ignored_comments = code.children_with_tokens().filter_map(|element| { | 264 | let mut pattern_children = pattern |
214 | element.as_token().and_then(|token| Comment::cast(token.clone())) | 265 | .children_with_tokens() |
215 | }); | 266 | .filter(|element| !element.kind().is_trivia()); |
216 | match_.ignored_comments.extend(new_ignored_comments); | 267 | let mut code_children = code |
217 | let match_from_children = pattern_children | 268 | .children_with_tokens() |
218 | .by_ref() | 269 | .filter(|element| !element.kind().is_trivia()); |
219 | .zip(code_children.by_ref()) | 270 | let new_ignored_comments = |
220 | .fold(Some(match_), |accum, (a, b)| { | 271 | code.children_with_tokens().filter_map(|element| { |
221 | accum.and_then(|match_| check(&a, &b, placeholders, match_)) | 272 | element.as_token().and_then(|token| Comment::cast(token.clone())) |
222 | }); | 273 | }); |
223 | match_from_children.and_then(|match_| { | 274 | match_.ignored_comments.extend(new_ignored_comments); |
224 | if pattern_children.count() == 0 && code_children.count() == 0 { | 275 | pattern_children |
225 | Some(match_) | 276 | .by_ref() |
226 | } else { | 277 | .zip(code_children.by_ref()) |
227 | None | 278 | .fold(Some(match_), |accum, (a, b)| { |
228 | } | 279 | accum.and_then(|match_| check(&a, &b, placeholders, match_)) |
229 | }) | 280 | }) |
281 | .filter(|_| { | ||
282 | pattern_children.next().is_none() && code_children.next().is_none() | ||
283 | }) | ||
284 | } | ||
230 | } | 285 | } |
231 | } | 286 | } |
232 | _ => None, | 287 | _ => None, |
@@ -434,4 +489,13 @@ mod tests { | |||
434 | "fn main() { bar(5)/* using 5 */ }", | 489 | "fn main() { bar(5)/* using 5 */ }", |
435 | ) | 490 | ) |
436 | } | 491 | } |
492 | |||
493 | #[test] | ||
494 | fn ssr_struct_lit() { | ||
495 | assert_ssr_transform( | ||
496 | "foo{a: $a:expr, b: $b:expr} ==>> foo::new($a, $b)", | ||
497 | "fn main() { foo{b:2, a:1} }", | ||
498 | "fn main() { foo::new(1, 2) }", | ||
499 | ) | ||
500 | } | ||
437 | } | 501 | } |
diff --git a/crates/ra_ide_db/src/lib.rs b/crates/ra_ide_db/src/lib.rs index 4faeefa8d..e6f2d36e9 100644 --- a/crates/ra_ide_db/src/lib.rs +++ b/crates/ra_ide_db/src/lib.rs | |||
@@ -115,12 +115,16 @@ impl RootDatabase { | |||
115 | db.set_crate_graph_with_durability(Default::default(), Durability::HIGH); | 115 | db.set_crate_graph_with_durability(Default::default(), Durability::HIGH); |
116 | db.set_local_roots_with_durability(Default::default(), Durability::HIGH); | 116 | db.set_local_roots_with_durability(Default::default(), Durability::HIGH); |
117 | db.set_library_roots_with_durability(Default::default(), Durability::HIGH); | 117 | db.set_library_roots_with_durability(Default::default(), Durability::HIGH); |
118 | let lru_capacity = lru_capacity.unwrap_or(ra_db::DEFAULT_LRU_CAP); | 118 | db.update_lru_capacity(lru_capacity); |
119 | db.query_mut(ra_db::ParseQuery).set_lru_capacity(lru_capacity); | ||
120 | db.query_mut(hir::db::ParseMacroQuery).set_lru_capacity(lru_capacity); | ||
121 | db.query_mut(hir::db::MacroExpandQuery).set_lru_capacity(lru_capacity); | ||
122 | db | 119 | db |
123 | } | 120 | } |
121 | |||
122 | pub fn update_lru_capacity(&mut self, lru_capacity: Option<usize>) { | ||
123 | let lru_capacity = lru_capacity.unwrap_or(ra_db::DEFAULT_LRU_CAP); | ||
124 | self.query_mut(ra_db::ParseQuery).set_lru_capacity(lru_capacity); | ||
125 | self.query_mut(hir::db::ParseMacroQuery).set_lru_capacity(lru_capacity); | ||
126 | self.query_mut(hir::db::MacroExpandQuery).set_lru_capacity(lru_capacity); | ||
127 | } | ||
124 | } | 128 | } |
125 | 129 | ||
126 | impl salsa::ParallelDatabase for RootDatabase { | 130 | impl salsa::ParallelDatabase for RootDatabase { |
diff --git a/crates/ra_proc_macro/Cargo.toml b/crates/ra_proc_macro/Cargo.toml index bc2c37296..d009ceb82 100644 --- a/crates/ra_proc_macro/Cargo.toml +++ b/crates/ra_proc_macro/Cargo.toml | |||
@@ -10,3 +10,8 @@ doctest = false | |||
10 | 10 | ||
11 | [dependencies] | 11 | [dependencies] |
12 | ra_tt = { path = "../ra_tt" } | 12 | ra_tt = { path = "../ra_tt" } |
13 | serde = { version = "1.0", features = ["derive"] } | ||
14 | serde_json = "1.0" | ||
15 | log = "0.4.8" | ||
16 | crossbeam-channel = "0.4.0" | ||
17 | jod-thread = "0.1.1" | ||
diff --git a/crates/ra_proc_macro/src/lib.rs b/crates/ra_proc_macro/src/lib.rs index 5e21dd487..51fbb046a 100644 --- a/crates/ra_proc_macro/src/lib.rs +++ b/crates/ra_proc_macro/src/lib.rs | |||
@@ -5,55 +5,104 @@ | |||
5 | //! is used to provide basic infrastructure for communication between two | 5 | //! is used to provide basic infrastructure for communication between two |
6 | //! processes: Client (RA itself), Server (the external program) | 6 | //! processes: Client (RA itself), Server (the external program) |
7 | 7 | ||
8 | mod rpc; | ||
9 | mod process; | ||
10 | pub mod msg; | ||
11 | |||
12 | use process::{ProcMacroProcessSrv, ProcMacroProcessThread}; | ||
8 | use ra_tt::{SmolStr, Subtree}; | 13 | use ra_tt::{SmolStr, Subtree}; |
14 | use rpc::ProcMacroKind; | ||
9 | use std::{ | 15 | use std::{ |
10 | path::{Path, PathBuf}, | 16 | path::{Path, PathBuf}, |
11 | sync::Arc, | 17 | sync::Arc, |
12 | }; | 18 | }; |
13 | 19 | ||
14 | #[derive(Debug, Clone, PartialEq, Eq)] | 20 | pub use rpc::{ExpansionResult, ExpansionTask}; |
21 | |||
22 | #[derive(Debug, Clone)] | ||
15 | pub struct ProcMacroProcessExpander { | 23 | pub struct ProcMacroProcessExpander { |
16 | process: Arc<ProcMacroProcessSrv>, | 24 | process: Arc<ProcMacroProcessSrv>, |
25 | dylib_path: PathBuf, | ||
17 | name: SmolStr, | 26 | name: SmolStr, |
18 | } | 27 | } |
19 | 28 | ||
29 | impl Eq for ProcMacroProcessExpander {} | ||
30 | impl PartialEq for ProcMacroProcessExpander { | ||
31 | fn eq(&self, other: &Self) -> bool { | ||
32 | self.name == other.name | ||
33 | && self.dylib_path == other.dylib_path | ||
34 | && Arc::ptr_eq(&self.process, &other.process) | ||
35 | } | ||
36 | } | ||
37 | |||
20 | impl ra_tt::TokenExpander for ProcMacroProcessExpander { | 38 | impl ra_tt::TokenExpander for ProcMacroProcessExpander { |
21 | fn expand( | 39 | fn expand( |
22 | &self, | 40 | &self, |
23 | _subtree: &Subtree, | 41 | subtree: &Subtree, |
24 | _attr: Option<&Subtree>, | 42 | _attr: Option<&Subtree>, |
25 | ) -> Result<Subtree, ra_tt::ExpansionError> { | 43 | ) -> Result<Subtree, ra_tt::ExpansionError> { |
26 | // FIXME: do nothing for now | 44 | self.process.custom_derive(&self.dylib_path, subtree, &self.name) |
27 | Ok(Subtree::default()) | ||
28 | } | 45 | } |
29 | } | 46 | } |
30 | 47 | ||
31 | #[derive(Debug, Clone, PartialEq, Eq)] | 48 | #[derive(Debug)] |
32 | pub struct ProcMacroProcessSrv { | 49 | enum ProcMacroClientKind { |
33 | path: PathBuf, | 50 | Process { process: Arc<ProcMacroProcessSrv>, thread: ProcMacroProcessThread }, |
51 | Dummy, | ||
34 | } | 52 | } |
35 | 53 | ||
36 | #[derive(Debug, Clone, PartialEq, Eq)] | 54 | #[derive(Debug)] |
37 | pub enum ProcMacroClient { | 55 | pub struct ProcMacroClient { |
38 | Process { process: Arc<ProcMacroProcessSrv> }, | 56 | kind: ProcMacroClientKind, |
39 | Dummy, | ||
40 | } | 57 | } |
41 | 58 | ||
42 | impl ProcMacroClient { | 59 | impl ProcMacroClient { |
43 | pub fn extern_process(process_path: &Path) -> ProcMacroClient { | 60 | pub fn extern_process(process_path: &Path) -> Result<ProcMacroClient, std::io::Error> { |
44 | let process = ProcMacroProcessSrv { path: process_path.into() }; | 61 | let (thread, process) = ProcMacroProcessSrv::run(process_path)?; |
45 | ProcMacroClient::Process { process: Arc::new(process) } | 62 | Ok(ProcMacroClient { |
63 | kind: ProcMacroClientKind::Process { process: Arc::new(process), thread }, | ||
64 | }) | ||
46 | } | 65 | } |
47 | 66 | ||
48 | pub fn dummy() -> ProcMacroClient { | 67 | pub fn dummy() -> ProcMacroClient { |
49 | ProcMacroClient::Dummy | 68 | ProcMacroClient { kind: ProcMacroClientKind::Dummy } |
50 | } | 69 | } |
51 | 70 | ||
52 | pub fn by_dylib_path( | 71 | pub fn by_dylib_path( |
53 | &self, | 72 | &self, |
54 | _dylib_path: &Path, | 73 | dylib_path: &Path, |
55 | ) -> Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)> { | 74 | ) -> Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)> { |
56 | // FIXME: return empty for now | 75 | match &self.kind { |
57 | vec![] | 76 | ProcMacroClientKind::Dummy => vec![], |
77 | ProcMacroClientKind::Process { process, .. } => { | ||
78 | let macros = match process.find_proc_macros(dylib_path) { | ||
79 | Err(err) => { | ||
80 | eprintln!("Fail to find proc macro. Error: {:#?}", err); | ||
81 | return vec![]; | ||
82 | } | ||
83 | Ok(macros) => macros, | ||
84 | }; | ||
85 | |||
86 | macros | ||
87 | .into_iter() | ||
88 | .filter_map(|(name, kind)| { | ||
89 | // FIXME: Support custom derive only for now. | ||
90 | match kind { | ||
91 | ProcMacroKind::CustomDerive => { | ||
92 | let name = SmolStr::new(&name); | ||
93 | let expander: Arc<dyn ra_tt::TokenExpander> = | ||
94 | Arc::new(ProcMacroProcessExpander { | ||
95 | process: process.clone(), | ||
96 | name: name.clone(), | ||
97 | dylib_path: dylib_path.into(), | ||
98 | }); | ||
99 | Some((name, expander)) | ||
100 | } | ||
101 | _ => None, | ||
102 | } | ||
103 | }) | ||
104 | .collect() | ||
105 | } | ||
106 | } | ||
58 | } | 107 | } |
59 | } | 108 | } |
diff --git a/crates/ra_proc_macro/src/msg.rs b/crates/ra_proc_macro/src/msg.rs new file mode 100644 index 000000000..aa95bcc8f --- /dev/null +++ b/crates/ra_proc_macro/src/msg.rs | |||
@@ -0,0 +1,93 @@ | |||
1 | //! Defines messages for cross-process message based on `ndjson` wire protocol | ||
2 | |||
3 | use std::{ | ||
4 | convert::TryFrom, | ||
5 | io::{self, BufRead, Write}, | ||
6 | }; | ||
7 | |||
8 | use crate::{ | ||
9 | rpc::{ListMacrosResult, ListMacrosTask}, | ||
10 | ExpansionResult, ExpansionTask, | ||
11 | }; | ||
12 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; | ||
13 | |||
14 | #[derive(Debug, Serialize, Deserialize, Clone)] | ||
15 | pub enum Request { | ||
16 | ListMacro(ListMacrosTask), | ||
17 | ExpansionMacro(ExpansionTask), | ||
18 | } | ||
19 | |||
20 | #[derive(Debug, Serialize, Deserialize, Clone)] | ||
21 | pub enum Response { | ||
22 | Error(ResponseError), | ||
23 | ListMacro(ListMacrosResult), | ||
24 | ExpansionMacro(ExpansionResult), | ||
25 | } | ||
26 | |||
27 | macro_rules! impl_try_from_response { | ||
28 | ($ty:ty, $tag:ident) => { | ||
29 | impl TryFrom<Response> for $ty { | ||
30 | type Error = &'static str; | ||
31 | fn try_from(value: Response) -> Result<Self, Self::Error> { | ||
32 | match value { | ||
33 | Response::$tag(res) => Ok(res), | ||
34 | _ => Err("Fail to convert from response"), | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | }; | ||
39 | } | ||
40 | |||
41 | impl_try_from_response!(ListMacrosResult, ListMacro); | ||
42 | impl_try_from_response!(ExpansionResult, ExpansionMacro); | ||
43 | |||
44 | #[derive(Debug, Serialize, Deserialize, Clone)] | ||
45 | pub struct ResponseError { | ||
46 | pub code: ErrorCode, | ||
47 | pub message: String, | ||
48 | } | ||
49 | |||
50 | #[derive(Debug, Serialize, Deserialize, Clone)] | ||
51 | pub enum ErrorCode { | ||
52 | ServerErrorEnd, | ||
53 | ExpansionError, | ||
54 | } | ||
55 | |||
56 | pub trait Message: Sized + Serialize + DeserializeOwned { | ||
57 | fn read(r: &mut impl BufRead) -> io::Result<Option<Self>> { | ||
58 | let text = match read_json(r)? { | ||
59 | None => return Ok(None), | ||
60 | Some(text) => text, | ||
61 | }; | ||
62 | let msg = serde_json::from_str(&text)?; | ||
63 | Ok(Some(msg)) | ||
64 | } | ||
65 | fn write(self, w: &mut impl Write) -> io::Result<()> { | ||
66 | let text = serde_json::to_string(&self)?; | ||
67 | write_json(w, &text) | ||
68 | } | ||
69 | } | ||
70 | |||
71 | impl Message for Request {} | ||
72 | impl Message for Response {} | ||
73 | |||
74 | fn read_json(inp: &mut impl BufRead) -> io::Result<Option<String>> { | ||
75 | let mut buf = String::new(); | ||
76 | if inp.read_line(&mut buf)? == 0 { | ||
77 | return Ok(None); | ||
78 | } | ||
79 | // Remove ending '\n' | ||
80 | let buf = &buf[..buf.len() - 1]; | ||
81 | if buf.is_empty() { | ||
82 | return Ok(None); | ||
83 | } | ||
84 | Ok(Some(buf.to_string())) | ||
85 | } | ||
86 | |||
87 | fn write_json(out: &mut impl Write, msg: &str) -> io::Result<()> { | ||
88 | log::debug!("> {}", msg); | ||
89 | out.write_all(msg.as_bytes())?; | ||
90 | out.write_all(b"\n")?; | ||
91 | out.flush()?; | ||
92 | Ok(()) | ||
93 | } | ||
diff --git a/crates/ra_proc_macro/src/process.rs b/crates/ra_proc_macro/src/process.rs new file mode 100644 index 000000000..e8c85be38 --- /dev/null +++ b/crates/ra_proc_macro/src/process.rs | |||
@@ -0,0 +1,196 @@ | |||
1 | //! Handle process life-time and message passing for proc-macro client | ||
2 | |||
3 | use crossbeam_channel::{bounded, Receiver, Sender}; | ||
4 | use ra_tt::Subtree; | ||
5 | |||
6 | use crate::msg::{ErrorCode, Message, Request, Response, ResponseError}; | ||
7 | use crate::rpc::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask, ProcMacroKind}; | ||
8 | |||
9 | use io::{BufRead, BufReader}; | ||
10 | use std::{ | ||
11 | convert::{TryFrom, TryInto}, | ||
12 | io::{self, Write}, | ||
13 | path::{Path, PathBuf}, | ||
14 | process::{Child, Command, Stdio}, | ||
15 | sync::{Arc, Weak}, | ||
16 | }; | ||
17 | |||
18 | #[derive(Debug, Default)] | ||
19 | pub(crate) struct ProcMacroProcessSrv { | ||
20 | inner: Option<Weak<Sender<Task>>>, | ||
21 | } | ||
22 | |||
23 | #[derive(Debug)] | ||
24 | pub(crate) struct ProcMacroProcessThread { | ||
25 | // XXX: drop order is significant | ||
26 | sender: Arc<Sender<Task>>, | ||
27 | handle: jod_thread::JoinHandle<()>, | ||
28 | } | ||
29 | |||
30 | struct Task { | ||
31 | req: Request, | ||
32 | result_tx: Sender<Option<Response>>, | ||
33 | } | ||
34 | |||
35 | struct Process { | ||
36 | path: PathBuf, | ||
37 | child: Child, | ||
38 | } | ||
39 | |||
40 | impl Drop for Process { | ||
41 | fn drop(&mut self) { | ||
42 | let _ = self.child.kill(); | ||
43 | } | ||
44 | } | ||
45 | |||
46 | impl Process { | ||
47 | fn run(process_path: &Path) -> Result<Process, io::Error> { | ||
48 | let child = Command::new(process_path.clone()) | ||
49 | .stdin(Stdio::piped()) | ||
50 | .stdout(Stdio::piped()) | ||
51 | .stderr(Stdio::null()) | ||
52 | .spawn()?; | ||
53 | |||
54 | Ok(Process { path: process_path.into(), child }) | ||
55 | } | ||
56 | |||
57 | fn restart(&mut self) -> Result<(), io::Error> { | ||
58 | let _ = self.child.kill(); | ||
59 | self.child = Command::new(self.path.clone()) | ||
60 | .stdin(Stdio::piped()) | ||
61 | .stdout(Stdio::piped()) | ||
62 | .stderr(Stdio::null()) | ||
63 | .spawn()?; | ||
64 | Ok(()) | ||
65 | } | ||
66 | |||
67 | fn stdio(&mut self) -> Option<(impl Write, impl BufRead)> { | ||
68 | let stdin = self.child.stdin.take()?; | ||
69 | let stdout = self.child.stdout.take()?; | ||
70 | let read = BufReader::new(stdout); | ||
71 | |||
72 | Some((stdin, read)) | ||
73 | } | ||
74 | } | ||
75 | |||
76 | impl ProcMacroProcessSrv { | ||
77 | pub fn run( | ||
78 | process_path: &Path, | ||
79 | ) -> Result<(ProcMacroProcessThread, ProcMacroProcessSrv), io::Error> { | ||
80 | let process = Process::run(process_path)?; | ||
81 | |||
82 | let (task_tx, task_rx) = bounded(0); | ||
83 | let handle = jod_thread::spawn(move || { | ||
84 | client_loop(task_rx, process); | ||
85 | }); | ||
86 | |||
87 | let task_tx = Arc::new(task_tx); | ||
88 | let srv = ProcMacroProcessSrv { inner: Some(Arc::downgrade(&task_tx)) }; | ||
89 | let thread = ProcMacroProcessThread { handle, sender: task_tx }; | ||
90 | |||
91 | Ok((thread, srv)) | ||
92 | } | ||
93 | |||
94 | pub fn find_proc_macros( | ||
95 | &self, | ||
96 | dylib_path: &Path, | ||
97 | ) -> Result<Vec<(String, ProcMacroKind)>, ra_tt::ExpansionError> { | ||
98 | let task = ListMacrosTask { lib: dylib_path.to_path_buf() }; | ||
99 | |||
100 | let result: ListMacrosResult = self.send_task(Request::ListMacro(task))?; | ||
101 | Ok(result.macros) | ||
102 | } | ||
103 | |||
104 | pub fn custom_derive( | ||
105 | &self, | ||
106 | dylib_path: &Path, | ||
107 | subtree: &Subtree, | ||
108 | derive_name: &str, | ||
109 | ) -> Result<Subtree, ra_tt::ExpansionError> { | ||
110 | let task = ExpansionTask { | ||
111 | macro_body: subtree.clone(), | ||
112 | macro_name: derive_name.to_string(), | ||
113 | attributes: None, | ||
114 | lib: dylib_path.to_path_buf(), | ||
115 | }; | ||
116 | |||
117 | let result: ExpansionResult = self.send_task(Request::ExpansionMacro(task))?; | ||
118 | Ok(result.expansion) | ||
119 | } | ||
120 | |||
121 | pub fn send_task<R>(&self, req: Request) -> Result<R, ra_tt::ExpansionError> | ||
122 | where | ||
123 | R: TryFrom<Response, Error = &'static str>, | ||
124 | { | ||
125 | let sender = match &self.inner { | ||
126 | None => return Err(ra_tt::ExpansionError::Unknown("No sender is found.".to_string())), | ||
127 | Some(it) => it, | ||
128 | }; | ||
129 | |||
130 | let (result_tx, result_rx) = bounded(0); | ||
131 | let sender = match sender.upgrade() { | ||
132 | None => { | ||
133 | return Err(ra_tt::ExpansionError::Unknown("Proc macro process is closed.".into())) | ||
134 | } | ||
135 | Some(it) => it, | ||
136 | }; | ||
137 | sender.send(Task { req: req.into(), result_tx }).unwrap(); | ||
138 | let res = result_rx | ||
139 | .recv() | ||
140 | .map_err(|_| ra_tt::ExpansionError::Unknown("Proc macro thread is closed.".into()))?; | ||
141 | |||
142 | match res { | ||
143 | Some(Response::Error(err)) => { | ||
144 | return Err(ra_tt::ExpansionError::ExpansionError(err.message)); | ||
145 | } | ||
146 | Some(res) => Ok(res.try_into().map_err(|err| { | ||
147 | ra_tt::ExpansionError::Unknown(format!( | ||
148 | "Fail to get response, reason : {:#?} ", | ||
149 | err | ||
150 | )) | ||
151 | })?), | ||
152 | None => Err(ra_tt::ExpansionError::Unknown("Empty result".into())), | ||
153 | } | ||
154 | } | ||
155 | } | ||
156 | |||
157 | fn client_loop(task_rx: Receiver<Task>, mut process: Process) { | ||
158 | let (mut stdin, mut stdout) = match process.stdio() { | ||
159 | None => return, | ||
160 | Some(it) => it, | ||
161 | }; | ||
162 | |||
163 | for task in task_rx { | ||
164 | let Task { req, result_tx } = task; | ||
165 | |||
166 | match send_request(&mut stdin, &mut stdout, req) { | ||
167 | Ok(res) => result_tx.send(res).unwrap(), | ||
168 | Err(_err) => { | ||
169 | let res = Response::Error(ResponseError { | ||
170 | code: ErrorCode::ServerErrorEnd, | ||
171 | message: "Server closed".into(), | ||
172 | }); | ||
173 | result_tx.send(res.into()).unwrap(); | ||
174 | // Restart the process | ||
175 | if process.restart().is_err() { | ||
176 | break; | ||
177 | } | ||
178 | let stdio = match process.stdio() { | ||
179 | None => break, | ||
180 | Some(it) => it, | ||
181 | }; | ||
182 | stdin = stdio.0; | ||
183 | stdout = stdio.1; | ||
184 | } | ||
185 | } | ||
186 | } | ||
187 | } | ||
188 | |||
189 | fn send_request( | ||
190 | mut writer: &mut impl Write, | ||
191 | mut reader: &mut impl BufRead, | ||
192 | req: Request, | ||
193 | ) -> Result<Option<Response>, io::Error> { | ||
194 | req.write(&mut writer)?; | ||
195 | Ok(Response::read(&mut reader)?) | ||
196 | } | ||
diff --git a/crates/ra_proc_macro/src/rpc.rs b/crates/ra_proc_macro/src/rpc.rs new file mode 100644 index 000000000..66b3f55db --- /dev/null +++ b/crates/ra_proc_macro/src/rpc.rs | |||
@@ -0,0 +1,266 @@ | |||
1 | //! Data struture serialization related stuffs for RPC | ||
2 | //! | ||
3 | //! Define all necessary rpc serialization data structure, | ||
4 | //! which include ra_tt related data and some task messages. | ||
5 | //! Although adding Serialize and Deserialize trait to ra_tt directly seem to be much easier, | ||
6 | //! we deliberately duplicate the ra_tt struct with #[serde(with = "XXDef")] | ||
7 | //! for separation of code responsibility. | ||
8 | |||
9 | use ra_tt::{ | ||
10 | Delimiter, DelimiterKind, Ident, Leaf, Literal, Punct, SmolStr, Spacing, Subtree, TokenId, | ||
11 | TokenTree, | ||
12 | }; | ||
13 | use serde::{Deserialize, Serialize}; | ||
14 | use std::path::PathBuf; | ||
15 | |||
16 | #[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] | ||
17 | pub struct ListMacrosTask { | ||
18 | pub lib: PathBuf, | ||
19 | } | ||
20 | |||
21 | #[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] | ||
22 | pub enum ProcMacroKind { | ||
23 | CustomDerive, | ||
24 | FuncLike, | ||
25 | Attr, | ||
26 | } | ||
27 | |||
28 | #[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] | ||
29 | pub struct ListMacrosResult { | ||
30 | pub macros: Vec<(String, ProcMacroKind)>, | ||
31 | } | ||
32 | |||
33 | #[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] | ||
34 | pub struct ExpansionTask { | ||
35 | /// Argument of macro call. | ||
36 | /// | ||
37 | /// In custom derive that would be a struct or enum; in attribute-like macro - underlying | ||
38 | /// item; in function-like macro - the macro body. | ||
39 | #[serde(with = "SubtreeDef")] | ||
40 | pub macro_body: Subtree, | ||
41 | |||
42 | /// Names of macros to expand. | ||
43 | /// | ||
44 | /// In custom derive those are names of derived traits (`Serialize`, `Getters`, etc.). In | ||
45 | /// attribute-like and functiona-like macros - single name of macro itself (`show_streams`). | ||
46 | pub macro_name: String, | ||
47 | |||
48 | /// Possible attributes for the attribute-like macros. | ||
49 | #[serde(with = "opt_subtree_def")] | ||
50 | pub attributes: Option<Subtree>, | ||
51 | |||
52 | pub lib: PathBuf, | ||
53 | } | ||
54 | |||
55 | #[derive(Clone, Eq, PartialEq, Debug, Default, Serialize, Deserialize)] | ||
56 | pub struct ExpansionResult { | ||
57 | #[serde(with = "SubtreeDef")] | ||
58 | pub expansion: Subtree, | ||
59 | } | ||
60 | |||
61 | #[derive(Serialize, Deserialize)] | ||
62 | #[serde(remote = "DelimiterKind")] | ||
63 | enum DelimiterKindDef { | ||
64 | Parenthesis, | ||
65 | Brace, | ||
66 | Bracket, | ||
67 | } | ||
68 | |||
69 | #[derive(Serialize, Deserialize)] | ||
70 | #[serde(remote = "TokenId")] | ||
71 | struct TokenIdDef(u32); | ||
72 | |||
73 | #[derive(Serialize, Deserialize)] | ||
74 | #[serde(remote = "Delimiter")] | ||
75 | struct DelimiterDef { | ||
76 | #[serde(with = "TokenIdDef")] | ||
77 | pub id: TokenId, | ||
78 | #[serde(with = "DelimiterKindDef")] | ||
79 | pub kind: DelimiterKind, | ||
80 | } | ||
81 | |||
82 | #[derive(Serialize, Deserialize)] | ||
83 | #[serde(remote = "Subtree")] | ||
84 | struct SubtreeDef { | ||
85 | #[serde(default, with = "opt_delimiter_def")] | ||
86 | pub delimiter: Option<Delimiter>, | ||
87 | #[serde(with = "vec_token_tree")] | ||
88 | pub token_trees: Vec<TokenTree>, | ||
89 | } | ||
90 | |||
91 | #[derive(Serialize, Deserialize)] | ||
92 | #[serde(remote = "TokenTree")] | ||
93 | enum TokenTreeDef { | ||
94 | #[serde(with = "LeafDef")] | ||
95 | Leaf(Leaf), | ||
96 | #[serde(with = "SubtreeDef")] | ||
97 | Subtree(Subtree), | ||
98 | } | ||
99 | |||
100 | #[derive(Serialize, Deserialize)] | ||
101 | #[serde(remote = "Leaf")] | ||
102 | enum LeafDef { | ||
103 | #[serde(with = "LiteralDef")] | ||
104 | Literal(Literal), | ||
105 | #[serde(with = "PunctDef")] | ||
106 | Punct(Punct), | ||
107 | #[serde(with = "IdentDef")] | ||
108 | Ident(Ident), | ||
109 | } | ||
110 | |||
111 | #[derive(Serialize, Deserialize)] | ||
112 | #[serde(remote = "Literal")] | ||
113 | struct LiteralDef { | ||
114 | pub text: SmolStr, | ||
115 | #[serde(with = "TokenIdDef")] | ||
116 | pub id: TokenId, | ||
117 | } | ||
118 | |||
119 | #[derive(Serialize, Deserialize)] | ||
120 | #[serde(remote = "Punct")] | ||
121 | struct PunctDef { | ||
122 | pub char: char, | ||
123 | #[serde(with = "SpacingDef")] | ||
124 | pub spacing: Spacing, | ||
125 | #[serde(with = "TokenIdDef")] | ||
126 | pub id: TokenId, | ||
127 | } | ||
128 | |||
129 | #[derive(Serialize, Deserialize)] | ||
130 | #[serde(remote = "Spacing")] | ||
131 | enum SpacingDef { | ||
132 | Alone, | ||
133 | Joint, | ||
134 | } | ||
135 | |||
136 | #[derive(Serialize, Deserialize)] | ||
137 | #[serde(remote = "Ident")] | ||
138 | struct IdentDef { | ||
139 | pub text: SmolStr, | ||
140 | #[serde(with = "TokenIdDef")] | ||
141 | pub id: TokenId, | ||
142 | } | ||
143 | |||
144 | mod opt_delimiter_def { | ||
145 | use super::{Delimiter, DelimiterDef}; | ||
146 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; | ||
147 | |||
148 | pub fn serialize<S>(value: &Option<Delimiter>, serializer: S) -> Result<S::Ok, S::Error> | ||
149 | where | ||
150 | S: Serializer, | ||
151 | { | ||
152 | #[derive(Serialize)] | ||
153 | struct Helper<'a>(#[serde(with = "DelimiterDef")] &'a Delimiter); | ||
154 | value.as_ref().map(Helper).serialize(serializer) | ||
155 | } | ||
156 | |||
157 | pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Delimiter>, D::Error> | ||
158 | where | ||
159 | D: Deserializer<'de>, | ||
160 | { | ||
161 | #[derive(Deserialize)] | ||
162 | struct Helper(#[serde(with = "DelimiterDef")] Delimiter); | ||
163 | let helper = Option::deserialize(deserializer)?; | ||
164 | Ok(helper.map(|Helper(external)| external)) | ||
165 | } | ||
166 | } | ||
167 | |||
168 | mod opt_subtree_def { | ||
169 | use super::{Subtree, SubtreeDef}; | ||
170 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; | ||
171 | |||
172 | pub fn serialize<S>(value: &Option<Subtree>, serializer: S) -> Result<S::Ok, S::Error> | ||
173 | where | ||
174 | S: Serializer, | ||
175 | { | ||
176 | #[derive(Serialize)] | ||
177 | struct Helper<'a>(#[serde(with = "SubtreeDef")] &'a Subtree); | ||
178 | value.as_ref().map(Helper).serialize(serializer) | ||
179 | } | ||
180 | |||
181 | pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Subtree>, D::Error> | ||
182 | where | ||
183 | D: Deserializer<'de>, | ||
184 | { | ||
185 | #[derive(Deserialize)] | ||
186 | struct Helper(#[serde(with = "SubtreeDef")] Subtree); | ||
187 | let helper = Option::deserialize(deserializer)?; | ||
188 | Ok(helper.map(|Helper(external)| external)) | ||
189 | } | ||
190 | } | ||
191 | |||
192 | mod vec_token_tree { | ||
193 | use super::{TokenTree, TokenTreeDef}; | ||
194 | use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer}; | ||
195 | |||
196 | pub fn serialize<S>(value: &Vec<TokenTree>, serializer: S) -> Result<S::Ok, S::Error> | ||
197 | where | ||
198 | S: Serializer, | ||
199 | { | ||
200 | #[derive(Serialize)] | ||
201 | struct Helper<'a>(#[serde(with = "TokenTreeDef")] &'a TokenTree); | ||
202 | |||
203 | let items: Vec<_> = value.iter().map(Helper).collect(); | ||
204 | let mut seq = serializer.serialize_seq(Some(items.len()))?; | ||
205 | for element in items { | ||
206 | seq.serialize_element(&element)?; | ||
207 | } | ||
208 | seq.end() | ||
209 | } | ||
210 | |||
211 | pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<TokenTree>, D::Error> | ||
212 | where | ||
213 | D: Deserializer<'de>, | ||
214 | { | ||
215 | #[derive(Deserialize)] | ||
216 | struct Helper(#[serde(with = "TokenTreeDef")] TokenTree); | ||
217 | |||
218 | let helper = Vec::deserialize(deserializer)?; | ||
219 | Ok(helper.into_iter().map(|Helper(external)| external).collect()) | ||
220 | } | ||
221 | } | ||
222 | |||
223 | #[cfg(test)] | ||
224 | mod tests { | ||
225 | use super::*; | ||
226 | |||
227 | fn fixture_token_tree() -> Subtree { | ||
228 | let mut subtree = Subtree::default(); | ||
229 | subtree | ||
230 | .token_trees | ||
231 | .push(TokenTree::Leaf(Ident { text: "struct".into(), id: TokenId(0) }.into())); | ||
232 | subtree | ||
233 | .token_trees | ||
234 | .push(TokenTree::Leaf(Ident { text: "Foo".into(), id: TokenId(1) }.into())); | ||
235 | subtree.token_trees.push(TokenTree::Subtree( | ||
236 | Subtree { | ||
237 | delimiter: Some(Delimiter { id: TokenId(2), kind: DelimiterKind::Brace }), | ||
238 | token_trees: vec![], | ||
239 | } | ||
240 | .into(), | ||
241 | )); | ||
242 | subtree | ||
243 | } | ||
244 | |||
245 | #[test] | ||
246 | fn test_proc_macro_rpc_works() { | ||
247 | let tt = fixture_token_tree(); | ||
248 | let task = ExpansionTask { | ||
249 | macro_body: tt.clone(), | ||
250 | macro_name: Default::default(), | ||
251 | attributes: None, | ||
252 | lib: Default::default(), | ||
253 | }; | ||
254 | |||
255 | let json = serde_json::to_string(&task).unwrap(); | ||
256 | let back: ExpansionTask = serde_json::from_str(&json).unwrap(); | ||
257 | |||
258 | assert_eq!(task.macro_body, back.macro_body); | ||
259 | |||
260 | let result = ExpansionResult { expansion: tt.clone() }; | ||
261 | let json = serde_json::to_string(&result).unwrap(); | ||
262 | let back: ExpansionResult = serde_json::from_str(&json).unwrap(); | ||
263 | |||
264 | assert_eq!(result, back); | ||
265 | } | ||
266 | } | ||
diff --git a/crates/ra_prof/src/lib.rs b/crates/ra_prof/src/lib.rs index 9e167db96..00ea3a9b0 100644 --- a/crates/ra_prof/src/lib.rs +++ b/crates/ra_prof/src/lib.rs | |||
@@ -339,6 +339,14 @@ pub fn print_backtrace() { | |||
339 | let bt = backtrace::Backtrace::new(); | 339 | let bt = backtrace::Backtrace::new(); |
340 | eprintln!("{:?}", bt); | 340 | eprintln!("{:?}", bt); |
341 | } | 341 | } |
342 | #[cfg(not(feature = "backtrace"))] | ||
343 | pub fn print_backtrace() { | ||
344 | eprintln!( | ||
345 | r#"enable the backtrace feature: | ||
346 | ra_prof = {{ path = "../ra_prof", features = [ "backtrace"] }} | ||
347 | "# | ||
348 | ); | ||
349 | } | ||
342 | 350 | ||
343 | thread_local!(static IN_SCOPE: RefCell<bool> = RefCell::new(false)); | 351 | thread_local!(static IN_SCOPE: RefCell<bool> = RefCell::new(false)); |
344 | 352 | ||
diff --git a/crates/ra_project_model/Cargo.toml b/crates/ra_project_model/Cargo.toml index cdcdd63c9..b10644b4b 100644 --- a/crates/ra_project_model/Cargo.toml +++ b/crates/ra_project_model/Cargo.toml | |||
@@ -16,7 +16,6 @@ cargo_metadata = "0.9.1" | |||
16 | ra_arena = { path = "../ra_arena" } | 16 | ra_arena = { path = "../ra_arena" } |
17 | ra_db = { path = "../ra_db" } | 17 | ra_db = { path = "../ra_db" } |
18 | ra_cfg = { path = "../ra_cfg" } | 18 | ra_cfg = { path = "../ra_cfg" } |
19 | ra_cargo_watch = { path = "../ra_cargo_watch" } | ||
20 | ra_proc_macro = { path = "../ra_proc_macro" } | 19 | ra_proc_macro = { path = "../ra_proc_macro" } |
21 | 20 | ||
22 | serde = { version = "1.0.104", features = ["derive"] } | 21 | serde = { version = "1.0.104", features = ["derive"] } |
diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs index 291594e2a..b50cda06f 100644 --- a/crates/ra_project_model/src/cargo_workspace.rs +++ b/crates/ra_project_model/src/cargo_workspace.rs | |||
@@ -1,17 +1,18 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use std::{ | 3 | use std::{ |
4 | env, | ||
5 | ffi::OsStr, | ||
4 | ops, | 6 | ops, |
5 | path::{Path, PathBuf}, | 7 | path::{Path, PathBuf}, |
8 | process::Command, | ||
6 | }; | 9 | }; |
7 | 10 | ||
8 | use anyhow::{Context, Result}; | 11 | use anyhow::{Context, Result}; |
9 | use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId}; | 12 | use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId}; |
10 | use ra_arena::{Arena, Idx}; | 13 | use ra_arena::{Arena, Idx}; |
11 | use ra_cargo_watch::run_cargo; | ||
12 | use ra_db::Edition; | 14 | use ra_db::Edition; |
13 | use rustc_hash::FxHashMap; | 15 | use rustc_hash::FxHashMap; |
14 | use serde::Deserialize; | ||
15 | 16 | ||
16 | /// `CargoWorkspace` represents the logical structure of, well, a Cargo | 17 | /// `CargoWorkspace` represents the logical structure of, well, a Cargo |
17 | /// workspace. It pretty closely mirrors `cargo metadata` output. | 18 | /// workspace. It pretty closely mirrors `cargo metadata` output. |
@@ -41,9 +42,8 @@ impl ops::Index<Target> for CargoWorkspace { | |||
41 | } | 42 | } |
42 | } | 43 | } |
43 | 44 | ||
44 | #[derive(Deserialize, Clone, Debug, PartialEq, Eq)] | 45 | #[derive(Clone, Debug, PartialEq, Eq)] |
45 | #[serde(rename_all = "camelCase", default)] | 46 | pub struct CargoConfig { |
46 | pub struct CargoFeatures { | ||
47 | /// Do not activate the `default` feature. | 47 | /// Do not activate the `default` feature. |
48 | pub no_default_features: bool, | 48 | pub no_default_features: bool, |
49 | 49 | ||
@@ -58,9 +58,9 @@ pub struct CargoFeatures { | |||
58 | pub load_out_dirs_from_check: bool, | 58 | pub load_out_dirs_from_check: bool, |
59 | } | 59 | } |
60 | 60 | ||
61 | impl Default for CargoFeatures { | 61 | impl Default for CargoConfig { |
62 | fn default() -> Self { | 62 | fn default() -> Self { |
63 | CargoFeatures { | 63 | CargoConfig { |
64 | no_default_features: false, | 64 | no_default_features: false, |
65 | all_features: true, | 65 | all_features: true, |
66 | features: Vec::new(), | 66 | features: Vec::new(), |
@@ -75,6 +75,7 @@ pub type Target = Idx<TargetData>; | |||
75 | 75 | ||
76 | #[derive(Debug, Clone)] | 76 | #[derive(Debug, Clone)] |
77 | pub struct PackageData { | 77 | pub struct PackageData { |
78 | pub version: String, | ||
78 | pub name: String, | 79 | pub name: String, |
79 | pub manifest: PathBuf, | 80 | pub manifest: PathBuf, |
80 | pub targets: Vec<Target>, | 81 | pub targets: Vec<Target>, |
@@ -138,7 +139,7 @@ impl PackageData { | |||
138 | impl CargoWorkspace { | 139 | impl CargoWorkspace { |
139 | pub fn from_cargo_metadata( | 140 | pub fn from_cargo_metadata( |
140 | cargo_toml: &Path, | 141 | cargo_toml: &Path, |
141 | cargo_features: &CargoFeatures, | 142 | cargo_features: &CargoConfig, |
142 | ) -> Result<CargoWorkspace> { | 143 | ) -> Result<CargoWorkspace> { |
143 | let mut meta = MetadataCommand::new(); | 144 | let mut meta = MetadataCommand::new(); |
144 | meta.manifest_path(cargo_toml); | 145 | meta.manifest_path(cargo_toml); |
@@ -161,7 +162,7 @@ impl CargoWorkspace { | |||
161 | let mut out_dir_by_id = FxHashMap::default(); | 162 | let mut out_dir_by_id = FxHashMap::default(); |
162 | let mut proc_macro_dylib_paths = FxHashMap::default(); | 163 | let mut proc_macro_dylib_paths = FxHashMap::default(); |
163 | if cargo_features.load_out_dirs_from_check { | 164 | if cargo_features.load_out_dirs_from_check { |
164 | let resources = load_extern_resources(cargo_toml, cargo_features); | 165 | let resources = load_extern_resources(cargo_toml, cargo_features)?; |
165 | out_dir_by_id = resources.out_dirs; | 166 | out_dir_by_id = resources.out_dirs; |
166 | proc_macro_dylib_paths = resources.proc_dylib_paths; | 167 | proc_macro_dylib_paths = resources.proc_dylib_paths; |
167 | } | 168 | } |
@@ -173,13 +174,15 @@ impl CargoWorkspace { | |||
173 | let ws_members = &meta.workspace_members; | 174 | let ws_members = &meta.workspace_members; |
174 | 175 | ||
175 | for meta_pkg in meta.packages { | 176 | for meta_pkg in meta.packages { |
176 | let cargo_metadata::Package { id, edition, name, manifest_path, .. } = meta_pkg; | 177 | let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } = |
178 | meta_pkg; | ||
177 | let is_member = ws_members.contains(&id); | 179 | let is_member = ws_members.contains(&id); |
178 | let edition = edition | 180 | let edition = edition |
179 | .parse::<Edition>() | 181 | .parse::<Edition>() |
180 | .with_context(|| format!("Failed to parse edition {}", edition))?; | 182 | .with_context(|| format!("Failed to parse edition {}", edition))?; |
181 | let pkg = packages.alloc(PackageData { | 183 | let pkg = packages.alloc(PackageData { |
182 | name, | 184 | name, |
185 | version: version.to_string(), | ||
183 | manifest: manifest_path, | 186 | manifest: manifest_path, |
184 | targets: Vec::new(), | 187 | targets: Vec::new(), |
185 | is_member, | 188 | is_member, |
@@ -249,6 +252,18 @@ impl CargoWorkspace { | |||
249 | pub fn workspace_root(&self) -> &Path { | 252 | pub fn workspace_root(&self) -> &Path { |
250 | &self.workspace_root | 253 | &self.workspace_root |
251 | } | 254 | } |
255 | |||
256 | pub fn package_flag(&self, package: &PackageData) -> String { | ||
257 | if self.is_unique(&*package.name) { | ||
258 | package.name.clone() | ||
259 | } else { | ||
260 | format!("{}:{}", package.name, package.version) | ||
261 | } | ||
262 | } | ||
263 | |||
264 | fn is_unique(&self, name: &str) -> bool { | ||
265 | self.packages.iter().filter(|(_, v)| v.name == name).count() == 1 | ||
266 | } | ||
252 | } | 267 | } |
253 | 268 | ||
254 | #[derive(Debug, Clone, Default)] | 269 | #[derive(Debug, Clone, Default)] |
@@ -257,48 +272,60 @@ pub struct ExternResources { | |||
257 | proc_dylib_paths: FxHashMap<PackageId, PathBuf>, | 272 | proc_dylib_paths: FxHashMap<PackageId, PathBuf>, |
258 | } | 273 | } |
259 | 274 | ||
260 | pub fn load_extern_resources(cargo_toml: &Path, cargo_features: &CargoFeatures) -> ExternResources { | 275 | pub fn load_extern_resources( |
261 | let mut args: Vec<String> = vec![ | 276 | cargo_toml: &Path, |
262 | "check".to_string(), | 277 | cargo_features: &CargoConfig, |
263 | "--message-format=json".to_string(), | 278 | ) -> Result<ExternResources> { |
264 | "--manifest-path".to_string(), | 279 | let mut cmd = Command::new(cargo_binary()); |
265 | cargo_toml.display().to_string(), | 280 | cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml); |
266 | ]; | ||
267 | |||
268 | if cargo_features.all_features { | 281 | if cargo_features.all_features { |
269 | args.push("--all-features".to_string()); | 282 | cmd.arg("--all-features"); |
270 | } else if cargo_features.no_default_features { | 283 | } else if cargo_features.no_default_features { |
271 | // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` | 284 | // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures` |
272 | // https://github.com/oli-obk/cargo_metadata/issues/79 | 285 | // https://github.com/oli-obk/cargo_metadata/issues/79 |
273 | args.push("--no-default-features".to_string()); | 286 | cmd.arg("--no-default-features"); |
274 | } else { | 287 | } else { |
275 | args.extend(cargo_features.features.iter().cloned()); | 288 | cmd.args(&cargo_features.features); |
276 | } | 289 | } |
277 | 290 | ||
278 | let mut acc = ExternResources::default(); | 291 | let output = cmd.output()?; |
279 | let res = run_cargo(&args, cargo_toml.parent(), &mut |message| { | ||
280 | match message { | ||
281 | Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => { | ||
282 | acc.out_dirs.insert(package_id, out_dir); | ||
283 | } | ||
284 | 292 | ||
285 | Message::CompilerArtifact(message) => { | 293 | let mut res = ExternResources::default(); |
286 | if message.target.kind.contains(&"proc-macro".to_string()) { | 294 | |
287 | let package_id = message.package_id; | 295 | for message in cargo_metadata::parse_messages(output.stdout.as_slice()) { |
288 | if let Some(filename) = message.filenames.get(0) { | 296 | if let Ok(message) = message { |
289 | acc.proc_dylib_paths.insert(package_id, filename.clone()); | 297 | match message { |
298 | Message::BuildScriptExecuted(BuildScript { package_id, out_dir, .. }) => { | ||
299 | res.out_dirs.insert(package_id, out_dir); | ||
300 | } | ||
301 | |||
302 | Message::CompilerArtifact(message) => { | ||
303 | if message.target.kind.contains(&"proc-macro".to_string()) { | ||
304 | let package_id = message.package_id; | ||
305 | // Skip rmeta file | ||
306 | if let Some(filename) = | ||
307 | message.filenames.iter().filter(|name| is_dylib(name)).next() | ||
308 | { | ||
309 | res.proc_dylib_paths.insert(package_id, filename.clone()); | ||
310 | } | ||
290 | } | 311 | } |
291 | } | 312 | } |
313 | Message::CompilerMessage(_) => (), | ||
314 | Message::Unknown => (), | ||
292 | } | 315 | } |
293 | Message::CompilerMessage(_) => (), | ||
294 | Message::Unknown => (), | ||
295 | } | 316 | } |
296 | true | 317 | } |
297 | }); | 318 | Ok(res) |
319 | } | ||
298 | 320 | ||
299 | if let Err(err) = res { | 321 | // FIXME: File a better way to know if it is a dylib |
300 | log::error!("Failed to load outdirs: {:?}", err); | 322 | fn is_dylib(path: &Path) -> bool { |
323 | match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) { | ||
324 | None => false, | ||
325 | Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"), | ||
301 | } | 326 | } |
327 | } | ||
302 | 328 | ||
303 | acc | 329 | fn cargo_binary() -> String { |
330 | env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()) | ||
304 | } | 331 | } |
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index 444d3bb3f..0ab64a1e0 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs | |||
@@ -19,7 +19,7 @@ use rustc_hash::FxHashMap; | |||
19 | use serde_json::from_reader; | 19 | use serde_json::from_reader; |
20 | 20 | ||
21 | pub use crate::{ | 21 | pub use crate::{ |
22 | cargo_workspace::{CargoFeatures, CargoWorkspace, Package, Target, TargetKind}, | 22 | cargo_workspace::{CargoConfig, CargoWorkspace, Package, Target, TargetKind}, |
23 | json_project::JsonProject, | 23 | json_project::JsonProject, |
24 | sysroot::Sysroot, | 24 | sysroot::Sysroot, |
25 | }; | 25 | }; |
@@ -62,30 +62,30 @@ pub struct PackageRoot { | |||
62 | /// Is a member of the current workspace | 62 | /// Is a member of the current workspace |
63 | is_member: bool, | 63 | is_member: bool, |
64 | } | 64 | } |
65 | |||
66 | impl PackageRoot { | 65 | impl PackageRoot { |
67 | pub fn new(path: PathBuf, is_member: bool) -> PackageRoot { | 66 | pub fn new_member(path: PathBuf) -> PackageRoot { |
68 | PackageRoot { path, is_member } | 67 | Self { path, is_member: true } |
69 | } | 68 | } |
70 | 69 | pub fn new_non_member(path: PathBuf) -> PackageRoot { | |
71 | pub fn path(&self) -> &PathBuf { | 70 | Self { path, is_member: false } |
71 | } | ||
72 | pub fn path(&self) -> &Path { | ||
72 | &self.path | 73 | &self.path |
73 | } | 74 | } |
74 | |||
75 | pub fn is_member(&self) -> bool { | 75 | pub fn is_member(&self) -> bool { |
76 | self.is_member | 76 | self.is_member |
77 | } | 77 | } |
78 | } | 78 | } |
79 | 79 | ||
80 | impl ProjectWorkspace { | 80 | impl ProjectWorkspace { |
81 | pub fn discover(path: &Path, cargo_features: &CargoFeatures) -> Result<ProjectWorkspace> { | 81 | pub fn discover(path: &Path, cargo_features: &CargoConfig) -> Result<ProjectWorkspace> { |
82 | ProjectWorkspace::discover_with_sysroot(path, true, cargo_features) | 82 | ProjectWorkspace::discover_with_sysroot(path, true, cargo_features) |
83 | } | 83 | } |
84 | 84 | ||
85 | pub fn discover_with_sysroot( | 85 | pub fn discover_with_sysroot( |
86 | path: &Path, | 86 | path: &Path, |
87 | with_sysroot: bool, | 87 | with_sysroot: bool, |
88 | cargo_features: &CargoFeatures, | 88 | cargo_features: &CargoConfig, |
89 | ) -> Result<ProjectWorkspace> { | 89 | ) -> Result<ProjectWorkspace> { |
90 | match find_rust_project_json(path) { | 90 | match find_rust_project_json(path) { |
91 | Some(json_path) => { | 91 | Some(json_path) => { |
@@ -130,70 +130,45 @@ impl ProjectWorkspace { | |||
130 | pub fn to_roots(&self) -> Vec<PackageRoot> { | 130 | pub fn to_roots(&self) -> Vec<PackageRoot> { |
131 | match self { | 131 | match self { |
132 | ProjectWorkspace::Json { project } => { | 132 | ProjectWorkspace::Json { project } => { |
133 | let mut roots = Vec::with_capacity(project.roots.len()); | 133 | project.roots.iter().map(|r| PackageRoot::new_member(r.path.clone())).collect() |
134 | for root in &project.roots { | ||
135 | roots.push(PackageRoot::new(root.path.clone(), true)); | ||
136 | } | ||
137 | roots | ||
138 | } | ||
139 | ProjectWorkspace::Cargo { cargo, sysroot } => { | ||
140 | let mut roots = Vec::with_capacity(cargo.packages().len() + sysroot.crates().len()); | ||
141 | for pkg in cargo.packages() { | ||
142 | let root = cargo[pkg].root().to_path_buf(); | ||
143 | let member = cargo[pkg].is_member; | ||
144 | roots.push(PackageRoot::new(root, member)); | ||
145 | } | ||
146 | for krate in sysroot.crates() { | ||
147 | roots.push(PackageRoot::new(sysroot[krate].root_dir().to_path_buf(), false)) | ||
148 | } | ||
149 | roots | ||
150 | } | 134 | } |
135 | ProjectWorkspace::Cargo { cargo, sysroot } => cargo | ||
136 | .packages() | ||
137 | .map(|pkg| PackageRoot { | ||
138 | path: cargo[pkg].root().to_path_buf(), | ||
139 | is_member: cargo[pkg].is_member, | ||
140 | }) | ||
141 | .chain(sysroot.crates().map(|krate| { | ||
142 | PackageRoot::new_non_member(sysroot[krate].root_dir().to_path_buf()) | ||
143 | })) | ||
144 | .collect(), | ||
151 | } | 145 | } |
152 | } | 146 | } |
153 | 147 | ||
154 | pub fn out_dirs(&self) -> Vec<PathBuf> { | 148 | pub fn out_dirs(&self) -> Vec<PathBuf> { |
155 | match self { | 149 | match self { |
156 | ProjectWorkspace::Json { project } => { | 150 | ProjectWorkspace::Json { project } => { |
157 | let mut out_dirs = Vec::with_capacity(project.crates.len()); | 151 | project.crates.iter().filter_map(|krate| krate.out_dir.as_ref()).cloned().collect() |
158 | for krate in &project.crates { | ||
159 | if let Some(out_dir) = &krate.out_dir { | ||
160 | out_dirs.push(out_dir.to_path_buf()); | ||
161 | } | ||
162 | } | ||
163 | out_dirs | ||
164 | } | 152 | } |
165 | ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => { | 153 | ProjectWorkspace::Cargo { cargo, sysroot: _ } => { |
166 | let mut out_dirs = Vec::with_capacity(cargo.packages().len()); | 154 | cargo.packages().filter_map(|pkg| cargo[pkg].out_dir.as_ref()).cloned().collect() |
167 | for pkg in cargo.packages() { | ||
168 | if let Some(out_dir) = &cargo[pkg].out_dir { | ||
169 | out_dirs.push(out_dir.to_path_buf()); | ||
170 | } | ||
171 | } | ||
172 | out_dirs | ||
173 | } | 155 | } |
174 | } | 156 | } |
175 | } | 157 | } |
176 | 158 | ||
177 | pub fn proc_macro_dylib_paths(&self) -> Vec<PathBuf> { | 159 | pub fn proc_macro_dylib_paths(&self) -> Vec<PathBuf> { |
178 | match self { | 160 | match self { |
179 | ProjectWorkspace::Json { project } => { | 161 | ProjectWorkspace::Json { project } => project |
180 | let mut proc_macro_dylib_paths = Vec::with_capacity(project.crates.len()); | 162 | .crates |
181 | for krate in &project.crates { | 163 | .iter() |
182 | if let Some(out_dir) = &krate.proc_macro_dylib_path { | 164 | .filter_map(|krate| krate.proc_macro_dylib_path.as_ref()) |
183 | proc_macro_dylib_paths.push(out_dir.to_path_buf()); | 165 | .cloned() |
184 | } | 166 | .collect(), |
185 | } | 167 | ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => cargo |
186 | proc_macro_dylib_paths | 168 | .packages() |
187 | } | 169 | .filter_map(|pkg| cargo[pkg].proc_macro_dylib_path.as_ref()) |
188 | ProjectWorkspace::Cargo { cargo, sysroot: _sysroot } => { | 170 | .cloned() |
189 | let mut proc_macro_dylib_paths = Vec::with_capacity(cargo.packages().len()); | 171 | .collect(), |
190 | for pkg in cargo.packages() { | ||
191 | if let Some(dylib_path) = &cargo[pkg].proc_macro_dylib_path { | ||
192 | proc_macro_dylib_paths.push(dylib_path.to_path_buf()); | ||
193 | } | ||
194 | } | ||
195 | proc_macro_dylib_paths | ||
196 | } | ||
197 | } | 172 | } |
198 | } | 173 | } |
199 | 174 | ||
@@ -216,10 +191,12 @@ impl ProjectWorkspace { | |||
216 | let mut crate_graph = CrateGraph::default(); | 191 | let mut crate_graph = CrateGraph::default(); |
217 | match self { | 192 | match self { |
218 | ProjectWorkspace::Json { project } => { | 193 | ProjectWorkspace::Json { project } => { |
219 | let mut crates = FxHashMap::default(); | 194 | let crates: FxHashMap<_, _> = project |
220 | for (id, krate) in project.crates.iter().enumerate() { | 195 | .crates |
221 | let crate_id = json_project::CrateId(id); | 196 | .iter() |
222 | if let Some(file_id) = load(&krate.root_module) { | 197 | .enumerate() |
198 | .filter_map(|(seq_index, krate)| { | ||
199 | let file_id = load(&krate.root_module)?; | ||
223 | let edition = match krate.edition { | 200 | let edition = match krate.edition { |
224 | json_project::Edition::Edition2015 => Edition::Edition2015, | 201 | json_project::Edition::Edition2015 => Edition::Edition2015, |
225 | json_project::Edition::Edition2018 => Edition::Edition2018, | 202 | json_project::Edition::Edition2018 => Edition::Edition2018, |
@@ -249,8 +226,8 @@ impl ProjectWorkspace { | |||
249 | .clone() | 226 | .clone() |
250 | .map(|it| proc_macro_client.by_dylib_path(&it)); | 227 | .map(|it| proc_macro_client.by_dylib_path(&it)); |
251 | // FIXME: No crate name in json definition such that we cannot add OUT_DIR to env | 228 | // FIXME: No crate name in json definition such that we cannot add OUT_DIR to env |
252 | crates.insert( | 229 | Some(( |
253 | crate_id, | 230 | json_project::CrateId(seq_index), |
254 | crate_graph.add_crate_root( | 231 | crate_graph.add_crate_root( |
255 | file_id, | 232 | file_id, |
256 | edition, | 233 | edition, |
@@ -261,9 +238,9 @@ impl ProjectWorkspace { | |||
261 | extern_source, | 238 | extern_source, |
262 | proc_macro.unwrap_or_default(), | 239 | proc_macro.unwrap_or_default(), |
263 | ), | 240 | ), |
264 | ); | 241 | )) |
265 | } | 242 | }) |
266 | } | 243 | .collect(); |
267 | 244 | ||
268 | for (id, krate) in project.crates.iter().enumerate() { | 245 | for (id, krate) in project.crates.iter().enumerate() { |
269 | for dep in &krate.deps { | 246 | for dep in &krate.deps { |
@@ -287,9 +264,11 @@ impl ProjectWorkspace { | |||
287 | } | 264 | } |
288 | } | 265 | } |
289 | ProjectWorkspace::Cargo { cargo, sysroot } => { | 266 | ProjectWorkspace::Cargo { cargo, sysroot } => { |
290 | let mut sysroot_crates = FxHashMap::default(); | 267 | let sysroot_crates: FxHashMap<_, _> = sysroot |
291 | for krate in sysroot.crates() { | 268 | .crates() |
292 | if let Some(file_id) = load(&sysroot[krate].root) { | 269 | .filter_map(|krate| { |
270 | let file_id = load(&sysroot[krate].root)?; | ||
271 | |||
293 | // Crates from sysroot have `cfg(test)` disabled | 272 | // Crates from sysroot have `cfg(test)` disabled |
294 | let cfg_options = { | 273 | let cfg_options = { |
295 | let mut opts = default_cfg_options.clone(); | 274 | let mut opts = default_cfg_options.clone(); |
@@ -300,22 +279,22 @@ impl ProjectWorkspace { | |||
300 | let env = Env::default(); | 279 | let env = Env::default(); |
301 | let extern_source = ExternSource::default(); | 280 | let extern_source = ExternSource::default(); |
302 | let proc_macro = vec![]; | 281 | let proc_macro = vec![]; |
282 | let crate_name = CrateName::new(&sysroot[krate].name) | ||
283 | .expect("Sysroot crate names should not contain dashes"); | ||
303 | 284 | ||
304 | let crate_id = crate_graph.add_crate_root( | 285 | let crate_id = crate_graph.add_crate_root( |
305 | file_id, | 286 | file_id, |
306 | Edition::Edition2018, | 287 | Edition::Edition2018, |
307 | Some( | 288 | Some(crate_name), |
308 | CrateName::new(&sysroot[krate].name) | ||
309 | .expect("Sysroot crate names should not contain dashes"), | ||
310 | ), | ||
311 | cfg_options, | 289 | cfg_options, |
312 | env, | 290 | env, |
313 | extern_source, | 291 | extern_source, |
314 | proc_macro, | 292 | proc_macro, |
315 | ); | 293 | ); |
316 | sysroot_crates.insert(krate, crate_id); | 294 | Some((krate, crate_id)) |
317 | } | 295 | }) |
318 | } | 296 | .collect(); |
297 | |||
319 | for from in sysroot.crates() { | 298 | for from in sysroot.crates() { |
320 | for &to in sysroot[from].deps.iter() { | 299 | for &to in sysroot[from].deps.iter() { |
321 | let name = &sysroot[to].name; | 300 | let name = &sysroot[to].name; |
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 2304e00cf..b69cae234 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs | |||
@@ -46,14 +46,46 @@ impl ast::FnDef { | |||
46 | } | 46 | } |
47 | } | 47 | } |
48 | 48 | ||
49 | fn make_multiline<N>(node: N) -> N | ||
50 | where | ||
51 | N: AstNode + Clone, | ||
52 | { | ||
53 | let l_curly = match node.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) { | ||
54 | Some(it) => it, | ||
55 | None => return node, | ||
56 | }; | ||
57 | let sibling = match l_curly.next_sibling_or_token() { | ||
58 | Some(it) => it, | ||
59 | None => return node, | ||
60 | }; | ||
61 | let existing_ws = match sibling.as_token() { | ||
62 | None => None, | ||
63 | Some(tok) if tok.kind() != WHITESPACE => None, | ||
64 | Some(ws) => { | ||
65 | if ws.text().contains('\n') { | ||
66 | return node; | ||
67 | } | ||
68 | Some(ws.clone()) | ||
69 | } | ||
70 | }; | ||
71 | |||
72 | let indent = leading_indent(node.syntax()).unwrap_or_default(); | ||
73 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | ||
74 | let to_insert = iter::once(ws.ws().into()); | ||
75 | match existing_ws { | ||
76 | None => node.insert_children(InsertPosition::After(l_curly), to_insert), | ||
77 | Some(ws) => node.replace_children(single_node(ws), to_insert), | ||
78 | } | ||
79 | } | ||
80 | |||
49 | impl ast::ItemList { | 81 | impl ast::ItemList { |
50 | #[must_use] | 82 | #[must_use] |
51 | pub fn append_items(&self, items: impl Iterator<Item = ast::ImplItem>) -> ast::ItemList { | 83 | pub fn append_items(&self, items: impl IntoIterator<Item = ast::ImplItem>) -> ast::ItemList { |
52 | let mut res = self.clone(); | 84 | let mut res = self.clone(); |
53 | if !self.syntax().text().contains_char('\n') { | 85 | if !self.syntax().text().contains_char('\n') { |
54 | res = res.make_multiline(); | 86 | res = make_multiline(res); |
55 | } | 87 | } |
56 | items.for_each(|it| res = res.append_item(it)); | 88 | items.into_iter().for_each(|it| res = res.append_item(it)); |
57 | res | 89 | res |
58 | } | 90 | } |
59 | 91 | ||
@@ -81,35 +113,6 @@ impl ast::ItemList { | |||
81 | fn l_curly(&self) -> Option<SyntaxElement> { | 113 | fn l_curly(&self) -> Option<SyntaxElement> { |
82 | self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) | 114 | self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) |
83 | } | 115 | } |
84 | |||
85 | fn make_multiline(&self) -> ast::ItemList { | ||
86 | let l_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) { | ||
87 | Some(it) => it, | ||
88 | None => return self.clone(), | ||
89 | }; | ||
90 | let sibling = match l_curly.next_sibling_or_token() { | ||
91 | Some(it) => it, | ||
92 | None => return self.clone(), | ||
93 | }; | ||
94 | let existing_ws = match sibling.as_token() { | ||
95 | None => None, | ||
96 | Some(tok) if tok.kind() != WHITESPACE => None, | ||
97 | Some(ws) => { | ||
98 | if ws.text().contains('\n') { | ||
99 | return self.clone(); | ||
100 | } | ||
101 | Some(ws.clone()) | ||
102 | } | ||
103 | }; | ||
104 | |||
105 | let indent = leading_indent(self.syntax()).unwrap_or_default(); | ||
106 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | ||
107 | let to_insert = iter::once(ws.ws().into()); | ||
108 | match existing_ws { | ||
109 | None => self.insert_children(InsertPosition::After(l_curly), to_insert), | ||
110 | Some(ws) => self.replace_children(single_node(ws), to_insert), | ||
111 | } | ||
112 | } | ||
113 | } | 116 | } |
114 | 117 | ||
115 | impl ast::RecordFieldList { | 118 | impl ast::RecordFieldList { |
@@ -334,6 +337,80 @@ impl ast::UseTree { | |||
334 | } | 337 | } |
335 | } | 338 | } |
336 | 339 | ||
340 | impl ast::MatchArmList { | ||
341 | #[must_use] | ||
342 | pub fn append_arms(&self, items: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList { | ||
343 | let mut res = self.clone(); | ||
344 | res = res.strip_if_only_whitespace(); | ||
345 | if !res.syntax().text().contains_char('\n') { | ||
346 | res = make_multiline(res); | ||
347 | } | ||
348 | items.into_iter().for_each(|it| res = res.append_arm(it)); | ||
349 | res | ||
350 | } | ||
351 | |||
352 | fn strip_if_only_whitespace(&self) -> ast::MatchArmList { | ||
353 | let mut iter = self.syntax().children_with_tokens().skip_while(|it| it.kind() != T!['{']); | ||
354 | iter.next(); // Eat the curly | ||
355 | let mut inner = iter.take_while(|it| it.kind() != T!['}']); | ||
356 | if !inner.clone().all(|it| it.kind() == WHITESPACE) { | ||
357 | return self.clone(); | ||
358 | } | ||
359 | let start = match inner.next() { | ||
360 | Some(s) => s, | ||
361 | None => return self.clone(), | ||
362 | }; | ||
363 | let end = match inner.last() { | ||
364 | Some(s) => s, | ||
365 | None => start.clone(), | ||
366 | }; | ||
367 | self.replace_children(start..=end, &mut iter::empty()) | ||
368 | } | ||
369 | |||
370 | #[must_use] | ||
371 | pub fn remove_placeholder(&self) -> ast::MatchArmList { | ||
372 | let placeholder = | ||
373 | self.arms().find(|arm| matches!(arm.pat(), Some(ast::Pat::PlaceholderPat(_)))); | ||
374 | if let Some(placeholder) = placeholder { | ||
375 | self.remove_arm(&placeholder) | ||
376 | } else { | ||
377 | self.clone() | ||
378 | } | ||
379 | } | ||
380 | |||
381 | #[must_use] | ||
382 | fn remove_arm(&self, arm: &ast::MatchArm) -> ast::MatchArmList { | ||
383 | let start = arm.syntax().clone(); | ||
384 | let end = if let Some(comma) = start | ||
385 | .siblings_with_tokens(Direction::Next) | ||
386 | .skip(1) | ||
387 | .skip_while(|it| it.kind().is_trivia()) | ||
388 | .next() | ||
389 | .filter(|it| it.kind() == T![,]) | ||
390 | { | ||
391 | comma | ||
392 | } else { | ||
393 | start.clone().into() | ||
394 | }; | ||
395 | self.replace_children(start.into()..=end, None) | ||
396 | } | ||
397 | |||
398 | #[must_use] | ||
399 | pub fn append_arm(&self, item: ast::MatchArm) -> ast::MatchArmList { | ||
400 | let r_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['}']) { | ||
401 | Some(t) => t, | ||
402 | None => return self.clone(), | ||
403 | }; | ||
404 | let position = InsertPosition::Before(r_curly.into()); | ||
405 | let arm_ws = tokens::WsBuilder::new(" "); | ||
406 | let match_indent = &leading_indent(self.syntax()).unwrap_or_default(); | ||
407 | let match_ws = tokens::WsBuilder::new(&format!("\n{}", match_indent)); | ||
408 | let to_insert: ArrayVec<[SyntaxElement; 3]> = | ||
409 | [arm_ws.ws().into(), item.syntax().clone().into(), match_ws.ws().into()].into(); | ||
410 | self.insert_children(position, to_insert) | ||
411 | } | ||
412 | } | ||
413 | |||
337 | #[must_use] | 414 | #[must_use] |
338 | pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { | 415 | pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { |
339 | N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() | 416 | N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() |
diff --git a/crates/ra_syntax/src/ast/expr_extensions.rs b/crates/ra_syntax/src/ast/expr_extensions.rs index 77cceb382..8bbd946c0 100644 --- a/crates/ra_syntax/src/ast/expr_extensions.rs +++ b/crates/ra_syntax/src/ast/expr_extensions.rs | |||
@@ -308,7 +308,7 @@ pub enum LiteralKind { | |||
308 | Byte, | 308 | Byte, |
309 | IntNumber { suffix: Option<SmolStr> }, | 309 | IntNumber { suffix: Option<SmolStr> }, |
310 | FloatNumber { suffix: Option<SmolStr> }, | 310 | FloatNumber { suffix: Option<SmolStr> }, |
311 | Bool, | 311 | Bool(bool), |
312 | } | 312 | } |
313 | 313 | ||
314 | impl ast::Literal { | 314 | impl ast::Literal { |
@@ -355,7 +355,8 @@ impl ast::Literal { | |||
355 | LiteralKind::FloatNumber { suffix: Self::find_suffix(&text, &FLOAT_SUFFIXES) } | 355 | LiteralKind::FloatNumber { suffix: Self::find_suffix(&text, &FLOAT_SUFFIXES) } |
356 | } | 356 | } |
357 | STRING | RAW_STRING => LiteralKind::String, | 357 | STRING | RAW_STRING => LiteralKind::String, |
358 | T![true] | T![false] => LiteralKind::Bool, | 358 | T![true] => LiteralKind::Bool(true), |
359 | T![false] => LiteralKind::Bool(false), | ||
359 | BYTE_STRING | RAW_BYTE_STRING => LiteralKind::ByteString, | 360 | BYTE_STRING | RAW_BYTE_STRING => LiteralKind::ByteString, |
360 | CHAR => LiteralKind::Char, | 361 | CHAR => LiteralKind::Char, |
361 | BYTE => LiteralKind::Byte, | 362 | BYTE => LiteralKind::Byte, |
diff --git a/crates/ra_syntax/src/parsing/text_tree_sink.rs b/crates/ra_syntax/src/parsing/text_tree_sink.rs index dd202601d..87bb21cd9 100644 --- a/crates/ra_syntax/src/parsing/text_tree_sink.rs +++ b/crates/ra_syntax/src/parsing/text_tree_sink.rs | |||
@@ -149,10 +149,21 @@ fn n_attached_trivias<'a>( | |||
149 | MACRO_CALL | CONST_DEF | TYPE_ALIAS_DEF | STRUCT_DEF | ENUM_DEF | ENUM_VARIANT | FN_DEF | 149 | MACRO_CALL | CONST_DEF | TYPE_ALIAS_DEF | STRUCT_DEF | ENUM_DEF | ENUM_VARIANT | FN_DEF |
150 | | TRAIT_DEF | MODULE | RECORD_FIELD_DEF | STATIC_DEF => { | 150 | | TRAIT_DEF | MODULE | RECORD_FIELD_DEF | STATIC_DEF => { |
151 | let mut res = 0; | 151 | let mut res = 0; |
152 | for (i, (kind, text)) in trivias.enumerate() { | 152 | let mut trivias = trivias.enumerate().peekable(); |
153 | |||
154 | while let Some((i, (kind, text))) = trivias.next() { | ||
153 | match kind { | 155 | match kind { |
154 | WHITESPACE => { | 156 | WHITESPACE => { |
155 | if text.contains("\n\n") { | 157 | if text.contains("\n\n") { |
158 | // we check whether the next token is a doc-comment | ||
159 | // and skip the whitespace in this case | ||
160 | if let Some((peek_kind, peek_text)) = | ||
161 | trivias.peek().map(|(_, pair)| pair) | ||
162 | { | ||
163 | if *peek_kind == COMMENT && peek_text.starts_with("///") { | ||
164 | continue; | ||
165 | } | ||
166 | } | ||
156 | break; | 167 | break; |
157 | } | 168 | } |
158 | } | 169 | } |
diff --git a/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.rs b/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.rs new file mode 100644 index 000000000..1fafe216b --- /dev/null +++ b/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.rs | |||
@@ -0,0 +1,3 @@ | |||
1 | /// Example | ||
2 | |||
3 | fn test() {} | ||
diff --git a/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.txt b/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.txt new file mode 100644 index 000000000..91d0c3736 --- /dev/null +++ b/crates/ra_syntax/test_data/parser/ok/0065_comment_newline.txt | |||
@@ -0,0 +1,17 @@ | |||
1 | SOURCE_FILE@[0; 26) | ||
2 | FN_DEF@[0; 25) | ||
3 | COMMENT@[0; 11) "/// Example" | ||
4 | WHITESPACE@[11; 13) "\n\n" | ||
5 | FN_KW@[13; 15) "fn" | ||
6 | WHITESPACE@[15; 16) " " | ||
7 | NAME@[16; 20) | ||
8 | IDENT@[16; 20) "test" | ||
9 | PARAM_LIST@[20; 22) | ||
10 | L_PAREN@[20; 21) "(" | ||
11 | R_PAREN@[21; 22) ")" | ||
12 | WHITESPACE@[22; 23) " " | ||
13 | BLOCK_EXPR@[23; 25) | ||
14 | BLOCK@[23; 25) | ||
15 | L_CURLY@[23; 24) "{" | ||
16 | R_CURLY@[24; 25) "}" | ||
17 | WHITESPACE@[25; 26) "\n" | ||
diff --git a/crates/ra_tt/src/lib.rs b/crates/ra_tt/src/lib.rs index 1015ce0a6..bd484aa30 100644 --- a/crates/ra_tt/src/lib.rs +++ b/crates/ra_tt/src/lib.rs | |||
@@ -189,7 +189,12 @@ impl Subtree { | |||
189 | pub mod buffer; | 189 | pub mod buffer; |
190 | 190 | ||
191 | #[derive(Debug, PartialEq, Eq)] | 191 | #[derive(Debug, PartialEq, Eq)] |
192 | pub enum ExpansionError {} | 192 | pub enum ExpansionError { |
193 | IOError(String), | ||
194 | JsonError(String), | ||
195 | Unknown(String), | ||
196 | ExpansionError(String), | ||
197 | } | ||
193 | 198 | ||
194 | pub trait TokenExpander: Debug + Send + Sync + RefUnwindSafe { | 199 | pub trait TokenExpander: Debug + Send + Sync + RefUnwindSafe { |
195 | fn expand(&self, subtree: &Subtree, attrs: Option<&Subtree>) | 200 | fn expand(&self, subtree: &Subtree, attrs: Option<&Subtree>) |
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 8fe6799d2..f5f773432 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml | |||
@@ -33,7 +33,7 @@ threadpool = "1.7.1" | |||
33 | stdx = { path = "../stdx" } | 33 | stdx = { path = "../stdx" } |
34 | 34 | ||
35 | lsp-server = "0.3.1" | 35 | lsp-server = "0.3.1" |
36 | ra_cargo_watch = { path = "../ra_cargo_watch" } | 36 | ra_flycheck = { path = "../ra_flycheck" } |
37 | ra_ide = { path = "../ra_ide" } | 37 | ra_ide = { path = "../ra_ide" } |
38 | ra_prof = { path = "../ra_prof" } | 38 | ra_prof = { path = "../ra_prof" } |
39 | ra_project_model = { path = "../ra_project_model" } | 39 | ra_project_model = { path = "../ra_project_model" } |
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index a744a6695..608f4f67b 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs | |||
@@ -4,8 +4,7 @@ | |||
4 | mod args; | 4 | mod args; |
5 | 5 | ||
6 | use lsp_server::Connection; | 6 | use lsp_server::Connection; |
7 | 7 | use rust_analyzer::{cli, config::Config, from_json, Result}; | |
8 | use rust_analyzer::{cli, from_json, show_message, Result, ServerConfig}; | ||
9 | 8 | ||
10 | use crate::args::HelpPrinted; | 9 | use crate::args::HelpPrinted; |
11 | 10 | ||
@@ -78,24 +77,18 @@ fn run_server() -> Result<()> { | |||
78 | .filter(|workspaces| !workspaces.is_empty()) | 77 | .filter(|workspaces| !workspaces.is_empty()) |
79 | .unwrap_or_else(|| vec![root]); | 78 | .unwrap_or_else(|| vec![root]); |
80 | 79 | ||
81 | let server_config = initialize_params | 80 | let config = { |
82 | .initialization_options | 81 | let mut config = Config::default(); |
83 | .and_then(|v| { | 82 | if let Some(value) = &initialize_params.initialization_options { |
84 | from_json::<ServerConfig>("config", v) | 83 | config.update(value); |
85 | .map_err(|e| { | 84 | } |
86 | log::error!("{}", e); | 85 | if let Some(caps) = &initialize_params.capabilities.text_document { |
87 | show_message(lsp_types::MessageType::Error, e.to_string(), &connection.sender); | 86 | config.update_caps(caps); |
88 | }) | 87 | } |
89 | .ok() | 88 | config |
90 | }) | 89 | }; |
91 | .unwrap_or_default(); | ||
92 | 90 | ||
93 | rust_analyzer::main_loop( | 91 | rust_analyzer::main_loop(workspace_roots, config, connection)?; |
94 | workspace_roots, | ||
95 | initialize_params.capabilities, | ||
96 | server_config, | ||
97 | connection, | ||
98 | )?; | ||
99 | 92 | ||
100 | log::info!("shutting down IO..."); | 93 | log::info!("shutting down IO..."); |
101 | io_threads.join()?; | 94 | io_threads.join()?; |
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs index f87bdcec5..942c30328 100644 --- a/crates/rust-analyzer/src/cargo_target_spec.rs +++ b/crates/rust-analyzer/src/cargo_target_spec.rs | |||
@@ -77,7 +77,7 @@ impl CargoTargetSpec { | |||
77 | ProjectWorkspace::Cargo { cargo, .. } => { | 77 | ProjectWorkspace::Cargo { cargo, .. } => { |
78 | let tgt = cargo.target_by_root(&path)?; | 78 | let tgt = cargo.target_by_root(&path)?; |
79 | Some(CargoTargetSpec { | 79 | Some(CargoTargetSpec { |
80 | package: cargo[cargo[tgt].package].name.clone(), | 80 | package: cargo.package_flag(&cargo[cargo[tgt].package]), |
81 | target: cargo[tgt].name.clone(), | 81 | target: cargo[tgt].name.clone(), |
82 | target_kind: cargo[tgt].kind, | 82 | target_kind: cargo[tgt].kind, |
83 | }) | 83 | }) |
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs index 7164b0ade..7667873d5 100644 --- a/crates/rust-analyzer/src/cli/analysis_bench.rs +++ b/crates/rust-analyzer/src/cli/analysis_bench.rs | |||
@@ -12,7 +12,7 @@ use ra_db::{ | |||
12 | salsa::{Database, Durability}, | 12 | salsa::{Database, Durability}, |
13 | FileId, SourceDatabaseExt, | 13 | FileId, SourceDatabaseExt, |
14 | }; | 14 | }; |
15 | use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CompletionOptions, FilePosition, LineCol}; | 15 | use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CompletionConfig, FilePosition, LineCol}; |
16 | 16 | ||
17 | use crate::cli::{load_cargo::load_cargo, Verbosity}; | 17 | use crate::cli::{load_cargo::load_cargo, Verbosity}; |
18 | 18 | ||
@@ -102,7 +102,7 @@ pub fn analysis_bench( | |||
102 | let file_position = FilePosition { file_id, offset }; | 102 | let file_position = FilePosition { file_id, offset }; |
103 | 103 | ||
104 | if is_completion { | 104 | if is_completion { |
105 | let options = CompletionOptions::default(); | 105 | let options = CompletionConfig::default(); |
106 | let res = do_work(&mut host, file_id, |analysis| { | 106 | let res = do_work(&mut host, file_id, |analysis| { |
107 | analysis.completions(file_position, &options) | 107 | analysis.completions(file_position, &options) |
108 | }); | 108 | }); |
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs index 832f04226..69133e4e4 100644 --- a/crates/rust-analyzer/src/cli/load_cargo.rs +++ b/crates/rust-analyzer/src/cli/load_cargo.rs | |||
@@ -8,7 +8,7 @@ use crossbeam_channel::{unbounded, Receiver}; | |||
8 | use ra_db::{ExternSourceId, FileId, SourceRootId}; | 8 | use ra_db::{ExternSourceId, FileId, SourceRootId}; |
9 | use ra_ide::{AnalysisChange, AnalysisHost}; | 9 | use ra_ide::{AnalysisChange, AnalysisHost}; |
10 | use ra_project_model::{ | 10 | use ra_project_model::{ |
11 | get_rustc_cfg_options, CargoFeatures, PackageRoot, ProcMacroClient, ProjectWorkspace, | 11 | get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectWorkspace, |
12 | }; | 12 | }; |
13 | use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; | 13 | use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; |
14 | use rustc_hash::{FxHashMap, FxHashSet}; | 14 | use rustc_hash::{FxHashMap, FxHashSet}; |
@@ -29,15 +29,14 @@ pub(crate) fn load_cargo( | |||
29 | let root = std::env::current_dir()?.join(root); | 29 | let root = std::env::current_dir()?.join(root); |
30 | let ws = ProjectWorkspace::discover( | 30 | let ws = ProjectWorkspace::discover( |
31 | root.as_ref(), | 31 | root.as_ref(), |
32 | &CargoFeatures { load_out_dirs_from_check, ..Default::default() }, | 32 | &CargoConfig { load_out_dirs_from_check, ..Default::default() }, |
33 | )?; | 33 | )?; |
34 | 34 | ||
35 | let mut extern_dirs = FxHashSet::default(); | 35 | let mut extern_dirs = FxHashSet::default(); |
36 | extern_dirs.extend(ws.out_dirs()); | 36 | extern_dirs.extend(ws.out_dirs()); |
37 | 37 | ||
38 | let mut project_roots = ws.to_roots(); | 38 | let mut project_roots = ws.to_roots(); |
39 | project_roots | 39 | project_roots.extend(extern_dirs.iter().cloned().map(PackageRoot::new_non_member)); |
40 | .extend(extern_dirs.iter().map(|path| PackageRoot::new(path.to_path_buf(), false))); | ||
41 | 40 | ||
42 | let (sender, receiver) = unbounded(); | 41 | let (sender, receiver) = unbounded(); |
43 | let sender = Box::new(move |t| sender.send(t).unwrap()); | 42 | let sender = Box::new(move |t| sender.send(t).unwrap()); |
@@ -46,7 +45,7 @@ pub(crate) fn load_cargo( | |||
46 | .iter() | 45 | .iter() |
47 | .map(|pkg_root| { | 46 | .map(|pkg_root| { |
48 | RootEntry::new( | 47 | RootEntry::new( |
49 | pkg_root.path().clone(), | 48 | pkg_root.path().to_owned(), |
50 | RustPackageFilterBuilder::default() | 49 | RustPackageFilterBuilder::default() |
51 | .set_member(pkg_root.is_member()) | 50 | .set_member(pkg_root.is_member()) |
52 | .into_vfs_filter(), | 51 | .into_vfs_filter(), |
@@ -58,12 +57,12 @@ pub(crate) fn load_cargo( | |||
58 | ); | 57 | ); |
59 | 58 | ||
60 | let source_roots = roots | 59 | let source_roots = roots |
61 | .iter() | 60 | .into_iter() |
62 | .map(|&vfs_root| { | 61 | .map(|vfs_root| { |
63 | let source_root_id = vfs_root_to_id(vfs_root); | 62 | let source_root_id = vfs_root_to_id(vfs_root); |
64 | let project_root = project_roots | 63 | let project_root = project_roots |
65 | .iter() | 64 | .iter() |
66 | .find(|it| it.path() == &vfs.root2path(vfs_root)) | 65 | .find(|it| it.path() == vfs.root2path(vfs_root)) |
67 | .unwrap() | 66 | .unwrap() |
68 | .clone(); | 67 | .clone(); |
69 | (source_root_id, project_root) | 68 | (source_root_id, project_root) |
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 628ed107e..04f5bb473 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs | |||
@@ -7,112 +7,191 @@ | |||
7 | //! configure the server itself, feature flags are passed into analysis, and | 7 | //! configure the server itself, feature flags are passed into analysis, and |
8 | //! tweak things like automatic insertion of `()` in completions. | 8 | //! tweak things like automatic insertion of `()` in completions. |
9 | 9 | ||
10 | use rustc_hash::FxHashMap; | 10 | use lsp_types::TextDocumentClientCapabilities; |
11 | 11 | use ra_flycheck::FlycheckConfig; | |
12 | use ra_project_model::CargoFeatures; | 12 | use ra_ide::{CompletionConfig, InlayHintsConfig}; |
13 | use serde::{Deserialize, Deserializer}; | 13 | use ra_project_model::CargoConfig; |
14 | 14 | use serde::Deserialize; | |
15 | /// Client provided initialization options | ||
16 | #[derive(Deserialize, Clone, Debug, PartialEq, Eq)] | ||
17 | #[serde(rename_all = "camelCase", default)] | ||
18 | pub struct ServerConfig { | ||
19 | /// Whether the client supports our custom highlighting publishing decorations. | ||
20 | /// This is different to the highlightingOn setting, which is whether the user | ||
21 | /// wants our custom highlighting to be used. | ||
22 | /// | ||
23 | /// Defaults to `false` | ||
24 | #[serde(deserialize_with = "nullable_bool_false")] | ||
25 | pub publish_decorations: bool, | ||
26 | |||
27 | pub exclude_globs: Vec<String>, | ||
28 | #[serde(deserialize_with = "nullable_bool_false")] | ||
29 | pub use_client_watching: bool, | ||
30 | 15 | ||
31 | pub lru_capacity: Option<usize>, | 16 | #[derive(Debug, Clone)] |
17 | pub struct Config { | ||
18 | pub client_caps: ClientCapsConfig, | ||
32 | 19 | ||
33 | #[serde(deserialize_with = "nullable_bool_true")] | ||
34 | pub inlay_hints_type: bool, | ||
35 | #[serde(deserialize_with = "nullable_bool_true")] | ||
36 | pub inlay_hints_parameter: bool, | ||
37 | #[serde(deserialize_with = "nullable_bool_true")] | ||
38 | pub inlay_hints_chaining: bool, | ||
39 | pub inlay_hints_max_length: Option<usize>, | ||
40 | |||
41 | pub cargo_watch_enable: bool, | ||
42 | pub cargo_watch_args: Vec<String>, | ||
43 | pub cargo_watch_command: String, | ||
44 | pub cargo_watch_all_targets: bool, | ||
45 | |||
46 | /// For internal usage to make integrated tests faster. | ||
47 | #[serde(deserialize_with = "nullable_bool_true")] | ||
48 | pub with_sysroot: bool, | 20 | pub with_sysroot: bool, |
21 | pub publish_diagnostics: bool, | ||
22 | pub lru_capacity: Option<usize>, | ||
23 | pub proc_macro_srv: Option<String>, | ||
24 | pub files: FilesConfig, | ||
25 | pub notifications: NotificationsConfig, | ||
49 | 26 | ||
50 | /// Fine grained feature flags to disable specific features. | 27 | pub cargo: CargoConfig, |
51 | pub feature_flags: FxHashMap<String, bool>, | 28 | pub rustfmt: RustfmtConfig, |
29 | pub check: Option<FlycheckConfig>, | ||
52 | 30 | ||
53 | pub rustfmt_args: Vec<String>, | 31 | pub inlay_hints: InlayHintsConfig, |
32 | pub completion: CompletionConfig, | ||
33 | pub call_info_full: bool, | ||
34 | } | ||
54 | 35 | ||
55 | /// Cargo feature configurations. | 36 | #[derive(Debug, Clone)] |
56 | pub cargo_features: CargoFeatures, | 37 | pub struct FilesConfig { |
38 | pub watcher: FilesWatcher, | ||
39 | pub exclude: Vec<String>, | ||
40 | } | ||
57 | 41 | ||
58 | /// Enabled if the vscode_lldb extension is available. | 42 | #[derive(Debug, Clone)] |
59 | pub vscode_lldb: bool, | 43 | pub enum FilesWatcher { |
44 | Client, | ||
45 | Notify, | ||
60 | } | 46 | } |
61 | 47 | ||
62 | impl Default for ServerConfig { | 48 | #[derive(Debug, Clone)] |
63 | fn default() -> ServerConfig { | 49 | pub struct NotificationsConfig { |
64 | ServerConfig { | 50 | pub workspace_loaded: bool, |
65 | publish_decorations: false, | 51 | pub cargo_toml_not_found: bool, |
66 | exclude_globs: Vec::new(), | 52 | } |
67 | use_client_watching: false, | 53 | |
68 | lru_capacity: None, | 54 | #[derive(Debug, Clone)] |
69 | inlay_hints_type: true, | 55 | pub enum RustfmtConfig { |
70 | inlay_hints_parameter: true, | 56 | Rustfmt { |
71 | inlay_hints_chaining: true, | 57 | extra_args: Vec<String>, |
72 | inlay_hints_max_length: None, | 58 | }, |
73 | cargo_watch_enable: true, | 59 | #[allow(unused)] |
74 | cargo_watch_args: Vec::new(), | 60 | CustomCommand { |
75 | cargo_watch_command: "check".to_string(), | 61 | command: String, |
76 | cargo_watch_all_targets: true, | 62 | args: Vec<String>, |
63 | }, | ||
64 | } | ||
65 | |||
66 | #[derive(Debug, Clone, Default)] | ||
67 | pub struct ClientCapsConfig { | ||
68 | pub location_link: bool, | ||
69 | pub line_folding_only: bool, | ||
70 | } | ||
71 | |||
72 | impl Default for Config { | ||
73 | fn default() -> Self { | ||
74 | Config { | ||
75 | client_caps: ClientCapsConfig::default(), | ||
76 | |||
77 | with_sysroot: true, | 77 | with_sysroot: true, |
78 | feature_flags: FxHashMap::default(), | 78 | publish_diagnostics: true, |
79 | cargo_features: Default::default(), | 79 | lru_capacity: None, |
80 | rustfmt_args: Vec::new(), | 80 | proc_macro_srv: None, |
81 | vscode_lldb: false, | 81 | files: FilesConfig { watcher: FilesWatcher::Notify, exclude: Vec::new() }, |
82 | notifications: NotificationsConfig { | ||
83 | workspace_loaded: true, | ||
84 | cargo_toml_not_found: true, | ||
85 | }, | ||
86 | |||
87 | cargo: CargoConfig::default(), | ||
88 | rustfmt: RustfmtConfig::Rustfmt { extra_args: Vec::new() }, | ||
89 | check: Some(FlycheckConfig::CargoCommand { | ||
90 | command: "check".to_string(), | ||
91 | all_targets: true, | ||
92 | extra_args: Vec::new(), | ||
93 | }), | ||
94 | |||
95 | inlay_hints: InlayHintsConfig { | ||
96 | type_hints: true, | ||
97 | parameter_hints: true, | ||
98 | chaining_hints: true, | ||
99 | max_length: None, | ||
100 | }, | ||
101 | completion: CompletionConfig { | ||
102 | enable_postfix_completions: true, | ||
103 | add_call_parenthesis: true, | ||
104 | add_call_argument_snippets: true, | ||
105 | }, | ||
106 | call_info_full: true, | ||
82 | } | 107 | } |
83 | } | 108 | } |
84 | } | 109 | } |
85 | 110 | ||
86 | /// Deserializes a null value to a bool false by default | 111 | impl Config { |
87 | fn nullable_bool_false<'de, D>(deserializer: D) -> Result<bool, D::Error> | 112 | #[rustfmt::skip] |
88 | where | 113 | pub fn update(&mut self, value: &serde_json::Value) { |
89 | D: Deserializer<'de>, | 114 | log::info!("Config::update({:#})", value); |
90 | { | 115 | |
91 | let opt = Option::deserialize(deserializer)?; | 116 | let client_caps = self.client_caps.clone(); |
92 | Ok(opt.unwrap_or(false)) | 117 | *self = Default::default(); |
93 | } | 118 | self.client_caps = client_caps; |
119 | |||
120 | set(value, "/withSysroot", &mut self.with_sysroot); | ||
121 | set(value, "/featureFlags/lsp.diagnostics", &mut self.publish_diagnostics); | ||
122 | set(value, "/lruCapacity", &mut self.lru_capacity); | ||
123 | if let Some(watcher) = get::<String>(value, "/files/watcher") { | ||
124 | self.files.watcher = match watcher.as_str() { | ||
125 | "client" => FilesWatcher::Client, | ||
126 | "notify"| _ => FilesWatcher::Notify, | ||
127 | } | ||
128 | } | ||
129 | set(value, "/notifications/workspaceLoaded", &mut self.notifications.workspace_loaded); | ||
130 | set(value, "/notifications/cargoTomlNotFound", &mut self.notifications.cargo_toml_not_found); | ||
131 | |||
132 | set(value, "/cargo/noDefaultFeatures", &mut self.cargo.no_default_features); | ||
133 | set(value, "/cargo/allFeatures", &mut self.cargo.all_features); | ||
134 | set(value, "/cargo/features", &mut self.cargo.features); | ||
135 | set(value, "/cargo/loadOutDirsFromCheck", &mut self.cargo.load_out_dirs_from_check); | ||
136 | if let Some(mut args) = get::<Vec<String>>(value, "/rustfmt/overrideCommand") { | ||
137 | if !args.is_empty() { | ||
138 | let command = args.remove(0); | ||
139 | self.rustfmt = RustfmtConfig::CustomCommand { | ||
140 | command, | ||
141 | args, | ||
142 | } | ||
143 | } | ||
144 | } else if let RustfmtConfig::Rustfmt { extra_args } = &mut self.rustfmt { | ||
145 | set(value, "/rustfmt/extraArgs", extra_args); | ||
146 | } | ||
147 | if let Some(false) = get(value, "/checkOnSave/enable") { | ||
148 | self.check = None | ||
149 | } else { | ||
150 | if let Some(mut args) = get::<Vec<String>>(value, "/checkOnSave/overrideCommand") { | ||
151 | if !args.is_empty() { | ||
152 | let command = args.remove(0); | ||
153 | self.check = Some(FlycheckConfig::CustomCommand { | ||
154 | command, | ||
155 | args, | ||
156 | }) | ||
157 | } | ||
158 | |||
159 | } else if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets }) = &mut self.check | ||
160 | { | ||
161 | set(value, "/checkOnSave/extraArgs", extra_args); | ||
162 | set(value, "/checkOnSave/command", command); | ||
163 | set(value, "/checkOnSave/allTargets", all_targets); | ||
164 | } | ||
165 | }; | ||
166 | |||
167 | set(value, "/inlayHints/typeHints", &mut self.inlay_hints.type_hints); | ||
168 | set(value, "/inlayHints/parameterHints", &mut self.inlay_hints.parameter_hints); | ||
169 | set(value, "/inlayHints/chainingHints", &mut self.inlay_hints.chaining_hints); | ||
170 | set(value, "/inlayHints/maxLength", &mut self.inlay_hints.max_length); | ||
171 | set(value, "/completion/postfix/enable", &mut self.completion.enable_postfix_completions); | ||
172 | set(value, "/completion/addCallParenthesis", &mut self.completion.add_call_parenthesis); | ||
173 | set(value, "/completion/addCallArgumentSnippets", &mut self.completion.add_call_argument_snippets); | ||
174 | set(value, "/callInfo/full", &mut self.call_info_full); | ||
175 | |||
176 | log::info!("Config::update() = {:#?}", self); | ||
177 | |||
178 | fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option<T> { | ||
179 | value.pointer(pointer).and_then(|it| T::deserialize(it).ok()) | ||
180 | } | ||
94 | 181 | ||
95 | /// Deserializes a null value to a bool true by default | 182 | fn set<'a, T: Deserialize<'a> + std::fmt::Debug>(value: &'a serde_json::Value, pointer: &str, slot: &mut T) { |
96 | fn nullable_bool_true<'de, D>(deserializer: D) -> Result<bool, D::Error> | 183 | if let Some(new_value) = get(value, pointer) { |
97 | where | 184 | *slot = new_value |
98 | D: Deserializer<'de>, | 185 | } |
99 | { | 186 | } |
100 | let opt = Option::deserialize(deserializer)?; | 187 | } |
101 | Ok(opt.unwrap_or(true)) | ||
102 | } | ||
103 | 188 | ||
104 | #[cfg(test)] | 189 | pub fn update_caps(&mut self, caps: &TextDocumentClientCapabilities) { |
105 | mod test { | 190 | if let Some(value) = caps.definition.as_ref().and_then(|it| it.link_support) { |
106 | use super::*; | 191 | self.client_caps.location_link = value; |
107 | 192 | } | |
108 | #[test] | 193 | if let Some(value) = caps.folding_range.as_ref().and_then(|it| it.line_folding_only) { |
109 | fn deserialize_init_options_defaults() { | 194 | self.client_caps.line_folding_only = value |
110 | // check that null == default for both fields | 195 | } |
111 | let default = ServerConfig::default(); | ||
112 | assert_eq!(default, serde_json::from_str(r#"{}"#).unwrap()); | ||
113 | assert_eq!( | ||
114 | default, | ||
115 | serde_json::from_str(r#"{"publishDecorations":null, "lruCapacity":null}"#).unwrap() | ||
116 | ); | ||
117 | } | 196 | } |
118 | } | 197 | } |
diff --git a/crates/rust-analyzer/src/conv.rs b/crates/rust-analyzer/src/conv.rs index 6edc03fe0..57c4c8ce5 100644 --- a/crates/rust-analyzer/src/conv.rs +++ b/crates/rust-analyzer/src/conv.rs | |||
@@ -579,7 +579,7 @@ impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<Vec<NavigationTarget>>) | |||
579 | .into_iter() | 579 | .into_iter() |
580 | .map(|nav| (file_id, RangeInfo::new(range, nav))) | 580 | .map(|nav| (file_id, RangeInfo::new(range, nav))) |
581 | .try_conv_with_to_vec(world)?; | 581 | .try_conv_with_to_vec(world)?; |
582 | if world.options.supports_location_link { | 582 | if world.config.client_caps.location_link { |
583 | Ok(links.into()) | 583 | Ok(links.into()) |
584 | } else { | 584 | } else { |
585 | let locations: Vec<Location> = links | 585 | let locations: Vec<Location> = links |
diff --git a/crates/rust-analyzer/src/feature_flags.rs b/crates/rust-analyzer/src/feature_flags.rs deleted file mode 100644 index dbb3f50a0..000000000 --- a/crates/rust-analyzer/src/feature_flags.rs +++ /dev/null | |||
@@ -1,77 +0,0 @@ | |||
1 | //! See docs for `FeatureFlags`. | ||
2 | |||
3 | use rustc_hash::FxHashMap; | ||
4 | |||
5 | // FIXME: looks like a much better design is to pass options to each call, | ||
6 | // rather than to have a global ambient feature flags -- that way, the clients | ||
7 | // can issue two successive calls with different options. | ||
8 | |||
9 | /// Feature flags hold fine-grained toggles for all *user-visible* features of | ||
10 | /// rust-analyzer. | ||
11 | /// | ||
12 | /// The exists such that users are able to disable any annoying feature (and, | ||
13 | /// with many users and many features, some features are bound to be annoying | ||
14 | /// for some users) | ||
15 | /// | ||
16 | /// Note that we purposefully use run-time checked strings, and not something | ||
17 | /// checked at compile time, to keep things simple and flexible. | ||
18 | /// | ||
19 | /// Also note that, at the moment, `FeatureFlags` also store features for | ||
20 | /// `rust-analyzer`. This should be benign layering violation. | ||
21 | #[derive(Debug)] | ||
22 | pub struct FeatureFlags { | ||
23 | flags: FxHashMap<String, bool>, | ||
24 | } | ||
25 | |||
26 | impl FeatureFlags { | ||
27 | fn new(flags: &[(&str, bool)]) -> FeatureFlags { | ||
28 | let flags = flags | ||
29 | .iter() | ||
30 | .map(|&(name, value)| { | ||
31 | check_flag_name(name); | ||
32 | (name.to_string(), value) | ||
33 | }) | ||
34 | .collect(); | ||
35 | FeatureFlags { flags } | ||
36 | } | ||
37 | |||
38 | pub fn set(&mut self, flag: &str, value: bool) -> Result<(), ()> { | ||
39 | match self.flags.get_mut(flag) { | ||
40 | None => Err(()), | ||
41 | Some(slot) => { | ||
42 | *slot = value; | ||
43 | Ok(()) | ||
44 | } | ||
45 | } | ||
46 | } | ||
47 | |||
48 | pub fn get(&self, flag: &str) -> bool { | ||
49 | match self.flags.get(flag) { | ||
50 | None => panic!("unknown flag: {:?}", flag), | ||
51 | Some(value) => *value, | ||
52 | } | ||
53 | } | ||
54 | } | ||
55 | |||
56 | impl Default for FeatureFlags { | ||
57 | fn default() -> FeatureFlags { | ||
58 | FeatureFlags::new(&[ | ||
59 | ("lsp.diagnostics", true), | ||
60 | ("completion.insertion.add-call-parenthesis", true), | ||
61 | ("completion.insertion.add-argument-snippets", true), | ||
62 | ("completion.enable-postfix", true), | ||
63 | ("call-info.full", true), | ||
64 | ("notifications.workspace-loaded", true), | ||
65 | ("notifications.cargo-toml-not-found", true), | ||
66 | ]) | ||
67 | } | ||
68 | } | ||
69 | |||
70 | fn check_flag_name(flag: &str) { | ||
71 | for c in flag.bytes() { | ||
72 | match c { | ||
73 | b'a'..=b'z' | b'-' | b'.' => (), | ||
74 | _ => panic!("flag name does not match conventions: {:?}", flag), | ||
75 | } | ||
76 | } | ||
77 | } | ||
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs index e50e47b19..02953be30 100644 --- a/crates/rust-analyzer/src/lib.rs +++ b/crates/rust-analyzer/src/lib.rs | |||
@@ -33,18 +33,16 @@ mod conv; | |||
33 | mod main_loop; | 33 | mod main_loop; |
34 | mod markdown; | 34 | mod markdown; |
35 | pub mod req; | 35 | pub mod req; |
36 | mod config; | 36 | pub mod config; |
37 | mod world; | 37 | mod world; |
38 | mod diagnostics; | 38 | mod diagnostics; |
39 | mod semantic_tokens; | 39 | mod semantic_tokens; |
40 | mod feature_flags; | ||
41 | 40 | ||
42 | use serde::de::DeserializeOwned; | 41 | use serde::de::DeserializeOwned; |
43 | 42 | ||
44 | pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; | 43 | pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; |
45 | pub use crate::{ | 44 | pub use crate::{ |
46 | caps::server_capabilities, | 45 | caps::server_capabilities, |
47 | config::ServerConfig, | ||
48 | main_loop::LspError, | 46 | main_loop::LspError, |
49 | main_loop::{main_loop, show_message}, | 47 | main_loop::{main_loop, show_message}, |
50 | }; | 48 | }; |
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 7825b0077..8d1429196 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -14,15 +14,16 @@ use std::{ | |||
14 | time::{Duration, Instant}, | 14 | time::{Duration, Instant}, |
15 | }; | 15 | }; |
16 | 16 | ||
17 | use crossbeam_channel::{select, unbounded, RecvError, Sender}; | 17 | use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; |
18 | use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; | 18 | use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; |
19 | use lsp_types::{ | 19 | use lsp_types::{ |
20 | ClientCapabilities, NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, | 20 | NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams, |
21 | WorkDoneProgressCreateParams, WorkDoneProgressEnd, WorkDoneProgressReport, | 21 | WorkDoneProgressEnd, WorkDoneProgressReport, |
22 | }; | 22 | }; |
23 | use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckTask}; | 23 | use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckTask}; |
24 | use ra_ide::{Canceled, FileId, InlayHintsOptions, LibraryData, SourceRootId}; | 24 | use ra_ide::{Canceled, FileId, LibraryData, SourceRootId}; |
25 | use ra_prof::profile; | 25 | use ra_prof::profile; |
26 | use ra_project_model::{PackageRoot, ProjectWorkspace}; | ||
26 | use ra_vfs::{VfsFile, VfsTask, Watch}; | 27 | use ra_vfs::{VfsFile, VfsTask, Watch}; |
27 | use relative_path::RelativePathBuf; | 28 | use relative_path::RelativePathBuf; |
28 | use rustc_hash::FxHashSet; | 29 | use rustc_hash::FxHashSet; |
@@ -30,15 +31,15 @@ use serde::{de::DeserializeOwned, Serialize}; | |||
30 | use threadpool::ThreadPool; | 31 | use threadpool::ThreadPool; |
31 | 32 | ||
32 | use crate::{ | 33 | use crate::{ |
34 | config::{Config, FilesWatcher}, | ||
33 | diagnostics::DiagnosticTask, | 35 | diagnostics::DiagnosticTask, |
34 | feature_flags::FeatureFlags, | ||
35 | main_loop::{ | 36 | main_loop::{ |
36 | pending_requests::{PendingRequest, PendingRequests}, | 37 | pending_requests::{PendingRequest, PendingRequests}, |
37 | subscriptions::Subscriptions, | 38 | subscriptions::Subscriptions, |
38 | }, | 39 | }, |
39 | req, | 40 | req, |
40 | world::{Options, WorldSnapshot, WorldState}, | 41 | world::{WorldSnapshot, WorldState}, |
41 | Result, ServerConfig, | 42 | Result, |
42 | }; | 43 | }; |
43 | 44 | ||
44 | #[derive(Debug)] | 45 | #[derive(Debug)] |
@@ -63,13 +64,8 @@ impl fmt::Display for LspError { | |||
63 | 64 | ||
64 | impl Error for LspError {} | 65 | impl Error for LspError {} |
65 | 66 | ||
66 | pub fn main_loop( | 67 | pub fn main_loop(ws_roots: Vec<PathBuf>, config: Config, connection: Connection) -> Result<()> { |
67 | ws_roots: Vec<PathBuf>, | 68 | log::info!("initial config: {:#?}", config); |
68 | client_caps: ClientCapabilities, | ||
69 | config: ServerConfig, | ||
70 | connection: Connection, | ||
71 | ) -> Result<()> { | ||
72 | log::info!("server_config: {:#?}", config); | ||
73 | 69 | ||
74 | // Windows scheduler implements priority boosts: if thread waits for an | 70 | // Windows scheduler implements priority boosts: if thread waits for an |
75 | // event (like a condvar), and event fires, priority of the thread is | 71 | // event (like a condvar), and event fires, priority of the thread is |
@@ -92,22 +88,6 @@ pub fn main_loop( | |||
92 | 88 | ||
93 | let mut loop_state = LoopState::default(); | 89 | let mut loop_state = LoopState::default(); |
94 | let mut world_state = { | 90 | let mut world_state = { |
95 | let feature_flags = { | ||
96 | let mut ff = FeatureFlags::default(); | ||
97 | for (flag, value) in config.feature_flags { | ||
98 | if ff.set(flag.as_str(), value).is_err() { | ||
99 | log::error!("unknown feature flag: {:?}", flag); | ||
100 | show_message( | ||
101 | req::MessageType::Error, | ||
102 | format!("unknown feature flag: {:?}", flag), | ||
103 | &connection.sender, | ||
104 | ); | ||
105 | } | ||
106 | } | ||
107 | ff | ||
108 | }; | ||
109 | log::info!("feature_flags: {:#?}", feature_flags); | ||
110 | |||
111 | // FIXME: support dynamic workspace loading. | 91 | // FIXME: support dynamic workspace loading. |
112 | let workspaces = { | 92 | let workspaces = { |
113 | let mut loaded_workspaces = Vec::new(); | 93 | let mut loaded_workspaces = Vec::new(); |
@@ -115,7 +95,7 @@ pub fn main_loop( | |||
115 | let workspace = ra_project_model::ProjectWorkspace::discover_with_sysroot( | 95 | let workspace = ra_project_model::ProjectWorkspace::discover_with_sysroot( |
116 | ws_root.as_path(), | 96 | ws_root.as_path(), |
117 | config.with_sysroot, | 97 | config.with_sysroot, |
118 | &config.cargo_features, | 98 | &config.cargo, |
119 | ); | 99 | ); |
120 | match workspace { | 100 | match workspace { |
121 | Ok(workspace) => loaded_workspaces.push(workspace), | 101 | Ok(workspace) => loaded_workspaces.push(workspace), |
@@ -125,7 +105,7 @@ pub fn main_loop( | |||
125 | if let Some(ra_project_model::CargoTomlNotFoundError { .. }) = | 105 | if let Some(ra_project_model::CargoTomlNotFoundError { .. }) = |
126 | e.downcast_ref() | 106 | e.downcast_ref() |
127 | { | 107 | { |
128 | if !feature_flags.get("notifications.cargo-toml-not-found") { | 108 | if !config.notifications.cargo_toml_not_found { |
129 | continue; | 109 | continue; |
130 | } | 110 | } |
131 | } | 111 | } |
@@ -142,17 +122,18 @@ pub fn main_loop( | |||
142 | }; | 122 | }; |
143 | 123 | ||
144 | let globs = config | 124 | let globs = config |
145 | .exclude_globs | 125 | .files |
126 | .exclude | ||
146 | .iter() | 127 | .iter() |
147 | .map(|glob| crate::vfs_glob::Glob::new(glob)) | 128 | .map(|glob| crate::vfs_glob::Glob::new(glob)) |
148 | .collect::<std::result::Result<Vec<_>, _>>()?; | 129 | .collect::<std::result::Result<Vec<_>, _>>()?; |
149 | 130 | ||
150 | if config.use_client_watching { | 131 | if let FilesWatcher::Client = config.files.watcher { |
151 | let registration_options = req::DidChangeWatchedFilesRegistrationOptions { | 132 | let registration_options = req::DidChangeWatchedFilesRegistrationOptions { |
152 | watchers: workspaces | 133 | watchers: workspaces |
153 | .iter() | 134 | .iter() |
154 | .flat_map(|ws| ws.to_roots()) | 135 | .flat_map(ProjectWorkspace::to_roots) |
155 | .filter(|root| root.is_member()) | 136 | .filter(PackageRoot::is_member) |
156 | .map(|root| format!("{}/**/*.rs", root.path().display())) | 137 | .map(|root| format!("{}/**/*.rs", root.path().display())) |
157 | .map(|glob_pattern| req::FileSystemWatcher { glob_pattern, kind: None }) | 138 | .map(|glob_pattern| req::FileSystemWatcher { glob_pattern, kind: None }) |
158 | .collect(), | 139 | .collect(), |
@@ -168,46 +149,19 @@ pub fn main_loop( | |||
168 | connection.sender.send(request.into()).unwrap(); | 149 | connection.sender.send(request.into()).unwrap(); |
169 | } | 150 | } |
170 | 151 | ||
171 | let options = { | ||
172 | let text_document_caps = client_caps.text_document.as_ref(); | ||
173 | Options { | ||
174 | publish_decorations: config.publish_decorations, | ||
175 | supports_location_link: text_document_caps | ||
176 | .and_then(|it| it.definition) | ||
177 | .and_then(|it| it.link_support) | ||
178 | .unwrap_or(false), | ||
179 | line_folding_only: text_document_caps | ||
180 | .and_then(|it| it.folding_range.as_ref()) | ||
181 | .and_then(|it| it.line_folding_only) | ||
182 | .unwrap_or(false), | ||
183 | inlay_hints: InlayHintsOptions { | ||
184 | type_hints: config.inlay_hints_type, | ||
185 | parameter_hints: config.inlay_hints_parameter, | ||
186 | chaining_hints: config.inlay_hints_chaining, | ||
187 | max_length: config.inlay_hints_max_length, | ||
188 | }, | ||
189 | cargo_watch: CheckOptions { | ||
190 | enable: config.cargo_watch_enable, | ||
191 | args: config.cargo_watch_args, | ||
192 | command: config.cargo_watch_command, | ||
193 | all_targets: config.cargo_watch_all_targets, | ||
194 | }, | ||
195 | rustfmt_args: config.rustfmt_args, | ||
196 | vscode_lldb: config.vscode_lldb, | ||
197 | } | ||
198 | }; | ||
199 | |||
200 | WorldState::new( | 152 | WorldState::new( |
201 | ws_roots, | 153 | ws_roots, |
202 | workspaces, | 154 | workspaces, |
203 | config.lru_capacity, | 155 | config.lru_capacity, |
204 | &globs, | 156 | &globs, |
205 | Watch(!config.use_client_watching), | 157 | Watch(matches!(config.files.watcher, FilesWatcher::Notify)), |
206 | options, | 158 | config, |
207 | feature_flags, | ||
208 | ) | 159 | ) |
209 | }; | 160 | }; |
210 | 161 | ||
162 | loop_state.roots_total = world_state.vfs.read().n_roots(); | ||
163 | loop_state.roots_scanned = 0; | ||
164 | |||
211 | let pool = ThreadPool::default(); | 165 | let pool = ThreadPool::default(); |
212 | let (task_sender, task_receiver) = unbounded::<Task>(); | 166 | let (task_sender, task_receiver) = unbounded::<Task>(); |
213 | let (libdata_sender, libdata_receiver) = unbounded::<LibraryData>(); | 167 | let (libdata_sender, libdata_receiver) = unbounded::<LibraryData>(); |
@@ -229,7 +183,7 @@ pub fn main_loop( | |||
229 | Err(RecvError) => return Err("vfs died".into()), | 183 | Err(RecvError) => return Err("vfs died".into()), |
230 | }, | 184 | }, |
231 | recv(libdata_receiver) -> data => Event::Lib(data.unwrap()), | 185 | recv(libdata_receiver) -> data => Event::Lib(data.unwrap()), |
232 | recv(world_state.check_watcher.task_recv) -> task => match task { | 186 | recv(world_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task { |
233 | Ok(task) => Event::CheckWatcher(task), | 187 | Ok(task) => Event::CheckWatcher(task), |
234 | Err(RecvError) => return Err("check watcher died".into()), | 188 | Err(RecvError) => return Err("check watcher died".into()), |
235 | } | 189 | } |
@@ -297,9 +251,7 @@ impl fmt::Debug for Event { | |||
297 | } | 251 | } |
298 | } | 252 | } |
299 | Event::Task(Task::Notify(not)) => { | 253 | Event::Task(Task::Notify(not)) => { |
300 | if notification_is::<req::PublishDecorations>(not) | 254 | if notification_is::<req::PublishDiagnostics>(not) { |
301 | || notification_is::<req::PublishDiagnostics>(not) | ||
302 | { | ||
303 | return debug_verbose_not(not, f); | 255 | return debug_verbose_not(not, f); |
304 | } | 256 | } |
305 | } | 257 | } |
@@ -333,7 +285,10 @@ struct LoopState { | |||
333 | in_flight_libraries: usize, | 285 | in_flight_libraries: usize, |
334 | pending_libraries: Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>, | 286 | pending_libraries: Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>, |
335 | workspace_loaded: bool, | 287 | workspace_loaded: bool, |
336 | roots_scanned_progress: Option<usize>, | 288 | roots_progress_reported: Option<usize>, |
289 | roots_scanned: usize, | ||
290 | roots_total: usize, | ||
291 | configuration_request_id: Option<RequestId>, | ||
337 | } | 292 | } |
338 | 293 | ||
339 | impl LoopState { | 294 | impl LoopState { |
@@ -377,6 +332,7 @@ fn loop_turn( | |||
377 | world_state.add_lib(lib); | 332 | world_state.add_lib(lib); |
378 | world_state.maybe_collect_garbage(); | 333 | world_state.maybe_collect_garbage(); |
379 | loop_state.in_flight_libraries -= 1; | 334 | loop_state.in_flight_libraries -= 1; |
335 | loop_state.roots_scanned += 1; | ||
380 | } | 336 | } |
381 | Event::CheckWatcher(task) => on_check_task(task, world_state, task_sender)?, | 337 | Event::CheckWatcher(task) => on_check_task(task, world_state, task_sender)?, |
382 | Event::Msg(msg) => match msg { | 338 | Event::Msg(msg) => match msg { |
@@ -390,25 +346,41 @@ fn loop_turn( | |||
390 | req, | 346 | req, |
391 | )?, | 347 | )?, |
392 | Message::Notification(not) => { | 348 | Message::Notification(not) => { |
393 | on_notification( | 349 | on_notification(&connection.sender, world_state, loop_state, not)?; |
394 | &connection.sender, | ||
395 | world_state, | ||
396 | &mut loop_state.pending_requests, | ||
397 | &mut loop_state.subscriptions, | ||
398 | not, | ||
399 | )?; | ||
400 | } | 350 | } |
401 | Message::Response(resp) => { | 351 | Message::Response(resp) => { |
402 | let removed = loop_state.pending_responses.remove(&resp.id); | 352 | let removed = loop_state.pending_responses.remove(&resp.id); |
403 | if !removed { | 353 | if !removed { |
404 | log::error!("unexpected response: {:?}", resp) | 354 | log::error!("unexpected response: {:?}", resp) |
405 | } | 355 | } |
356 | |||
357 | if Some(&resp.id) == loop_state.configuration_request_id.as_ref() { | ||
358 | loop_state.configuration_request_id = None; | ||
359 | log::debug!("config update response: '{:?}", resp); | ||
360 | let Response { error, result, .. } = resp; | ||
361 | |||
362 | match (error, result) { | ||
363 | (Some(err), _) => { | ||
364 | log::error!("failed to fetch the server settings: {:?}", err) | ||
365 | } | ||
366 | (None, Some(configs)) => { | ||
367 | if let Some(new_config) = configs.get(0) { | ||
368 | let mut config = world_state.config.clone(); | ||
369 | config.update(&new_config); | ||
370 | world_state.update_configuration(config); | ||
371 | } | ||
372 | } | ||
373 | (None, None) => { | ||
374 | log::error!("received empty server settings response from the client") | ||
375 | } | ||
376 | } | ||
377 | } | ||
406 | } | 378 | } |
407 | }, | 379 | }, |
408 | }; | 380 | }; |
409 | 381 | ||
410 | let mut state_changed = false; | 382 | let mut state_changed = false; |
411 | if let Some(changes) = world_state.process_changes() { | 383 | if let Some(changes) = world_state.process_changes(&mut loop_state.roots_scanned) { |
412 | state_changed = true; | 384 | state_changed = true; |
413 | loop_state.pending_libraries.extend(changes); | 385 | loop_state.pending_libraries.extend(changes); |
414 | } | 386 | } |
@@ -427,28 +399,33 @@ fn loop_turn( | |||
427 | }); | 399 | }); |
428 | } | 400 | } |
429 | 401 | ||
402 | let show_progress = | ||
403 | !loop_state.workspace_loaded && world_state.config.notifications.workspace_loaded; | ||
404 | |||
430 | if !loop_state.workspace_loaded | 405 | if !loop_state.workspace_loaded |
431 | && world_state.roots_to_scan == 0 | 406 | && loop_state.roots_scanned == loop_state.roots_total |
432 | && loop_state.pending_libraries.is_empty() | 407 | && loop_state.pending_libraries.is_empty() |
433 | && loop_state.in_flight_libraries == 0 | 408 | && loop_state.in_flight_libraries == 0 |
434 | { | 409 | { |
435 | loop_state.workspace_loaded = true; | 410 | loop_state.workspace_loaded = true; |
436 | world_state.check_watcher.update(); | 411 | if let Some(flycheck) = &world_state.flycheck { |
412 | flycheck.update(); | ||
413 | } | ||
437 | pool.execute({ | 414 | pool.execute({ |
438 | let subs = loop_state.subscriptions.subscriptions(); | 415 | let subs = loop_state.subscriptions.subscriptions(); |
439 | let snap = world_state.snapshot(); | 416 | let snap = world_state.snapshot(); |
440 | move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ()) | 417 | move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ()) |
441 | }); | 418 | }); |
442 | send_startup_progress(&connection.sender, loop_state, world_state); | 419 | } |
443 | } else if !loop_state.workspace_loaded { | 420 | |
444 | send_startup_progress(&connection.sender, loop_state, world_state); | 421 | if show_progress { |
422 | send_startup_progress(&connection.sender, loop_state); | ||
445 | } | 423 | } |
446 | 424 | ||
447 | if state_changed { | 425 | if state_changed { |
448 | update_file_notifications_on_threadpool( | 426 | update_file_notifications_on_threadpool( |
449 | pool, | 427 | pool, |
450 | world_state.snapshot(), | 428 | world_state.snapshot(), |
451 | world_state.options.publish_decorations, | ||
452 | task_sender.clone(), | 429 | task_sender.clone(), |
453 | loop_state.subscriptions.subscriptions(), | 430 | loop_state.subscriptions.subscriptions(), |
454 | ) | 431 | ) |
@@ -529,7 +506,6 @@ fn on_request( | |||
529 | .on::<req::GotoTypeDefinition>(handlers::handle_goto_type_definition)? | 506 | .on::<req::GotoTypeDefinition>(handlers::handle_goto_type_definition)? |
530 | .on::<req::ParentModule>(handlers::handle_parent_module)? | 507 | .on::<req::ParentModule>(handlers::handle_parent_module)? |
531 | .on::<req::Runnables>(handlers::handle_runnables)? | 508 | .on::<req::Runnables>(handlers::handle_runnables)? |
532 | .on::<req::DecorationsRequest>(handlers::handle_decorations)? | ||
533 | .on::<req::Completion>(handlers::handle_completion)? | 509 | .on::<req::Completion>(handlers::handle_completion)? |
534 | .on::<req::CodeActionRequest>(handlers::handle_code_action)? | 510 | .on::<req::CodeActionRequest>(handlers::handle_code_action)? |
535 | .on::<req::CodeLensRequest>(handlers::handle_code_lens)? | 511 | .on::<req::CodeLensRequest>(handlers::handle_code_lens)? |
@@ -556,8 +532,7 @@ fn on_request( | |||
556 | fn on_notification( | 532 | fn on_notification( |
557 | msg_sender: &Sender<Message>, | 533 | msg_sender: &Sender<Message>, |
558 | state: &mut WorldState, | 534 | state: &mut WorldState, |
559 | pending_requests: &mut PendingRequests, | 535 | loop_state: &mut LoopState, |
560 | subs: &mut Subscriptions, | ||
561 | not: Notification, | 536 | not: Notification, |
562 | ) -> Result<()> { | 537 | ) -> Result<()> { |
563 | let not = match notification_cast::<req::Cancel>(not) { | 538 | let not = match notification_cast::<req::Cancel>(not) { |
@@ -566,7 +541,7 @@ fn on_notification( | |||
566 | NumberOrString::Number(id) => id.into(), | 541 | NumberOrString::Number(id) => id.into(), |
567 | NumberOrString::String(id) => id.into(), | 542 | NumberOrString::String(id) => id.into(), |
568 | }; | 543 | }; |
569 | if pending_requests.cancel(&id) { | 544 | if loop_state.pending_requests.cancel(&id) { |
570 | let response = Response::new_err( | 545 | let response = Response::new_err( |
571 | id, | 546 | id, |
572 | ErrorCode::RequestCanceled as i32, | 547 | ErrorCode::RequestCanceled as i32, |
@@ -585,7 +560,7 @@ fn on_notification( | |||
585 | if let Some(file_id) = | 560 | if let Some(file_id) = |
586 | state.vfs.write().add_file_overlay(&path, params.text_document.text) | 561 | state.vfs.write().add_file_overlay(&path, params.text_document.text) |
587 | { | 562 | { |
588 | subs.add_sub(FileId(file_id.0)); | 563 | loop_state.subscriptions.add_sub(FileId(file_id.0)); |
589 | } | 564 | } |
590 | return Ok(()); | 565 | return Ok(()); |
591 | } | 566 | } |
@@ -604,7 +579,9 @@ fn on_notification( | |||
604 | }; | 579 | }; |
605 | let not = match notification_cast::<req::DidSaveTextDocument>(not) { | 580 | let not = match notification_cast::<req::DidSaveTextDocument>(not) { |
606 | Ok(_params) => { | 581 | Ok(_params) => { |
607 | state.check_watcher.update(); | 582 | if let Some(flycheck) = &state.flycheck { |
583 | flycheck.update(); | ||
584 | } | ||
608 | return Ok(()); | 585 | return Ok(()); |
609 | } | 586 | } |
610 | Err(not) => not, | 587 | Err(not) => not, |
@@ -614,7 +591,7 @@ fn on_notification( | |||
614 | let uri = params.text_document.uri; | 591 | let uri = params.text_document.uri; |
615 | let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; | 592 | let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; |
616 | if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) { | 593 | if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) { |
617 | subs.remove_sub(FileId(file_id.0)); | 594 | loop_state.subscriptions.remove_sub(FileId(file_id.0)); |
618 | } | 595 | } |
619 | let params = | 596 | let params = |
620 | req::PublishDiagnosticsParams { uri, diagnostics: Vec::new(), version: None }; | 597 | req::PublishDiagnosticsParams { uri, diagnostics: Vec::new(), version: None }; |
@@ -625,7 +602,22 @@ fn on_notification( | |||
625 | Err(not) => not, | 602 | Err(not) => not, |
626 | }; | 603 | }; |
627 | let not = match notification_cast::<req::DidChangeConfiguration>(not) { | 604 | let not = match notification_cast::<req::DidChangeConfiguration>(not) { |
628 | Ok(_params) => { | 605 | Ok(_) => { |
606 | // As stated in https://github.com/microsoft/language-server-protocol/issues/676, | ||
607 | // this notification's parameters should be ignored and the actual config queried separately. | ||
608 | let request_id = loop_state.next_request_id(); | ||
609 | let request = request_new::<req::WorkspaceConfiguration>( | ||
610 | request_id.clone(), | ||
611 | req::ConfigurationParams { | ||
612 | items: vec![req::ConfigurationItem { | ||
613 | scope_uri: None, | ||
614 | section: Some("rust-analyzer".to_string()), | ||
615 | }], | ||
616 | }, | ||
617 | ); | ||
618 | msg_sender.send(request.into())?; | ||
619 | loop_state.configuration_request_id = Some(request_id); | ||
620 | |||
629 | return Ok(()); | 621 | return Ok(()); |
630 | } | 622 | } |
631 | Err(not) => not, | 623 | Err(not) => not, |
@@ -706,21 +698,13 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state: | |||
706 | } | 698 | } |
707 | } | 699 | } |
708 | 700 | ||
709 | fn send_startup_progress( | 701 | fn send_startup_progress(sender: &Sender<Message>, loop_state: &mut LoopState) { |
710 | sender: &Sender<Message>, | 702 | let total: usize = loop_state.roots_total; |
711 | loop_state: &mut LoopState, | 703 | let prev = loop_state.roots_progress_reported; |
712 | world_state: &WorldState, | 704 | let progress = loop_state.roots_scanned; |
713 | ) { | 705 | loop_state.roots_progress_reported = Some(progress); |
714 | if !world_state.feature_flags.get("notifications.workspace-loaded") { | ||
715 | return; | ||
716 | } | ||
717 | |||
718 | let total: usize = world_state.workspaces.iter().map(|it| it.n_packages()).sum(); | ||
719 | let prev_progress = loop_state.roots_scanned_progress; | ||
720 | let progress = total - world_state.roots_to_scan; | ||
721 | loop_state.roots_scanned_progress = Some(progress); | ||
722 | 706 | ||
723 | match (prev_progress, loop_state.workspace_loaded) { | 707 | match (prev, loop_state.workspace_loaded) { |
724 | (None, false) => { | 708 | (None, false) => { |
725 | let work_done_progress_create = request_new::<req::WorkDoneProgressCreate>( | 709 | let work_done_progress_create = request_new::<req::WorkDoneProgressCreate>( |
726 | loop_state.next_request_id(), | 710 | loop_state.next_request_id(), |
@@ -755,14 +739,14 @@ fn send_startup_progress( | |||
755 | ), | 739 | ), |
756 | _ => {} | 740 | _ => {} |
757 | } | 741 | } |
758 | } | ||
759 | 742 | ||
760 | fn send_startup_progress_notif(sender: &Sender<Message>, work_done_progress: WorkDoneProgress) { | 743 | fn send_startup_progress_notif(sender: &Sender<Message>, work_done_progress: WorkDoneProgress) { |
761 | let notif = notification_new::<req::Progress>(req::ProgressParams { | 744 | let notif = notification_new::<req::Progress>(req::ProgressParams { |
762 | token: req::ProgressToken::String("rustAnalyzer/startup".into()), | 745 | token: req::ProgressToken::String("rustAnalyzer/startup".into()), |
763 | value: req::ProgressParamsValue::WorkDone(work_done_progress), | 746 | value: req::ProgressParamsValue::WorkDone(work_done_progress), |
764 | }); | 747 | }); |
765 | sender.send(notif.into()).unwrap(); | 748 | sender.send(notif.into()).unwrap(); |
749 | } | ||
766 | } | 750 | } |
767 | 751 | ||
768 | struct PoolDispatcher<'a> { | 752 | struct PoolDispatcher<'a> { |
@@ -902,15 +886,13 @@ where | |||
902 | fn update_file_notifications_on_threadpool( | 886 | fn update_file_notifications_on_threadpool( |
903 | pool: &ThreadPool, | 887 | pool: &ThreadPool, |
904 | world: WorldSnapshot, | 888 | world: WorldSnapshot, |
905 | publish_decorations: bool, | ||
906 | task_sender: Sender<Task>, | 889 | task_sender: Sender<Task>, |
907 | subscriptions: Vec<FileId>, | 890 | subscriptions: Vec<FileId>, |
908 | ) { | 891 | ) { |
909 | log::trace!("updating notifications for {:?}", subscriptions); | 892 | log::trace!("updating notifications for {:?}", subscriptions); |
910 | let publish_diagnostics = world.feature_flags.get("lsp.diagnostics"); | 893 | if world.config.publish_diagnostics { |
911 | pool.execute(move || { | 894 | pool.execute(move || { |
912 | for file_id in subscriptions { | 895 | for file_id in subscriptions { |
913 | if publish_diagnostics { | ||
914 | match handlers::publish_diagnostics(&world, file_id) { | 896 | match handlers::publish_diagnostics(&world, file_id) { |
915 | Err(e) => { | 897 | Err(e) => { |
916 | if !is_canceled(&e) { | 898 | if !is_canceled(&e) { |
@@ -922,21 +904,8 @@ fn update_file_notifications_on_threadpool( | |||
922 | } | 904 | } |
923 | } | 905 | } |
924 | } | 906 | } |
925 | if publish_decorations { | 907 | }) |
926 | match handlers::publish_decorations(&world, file_id) { | 908 | } |
927 | Err(e) => { | ||
928 | if !is_canceled(&e) { | ||
929 | log::error!("failed to compute decorations: {:?}", e); | ||
930 | } | ||
931 | } | ||
932 | Ok(params) => { | ||
933 | let not = notification_new::<req::PublishDecorations>(params); | ||
934 | task_sender.send(Task::Notify(not)).unwrap(); | ||
935 | } | ||
936 | } | ||
937 | } | ||
938 | } | ||
939 | }); | ||
940 | } | 909 | } |
941 | 910 | ||
942 | pub fn show_message(typ: req::MessageType, message: impl Into<String>, sender: &Sender<Message>) { | 911 | pub fn show_message(typ: req::MessageType, message: impl Into<String>, sender: &Sender<Message>) { |
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index 12f8ca297..b207f0764 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs | |||
@@ -14,13 +14,13 @@ use lsp_types::{ | |||
14 | CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic, | 14 | CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic, |
15 | DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams, | 15 | DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams, |
16 | Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse, | 16 | Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse, |
17 | Range, RenameParams, SemanticTokens, SemanticTokensParams, SemanticTokensRangeParams, | 17 | Range, RenameParams, SemanticTokensParams, SemanticTokensRangeParams, |
18 | SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, | 18 | SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, |
19 | TextEdit, WorkspaceEdit, | 19 | TextEdit, WorkspaceEdit, |
20 | }; | 20 | }; |
21 | use ra_ide::{ | 21 | use ra_ide::{ |
22 | Assist, AssistId, CompletionOptions, FileId, FilePosition, FileRange, Query, RangeInfo, | 22 | Assist, AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, |
23 | Runnable, RunnableKind, SearchScope, | 23 | SearchScope, |
24 | }; | 24 | }; |
25 | use ra_prof::profile; | 25 | use ra_prof::profile; |
26 | use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit}; | 26 | use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit}; |
@@ -31,13 +31,14 @@ use stdx::format_to; | |||
31 | 31 | ||
32 | use crate::{ | 32 | use crate::{ |
33 | cargo_target_spec::CargoTargetSpec, | 33 | cargo_target_spec::CargoTargetSpec, |
34 | config::RustfmtConfig, | ||
34 | conv::{ | 35 | conv::{ |
35 | to_call_hierarchy_item, to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, | 36 | to_call_hierarchy_item, to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, |
36 | TryConvWithToVec, | 37 | TryConvWithToVec, |
37 | }, | 38 | }, |
38 | diagnostics::DiagnosticTask, | 39 | diagnostics::DiagnosticTask, |
39 | from_json, | 40 | from_json, |
40 | req::{self, Decoration, InlayHint, InlayHintsParams}, | 41 | req::{self, InlayHint, InlayHintsParams}, |
41 | semantic_tokens::SemanticTokensBuilder, | 42 | semantic_tokens::SemanticTokensBuilder, |
42 | world::WorldSnapshot, | 43 | world::WorldSnapshot, |
43 | LspError, Result, | 44 | LspError, Result, |
@@ -388,15 +389,6 @@ pub fn handle_runnables( | |||
388 | Ok(res) | 389 | Ok(res) |
389 | } | 390 | } |
390 | 391 | ||
391 | pub fn handle_decorations( | ||
392 | world: WorldSnapshot, | ||
393 | params: TextDocumentIdentifier, | ||
394 | ) -> Result<Vec<Decoration>> { | ||
395 | let _p = profile("handle_decorations"); | ||
396 | let file_id = params.try_conv_with(&world)?; | ||
397 | highlight(&world, file_id) | ||
398 | } | ||
399 | |||
400 | pub fn handle_completion( | 392 | pub fn handle_completion( |
401 | world: WorldSnapshot, | 393 | world: WorldSnapshot, |
402 | params: req::CompletionParams, | 394 | params: req::CompletionParams, |
@@ -425,15 +417,7 @@ pub fn handle_completion( | |||
425 | return Ok(None); | 417 | return Ok(None); |
426 | } | 418 | } |
427 | 419 | ||
428 | let options = CompletionOptions { | 420 | let items = match world.analysis().completions(position, &world.config.completion)? { |
429 | enable_postfix_completions: world.feature_flags.get("completion.enable-postfix"), | ||
430 | add_call_parenthesis: world.feature_flags.get("completion.insertion.add-call-parenthesis"), | ||
431 | add_call_argument_snippets: world | ||
432 | .feature_flags | ||
433 | .get("completion.insertion.add-argument-snippets"), | ||
434 | }; | ||
435 | |||
436 | let items = match world.analysis().completions(position, &options)? { | ||
437 | None => return Ok(None), | 421 | None => return Ok(None), |
438 | Some(items) => items, | 422 | Some(items) => items, |
439 | }; | 423 | }; |
@@ -457,7 +441,7 @@ pub fn handle_folding_range( | |||
457 | let ctx = FoldConvCtx { | 441 | let ctx = FoldConvCtx { |
458 | text: &text, | 442 | text: &text, |
459 | line_index: &line_index, | 443 | line_index: &line_index, |
460 | line_folding_only: world.options.line_folding_only, | 444 | line_folding_only: world.config.client_caps.line_folding_only, |
461 | }; | 445 | }; |
462 | let res = Some(folds.into_iter().map_conv_with(&ctx).collect()); | 446 | let res = Some(folds.into_iter().map_conv_with(&ctx).collect()); |
463 | Ok(res) | 447 | Ok(res) |
@@ -470,7 +454,7 @@ pub fn handle_signature_help( | |||
470 | let _p = profile("handle_signature_help"); | 454 | let _p = profile("handle_signature_help"); |
471 | let position = params.try_conv_with(&world)?; | 455 | let position = params.try_conv_with(&world)?; |
472 | if let Some(call_info) = world.analysis().call_info(position)? { | 456 | if let Some(call_info) = world.analysis().call_info(position)? { |
473 | let concise = !world.feature_flags.get("call-info.full"); | 457 | let concise = !world.config.call_info_full; |
474 | let mut active_parameter = call_info.active_parameter.map(|it| it as i64); | 458 | let mut active_parameter = call_info.active_parameter.map(|it| it as i64); |
475 | if concise && call_info.signature.has_self_param { | 459 | if concise && call_info.signature.has_self_param { |
476 | active_parameter = active_parameter.map(|it| it.saturating_sub(1)); | 460 | active_parameter = active_parameter.map(|it| it.saturating_sub(1)); |
@@ -610,13 +594,24 @@ pub fn handle_formatting( | |||
610 | let file_line_index = world.analysis().file_line_index(file_id)?; | 594 | let file_line_index = world.analysis().file_line_index(file_id)?; |
611 | let end_position = TextUnit::of_str(&file).conv_with(&file_line_index); | 595 | let end_position = TextUnit::of_str(&file).conv_with(&file_line_index); |
612 | 596 | ||
613 | let mut rustfmt = process::Command::new("rustfmt"); | 597 | let mut rustfmt = match &world.config.rustfmt { |
614 | rustfmt.args(&world.options.rustfmt_args); | 598 | RustfmtConfig::Rustfmt { extra_args } => { |
615 | if let Some(&crate_id) = crate_ids.first() { | 599 | let mut cmd = process::Command::new("rustfmt"); |
616 | // Assume all crates are in the same edition | 600 | cmd.args(extra_args); |
617 | let edition = world.analysis().crate_edition(crate_id)?; | 601 | if let Some(&crate_id) = crate_ids.first() { |
618 | rustfmt.args(&["--edition", &edition.to_string()]); | 602 | // Assume all crates are in the same edition |
619 | } | 603 | let edition = world.analysis().crate_edition(crate_id)?; |
604 | cmd.arg("--edition"); | ||
605 | cmd.arg(edition.to_string()); | ||
606 | } | ||
607 | cmd | ||
608 | } | ||
609 | RustfmtConfig::CustomCommand { command, args } => { | ||
610 | let mut cmd = process::Command::new(command); | ||
611 | cmd.args(args); | ||
612 | cmd | ||
613 | } | ||
614 | }; | ||
620 | 615 | ||
621 | if let Ok(path) = params.text_document.uri.to_file_path() { | 616 | if let Ok(path) = params.text_document.uri.to_file_path() { |
622 | if let Some(parent) = path.parent() { | 617 | if let Some(parent) = path.parent() { |
@@ -815,23 +810,21 @@ pub fn handle_code_lens( | |||
815 | }; | 810 | }; |
816 | lenses.push(lens); | 811 | lenses.push(lens); |
817 | 812 | ||
818 | if world.options.vscode_lldb { | 813 | if r.args[0] == "run" { |
819 | if r.args[0] == "run" { | 814 | r.args[0] = "build".into(); |
820 | r.args[0] = "build".into(); | 815 | } else { |
821 | } else { | 816 | r.args.push("--no-run".into()); |
822 | r.args.push("--no-run".into()); | ||
823 | } | ||
824 | let debug_lens = CodeLens { | ||
825 | range: r.range, | ||
826 | command: Some(Command { | ||
827 | title: "Debug".into(), | ||
828 | command: "rust-analyzer.debugSingle".into(), | ||
829 | arguments: Some(vec![to_value(r).unwrap()]), | ||
830 | }), | ||
831 | data: None, | ||
832 | }; | ||
833 | lenses.push(debug_lens); | ||
834 | } | 817 | } |
818 | let debug_lens = CodeLens { | ||
819 | range: r.range, | ||
820 | command: Some(Command { | ||
821 | title: "Debug".into(), | ||
822 | command: "rust-analyzer.debugSingle".into(), | ||
823 | arguments: Some(vec![to_value(r).unwrap()]), | ||
824 | }), | ||
825 | data: None, | ||
826 | }; | ||
827 | lenses.push(debug_lens); | ||
835 | } | 828 | } |
836 | 829 | ||
837 | // Handle impls | 830 | // Handle impls |
@@ -966,15 +959,6 @@ pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<Dia | |||
966 | Ok(DiagnosticTask::SetNative(file_id, diagnostics)) | 959 | Ok(DiagnosticTask::SetNative(file_id, diagnostics)) |
967 | } | 960 | } |
968 | 961 | ||
969 | pub fn publish_decorations( | ||
970 | world: &WorldSnapshot, | ||
971 | file_id: FileId, | ||
972 | ) -> Result<req::PublishDecorationsParams> { | ||
973 | let _p = profile("publish_decorations"); | ||
974 | let uri = world.file_id_to_uri(file_id)?; | ||
975 | Ok(req::PublishDecorationsParams { uri, decorations: highlight(&world, file_id)? }) | ||
976 | } | ||
977 | |||
978 | fn to_lsp_runnable( | 962 | fn to_lsp_runnable( |
979 | world: &WorldSnapshot, | 963 | world: &WorldSnapshot, |
980 | file_id: FileId, | 964 | file_id: FileId, |
@@ -1004,21 +988,6 @@ fn to_lsp_runnable( | |||
1004 | }) | 988 | }) |
1005 | } | 989 | } |
1006 | 990 | ||
1007 | fn highlight(world: &WorldSnapshot, file_id: FileId) -> Result<Vec<Decoration>> { | ||
1008 | let line_index = world.analysis().file_line_index(file_id)?; | ||
1009 | let res = world | ||
1010 | .analysis() | ||
1011 | .highlight(file_id)? | ||
1012 | .into_iter() | ||
1013 | .map(|h| Decoration { | ||
1014 | range: h.range.conv_with(&line_index), | ||
1015 | tag: h.highlight.to_string(), | ||
1016 | binding_hash: h.binding_hash.map(|x| x.to_string()), | ||
1017 | }) | ||
1018 | .collect(); | ||
1019 | Ok(res) | ||
1020 | } | ||
1021 | |||
1022 | pub fn handle_inlay_hints( | 991 | pub fn handle_inlay_hints( |
1023 | world: WorldSnapshot, | 992 | world: WorldSnapshot, |
1024 | params: InlayHintsParams, | 993 | params: InlayHintsParams, |
@@ -1028,7 +997,7 @@ pub fn handle_inlay_hints( | |||
1028 | let analysis = world.analysis(); | 997 | let analysis = world.analysis(); |
1029 | let line_index = analysis.file_line_index(file_id)?; | 998 | let line_index = analysis.file_line_index(file_id)?; |
1030 | Ok(analysis | 999 | Ok(analysis |
1031 | .inlay_hints(file_id, &world.options.inlay_hints)? | 1000 | .inlay_hints(file_id, &world.config.inlay_hints)? |
1032 | .into_iter() | 1001 | .into_iter() |
1033 | .map_conv_with(&line_index) | 1002 | .map_conv_with(&line_index) |
1034 | .collect()) | 1003 | .collect()) |
@@ -1145,7 +1114,7 @@ pub fn handle_semantic_tokens( | |||
1145 | } | 1114 | } |
1146 | } | 1115 | } |
1147 | 1116 | ||
1148 | let tokens = SemanticTokens { data: builder.build(), ..Default::default() }; | 1117 | let tokens = builder.build(); |
1149 | 1118 | ||
1150 | Ok(Some(tokens.into())) | 1119 | Ok(Some(tokens.into())) |
1151 | } | 1120 | } |
@@ -1166,7 +1135,7 @@ pub fn handle_semantic_tokens_range( | |||
1166 | builder.push(highlight_range.range.conv_with(&line_index), token_type, token_modifiers); | 1135 | builder.push(highlight_range.range.conv_with(&line_index), token_type, token_modifiers); |
1167 | } | 1136 | } |
1168 | 1137 | ||
1169 | let tokens = SemanticTokens { data: builder.build(), ..Default::default() }; | 1138 | let tokens = builder.build(); |
1170 | 1139 | ||
1171 | Ok(Some(tokens.into())) | 1140 | Ok(Some(tokens.into())) |
1172 | } | 1141 | } |
diff --git a/crates/rust-analyzer/src/main_loop/subscriptions.rs b/crates/rust-analyzer/src/main_loop/subscriptions.rs index bee6437cf..2c76418be 100644 --- a/crates/rust-analyzer/src/main_loop/subscriptions.rs +++ b/crates/rust-analyzer/src/main_loop/subscriptions.rs | |||
@@ -17,6 +17,6 @@ impl Subscriptions { | |||
17 | self.subs.remove(&file_id); | 17 | self.subs.remove(&file_id); |
18 | } | 18 | } |
19 | pub(crate) fn subscriptions(&self) -> Vec<FileId> { | 19 | pub(crate) fn subscriptions(&self) -> Vec<FileId> { |
20 | self.subs.iter().cloned().collect() | 20 | self.subs.iter().copied().collect() |
21 | } | 21 | } |
22 | } | 22 | } |
diff --git a/crates/rust-analyzer/src/req.rs b/crates/rust-analyzer/src/req.rs index 8557294f6..b8b627e28 100644 --- a/crates/rust-analyzer/src/req.rs +++ b/crates/rust-analyzer/src/req.rs | |||
@@ -1,13 +1,13 @@ | |||
1 | //! Defines `rust-analyzer` specific custom messages. | 1 | //! Defines `rust-analyzer` specific custom messages. |
2 | 2 | ||
3 | use lsp_types::{Location, Position, Range, TextDocumentIdentifier, Url}; | 3 | use lsp_types::{Location, Position, Range, TextDocumentIdentifier}; |
4 | use rustc_hash::FxHashMap; | 4 | use rustc_hash::FxHashMap; |
5 | use serde::{Deserialize, Serialize}; | 5 | use serde::{Deserialize, Serialize}; |
6 | 6 | ||
7 | pub use lsp_types::{ | 7 | pub use lsp_types::{ |
8 | notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CodeLens, | 8 | notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CodeLens, |
9 | CodeLensParams, CompletionParams, CompletionResponse, DiagnosticTag, | 9 | CodeLensParams, CompletionParams, CompletionResponse, ConfigurationItem, ConfigurationParams, |
10 | DidChangeConfigurationParams, DidChangeWatchedFilesParams, | 10 | DiagnosticTag, DidChangeConfigurationParams, DidChangeWatchedFilesParams, |
11 | DidChangeWatchedFilesRegistrationOptions, DocumentOnTypeFormattingParams, DocumentSymbolParams, | 11 | DidChangeWatchedFilesRegistrationOptions, DocumentOnTypeFormattingParams, DocumentSymbolParams, |
12 | DocumentSymbolResponse, FileSystemWatcher, Hover, InitializeResult, MessageType, | 12 | DocumentSymbolResponse, FileSystemWatcher, Hover, InitializeResult, MessageType, |
13 | PartialResultParams, ProgressParams, ProgressParamsValue, ProgressToken, | 13 | PartialResultParams, ProgressParams, ProgressParamsValue, ProgressToken, |
@@ -86,36 +86,6 @@ pub struct FindMatchingBraceParams { | |||
86 | pub offsets: Vec<Position>, | 86 | pub offsets: Vec<Position>, |
87 | } | 87 | } |
88 | 88 | ||
89 | pub enum DecorationsRequest {} | ||
90 | |||
91 | impl Request for DecorationsRequest { | ||
92 | type Params = TextDocumentIdentifier; | ||
93 | type Result = Vec<Decoration>; | ||
94 | const METHOD: &'static str = "rust-analyzer/decorationsRequest"; | ||
95 | } | ||
96 | |||
97 | pub enum PublishDecorations {} | ||
98 | |||
99 | impl Notification for PublishDecorations { | ||
100 | type Params = PublishDecorationsParams; | ||
101 | const METHOD: &'static str = "rust-analyzer/publishDecorations"; | ||
102 | } | ||
103 | |||
104 | #[derive(Deserialize, Serialize, Debug)] | ||
105 | #[serde(rename_all = "camelCase")] | ||
106 | pub struct PublishDecorationsParams { | ||
107 | pub uri: Url, | ||
108 | pub decorations: Vec<Decoration>, | ||
109 | } | ||
110 | |||
111 | #[derive(Deserialize, Serialize, Debug)] | ||
112 | #[serde(rename_all = "camelCase")] | ||
113 | pub struct Decoration { | ||
114 | pub range: Range, | ||
115 | pub tag: String, | ||
116 | pub binding_hash: Option<String>, | ||
117 | } | ||
118 | |||
119 | pub enum ParentModule {} | 89 | pub enum ParentModule {} |
120 | 90 | ||
121 | impl Request for ParentModule { | 91 | impl Request for ParentModule { |
diff --git a/crates/rust-analyzer/src/semantic_tokens.rs b/crates/rust-analyzer/src/semantic_tokens.rs index d9ba77050..2a66bbfd8 100644 --- a/crates/rust-analyzer/src/semantic_tokens.rs +++ b/crates/rust-analyzer/src/semantic_tokens.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | use std::ops; | 3 | use std::ops; |
4 | 4 | ||
5 | use lsp_types::{Range, SemanticToken, SemanticTokenModifier, SemanticTokenType}; | 5 | use lsp_types::{Range, SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens}; |
6 | 6 | ||
7 | pub(crate) const ATTRIBUTE: SemanticTokenType = SemanticTokenType::new("attribute"); | 7 | pub(crate) const ATTRIBUTE: SemanticTokenType = SemanticTokenType::new("attribute"); |
8 | pub(crate) const BUILTIN_TYPE: SemanticTokenType = SemanticTokenType::new("builtinType"); | 8 | pub(crate) const BUILTIN_TYPE: SemanticTokenType = SemanticTokenType::new("builtinType"); |
@@ -109,8 +109,8 @@ impl SemanticTokensBuilder { | |||
109 | self.prev_char = range.start.character as u32; | 109 | self.prev_char = range.start.character as u32; |
110 | } | 110 | } |
111 | 111 | ||
112 | pub fn build(self) -> Vec<SemanticToken> { | 112 | pub fn build(self) -> SemanticTokens { |
113 | self.data | 113 | SemanticTokens { result_id: None, data: self.data } |
114 | } | 114 | } |
115 | } | 115 | } |
116 | 116 | ||
diff --git a/crates/rust-analyzer/src/vfs_glob.rs b/crates/rust-analyzer/src/vfs_glob.rs index 91b33f94e..ff37a7008 100644 --- a/crates/rust-analyzer/src/vfs_glob.rs +++ b/crates/rust-analyzer/src/vfs_glob.rs | |||
@@ -29,10 +29,14 @@ impl RustPackageFilterBuilder { | |||
29 | self.is_member = is_member; | 29 | self.is_member = is_member; |
30 | self | 30 | self |
31 | } | 31 | } |
32 | pub fn exclude(mut self, glob: Glob) -> RustPackageFilterBuilder { | 32 | |
33 | self.exclude.add(glob); | 33 | pub fn exclude(mut self, globs: impl IntoIterator<Item = Glob>) -> RustPackageFilterBuilder { |
34 | for glob in globs.into_iter() { | ||
35 | self.exclude.add(glob); | ||
36 | } | ||
34 | self | 37 | self |
35 | } | 38 | } |
39 | |||
36 | pub fn into_vfs_filter(self) -> Box<dyn Filter> { | 40 | pub fn into_vfs_filter(self) -> Box<dyn Filter> { |
37 | let RustPackageFilterBuilder { is_member, mut exclude } = self; | 41 | let RustPackageFilterBuilder { is_member, mut exclude } = self; |
38 | for &glob in ALWAYS_IGNORED { | 42 | for &glob in ALWAYS_IGNORED { |
@@ -87,7 +91,7 @@ fn test_globs() { | |||
87 | 91 | ||
88 | let filter = RustPackageFilterBuilder::default() | 92 | let filter = RustPackageFilterBuilder::default() |
89 | .set_member(true) | 93 | .set_member(true) |
90 | .exclude(Glob::new("src/llvm-project/**").unwrap()) | 94 | .exclude(std::iter::once(Glob::new("src/llvm-project/**").unwrap())) |
91 | .into_vfs_filter(); | 95 | .into_vfs_filter(); |
92 | 96 | ||
93 | assert!(!filter.include_dir(RelativePath::new("src/llvm-project/clang"))); | 97 | assert!(!filter.include_dir(RelativePath::new("src/llvm-project/clang"))); |
diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs index 64a7b907e..365f57d8c 100644 --- a/crates/rust-analyzer/src/world.rs +++ b/crates/rust-analyzer/src/world.rs | |||
@@ -11,10 +11,9 @@ use std::{ | |||
11 | use crossbeam_channel::{unbounded, Receiver}; | 11 | use crossbeam_channel::{unbounded, Receiver}; |
12 | use lsp_types::Url; | 12 | use lsp_types::Url; |
13 | use parking_lot::RwLock; | 13 | use parking_lot::RwLock; |
14 | use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckWatcher}; | 14 | use ra_flycheck::{url_from_path_with_drive_lowercasing, Flycheck, FlycheckConfig}; |
15 | use ra_ide::{ | 15 | use ra_ide::{ |
16 | Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, InlayHintsOptions, LibraryData, | 16 | Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId, |
17 | SourceRootId, | ||
18 | }; | 17 | }; |
19 | use ra_project_model::{get_rustc_cfg_options, ProcMacroClient, ProjectWorkspace}; | 18 | use ra_project_model::{get_rustc_cfg_options, ProcMacroClient, ProjectWorkspace}; |
20 | use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch}; | 19 | use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch}; |
@@ -22,8 +21,8 @@ use relative_path::RelativePathBuf; | |||
22 | use stdx::format_to; | 21 | use stdx::format_to; |
23 | 22 | ||
24 | use crate::{ | 23 | use crate::{ |
24 | config::Config, | ||
25 | diagnostics::{CheckFixes, DiagnosticCollection}, | 25 | diagnostics::{CheckFixes, DiagnosticCollection}, |
26 | feature_flags::FeatureFlags, | ||
27 | main_loop::pending_requests::{CompletedRequest, LatestRequests}, | 26 | main_loop::pending_requests::{CompletedRequest, LatestRequests}, |
28 | vfs_glob::{Glob, RustPackageFilterBuilder}, | 27 | vfs_glob::{Glob, RustPackageFilterBuilder}, |
29 | LspError, Result, | 28 | LspError, Result, |
@@ -31,15 +30,22 @@ use crate::{ | |||
31 | use ra_db::ExternSourceId; | 30 | use ra_db::ExternSourceId; |
32 | use rustc_hash::{FxHashMap, FxHashSet}; | 31 | use rustc_hash::{FxHashMap, FxHashSet}; |
33 | 32 | ||
34 | #[derive(Debug, Clone)] | 33 | fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> { |
35 | pub struct Options { | 34 | // FIXME: Figure out the multi-workspace situation |
36 | pub publish_decorations: bool, | 35 | workspaces |
37 | pub supports_location_link: bool, | 36 | .iter() |
38 | pub line_folding_only: bool, | 37 | .find_map(|w| match w { |
39 | pub inlay_hints: InlayHintsOptions, | 38 | ProjectWorkspace::Cargo { cargo, .. } => Some(cargo), |
40 | pub rustfmt_args: Vec<String>, | 39 | ProjectWorkspace::Json { .. } => None, |
41 | pub cargo_watch: CheckOptions, | 40 | }) |
42 | pub vscode_lldb: bool, | 41 | .map(|cargo| { |
42 | let cargo_project_root = cargo.workspace_root().to_path_buf(); | ||
43 | Some(Flycheck::new(config.clone(), cargo_project_root)) | ||
44 | }) | ||
45 | .unwrap_or_else(|| { | ||
46 | log::warn!("Cargo check watching only supported for cargo workspaces, disabling"); | ||
47 | None | ||
48 | }) | ||
43 | } | 49 | } |
44 | 50 | ||
45 | /// `WorldState` is the primary mutable state of the language server | 51 | /// `WorldState` is the primary mutable state of the language server |
@@ -49,24 +55,20 @@ pub struct Options { | |||
49 | /// incremental salsa database. | 55 | /// incremental salsa database. |
50 | #[derive(Debug)] | 56 | #[derive(Debug)] |
51 | pub struct WorldState { | 57 | pub struct WorldState { |
52 | pub options: Options, | 58 | pub config: Config, |
53 | pub feature_flags: Arc<FeatureFlags>, | ||
54 | //FIXME: this belongs to `LoopState` rather than to `WorldState` | ||
55 | pub roots_to_scan: usize, | ||
56 | pub roots: Vec<PathBuf>, | 59 | pub roots: Vec<PathBuf>, |
57 | pub workspaces: Arc<Vec<ProjectWorkspace>>, | 60 | pub workspaces: Arc<Vec<ProjectWorkspace>>, |
58 | pub analysis_host: AnalysisHost, | 61 | pub analysis_host: AnalysisHost, |
59 | pub vfs: Arc<RwLock<Vfs>>, | 62 | pub vfs: Arc<RwLock<Vfs>>, |
60 | pub task_receiver: Receiver<VfsTask>, | 63 | pub task_receiver: Receiver<VfsTask>, |
61 | pub latest_requests: Arc<RwLock<LatestRequests>>, | 64 | pub latest_requests: Arc<RwLock<LatestRequests>>, |
62 | pub check_watcher: CheckWatcher, | 65 | pub flycheck: Option<Flycheck>, |
63 | pub diagnostics: DiagnosticCollection, | 66 | pub diagnostics: DiagnosticCollection, |
64 | } | 67 | } |
65 | 68 | ||
66 | /// An immutable snapshot of the world's state at a point in time. | 69 | /// An immutable snapshot of the world's state at a point in time. |
67 | pub struct WorldSnapshot { | 70 | pub struct WorldSnapshot { |
68 | pub options: Options, | 71 | pub config: Config, |
69 | pub feature_flags: Arc<FeatureFlags>, | ||
70 | pub workspaces: Arc<Vec<ProjectWorkspace>>, | 72 | pub workspaces: Arc<Vec<ProjectWorkspace>>, |
71 | pub analysis: Analysis, | 73 | pub analysis: Analysis, |
72 | pub latest_requests: Arc<RwLock<LatestRequests>>, | 74 | pub latest_requests: Arc<RwLock<LatestRequests>>, |
@@ -81,49 +83,39 @@ impl WorldState { | |||
81 | lru_capacity: Option<usize>, | 83 | lru_capacity: Option<usize>, |
82 | exclude_globs: &[Glob], | 84 | exclude_globs: &[Glob], |
83 | watch: Watch, | 85 | watch: Watch, |
84 | options: Options, | 86 | config: Config, |
85 | feature_flags: FeatureFlags, | ||
86 | ) -> WorldState { | 87 | ) -> WorldState { |
87 | let mut change = AnalysisChange::new(); | 88 | let mut change = AnalysisChange::new(); |
88 | 89 | ||
89 | let mut roots = Vec::new(); | 90 | let extern_dirs: FxHashSet<_> = |
90 | roots.extend(folder_roots.iter().map(|path| { | 91 | workspaces.iter().flat_map(ProjectWorkspace::out_dirs).collect(); |
91 | let mut filter = RustPackageFilterBuilder::default().set_member(true); | 92 | |
92 | for glob in exclude_globs.iter() { | 93 | let roots: Vec<_> = { |
93 | filter = filter.exclude(glob.clone()); | 94 | let create_filter = |is_member| { |
94 | } | 95 | RustPackageFilterBuilder::default() |
95 | RootEntry::new(path.clone(), filter.into_vfs_filter()) | 96 | .set_member(is_member) |
96 | })); | 97 | .exclude(exclude_globs.iter().cloned()) |
97 | for ws in workspaces.iter() { | 98 | .into_vfs_filter() |
98 | roots.extend(ws.to_roots().into_iter().map(|pkg_root| { | 99 | }; |
99 | let mut filter = | 100 | folder_roots |
100 | RustPackageFilterBuilder::default().set_member(pkg_root.is_member()); | 101 | .iter() |
101 | for glob in exclude_globs.iter() { | 102 | .map(|path| RootEntry::new(path.clone(), create_filter(true))) |
102 | filter = filter.exclude(glob.clone()); | 103 | .chain(workspaces.iter().flat_map(ProjectWorkspace::to_roots).map(|pkg_root| { |
103 | } | 104 | RootEntry::new(pkg_root.path().to_owned(), create_filter(pkg_root.is_member())) |
104 | RootEntry::new(pkg_root.path().clone(), filter.into_vfs_filter()) | 105 | })) |
105 | })); | 106 | .chain( |
106 | } | 107 | extern_dirs |
107 | 108 | .iter() | |
108 | let mut extern_dirs = FxHashSet::default(); | 109 | .map(|path| RootEntry::new(path.to_owned(), create_filter(false))), |
109 | for ws in workspaces.iter() { | 110 | ) |
110 | extern_dirs.extend(ws.out_dirs()); | 111 | .collect() |
111 | } | 112 | }; |
112 | |||
113 | let mut extern_source_roots = FxHashMap::default(); | ||
114 | |||
115 | roots.extend(extern_dirs.iter().map(|path| { | ||
116 | let mut filter = RustPackageFilterBuilder::default().set_member(false); | ||
117 | for glob in exclude_globs.iter() { | ||
118 | filter = filter.exclude(glob.clone()); | ||
119 | } | ||
120 | RootEntry::new(PathBuf::from(&path), filter.into_vfs_filter()) | ||
121 | })); | ||
122 | 113 | ||
123 | let (task_sender, task_receiver) = unbounded(); | 114 | let (task_sender, task_receiver) = unbounded(); |
124 | let task_sender = Box::new(move |t| task_sender.send(t).unwrap()); | 115 | let task_sender = Box::new(move |t| task_sender.send(t).unwrap()); |
125 | let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch); | 116 | let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch); |
126 | let roots_to_scan = vfs_roots.len(); | 117 | |
118 | let mut extern_source_roots = FxHashMap::default(); | ||
127 | for r in vfs_roots { | 119 | for r in vfs_roots { |
128 | let vfs_root_path = vfs.root2path(r); | 120 | let vfs_root_path = vfs.root2path(r); |
129 | let is_local = folder_roots.iter().any(|it| vfs_root_path.starts_with(it)); | 121 | let is_local = folder_roots.iter().any(|it| vfs_root_path.starts_with(it)); |
@@ -151,8 +143,23 @@ impl WorldState { | |||
151 | vfs_file.map(|f| FileId(f.0)) | 143 | vfs_file.map(|f| FileId(f.0)) |
152 | }; | 144 | }; |
153 | 145 | ||
154 | let proc_macro_client = | 146 | let proc_macro_client = match &config.proc_macro_srv { |
155 | ProcMacroClient::extern_process(std::path::Path::new("ra_proc_macro_srv")); | 147 | None => ProcMacroClient::dummy(), |
148 | Some(srv) => { | ||
149 | let path = Path::new(&srv); | ||
150 | match ProcMacroClient::extern_process(path) { | ||
151 | Ok(it) => it, | ||
152 | Err(err) => { | ||
153 | log::error!( | ||
154 | "Fail to run ra_proc_macro_srv from path {}, error : {}", | ||
155 | path.to_string_lossy(), | ||
156 | err | ||
157 | ); | ||
158 | ProcMacroClient::dummy() | ||
159 | } | ||
160 | } | ||
161 | } | ||
162 | }; | ||
156 | 163 | ||
157 | workspaces | 164 | workspaces |
158 | .iter() | 165 | .iter() |
@@ -169,43 +176,38 @@ impl WorldState { | |||
169 | }); | 176 | }); |
170 | change.set_crate_graph(crate_graph); | 177 | change.set_crate_graph(crate_graph); |
171 | 178 | ||
172 | // FIXME: Figure out the multi-workspace situation | 179 | let flycheck = config.check.as_ref().and_then(|c| create_flycheck(&workspaces, c)); |
173 | let check_watcher = workspaces | ||
174 | .iter() | ||
175 | .find_map(|w| match w { | ||
176 | ProjectWorkspace::Cargo { cargo, .. } => Some(cargo), | ||
177 | ProjectWorkspace::Json { .. } => None, | ||
178 | }) | ||
179 | .map(|cargo| { | ||
180 | let cargo_project_root = cargo.workspace_root().to_path_buf(); | ||
181 | CheckWatcher::new(&options.cargo_watch, cargo_project_root) | ||
182 | }) | ||
183 | .unwrap_or_else(|| { | ||
184 | log::warn!("Cargo check watching only supported for cargo workspaces, disabling"); | ||
185 | CheckWatcher::dummy() | ||
186 | }); | ||
187 | 180 | ||
188 | let mut analysis_host = AnalysisHost::new(lru_capacity); | 181 | let mut analysis_host = AnalysisHost::new(lru_capacity); |
189 | analysis_host.apply_change(change); | 182 | analysis_host.apply_change(change); |
190 | WorldState { | 183 | WorldState { |
191 | options, | 184 | config: config, |
192 | feature_flags: Arc::new(feature_flags), | ||
193 | roots_to_scan, | ||
194 | roots: folder_roots, | 185 | roots: folder_roots, |
195 | workspaces: Arc::new(workspaces), | 186 | workspaces: Arc::new(workspaces), |
196 | analysis_host, | 187 | analysis_host, |
197 | vfs: Arc::new(RwLock::new(vfs)), | 188 | vfs: Arc::new(RwLock::new(vfs)), |
198 | task_receiver, | 189 | task_receiver, |
199 | latest_requests: Default::default(), | 190 | latest_requests: Default::default(), |
200 | check_watcher, | 191 | flycheck, |
201 | diagnostics: Default::default(), | 192 | diagnostics: Default::default(), |
202 | } | 193 | } |
203 | } | 194 | } |
204 | 195 | ||
196 | pub fn update_configuration(&mut self, config: Config) { | ||
197 | self.analysis_host.update_lru_capacity(config.lru_capacity); | ||
198 | if config.check != self.config.check { | ||
199 | self.flycheck = | ||
200 | config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it)); | ||
201 | } | ||
202 | |||
203 | self.config = config; | ||
204 | } | ||
205 | |||
205 | /// Returns a vec of libraries | 206 | /// Returns a vec of libraries |
206 | /// FIXME: better API here | 207 | /// FIXME: better API here |
207 | pub fn process_changes( | 208 | pub fn process_changes( |
208 | &mut self, | 209 | &mut self, |
210 | roots_scanned: &mut usize, | ||
209 | ) -> Option<Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>> { | 211 | ) -> Option<Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>> { |
210 | let changes = self.vfs.write().commit_changes(); | 212 | let changes = self.vfs.write().commit_changes(); |
211 | if changes.is_empty() { | 213 | if changes.is_empty() { |
@@ -219,7 +221,7 @@ impl WorldState { | |||
219 | let root_path = self.vfs.read().root2path(root); | 221 | let root_path = self.vfs.read().root2path(root); |
220 | let is_local = self.roots.iter().any(|r| root_path.starts_with(r)); | 222 | let is_local = self.roots.iter().any(|r| root_path.starts_with(r)); |
221 | if is_local { | 223 | if is_local { |
222 | self.roots_to_scan -= 1; | 224 | *roots_scanned += 1; |
223 | for (file, path, text) in files { | 225 | for (file, path, text) in files { |
224 | change.add_file(SourceRootId(root.0), FileId(file.0), path, text); | 226 | change.add_file(SourceRootId(root.0), FileId(file.0), path, text); |
225 | } | 227 | } |
@@ -247,7 +249,6 @@ impl WorldState { | |||
247 | } | 249 | } |
248 | 250 | ||
249 | pub fn add_lib(&mut self, data: LibraryData) { | 251 | pub fn add_lib(&mut self, data: LibraryData) { |
250 | self.roots_to_scan -= 1; | ||
251 | let mut change = AnalysisChange::new(); | 252 | let mut change = AnalysisChange::new(); |
252 | change.add_library(data); | 253 | change.add_library(data); |
253 | self.analysis_host.apply_change(change); | 254 | self.analysis_host.apply_change(change); |
@@ -255,8 +256,7 @@ impl WorldState { | |||
255 | 256 | ||
256 | pub fn snapshot(&self) -> WorldSnapshot { | 257 | pub fn snapshot(&self) -> WorldSnapshot { |
257 | WorldSnapshot { | 258 | WorldSnapshot { |
258 | options: self.options.clone(), | 259 | config: self.config.clone(), |
259 | feature_flags: Arc::clone(&self.feature_flags), | ||
260 | workspaces: Arc::clone(&self.workspaces), | 260 | workspaces: Arc::clone(&self.workspaces), |
261 | analysis: self.analysis_host.analysis(), | 261 | analysis: self.analysis_host.analysis(), |
262 | vfs: Arc::clone(&self.vfs), | 262 | vfs: Arc::clone(&self.vfs), |
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs index 145429571..638813311 100644 --- a/crates/rust-analyzer/tests/heavy_tests/main.rs +++ b/crates/rust-analyzer/tests/heavy_tests/main.rs | |||
@@ -9,7 +9,7 @@ use lsp_types::{ | |||
9 | }; | 9 | }; |
10 | use rust_analyzer::req::{ | 10 | use rust_analyzer::req::{ |
11 | CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument, | 11 | CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument, |
12 | Formatting, OnEnter, Runnables, RunnablesParams, | 12 | Formatting, GotoDefinition, OnEnter, Runnables, RunnablesParams, |
13 | }; | 13 | }; |
14 | use serde_json::json; | 14 | use serde_json::json; |
15 | use tempfile::TempDir; | 15 | use tempfile::TempDir; |
@@ -581,3 +581,47 @@ version = \"0.0.0\" | |||
581 | }), | 581 | }), |
582 | ); | 582 | ); |
583 | } | 583 | } |
584 | |||
585 | #[test] | ||
586 | fn resolve_include_concat_env() { | ||
587 | if skip_slow_tests() { | ||
588 | return; | ||
589 | } | ||
590 | |||
591 | let server = Project::with_fixture( | ||
592 | r###" | ||
593 | //- Cargo.toml | ||
594 | [package] | ||
595 | name = "foo" | ||
596 | version = "0.0.0" | ||
597 | |||
598 | //- build.rs | ||
599 | use std::{env, fs, path::Path}; | ||
600 | |||
601 | fn main() { | ||
602 | let out_dir = env::var_os("OUT_DIR").unwrap(); | ||
603 | let dest_path = Path::new(&out_dir).join("hello.rs"); | ||
604 | fs::write( | ||
605 | &dest_path, | ||
606 | r#"pub fn message() -> &'static str { "Hello, World!" }"#, | ||
607 | ) | ||
608 | .unwrap(); | ||
609 | println!("cargo:rerun-if-changed=build.rs"); | ||
610 | } | ||
611 | //- src/main.rs | ||
612 | include!(concat!(env!("OUT_DIR"), "/hello.rs")); | ||
613 | |||
614 | fn main() { message(); } | ||
615 | "###, | ||
616 | ) | ||
617 | .with_config(|config| { | ||
618 | config.cargo.load_out_dirs_from_check = true; | ||
619 | }) | ||
620 | .server(); | ||
621 | server.wait_until_workspace_is_loaded(); | ||
622 | let res = server.send_request::<GotoDefinition>(TextDocumentPositionParams::new( | ||
623 | server.doc_id("src/main.rs"), | ||
624 | Position::new(2, 15), | ||
625 | )); | ||
626 | assert!(format!("{}", res).contains("hello.rs")); | ||
627 | } | ||
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs index 1d7062bdf..7eebedff7 100644 --- a/crates/rust-analyzer/tests/heavy_tests/support.rs +++ b/crates/rust-analyzer/tests/heavy_tests/support.rs | |||
@@ -11,8 +11,7 @@ use lsp_server::{Connection, Message, Notification, Request}; | |||
11 | use lsp_types::{ | 11 | use lsp_types::{ |
12 | notification::{DidOpenTextDocument, Exit}, | 12 | notification::{DidOpenTextDocument, Exit}, |
13 | request::Shutdown, | 13 | request::Shutdown, |
14 | ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, TextDocumentClientCapabilities, | 14 | DidOpenTextDocumentParams, TextDocumentIdentifier, TextDocumentItem, Url, WorkDoneProgress, |
15 | TextDocumentIdentifier, TextDocumentItem, Url, WorkDoneProgress, | ||
16 | }; | 15 | }; |
17 | use serde::Serialize; | 16 | use serde::Serialize; |
18 | use serde_json::{to_string_pretty, Value}; | 17 | use serde_json::{to_string_pretty, Value}; |
@@ -20,18 +19,22 @@ use tempfile::TempDir; | |||
20 | use test_utils::{find_mismatch, parse_fixture}; | 19 | use test_utils::{find_mismatch, parse_fixture}; |
21 | 20 | ||
22 | use req::{ProgressParams, ProgressParamsValue}; | 21 | use req::{ProgressParams, ProgressParamsValue}; |
23 | use rust_analyzer::{main_loop, req, ServerConfig}; | 22 | use rust_analyzer::{ |
23 | config::{ClientCapsConfig, Config}, | ||
24 | main_loop, req, | ||
25 | }; | ||
24 | 26 | ||
25 | pub struct Project<'a> { | 27 | pub struct Project<'a> { |
26 | fixture: &'a str, | 28 | fixture: &'a str, |
27 | with_sysroot: bool, | 29 | with_sysroot: bool, |
28 | tmp_dir: Option<TempDir>, | 30 | tmp_dir: Option<TempDir>, |
29 | roots: Vec<PathBuf>, | 31 | roots: Vec<PathBuf>, |
32 | config: Option<Box<dyn Fn(&mut Config)>>, | ||
30 | } | 33 | } |
31 | 34 | ||
32 | impl<'a> Project<'a> { | 35 | impl<'a> Project<'a> { |
33 | pub fn with_fixture(fixture: &str) -> Project { | 36 | pub fn with_fixture(fixture: &str) -> Project { |
34 | Project { fixture, tmp_dir: None, roots: vec![], with_sysroot: false } | 37 | Project { fixture, tmp_dir: None, roots: vec![], with_sysroot: false, config: None } |
35 | } | 38 | } |
36 | 39 | ||
37 | pub fn tmp_dir(mut self, tmp_dir: TempDir) -> Project<'a> { | 40 | pub fn tmp_dir(mut self, tmp_dir: TempDir) -> Project<'a> { |
@@ -49,6 +52,11 @@ impl<'a> Project<'a> { | |||
49 | self | 52 | self |
50 | } | 53 | } |
51 | 54 | ||
55 | pub fn with_config(mut self, config: impl Fn(&mut Config) + 'static) -> Project<'a> { | ||
56 | self.config = Some(Box::new(config)); | ||
57 | self | ||
58 | } | ||
59 | |||
52 | pub fn server(self) -> Server { | 60 | pub fn server(self) -> Server { |
53 | let tmp_dir = self.tmp_dir.unwrap_or_else(|| TempDir::new().unwrap()); | 61 | let tmp_dir = self.tmp_dir.unwrap_or_else(|| TempDir::new().unwrap()); |
54 | static INIT: Once = Once::new(); | 62 | static INIT: Once = Once::new(); |
@@ -72,7 +80,17 @@ impl<'a> Project<'a> { | |||
72 | 80 | ||
73 | let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect(); | 81 | let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect(); |
74 | 82 | ||
75 | Server::new(tmp_dir, self.with_sysroot, roots, paths) | 83 | let mut config = Config { |
84 | client_caps: ClientCapsConfig { location_link: true, ..Default::default() }, | ||
85 | with_sysroot: self.with_sysroot, | ||
86 | ..Config::default() | ||
87 | }; | ||
88 | |||
89 | if let Some(f) = &self.config { | ||
90 | f(&mut config) | ||
91 | } | ||
92 | |||
93 | Server::new(tmp_dir, config, roots, paths) | ||
76 | } | 94 | } |
77 | } | 95 | } |
78 | 96 | ||
@@ -83,15 +101,16 @@ pub fn project(fixture: &str) -> Server { | |||
83 | pub struct Server { | 101 | pub struct Server { |
84 | req_id: Cell<u64>, | 102 | req_id: Cell<u64>, |
85 | messages: RefCell<Vec<Message>>, | 103 | messages: RefCell<Vec<Message>>, |
86 | dir: TempDir, | ||
87 | _thread: jod_thread::JoinHandle<()>, | 104 | _thread: jod_thread::JoinHandle<()>, |
88 | client: Connection, | 105 | client: Connection, |
106 | /// XXX: remove the tempdir last | ||
107 | dir: TempDir, | ||
89 | } | 108 | } |
90 | 109 | ||
91 | impl Server { | 110 | impl Server { |
92 | fn new( | 111 | fn new( |
93 | dir: TempDir, | 112 | dir: TempDir, |
94 | with_sysroot: bool, | 113 | config: Config, |
95 | roots: Vec<PathBuf>, | 114 | roots: Vec<PathBuf>, |
96 | files: Vec<(PathBuf, String)>, | 115 | files: Vec<(PathBuf, String)>, |
97 | ) -> Server { | 116 | ) -> Server { |
@@ -102,26 +121,7 @@ impl Server { | |||
102 | 121 | ||
103 | let _thread = jod_thread::Builder::new() | 122 | let _thread = jod_thread::Builder::new() |
104 | .name("test server".to_string()) | 123 | .name("test server".to_string()) |
105 | .spawn(move || { | 124 | .spawn(move || main_loop(roots, config, connection).unwrap()) |
106 | main_loop( | ||
107 | roots, | ||
108 | ClientCapabilities { | ||
109 | workspace: None, | ||
110 | text_document: Some(TextDocumentClientCapabilities { | ||
111 | definition: Some(GotoCapability { | ||
112 | dynamic_registration: None, | ||
113 | link_support: Some(true), | ||
114 | }), | ||
115 | ..Default::default() | ||
116 | }), | ||
117 | window: None, | ||
118 | experimental: None, | ||
119 | }, | ||
120 | ServerConfig { with_sysroot, ..ServerConfig::default() }, | ||
121 | connection, | ||
122 | ) | ||
123 | .unwrap() | ||
124 | }) | ||
125 | .expect("failed to spawn a thread"); | 125 | .expect("failed to spawn a thread"); |
126 | 126 | ||
127 | let res = | 127 | let res = |
@@ -187,6 +187,7 @@ impl Server { | |||
187 | self.client.sender.send(r.into()).unwrap(); | 187 | self.client.sender.send(r.into()).unwrap(); |
188 | while let Some(msg) = self.recv() { | 188 | while let Some(msg) = self.recv() { |
189 | match msg { | 189 | match msg { |
190 | Message::Request(req) if req.method == "window/workDoneProgress/create" => (), | ||
190 | Message::Request(req) => panic!("unexpected request: {:?}", req), | 191 | Message::Request(req) => panic!("unexpected request: {:?}", req), |
191 | Message::Notification(_) => (), | 192 | Message::Notification(_) => (), |
192 | Message::Response(res) => { | 193 | Message::Response(res) => { |
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs index 8492c17af..d2efa2236 100644 --- a/crates/stdx/src/lib.rs +++ b/crates/stdx/src/lib.rs | |||
@@ -5,7 +5,7 @@ use std::{cell::Cell, fmt}; | |||
5 | /// Appends formatted string to a `String`. | 5 | /// Appends formatted string to a `String`. |
6 | #[macro_export] | 6 | #[macro_export] |
7 | macro_rules! format_to { | 7 | macro_rules! format_to { |
8 | (&buf:expr) => (); | 8 | ($buf:expr) => (); |
9 | ($buf:expr, $lit:literal $($arg:tt)*) => { | 9 | ($buf:expr, $lit:literal $($arg:tt)*) => { |
10 | { use ::std::fmt::Write as _; let _ = ::std::write!($buf, $lit $($arg)*); } | 10 | { use ::std::fmt::Write as _; let _ = ::std::write!($buf, $lit $($arg)*); } |
11 | }; | 11 | }; |