diff options
Diffstat (limited to 'crates')
397 files changed, 36615 insertions, 27931 deletions
diff --git a/crates/expect/Cargo.toml b/crates/expect/Cargo.toml new file mode 100644 index 000000000..77775630d --- /dev/null +++ b/crates/expect/Cargo.toml | |||
@@ -0,0 +1,14 @@ | |||
1 | [package] | ||
2 | name = "expect" | ||
3 | version = "0.1.0" | ||
4 | authors = ["rust-analyzer developers"] | ||
5 | edition = "2018" | ||
6 | license = "MIT OR Apache-2.0" | ||
7 | |||
8 | [lib] | ||
9 | doctest = false | ||
10 | |||
11 | [dependencies] | ||
12 | once_cell = "1" | ||
13 | difference = "2" | ||
14 | stdx = { path = "../stdx" } | ||
diff --git a/crates/expect/src/lib.rs b/crates/expect/src/lib.rs new file mode 100644 index 000000000..21a458d47 --- /dev/null +++ b/crates/expect/src/lib.rs | |||
@@ -0,0 +1,356 @@ | |||
1 | //! Snapshot testing library, see | ||
2 | //! https://github.com/rust-analyzer/rust-analyzer/pull/5101 | ||
3 | use std::{ | ||
4 | collections::HashMap, | ||
5 | env, fmt, fs, mem, | ||
6 | ops::Range, | ||
7 | panic, | ||
8 | path::{Path, PathBuf}, | ||
9 | sync::Mutex, | ||
10 | }; | ||
11 | |||
12 | use difference::Changeset; | ||
13 | use once_cell::sync::Lazy; | ||
14 | use stdx::{lines_with_ends, trim_indent}; | ||
15 | |||
16 | const HELP: &str = " | ||
17 | You can update all `expect![[]]` tests by running: | ||
18 | |||
19 | env UPDATE_EXPECT=1 cargo test | ||
20 | |||
21 | To update a single test, place the cursor on `expect` token and use `run` feature of rust-analyzer. | ||
22 | "; | ||
23 | |||
24 | fn update_expect() -> bool { | ||
25 | env::var("UPDATE_EXPECT").is_ok() | ||
26 | } | ||
27 | |||
28 | /// expect![[r#"inline snapshot"#]] | ||
29 | #[macro_export] | ||
30 | macro_rules! expect { | ||
31 | [[$data:literal]] => {$crate::Expect { | ||
32 | position: $crate::Position { | ||
33 | file: file!(), | ||
34 | line: line!(), | ||
35 | column: column!(), | ||
36 | }, | ||
37 | data: $data, | ||
38 | }}; | ||
39 | [[]] => { $crate::expect![[""]] }; | ||
40 | } | ||
41 | |||
42 | /// expect_file!["/crates/foo/test_data/bar.html"] | ||
43 | #[macro_export] | ||
44 | macro_rules! expect_file { | ||
45 | [$path:expr] => {$crate::ExpectFile { | ||
46 | path: std::path::PathBuf::from($path) | ||
47 | }}; | ||
48 | } | ||
49 | |||
50 | #[derive(Debug)] | ||
51 | pub struct Expect { | ||
52 | pub position: Position, | ||
53 | pub data: &'static str, | ||
54 | } | ||
55 | |||
56 | #[derive(Debug)] | ||
57 | pub struct ExpectFile { | ||
58 | pub path: PathBuf, | ||
59 | } | ||
60 | |||
61 | #[derive(Debug)] | ||
62 | pub struct Position { | ||
63 | pub file: &'static str, | ||
64 | pub line: u32, | ||
65 | pub column: u32, | ||
66 | } | ||
67 | |||
68 | impl fmt::Display for Position { | ||
69 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
70 | write!(f, "{}:{}:{}", self.file, self.line, self.column) | ||
71 | } | ||
72 | } | ||
73 | |||
74 | impl Expect { | ||
75 | pub fn assert_eq(&self, actual: &str) { | ||
76 | let trimmed = self.trimmed(); | ||
77 | if &trimmed == actual { | ||
78 | return; | ||
79 | } | ||
80 | Runtime::fail_expect(self, &trimmed, actual); | ||
81 | } | ||
82 | pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) { | ||
83 | let actual = format!("{:#?}\n", actual); | ||
84 | self.assert_eq(&actual) | ||
85 | } | ||
86 | |||
87 | fn trimmed(&self) -> String { | ||
88 | if !self.data.contains('\n') { | ||
89 | return self.data.to_string(); | ||
90 | } | ||
91 | trim_indent(self.data) | ||
92 | } | ||
93 | |||
94 | fn locate(&self, file: &str) -> Location { | ||
95 | let mut target_line = None; | ||
96 | let mut line_start = 0; | ||
97 | for (i, line) in lines_with_ends(file).enumerate() { | ||
98 | if i == self.position.line as usize - 1 { | ||
99 | let pat = "expect![["; | ||
100 | let offset = line.find(pat).unwrap(); | ||
101 | let literal_start = line_start + offset + pat.len(); | ||
102 | let indent = line.chars().take_while(|&it| it == ' ').count(); | ||
103 | target_line = Some((literal_start, indent)); | ||
104 | break; | ||
105 | } | ||
106 | line_start += line.len(); | ||
107 | } | ||
108 | let (literal_start, line_indent) = target_line.unwrap(); | ||
109 | let literal_length = | ||
110 | file[literal_start..].find("]]").expect("Couldn't find matching `]]` for `expect![[`."); | ||
111 | let literal_range = literal_start..literal_start + literal_length; | ||
112 | Location { line_indent, literal_range } | ||
113 | } | ||
114 | } | ||
115 | |||
116 | impl ExpectFile { | ||
117 | pub fn assert_eq(&self, actual: &str) { | ||
118 | let expected = self.read(); | ||
119 | if actual == expected { | ||
120 | return; | ||
121 | } | ||
122 | Runtime::fail_file(self, &expected, actual); | ||
123 | } | ||
124 | pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) { | ||
125 | let actual = format!("{:#?}\n", actual); | ||
126 | self.assert_eq(&actual) | ||
127 | } | ||
128 | fn read(&self) -> String { | ||
129 | fs::read_to_string(self.abs_path()).unwrap_or_default().replace("\r\n", "\n") | ||
130 | } | ||
131 | fn write(&self, contents: &str) { | ||
132 | fs::write(self.abs_path(), contents).unwrap() | ||
133 | } | ||
134 | fn abs_path(&self) -> PathBuf { | ||
135 | WORKSPACE_ROOT.join(&self.path) | ||
136 | } | ||
137 | } | ||
138 | |||
139 | #[derive(Default)] | ||
140 | struct Runtime { | ||
141 | help_printed: bool, | ||
142 | per_file: HashMap<&'static str, FileRuntime>, | ||
143 | } | ||
144 | static RT: Lazy<Mutex<Runtime>> = Lazy::new(Default::default); | ||
145 | |||
146 | impl Runtime { | ||
147 | fn fail_expect(expect: &Expect, expected: &str, actual: &str) { | ||
148 | let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); | ||
149 | if update_expect() { | ||
150 | println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.position); | ||
151 | rt.per_file | ||
152 | .entry(expect.position.file) | ||
153 | .or_insert_with(|| FileRuntime::new(expect)) | ||
154 | .update(expect, actual); | ||
155 | return; | ||
156 | } | ||
157 | rt.panic(expect.position.to_string(), expected, actual); | ||
158 | } | ||
159 | |||
160 | fn fail_file(expect: &ExpectFile, expected: &str, actual: &str) { | ||
161 | let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); | ||
162 | if update_expect() { | ||
163 | println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.path.display()); | ||
164 | expect.write(actual); | ||
165 | return; | ||
166 | } | ||
167 | rt.panic(expect.path.display().to_string(), expected, actual); | ||
168 | } | ||
169 | |||
170 | fn panic(&mut self, position: String, expected: &str, actual: &str) { | ||
171 | let print_help = !mem::replace(&mut self.help_printed, true); | ||
172 | let help = if print_help { HELP } else { "" }; | ||
173 | |||
174 | let diff = Changeset::new(actual, expected, "\n"); | ||
175 | |||
176 | println!( | ||
177 | "\n | ||
178 | \x1b[1m\x1b[91merror\x1b[97m: expect test failed\x1b[0m | ||
179 | \x1b[1m\x1b[34m-->\x1b[0m {} | ||
180 | {} | ||
181 | \x1b[1mExpect\x1b[0m: | ||
182 | ---- | ||
183 | {} | ||
184 | ---- | ||
185 | |||
186 | \x1b[1mActual\x1b[0m: | ||
187 | ---- | ||
188 | {} | ||
189 | ---- | ||
190 | |||
191 | \x1b[1mDiff\x1b[0m: | ||
192 | ---- | ||
193 | {} | ||
194 | ---- | ||
195 | ", | ||
196 | position, help, expected, actual, diff | ||
197 | ); | ||
198 | // Use resume_unwind instead of panic!() to prevent a backtrace, which is unnecessary noise. | ||
199 | panic::resume_unwind(Box::new(())); | ||
200 | } | ||
201 | } | ||
202 | |||
203 | struct FileRuntime { | ||
204 | path: PathBuf, | ||
205 | original_text: String, | ||
206 | patchwork: Patchwork, | ||
207 | } | ||
208 | |||
209 | impl FileRuntime { | ||
210 | fn new(expect: &Expect) -> FileRuntime { | ||
211 | let path = WORKSPACE_ROOT.join(expect.position.file); | ||
212 | let original_text = fs::read_to_string(&path).unwrap(); | ||
213 | let patchwork = Patchwork::new(original_text.clone()); | ||
214 | FileRuntime { path, original_text, patchwork } | ||
215 | } | ||
216 | fn update(&mut self, expect: &Expect, actual: &str) { | ||
217 | let loc = expect.locate(&self.original_text); | ||
218 | let patch = format_patch(loc.line_indent.clone(), actual); | ||
219 | self.patchwork.patch(loc.literal_range, &patch); | ||
220 | fs::write(&self.path, &self.patchwork.text).unwrap() | ||
221 | } | ||
222 | } | ||
223 | |||
224 | #[derive(Debug)] | ||
225 | struct Location { | ||
226 | line_indent: usize, | ||
227 | literal_range: Range<usize>, | ||
228 | } | ||
229 | |||
230 | #[derive(Debug)] | ||
231 | struct Patchwork { | ||
232 | text: String, | ||
233 | indels: Vec<(Range<usize>, usize)>, | ||
234 | } | ||
235 | |||
236 | impl Patchwork { | ||
237 | fn new(text: String) -> Patchwork { | ||
238 | Patchwork { text, indels: Vec::new() } | ||
239 | } | ||
240 | fn patch(&mut self, mut range: Range<usize>, patch: &str) { | ||
241 | self.indels.push((range.clone(), patch.len())); | ||
242 | self.indels.sort_by_key(|(delete, _insert)| delete.start); | ||
243 | |||
244 | let (delete, insert) = self | ||
245 | .indels | ||
246 | .iter() | ||
247 | .take_while(|(delete, _)| delete.start < range.start) | ||
248 | .map(|(delete, insert)| (delete.end - delete.start, insert)) | ||
249 | .fold((0usize, 0usize), |(x1, y1), (x2, y2)| (x1 + x2, y1 + y2)); | ||
250 | |||
251 | for pos in &mut [&mut range.start, &mut range.end] { | ||
252 | **pos -= delete; | ||
253 | **pos += insert; | ||
254 | } | ||
255 | |||
256 | self.text.replace_range(range, &patch); | ||
257 | } | ||
258 | } | ||
259 | |||
260 | fn format_patch(line_indent: usize, patch: &str) -> String { | ||
261 | let mut max_hashes = 0; | ||
262 | let mut cur_hashes = 0; | ||
263 | for byte in patch.bytes() { | ||
264 | if byte != b'#' { | ||
265 | cur_hashes = 0; | ||
266 | continue; | ||
267 | } | ||
268 | cur_hashes += 1; | ||
269 | max_hashes = max_hashes.max(cur_hashes); | ||
270 | } | ||
271 | let hashes = &"#".repeat(max_hashes + 1); | ||
272 | let indent = &" ".repeat(line_indent); | ||
273 | let is_multiline = patch.contains('\n'); | ||
274 | |||
275 | let mut buf = String::new(); | ||
276 | buf.push('r'); | ||
277 | buf.push_str(hashes); | ||
278 | buf.push('"'); | ||
279 | if is_multiline { | ||
280 | buf.push('\n'); | ||
281 | } | ||
282 | let mut final_newline = false; | ||
283 | for line in lines_with_ends(patch) { | ||
284 | if is_multiline && !line.trim().is_empty() { | ||
285 | buf.push_str(indent); | ||
286 | buf.push_str(" "); | ||
287 | } | ||
288 | buf.push_str(line); | ||
289 | final_newline = line.ends_with('\n'); | ||
290 | } | ||
291 | if final_newline { | ||
292 | buf.push_str(indent); | ||
293 | } | ||
294 | buf.push('"'); | ||
295 | buf.push_str(hashes); | ||
296 | buf | ||
297 | } | ||
298 | |||
299 | static WORKSPACE_ROOT: Lazy<PathBuf> = Lazy::new(|| { | ||
300 | let my_manifest = | ||
301 | env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()); | ||
302 | // Heuristic, see https://github.com/rust-lang/cargo/issues/3946 | ||
303 | Path::new(&my_manifest) | ||
304 | .ancestors() | ||
305 | .filter(|it| it.join("Cargo.toml").exists()) | ||
306 | .last() | ||
307 | .unwrap() | ||
308 | .to_path_buf() | ||
309 | }); | ||
310 | |||
311 | #[cfg(test)] | ||
312 | mod tests { | ||
313 | use super::*; | ||
314 | |||
315 | #[test] | ||
316 | fn test_format_patch() { | ||
317 | let patch = format_patch(0, "hello\nworld\n"); | ||
318 | expect![[r##" | ||
319 | r#" | ||
320 | hello | ||
321 | world | ||
322 | "#"##]] | ||
323 | .assert_eq(&patch); | ||
324 | |||
325 | let patch = format_patch(4, "single line"); | ||
326 | expect![[r##"r#"single line"#"##]].assert_eq(&patch); | ||
327 | } | ||
328 | |||
329 | #[test] | ||
330 | fn test_patchwork() { | ||
331 | let mut patchwork = Patchwork::new("one two three".to_string()); | ||
332 | patchwork.patch(4..7, "zwei"); | ||
333 | patchwork.patch(0..3, "один"); | ||
334 | patchwork.patch(8..13, "3"); | ||
335 | expect![[r#" | ||
336 | Patchwork { | ||
337 | text: "один zwei 3", | ||
338 | indels: [ | ||
339 | ( | ||
340 | 0..3, | ||
341 | 8, | ||
342 | ), | ||
343 | ( | ||
344 | 4..7, | ||
345 | 4, | ||
346 | ), | ||
347 | ( | ||
348 | 8..13, | ||
349 | 1, | ||
350 | ), | ||
351 | ], | ||
352 | } | ||
353 | "#]] | ||
354 | .assert_debug_eq(&patchwork); | ||
355 | } | ||
356 | } | ||
diff --git a/crates/ra_flycheck/Cargo.toml b/crates/flycheck/Cargo.toml index 1aa39bade..bea485694 100644 --- a/crates/ra_flycheck/Cargo.toml +++ b/crates/flycheck/Cargo.toml | |||
@@ -1,8 +1,9 @@ | |||
1 | [package] | 1 | [package] |
2 | edition = "2018" | 2 | edition = "2018" |
3 | name = "ra_flycheck" | 3 | name = "flycheck" |
4 | version = "0.1.0" | 4 | version = "0.1.0" |
5 | authors = ["rust-analyzer developers"] | 5 | authors = ["rust-analyzer developers"] |
6 | license = "MIT OR Apache-2.0" | ||
6 | 7 | ||
7 | [lib] | 8 | [lib] |
8 | doctest = false | 9 | doctest = false |
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs new file mode 100644 index 000000000..6804d9bda --- /dev/null +++ b/crates/flycheck/src/lib.rs | |||
@@ -0,0 +1,317 @@ | |||
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 | |||
5 | use std::{ | ||
6 | fmt, | ||
7 | io::{self, BufReader}, | ||
8 | ops, | ||
9 | path::PathBuf, | ||
10 | process::{self, Command, Stdio}, | ||
11 | time::Duration, | ||
12 | }; | ||
13 | |||
14 | use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; | ||
15 | |||
16 | pub use cargo_metadata::diagnostic::{ | ||
17 | Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan, | ||
18 | DiagnosticSpanMacroExpansion, | ||
19 | }; | ||
20 | |||
21 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
22 | pub enum FlycheckConfig { | ||
23 | CargoCommand { | ||
24 | command: String, | ||
25 | all_targets: bool, | ||
26 | all_features: bool, | ||
27 | features: Vec<String>, | ||
28 | extra_args: Vec<String>, | ||
29 | }, | ||
30 | CustomCommand { | ||
31 | command: String, | ||
32 | args: Vec<String>, | ||
33 | }, | ||
34 | } | ||
35 | |||
36 | impl fmt::Display for FlycheckConfig { | ||
37 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
38 | match self { | ||
39 | FlycheckConfig::CargoCommand { command, .. } => write!(f, "cargo {}", command), | ||
40 | FlycheckConfig::CustomCommand { command, args } => { | ||
41 | write!(f, "{} {}", command, args.join(" ")) | ||
42 | } | ||
43 | } | ||
44 | } | ||
45 | } | ||
46 | |||
47 | /// Flycheck wraps the shared state and communication machinery used for | ||
48 | /// running `cargo check` (or other compatible command) and providing | ||
49 | /// diagnostics based on the output. | ||
50 | /// The spawned thread is shut down when this struct is dropped. | ||
51 | #[derive(Debug)] | ||
52 | pub struct FlycheckHandle { | ||
53 | // XXX: drop order is significant | ||
54 | sender: Sender<Restart>, | ||
55 | thread: jod_thread::JoinHandle, | ||
56 | } | ||
57 | |||
58 | impl FlycheckHandle { | ||
59 | pub fn spawn( | ||
60 | sender: Box<dyn Fn(Message) + Send>, | ||
61 | config: FlycheckConfig, | ||
62 | workspace_root: PathBuf, | ||
63 | ) -> FlycheckHandle { | ||
64 | let actor = FlycheckActor::new(sender, config, workspace_root); | ||
65 | let (sender, receiver) = unbounded::<Restart>(); | ||
66 | let thread = jod_thread::spawn(move || actor.run(receiver)); | ||
67 | FlycheckHandle { sender, thread } | ||
68 | } | ||
69 | |||
70 | /// Schedule a re-start of the cargo check worker. | ||
71 | pub fn update(&self) { | ||
72 | self.sender.send(Restart).unwrap(); | ||
73 | } | ||
74 | } | ||
75 | |||
76 | #[derive(Debug)] | ||
77 | pub enum Message { | ||
78 | /// Request adding a diagnostic with fixes included to a file | ||
79 | AddDiagnostic { workspace_root: PathBuf, diagnostic: Diagnostic }, | ||
80 | |||
81 | /// Request check progress notification to client | ||
82 | Progress(Progress), | ||
83 | } | ||
84 | |||
85 | #[derive(Debug)] | ||
86 | pub enum Progress { | ||
87 | DidStart, | ||
88 | DidCheckCrate(String), | ||
89 | DidFinish(io::Result<()>), | ||
90 | DidCancel, | ||
91 | } | ||
92 | |||
93 | struct Restart; | ||
94 | |||
95 | struct FlycheckActor { | ||
96 | sender: Box<dyn Fn(Message) + Send>, | ||
97 | config: FlycheckConfig, | ||
98 | workspace_root: PathBuf, | ||
99 | /// WatchThread exists to wrap around the communication needed to be able to | ||
100 | /// run `cargo check` without blocking. Currently the Rust standard library | ||
101 | /// doesn't provide a way to read sub-process output without blocking, so we | ||
102 | /// have to wrap sub-processes output handling in a thread and pass messages | ||
103 | /// back over a channel. | ||
104 | cargo_handle: Option<CargoHandle>, | ||
105 | } | ||
106 | |||
107 | enum Event { | ||
108 | Restart(Restart), | ||
109 | CheckEvent(Option<cargo_metadata::Message>), | ||
110 | } | ||
111 | |||
112 | impl FlycheckActor { | ||
113 | fn new( | ||
114 | sender: Box<dyn Fn(Message) + Send>, | ||
115 | config: FlycheckConfig, | ||
116 | workspace_root: PathBuf, | ||
117 | ) -> FlycheckActor { | ||
118 | FlycheckActor { sender, config, workspace_root, cargo_handle: None } | ||
119 | } | ||
120 | fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> { | ||
121 | let check_chan = self.cargo_handle.as_ref().map(|cargo| &cargo.receiver); | ||
122 | select! { | ||
123 | recv(inbox) -> msg => msg.ok().map(Event::Restart), | ||
124 | recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())), | ||
125 | } | ||
126 | } | ||
127 | fn run(mut self, inbox: Receiver<Restart>) { | ||
128 | while let Some(event) = self.next_event(&inbox) { | ||
129 | match event { | ||
130 | Event::Restart(Restart) => { | ||
131 | while let Ok(Restart) = inbox.recv_timeout(Duration::from_millis(50)) {} | ||
132 | |||
133 | self.cancel_check_process(); | ||
134 | |||
135 | let mut command = self.check_command(); | ||
136 | log::info!("restart flycheck {:?}", command); | ||
137 | command.stdout(Stdio::piped()).stderr(Stdio::null()).stdin(Stdio::null()); | ||
138 | if let Ok(child) = command.spawn().map(JodChild) { | ||
139 | self.cargo_handle = Some(CargoHandle::spawn(child)); | ||
140 | self.send(Message::Progress(Progress::DidStart)); | ||
141 | } | ||
142 | } | ||
143 | Event::CheckEvent(None) => { | ||
144 | // Watcher finished, replace it with a never channel to | ||
145 | // avoid busy-waiting. | ||
146 | let cargo_handle = self.cargo_handle.take().unwrap(); | ||
147 | let res = cargo_handle.join(); | ||
148 | self.send(Message::Progress(Progress::DidFinish(res))); | ||
149 | } | ||
150 | Event::CheckEvent(Some(message)) => match message { | ||
151 | cargo_metadata::Message::CompilerArtifact(msg) => { | ||
152 | self.send(Message::Progress(Progress::DidCheckCrate(msg.target.name))); | ||
153 | } | ||
154 | |||
155 | cargo_metadata::Message::CompilerMessage(msg) => { | ||
156 | self.send(Message::AddDiagnostic { | ||
157 | workspace_root: self.workspace_root.clone(), | ||
158 | diagnostic: msg.message, | ||
159 | }); | ||
160 | } | ||
161 | |||
162 | cargo_metadata::Message::BuildScriptExecuted(_) | ||
163 | | cargo_metadata::Message::BuildFinished(_) | ||
164 | | cargo_metadata::Message::TextLine(_) | ||
165 | | cargo_metadata::Message::Unknown => {} | ||
166 | }, | ||
167 | } | ||
168 | } | ||
169 | // If we rerun the thread, we need to discard the previous check results first | ||
170 | self.cancel_check_process(); | ||
171 | } | ||
172 | fn cancel_check_process(&mut self) { | ||
173 | if self.cargo_handle.take().is_some() { | ||
174 | self.send(Message::Progress(Progress::DidCancel)); | ||
175 | } | ||
176 | } | ||
177 | fn check_command(&self) -> Command { | ||
178 | let mut cmd = match &self.config { | ||
179 | FlycheckConfig::CargoCommand { | ||
180 | command, | ||
181 | all_targets, | ||
182 | all_features, | ||
183 | extra_args, | ||
184 | features, | ||
185 | } => { | ||
186 | let mut cmd = Command::new(ra_toolchain::cargo()); | ||
187 | cmd.arg(command); | ||
188 | cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]) | ||
189 | .arg(self.workspace_root.join("Cargo.toml")); | ||
190 | if *all_targets { | ||
191 | cmd.arg("--all-targets"); | ||
192 | } | ||
193 | if *all_features { | ||
194 | cmd.arg("--all-features"); | ||
195 | } else if !features.is_empty() { | ||
196 | cmd.arg("--features"); | ||
197 | cmd.arg(features.join(" ")); | ||
198 | } | ||
199 | cmd.args(extra_args); | ||
200 | cmd | ||
201 | } | ||
202 | FlycheckConfig::CustomCommand { command, args } => { | ||
203 | let mut cmd = Command::new(command); | ||
204 | cmd.args(args); | ||
205 | cmd | ||
206 | } | ||
207 | }; | ||
208 | cmd.current_dir(&self.workspace_root); | ||
209 | cmd | ||
210 | } | ||
211 | |||
212 | fn send(&self, check_task: Message) { | ||
213 | (self.sender)(check_task) | ||
214 | } | ||
215 | } | ||
216 | |||
217 | struct CargoHandle { | ||
218 | child: JodChild, | ||
219 | #[allow(unused)] | ||
220 | thread: jod_thread::JoinHandle<io::Result<bool>>, | ||
221 | receiver: Receiver<cargo_metadata::Message>, | ||
222 | } | ||
223 | |||
224 | impl CargoHandle { | ||
225 | fn spawn(mut child: JodChild) -> CargoHandle { | ||
226 | let child_stdout = child.stdout.take().unwrap(); | ||
227 | let (sender, receiver) = unbounded(); | ||
228 | let actor = CargoActor::new(child_stdout, sender); | ||
229 | let thread = jod_thread::spawn(move || actor.run()); | ||
230 | CargoHandle { child, thread, receiver } | ||
231 | } | ||
232 | fn join(mut self) -> io::Result<()> { | ||
233 | // It is okay to ignore the result, as it only errors if the process is already dead | ||
234 | let _ = self.child.kill(); | ||
235 | let exit_status = self.child.wait()?; | ||
236 | let read_at_least_one_message = self.thread.join()?; | ||
237 | if !exit_status.success() && !read_at_least_one_message { | ||
238 | // FIXME: Read the stderr to display the reason, see `read2()` reference in PR comment: | ||
239 | // https://github.com/rust-analyzer/rust-analyzer/pull/3632#discussion_r395605298 | ||
240 | return Err(io::Error::new( | ||
241 | io::ErrorKind::Other, | ||
242 | format!( | ||
243 | "Cargo watcher failed,the command produced no valid metadata (exit code: {:?})", | ||
244 | exit_status | ||
245 | ), | ||
246 | )); | ||
247 | } | ||
248 | Ok(()) | ||
249 | } | ||
250 | } | ||
251 | |||
252 | struct CargoActor { | ||
253 | child_stdout: process::ChildStdout, | ||
254 | sender: Sender<cargo_metadata::Message>, | ||
255 | } | ||
256 | |||
257 | impl CargoActor { | ||
258 | fn new( | ||
259 | child_stdout: process::ChildStdout, | ||
260 | sender: Sender<cargo_metadata::Message>, | ||
261 | ) -> CargoActor { | ||
262 | CargoActor { child_stdout, sender } | ||
263 | } | ||
264 | fn run(self) -> io::Result<bool> { | ||
265 | // We manually read a line at a time, instead of using serde's | ||
266 | // stream deserializers, because the deserializer cannot recover | ||
267 | // from an error, resulting in it getting stuck, because we try to | ||
268 | // be resilient against failures. | ||
269 | // | ||
270 | // Because cargo only outputs one JSON object per line, we can | ||
271 | // simply skip a line if it doesn't parse, which just ignores any | ||
272 | // erroneus output. | ||
273 | let stdout = BufReader::new(self.child_stdout); | ||
274 | let mut read_at_least_one_message = false; | ||
275 | for message in cargo_metadata::Message::parse_stream(stdout) { | ||
276 | let message = match message { | ||
277 | Ok(message) => message, | ||
278 | Err(err) => { | ||
279 | log::error!("Invalid json from cargo check, ignoring ({})", err); | ||
280 | continue; | ||
281 | } | ||
282 | }; | ||
283 | |||
284 | read_at_least_one_message = true; | ||
285 | |||
286 | // Skip certain kinds of messages to only spend time on what's useful | ||
287 | match &message { | ||
288 | cargo_metadata::Message::CompilerArtifact(artifact) if artifact.fresh => (), | ||
289 | cargo_metadata::Message::BuildScriptExecuted(_) | ||
290 | | cargo_metadata::Message::Unknown => (), | ||
291 | _ => self.sender.send(message).unwrap(), | ||
292 | } | ||
293 | } | ||
294 | Ok(read_at_least_one_message) | ||
295 | } | ||
296 | } | ||
297 | |||
298 | struct JodChild(process::Child); | ||
299 | |||
300 | impl ops::Deref for JodChild { | ||
301 | type Target = process::Child; | ||
302 | fn deref(&self) -> &process::Child { | ||
303 | &self.0 | ||
304 | } | ||
305 | } | ||
306 | |||
307 | impl ops::DerefMut for JodChild { | ||
308 | fn deref_mut(&mut self) -> &mut process::Child { | ||
309 | &mut self.0 | ||
310 | } | ||
311 | } | ||
312 | |||
313 | impl Drop for JodChild { | ||
314 | fn drop(&mut self) { | ||
315 | let _ = self.0.kill(); | ||
316 | } | ||
317 | } | ||
diff --git a/crates/paths/Cargo.toml b/crates/paths/Cargo.toml new file mode 100644 index 000000000..cbe2c26e2 --- /dev/null +++ b/crates/paths/Cargo.toml | |||
@@ -0,0 +1,9 @@ | |||
1 | [package] | ||
2 | name = "paths" | ||
3 | version = "0.1.0" | ||
4 | authors = ["rust-analyzer developers"] | ||
5 | edition = "2018" | ||
6 | license = "MIT OR Apache-2.0" | ||
7 | |||
8 | [lib] | ||
9 | doctest = false | ||
diff --git a/crates/paths/src/lib.rs b/crates/paths/src/lib.rs new file mode 100644 index 000000000..1b259682d --- /dev/null +++ b/crates/paths/src/lib.rs | |||
@@ -0,0 +1,217 @@ | |||
1 | //! Thin wrappers around `std::path`, distinguishing between absolute and | ||
2 | //! relative paths. | ||
3 | use std::{ | ||
4 | convert::{TryFrom, TryInto}, | ||
5 | ops, | ||
6 | path::{Component, Path, PathBuf}, | ||
7 | }; | ||
8 | |||
9 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] | ||
10 | pub struct AbsPathBuf(PathBuf); | ||
11 | |||
12 | impl From<AbsPathBuf> for PathBuf { | ||
13 | fn from(AbsPathBuf(path_buf): AbsPathBuf) -> PathBuf { | ||
14 | path_buf | ||
15 | } | ||
16 | } | ||
17 | |||
18 | impl ops::Deref for AbsPathBuf { | ||
19 | type Target = AbsPath; | ||
20 | fn deref(&self) -> &AbsPath { | ||
21 | self.as_path() | ||
22 | } | ||
23 | } | ||
24 | |||
25 | impl AsRef<Path> for AbsPathBuf { | ||
26 | fn as_ref(&self) -> &Path { | ||
27 | self.0.as_path() | ||
28 | } | ||
29 | } | ||
30 | |||
31 | impl AsRef<AbsPath> for AbsPathBuf { | ||
32 | fn as_ref(&self) -> &AbsPath { | ||
33 | self.as_path() | ||
34 | } | ||
35 | } | ||
36 | |||
37 | impl TryFrom<PathBuf> for AbsPathBuf { | ||
38 | type Error = PathBuf; | ||
39 | fn try_from(path_buf: PathBuf) -> Result<AbsPathBuf, PathBuf> { | ||
40 | if !path_buf.is_absolute() { | ||
41 | return Err(path_buf); | ||
42 | } | ||
43 | Ok(AbsPathBuf(path_buf)) | ||
44 | } | ||
45 | } | ||
46 | |||
47 | impl TryFrom<&str> for AbsPathBuf { | ||
48 | type Error = PathBuf; | ||
49 | fn try_from(path: &str) -> Result<AbsPathBuf, PathBuf> { | ||
50 | AbsPathBuf::try_from(PathBuf::from(path)) | ||
51 | } | ||
52 | } | ||
53 | |||
54 | impl PartialEq<AbsPath> for AbsPathBuf { | ||
55 | fn eq(&self, other: &AbsPath) -> bool { | ||
56 | self.as_path() == other | ||
57 | } | ||
58 | } | ||
59 | |||
60 | impl AbsPathBuf { | ||
61 | pub fn assert(path: PathBuf) -> AbsPathBuf { | ||
62 | AbsPathBuf::try_from(path) | ||
63 | .unwrap_or_else(|path| panic!("expected absolute path, got {}", path.display())) | ||
64 | } | ||
65 | pub fn as_path(&self) -> &AbsPath { | ||
66 | AbsPath::assert(self.0.as_path()) | ||
67 | } | ||
68 | pub fn pop(&mut self) -> bool { | ||
69 | self.0.pop() | ||
70 | } | ||
71 | } | ||
72 | |||
73 | #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] | ||
74 | #[repr(transparent)] | ||
75 | pub struct AbsPath(Path); | ||
76 | |||
77 | impl ops::Deref for AbsPath { | ||
78 | type Target = Path; | ||
79 | fn deref(&self) -> &Path { | ||
80 | &self.0 | ||
81 | } | ||
82 | } | ||
83 | |||
84 | impl AsRef<Path> for AbsPath { | ||
85 | fn as_ref(&self) -> &Path { | ||
86 | &self.0 | ||
87 | } | ||
88 | } | ||
89 | |||
90 | impl<'a> TryFrom<&'a Path> for &'a AbsPath { | ||
91 | type Error = &'a Path; | ||
92 | fn try_from(path: &'a Path) -> Result<&'a AbsPath, &'a Path> { | ||
93 | if !path.is_absolute() { | ||
94 | return Err(path); | ||
95 | } | ||
96 | Ok(AbsPath::assert(path)) | ||
97 | } | ||
98 | } | ||
99 | |||
100 | impl AbsPath { | ||
101 | pub fn assert(path: &Path) -> &AbsPath { | ||
102 | assert!(path.is_absolute()); | ||
103 | unsafe { &*(path as *const Path as *const AbsPath) } | ||
104 | } | ||
105 | |||
106 | pub fn parent(&self) -> Option<&AbsPath> { | ||
107 | self.0.parent().map(AbsPath::assert) | ||
108 | } | ||
109 | pub fn join(&self, path: impl AsRef<Path>) -> AbsPathBuf { | ||
110 | self.as_ref().join(path).try_into().unwrap() | ||
111 | } | ||
112 | pub fn normalize(&self) -> AbsPathBuf { | ||
113 | AbsPathBuf(normalize_path(&self.0)) | ||
114 | } | ||
115 | pub fn to_path_buf(&self) -> AbsPathBuf { | ||
116 | AbsPathBuf::try_from(self.0.to_path_buf()).unwrap() | ||
117 | } | ||
118 | pub fn strip_prefix(&self, base: &AbsPath) -> Option<&RelPath> { | ||
119 | self.0.strip_prefix(base).ok().map(RelPath::new_unchecked) | ||
120 | } | ||
121 | } | ||
122 | |||
123 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] | ||
124 | pub struct RelPathBuf(PathBuf); | ||
125 | |||
126 | impl From<RelPathBuf> for PathBuf { | ||
127 | fn from(RelPathBuf(path_buf): RelPathBuf) -> PathBuf { | ||
128 | path_buf | ||
129 | } | ||
130 | } | ||
131 | |||
132 | impl ops::Deref for RelPathBuf { | ||
133 | type Target = RelPath; | ||
134 | fn deref(&self) -> &RelPath { | ||
135 | self.as_path() | ||
136 | } | ||
137 | } | ||
138 | |||
139 | impl AsRef<Path> for RelPathBuf { | ||
140 | fn as_ref(&self) -> &Path { | ||
141 | self.0.as_path() | ||
142 | } | ||
143 | } | ||
144 | |||
145 | impl TryFrom<PathBuf> for RelPathBuf { | ||
146 | type Error = PathBuf; | ||
147 | fn try_from(path_buf: PathBuf) -> Result<RelPathBuf, PathBuf> { | ||
148 | if !path_buf.is_relative() { | ||
149 | return Err(path_buf); | ||
150 | } | ||
151 | Ok(RelPathBuf(path_buf)) | ||
152 | } | ||
153 | } | ||
154 | |||
155 | impl TryFrom<&str> for RelPathBuf { | ||
156 | type Error = PathBuf; | ||
157 | fn try_from(path: &str) -> Result<RelPathBuf, PathBuf> { | ||
158 | RelPathBuf::try_from(PathBuf::from(path)) | ||
159 | } | ||
160 | } | ||
161 | |||
162 | impl RelPathBuf { | ||
163 | pub fn as_path(&self) -> &RelPath { | ||
164 | RelPath::new_unchecked(self.0.as_path()) | ||
165 | } | ||
166 | } | ||
167 | |||
168 | #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] | ||
169 | #[repr(transparent)] | ||
170 | pub struct RelPath(Path); | ||
171 | |||
172 | impl ops::Deref for RelPath { | ||
173 | type Target = Path; | ||
174 | fn deref(&self) -> &Path { | ||
175 | &self.0 | ||
176 | } | ||
177 | } | ||
178 | |||
179 | impl AsRef<Path> for RelPath { | ||
180 | fn as_ref(&self) -> &Path { | ||
181 | &self.0 | ||
182 | } | ||
183 | } | ||
184 | |||
185 | impl RelPath { | ||
186 | pub fn new_unchecked(path: &Path) -> &RelPath { | ||
187 | unsafe { &*(path as *const Path as *const RelPath) } | ||
188 | } | ||
189 | } | ||
190 | |||
191 | // https://github.com/rust-lang/cargo/blob/79c769c3d7b4c2cf6a93781575b7f592ef974255/src/cargo/util/paths.rs#L60-L85 | ||
192 | fn normalize_path(path: &Path) -> PathBuf { | ||
193 | let mut components = path.components().peekable(); | ||
194 | let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { | ||
195 | components.next(); | ||
196 | PathBuf::from(c.as_os_str()) | ||
197 | } else { | ||
198 | PathBuf::new() | ||
199 | }; | ||
200 | |||
201 | for component in components { | ||
202 | match component { | ||
203 | Component::Prefix(..) => unreachable!(), | ||
204 | Component::RootDir => { | ||
205 | ret.push(component.as_os_str()); | ||
206 | } | ||
207 | Component::CurDir => {} | ||
208 | Component::ParentDir => { | ||
209 | ret.pop(); | ||
210 | } | ||
211 | Component::Normal(c) => { | ||
212 | ret.push(c); | ||
213 | } | ||
214 | } | ||
215 | } | ||
216 | ret | ||
217 | } | ||
diff --git a/crates/ra_arena/Cargo.toml b/crates/ra_arena/Cargo.toml index d287dbb73..66c3738f4 100644 --- a/crates/ra_arena/Cargo.toml +++ b/crates/ra_arena/Cargo.toml | |||
@@ -3,6 +3,7 @@ edition = "2018" | |||
3 | name = "ra_arena" | 3 | name = "ra_arena" |
4 | version = "0.1.0" | 4 | version = "0.1.0" |
5 | authors = ["rust-analyzer developers"] | 5 | authors = ["rust-analyzer developers"] |
6 | license = "MIT OR Apache-2.0" | ||
6 | 7 | ||
7 | [lib] | 8 | [lib] |
8 | doctest = false | 9 | doctest = false |
diff --git a/crates/ra_arena/src/lib.rs b/crates/ra_arena/src/lib.rs index 441fbb3cb..3169aa5b8 100644 --- a/crates/ra_arena/src/lib.rs +++ b/crates/ra_arena/src/lib.rs | |||
@@ -116,6 +116,9 @@ impl<T> Arena<T> { | |||
116 | ) -> impl Iterator<Item = (Idx<T>, &T)> + ExactSizeIterator + DoubleEndedIterator { | 116 | ) -> impl Iterator<Item = (Idx<T>, &T)> + ExactSizeIterator + DoubleEndedIterator { |
117 | self.data.iter().enumerate().map(|(idx, value)| (Idx::from_raw(RawId(idx as u32)), value)) | 117 | self.data.iter().enumerate().map(|(idx, value)| (Idx::from_raw(RawId(idx as u32)), value)) |
118 | } | 118 | } |
119 | pub fn shrink_to_fit(&mut self) { | ||
120 | self.data.shrink_to_fit(); | ||
121 | } | ||
119 | } | 122 | } |
120 | 123 | ||
121 | impl<T> Default for Arena<T> { | 124 | impl<T> Default for Arena<T> { |
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml index 3bcf58ba4..bd2905f08 100644 --- a/crates/ra_assists/Cargo.toml +++ b/crates/ra_assists/Cargo.toml | |||
@@ -3,6 +3,7 @@ edition = "2018" | |||
3 | name = "ra_assists" | 3 | name = "ra_assists" |
4 | version = "0.1.0" | 4 | version = "0.1.0" |
5 | authors = ["rust-analyzer developers"] | 5 | authors = ["rust-analyzer developers"] |
6 | license = "MIT OR Apache-2.0" | ||
6 | 7 | ||
7 | [lib] | 8 | [lib] |
8 | doctest = false | 9 | doctest = false |
diff --git a/crates/ra_assists/src/assist_config.rs b/crates/ra_assists/src/assist_config.rs index c0a0226fb..cda2abfb9 100644 --- a/crates/ra_assists/src/assist_config.rs +++ b/crates/ra_assists/src/assist_config.rs | |||
@@ -4,9 +4,12 @@ | |||
4 | //! module, and we use to statically check that we only produce snippet | 4 | //! module, and we use to statically check that we only produce snippet |
5 | //! assists if we are allowed to. | 5 | //! assists if we are allowed to. |
6 | 6 | ||
7 | use crate::AssistKind; | ||
8 | |||
7 | #[derive(Clone, Debug, PartialEq, Eq)] | 9 | #[derive(Clone, Debug, PartialEq, Eq)] |
8 | pub struct AssistConfig { | 10 | pub struct AssistConfig { |
9 | pub snippet_cap: Option<SnippetCap>, | 11 | pub snippet_cap: Option<SnippetCap>, |
12 | pub allowed: Option<Vec<AssistKind>>, | ||
10 | } | 13 | } |
11 | 14 | ||
12 | impl AssistConfig { | 15 | impl AssistConfig { |
@@ -22,6 +25,6 @@ pub struct SnippetCap { | |||
22 | 25 | ||
23 | impl Default for AssistConfig { | 26 | impl Default for AssistConfig { |
24 | fn default() -> Self { | 27 | fn default() -> Self { |
25 | AssistConfig { snippet_cap: Some(SnippetCap { _private: () }) } | 28 | AssistConfig { snippet_cap: Some(SnippetCap { _private: () }), allowed: None } |
26 | } | 29 | } |
27 | } | 30 | } |
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs index 5b1a4680b..3407df856 100644 --- a/crates/ra_assists/src/assist_context.rs +++ b/crates/ra_assists/src/assist_context.rs | |||
@@ -1,5 +1,7 @@ | |||
1 | //! See `AssistContext` | 1 | //! See `AssistContext` |
2 | 2 | ||
3 | use std::mem; | ||
4 | |||
3 | use algo::find_covering_element; | 5 | use algo::find_covering_element; |
4 | use hir::Semantics; | 6 | use hir::Semantics; |
5 | use ra_db::{FileId, FileRange}; | 7 | use ra_db::{FileId, FileRange}; |
@@ -17,7 +19,7 @@ use ra_text_edit::TextEditBuilder; | |||
17 | 19 | ||
18 | use crate::{ | 20 | use crate::{ |
19 | assist_config::{AssistConfig, SnippetCap}, | 21 | assist_config::{AssistConfig, SnippetCap}, |
20 | Assist, AssistId, GroupLabel, ResolvedAssist, | 22 | Assist, AssistId, AssistKind, GroupLabel, ResolvedAssist, |
21 | }; | 23 | }; |
22 | 24 | ||
23 | /// `AssistContext` allows to apply an assist or check if it could be applied. | 25 | /// `AssistContext` allows to apply an assist or check if it could be applied. |
@@ -53,7 +55,6 @@ use crate::{ | |||
53 | pub(crate) struct AssistContext<'a> { | 55 | pub(crate) struct AssistContext<'a> { |
54 | pub(crate) config: &'a AssistConfig, | 56 | pub(crate) config: &'a AssistConfig, |
55 | pub(crate) sema: Semantics<'a, RootDatabase>, | 57 | pub(crate) sema: Semantics<'a, RootDatabase>, |
56 | pub(crate) db: &'a RootDatabase, | ||
57 | pub(crate) frange: FileRange, | 58 | pub(crate) frange: FileRange, |
58 | source_file: SourceFile, | 59 | source_file: SourceFile, |
59 | } | 60 | } |
@@ -65,8 +66,11 @@ impl<'a> AssistContext<'a> { | |||
65 | frange: FileRange, | 66 | frange: FileRange, |
66 | ) -> AssistContext<'a> { | 67 | ) -> AssistContext<'a> { |
67 | let source_file = sema.parse(frange.file_id); | 68 | let source_file = sema.parse(frange.file_id); |
68 | let db = sema.db; | 69 | AssistContext { config, sema, frange, source_file } |
69 | AssistContext { config, sema, db, frange, source_file } | 70 | } |
71 | |||
72 | pub(crate) fn db(&self) -> &RootDatabase { | ||
73 | self.sema.db | ||
70 | } | 74 | } |
71 | 75 | ||
72 | // NB, this ignores active selection. | 76 | // NB, this ignores active selection. |
@@ -99,14 +103,26 @@ pub(crate) struct Assists { | |||
99 | resolve: bool, | 103 | resolve: bool, |
100 | file: FileId, | 104 | file: FileId, |
101 | buf: Vec<(Assist, Option<SourceChange>)>, | 105 | buf: Vec<(Assist, Option<SourceChange>)>, |
106 | allowed: Option<Vec<AssistKind>>, | ||
102 | } | 107 | } |
103 | 108 | ||
104 | impl Assists { | 109 | impl Assists { |
105 | pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { | 110 | pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { |
106 | Assists { resolve: true, file: ctx.frange.file_id, buf: Vec::new() } | 111 | Assists { |
112 | resolve: true, | ||
113 | file: ctx.frange.file_id, | ||
114 | buf: Vec::new(), | ||
115 | allowed: ctx.config.allowed.clone(), | ||
116 | } | ||
107 | } | 117 | } |
118 | |||
108 | pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { | 119 | pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { |
109 | Assists { resolve: false, file: ctx.frange.file_id, buf: Vec::new() } | 120 | Assists { |
121 | resolve: false, | ||
122 | file: ctx.frange.file_id, | ||
123 | buf: Vec::new(), | ||
124 | allowed: ctx.config.allowed.clone(), | ||
125 | } | ||
110 | } | 126 | } |
111 | 127 | ||
112 | pub(crate) fn finish_unresolved(self) -> Vec<Assist> { | 128 | pub(crate) fn finish_unresolved(self) -> Vec<Assist> { |
@@ -135,9 +151,13 @@ impl Assists { | |||
135 | target: TextRange, | 151 | target: TextRange, |
136 | f: impl FnOnce(&mut AssistBuilder), | 152 | f: impl FnOnce(&mut AssistBuilder), |
137 | ) -> Option<()> { | 153 | ) -> Option<()> { |
154 | if !self.is_allowed(&id) { | ||
155 | return None; | ||
156 | } | ||
138 | let label = Assist::new(id, label.into(), None, target); | 157 | let label = Assist::new(id, label.into(), None, target); |
139 | self.add_impl(label, f) | 158 | self.add_impl(label, f) |
140 | } | 159 | } |
160 | |||
141 | pub(crate) fn add_group( | 161 | pub(crate) fn add_group( |
142 | &mut self, | 162 | &mut self, |
143 | group: &GroupLabel, | 163 | group: &GroupLabel, |
@@ -146,9 +166,14 @@ impl Assists { | |||
146 | target: TextRange, | 166 | target: TextRange, |
147 | f: impl FnOnce(&mut AssistBuilder), | 167 | f: impl FnOnce(&mut AssistBuilder), |
148 | ) -> Option<()> { | 168 | ) -> Option<()> { |
169 | if !self.is_allowed(&id) { | ||
170 | return None; | ||
171 | } | ||
172 | |||
149 | let label = Assist::new(id, label.into(), Some(group.clone()), target); | 173 | let label = Assist::new(id, label.into(), Some(group.clone()), target); |
150 | self.add_impl(label, f) | 174 | self.add_impl(label, f) |
151 | } | 175 | } |
176 | |||
152 | fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { | 177 | fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { |
153 | let source_change = if self.resolve { | 178 | let source_change = if self.resolve { |
154 | let mut builder = AssistBuilder::new(self.file); | 179 | let mut builder = AssistBuilder::new(self.file); |
@@ -166,17 +191,43 @@ impl Assists { | |||
166 | self.buf.sort_by_key(|(label, _edit)| label.target.len()); | 191 | self.buf.sort_by_key(|(label, _edit)| label.target.len()); |
167 | self.buf | 192 | self.buf |
168 | } | 193 | } |
194 | |||
195 | fn is_allowed(&self, id: &AssistId) -> bool { | ||
196 | match &self.allowed { | ||
197 | Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)), | ||
198 | None => true, | ||
199 | } | ||
200 | } | ||
169 | } | 201 | } |
170 | 202 | ||
171 | pub(crate) struct AssistBuilder { | 203 | pub(crate) struct AssistBuilder { |
172 | edit: TextEditBuilder, | 204 | edit: TextEditBuilder, |
173 | file: FileId, | 205 | file_id: FileId, |
174 | is_snippet: bool, | 206 | is_snippet: bool, |
207 | change: SourceChange, | ||
175 | } | 208 | } |
176 | 209 | ||
177 | impl AssistBuilder { | 210 | impl AssistBuilder { |
178 | pub(crate) fn new(file: FileId) -> AssistBuilder { | 211 | pub(crate) fn new(file_id: FileId) -> AssistBuilder { |
179 | AssistBuilder { edit: TextEditBuilder::default(), file, is_snippet: false } | 212 | AssistBuilder { |
213 | edit: TextEditBuilder::default(), | ||
214 | file_id, | ||
215 | is_snippet: false, | ||
216 | change: SourceChange::default(), | ||
217 | } | ||
218 | } | ||
219 | |||
220 | pub(crate) fn edit_file(&mut self, file_id: FileId) { | ||
221 | self.file_id = file_id; | ||
222 | } | ||
223 | |||
224 | fn commit(&mut self) { | ||
225 | let edit = mem::take(&mut self.edit).finish(); | ||
226 | if !edit.is_empty() { | ||
227 | let new_edit = SourceFileEdit { file_id: self.file_id, edit }; | ||
228 | assert!(!self.change.source_file_edits.iter().any(|it| it.file_id == new_edit.file_id)); | ||
229 | self.change.source_file_edits.push(new_edit); | ||
230 | } | ||
180 | } | 231 | } |
181 | 232 | ||
182 | /// Remove specified `range` of text. | 233 | /// Remove specified `range` of text. |
@@ -231,12 +282,7 @@ impl AssistBuilder { | |||
231 | pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { | 282 | pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { |
232 | let node = rewriter.rewrite_root().unwrap(); | 283 | let node = rewriter.rewrite_root().unwrap(); |
233 | let new = rewriter.rewrite(&node); | 284 | let new = rewriter.rewrite(&node); |
234 | algo::diff(&node, &new).into_text_edit(&mut self.edit) | 285 | algo::diff(&node, &new).into_text_edit(&mut self.edit); |
235 | } | ||
236 | |||
237 | // FIXME: better API | ||
238 | pub(crate) fn set_file(&mut self, assist_file: FileId) { | ||
239 | self.file = assist_file; | ||
240 | } | 286 | } |
241 | 287 | ||
242 | // FIXME: kill this API | 288 | // FIXME: kill this API |
@@ -245,13 +291,12 @@ impl AssistBuilder { | |||
245 | &mut self.edit | 291 | &mut self.edit |
246 | } | 292 | } |
247 | 293 | ||
248 | fn finish(self) -> SourceChange { | 294 | fn finish(mut self) -> SourceChange { |
249 | let edit = self.edit.finish(); | 295 | self.commit(); |
250 | let source_file_edit = SourceFileEdit { file_id: self.file, edit }; | 296 | let mut change = mem::take(&mut self.change); |
251 | let mut res: SourceChange = source_file_edit.into(); | ||
252 | if self.is_snippet { | 297 | if self.is_snippet { |
253 | res.is_snippet = true; | 298 | change.is_snippet = true; |
254 | } | 299 | } |
255 | res | 300 | change |
256 | } | 301 | } |
257 | } | 302 | } |
diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs index 3079a02a2..01adb834c 100644 --- a/crates/ra_assists/src/ast_transform.rs +++ b/crates/ra_assists/src/ast_transform.rs | |||
@@ -2,7 +2,6 @@ | |||
2 | use rustc_hash::FxHashMap; | 2 | use rustc_hash::FxHashMap; |
3 | 3 | ||
4 | use hir::{HirDisplay, PathResolution, SemanticsScope}; | 4 | use hir::{HirDisplay, PathResolution, SemanticsScope}; |
5 | use ra_ide_db::RootDatabase; | ||
6 | use ra_syntax::{ | 5 | use ra_syntax::{ |
7 | algo::SyntaxRewriter, | 6 | algo::SyntaxRewriter, |
8 | ast::{self, AstNode}, | 7 | ast::{self, AstNode}, |
@@ -32,14 +31,14 @@ impl<'a> AstTransform<'a> for NullTransformer { | |||
32 | } | 31 | } |
33 | 32 | ||
34 | pub struct SubstituteTypeParams<'a> { | 33 | pub struct SubstituteTypeParams<'a> { |
35 | source_scope: &'a SemanticsScope<'a, RootDatabase>, | 34 | source_scope: &'a SemanticsScope<'a>, |
36 | substs: FxHashMap<hir::TypeParam, ast::TypeRef>, | 35 | substs: FxHashMap<hir::TypeParam, ast::TypeRef>, |
37 | previous: Box<dyn AstTransform<'a> + 'a>, | 36 | previous: Box<dyn AstTransform<'a> + 'a>, |
38 | } | 37 | } |
39 | 38 | ||
40 | impl<'a> SubstituteTypeParams<'a> { | 39 | impl<'a> SubstituteTypeParams<'a> { |
41 | pub fn for_trait_impl( | 40 | pub fn for_trait_impl( |
42 | source_scope: &'a SemanticsScope<'a, RootDatabase>, | 41 | source_scope: &'a SemanticsScope<'a>, |
43 | // FIXME: there's implicit invariant that `trait_` and `source_scope` match... | 42 | // FIXME: there's implicit invariant that `trait_` and `source_scope` match... |
44 | trait_: hir::Trait, | 43 | trait_: hir::Trait, |
45 | impl_def: ast::ImplDef, | 44 | impl_def: ast::ImplDef, |
@@ -106,6 +105,7 @@ impl<'a> SubstituteTypeParams<'a> { | |||
106 | _ => return None, | 105 | _ => return None, |
107 | }; | 106 | }; |
108 | // FIXME: use `hir::Path::from_src` instead. | 107 | // FIXME: use `hir::Path::from_src` instead. |
108 | #[allow(deprecated)] | ||
109 | let path = hir::Path::from_ast(path)?; | 109 | let path = hir::Path::from_ast(path)?; |
110 | let resolution = self.source_scope.resolve_hir_path(&path)?; | 110 | let resolution = self.source_scope.resolve_hir_path(&path)?; |
111 | match resolution { | 111 | match resolution { |
@@ -125,16 +125,13 @@ impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> { | |||
125 | } | 125 | } |
126 | 126 | ||
127 | pub struct QualifyPaths<'a> { | 127 | pub struct QualifyPaths<'a> { |
128 | target_scope: &'a SemanticsScope<'a, RootDatabase>, | 128 | target_scope: &'a SemanticsScope<'a>, |
129 | source_scope: &'a SemanticsScope<'a, RootDatabase>, | 129 | source_scope: &'a SemanticsScope<'a>, |
130 | previous: Box<dyn AstTransform<'a> + 'a>, | 130 | previous: Box<dyn AstTransform<'a> + 'a>, |
131 | } | 131 | } |
132 | 132 | ||
133 | impl<'a> QualifyPaths<'a> { | 133 | impl<'a> QualifyPaths<'a> { |
134 | pub fn new( | 134 | pub fn new(target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>) -> Self { |
135 | target_scope: &'a SemanticsScope<'a, RootDatabase>, | ||
136 | source_scope: &'a SemanticsScope<'a, RootDatabase>, | ||
137 | ) -> Self { | ||
138 | Self { target_scope, source_scope, previous: Box::new(NullTransformer) } | 135 | Self { target_scope, source_scope, previous: Box::new(NullTransformer) } |
139 | } | 136 | } |
140 | 137 | ||
@@ -150,11 +147,12 @@ impl<'a> QualifyPaths<'a> { | |||
150 | return None; | 147 | return None; |
151 | } | 148 | } |
152 | // FIXME: use `hir::Path::from_src` instead. | 149 | // FIXME: use `hir::Path::from_src` instead. |
150 | #[allow(deprecated)] | ||
153 | let hir_path = hir::Path::from_ast(p.clone()); | 151 | let hir_path = hir::Path::from_ast(p.clone()); |
154 | let resolution = self.source_scope.resolve_hir_path(&hir_path?)?; | 152 | let resolution = self.source_scope.resolve_hir_path(&hir_path?)?; |
155 | match resolution { | 153 | match resolution { |
156 | PathResolution::Def(def) => { | 154 | PathResolution::Def(def) => { |
157 | let found_path = from.find_use_path(self.source_scope.db, def)?; | 155 | let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?; |
158 | let mut path = path_to_ast(found_path); | 156 | let mut path = path_to_ast(found_path); |
159 | 157 | ||
160 | let type_args = p | 158 | let type_args = p |
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs index fa70c8496..acb07e36a 100644 --- a/crates/ra_assists/src/handlers/add_custom_impl.rs +++ b/crates/ra_assists/src/handlers/add_custom_impl.rs | |||
@@ -8,7 +8,7 @@ use stdx::SepBy; | |||
8 | 8 | ||
9 | use crate::{ | 9 | use crate::{ |
10 | assist_context::{AssistContext, Assists}, | 10 | assist_context::{AssistContext, Assists}, |
11 | AssistId, | 11 | AssistId, AssistKind, |
12 | }; | 12 | }; |
13 | 13 | ||
14 | // Assist: add_custom_impl | 14 | // Assist: add_custom_impl |
@@ -52,7 +52,7 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
52 | format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name); | 52 | format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name); |
53 | 53 | ||
54 | let target = attr.syntax().text_range(); | 54 | let target = attr.syntax().text_range(); |
55 | acc.add(AssistId("add_custom_impl"), label, target, |builder| { | 55 | acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| { |
56 | let new_attr_input = input | 56 | let new_attr_input = input |
57 | .syntax() | 57 | .syntax() |
58 | .descendants_with_tokens() | 58 | .descendants_with_tokens() |
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs index ab20c6649..39a5321d1 100644 --- a/crates/ra_assists/src/handlers/add_explicit_type.rs +++ b/crates/ra_assists/src/handlers/add_explicit_type.rs | |||
@@ -4,7 +4,7 @@ use ra_syntax::{ | |||
4 | TextRange, | 4 | TextRange, |
5 | }; | 5 | }; |
6 | 6 | ||
7 | use crate::{AssistContext, AssistId, Assists}; | 7 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
8 | 8 | ||
9 | // Assist: add_explicit_type | 9 | // Assist: add_explicit_type |
10 | // | 10 | // |
@@ -57,9 +57,9 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio | |||
57 | return None; | 57 | return None; |
58 | } | 58 | } |
59 | 59 | ||
60 | let inferred_type = ty.display_source_code(ctx.db, module.into()).ok()?; | 60 | let inferred_type = ty.display_source_code(ctx.db(), module.into()).ok()?; |
61 | acc.add( | 61 | acc.add( |
62 | AssistId("add_explicit_type"), | 62 | AssistId("add_explicit_type", AssistKind::RefactorRewrite), |
63 | format!("Insert explicit type `{}`", inferred_type), | 63 | format!("Insert explicit type `{}`", inferred_type), |
64 | pat_range, | 64 | pat_range, |
65 | |builder| match ascribed_ty { | 65 | |builder| match ascribed_ty { |
@@ -195,7 +195,7 @@ struct Test<K, T = u8> { | |||
195 | } | 195 | } |
196 | 196 | ||
197 | fn main() { | 197 | fn main() { |
198 | let test<|> = Test { t: 23, k: 33 }; | 198 | let test<|> = Test { t: 23u8, k: 33 }; |
199 | }"#, | 199 | }"#, |
200 | r#" | 200 | r#" |
201 | struct Test<K, T = u8> { | 201 | struct Test<K, T = u8> { |
@@ -204,7 +204,7 @@ struct Test<K, T = u8> { | |||
204 | } | 204 | } |
205 | 205 | ||
206 | fn main() { | 206 | fn main() { |
207 | let test: Test<i32> = Test { t: 23, k: 33 }; | 207 | let test: Test<i32> = Test { t: 23u8, k: 33 }; |
208 | }"#, | 208 | }"#, |
209 | ); | 209 | ); |
210 | } | 210 | } |
diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs deleted file mode 100644 index eceba7d0a..000000000 --- a/crates/ra_assists/src/handlers/add_impl.rs +++ /dev/null | |||
@@ -1,98 +0,0 @@ | |||
1 | use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner}; | ||
2 | use stdx::{format_to, SepBy}; | ||
3 | |||
4 | use crate::{AssistContext, AssistId, Assists}; | ||
5 | |||
6 | // Assist: add_impl | ||
7 | // | ||
8 | // Adds a new inherent impl for a type. | ||
9 | // | ||
10 | // ``` | ||
11 | // struct Ctx<T: Clone> { | ||
12 | // data: T,<|> | ||
13 | // } | ||
14 | // ``` | ||
15 | // -> | ||
16 | // ``` | ||
17 | // struct Ctx<T: Clone> { | ||
18 | // data: T, | ||
19 | // } | ||
20 | // | ||
21 | // impl<T: Clone> Ctx<T> { | ||
22 | // $0 | ||
23 | // } | ||
24 | // ``` | ||
25 | pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
26 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; | ||
27 | let name = nominal.name()?; | ||
28 | let target = nominal.syntax().text_range(); | ||
29 | acc.add(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), target, |edit| { | ||
30 | let type_params = nominal.type_param_list(); | ||
31 | let start_offset = nominal.syntax().text_range().end(); | ||
32 | let mut buf = String::new(); | ||
33 | buf.push_str("\n\nimpl"); | ||
34 | if let Some(type_params) = &type_params { | ||
35 | format_to!(buf, "{}", type_params.syntax()); | ||
36 | } | ||
37 | buf.push_str(" "); | ||
38 | buf.push_str(name.text().as_str()); | ||
39 | if let Some(type_params) = type_params { | ||
40 | let lifetime_params = type_params | ||
41 | .lifetime_params() | ||
42 | .filter_map(|it| it.lifetime_token()) | ||
43 | .map(|it| it.text().clone()); | ||
44 | let type_params = | ||
45 | type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone()); | ||
46 | |||
47 | let generic_params = lifetime_params.chain(type_params).sep_by(", "); | ||
48 | format_to!(buf, "<{}>", generic_params) | ||
49 | } | ||
50 | match ctx.config.snippet_cap { | ||
51 | Some(cap) => { | ||
52 | buf.push_str(" {\n $0\n}"); | ||
53 | edit.insert_snippet(cap, start_offset, buf); | ||
54 | } | ||
55 | None => { | ||
56 | buf.push_str(" {\n}"); | ||
57 | edit.insert(start_offset, buf); | ||
58 | } | ||
59 | } | ||
60 | }) | ||
61 | } | ||
62 | |||
63 | #[cfg(test)] | ||
64 | mod tests { | ||
65 | use crate::tests::{check_assist, check_assist_target}; | ||
66 | |||
67 | use super::*; | ||
68 | |||
69 | #[test] | ||
70 | fn test_add_impl() { | ||
71 | check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n $0\n}\n"); | ||
72 | check_assist( | ||
73 | add_impl, | ||
74 | "struct Foo<T: Clone> {<|>}", | ||
75 | "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}", | ||
76 | ); | ||
77 | check_assist( | ||
78 | add_impl, | ||
79 | "struct Foo<'a, T: Foo<'a>> {<|>}", | ||
80 | "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}", | ||
81 | ); | ||
82 | } | ||
83 | |||
84 | #[test] | ||
85 | fn add_impl_target() { | ||
86 | check_assist_target( | ||
87 | add_impl, | ||
88 | " | ||
89 | struct SomeThingIrrelevant; | ||
90 | /// Has a lifetime parameter | ||
91 | struct Foo<'a, T: Foo<'a>> {<|>} | ||
92 | struct EvenMoreIrrelevant; | ||
93 | ", | ||
94 | "/// Has a lifetime parameter | ||
95 | struct Foo<'a, T: Foo<'a>> {}", | ||
96 | ); | ||
97 | } | ||
98 | } | ||
diff --git a/crates/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs index abacd4065..f185e61e5 100644 --- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs | |||
@@ -12,7 +12,7 @@ use crate::{ | |||
12 | assist_context::{AssistContext, Assists}, | 12 | assist_context::{AssistContext, Assists}, |
13 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | 13 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, |
14 | utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor}, | 14 | utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor}, |
15 | AssistId, | 15 | AssistId, AssistKind, |
16 | }; | 16 | }; |
17 | 17 | ||
18 | #[derive(PartialEq)] | 18 | #[derive(PartialEq)] |
@@ -128,9 +128,9 @@ fn add_missing_impl_members_inner( | |||
128 | let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def) | 128 | let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def) |
129 | .iter() | 129 | .iter() |
130 | .map(|i| match i { | 130 | .map(|i| match i { |
131 | hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value), | 131 | hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db()).value), |
132 | hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAliasDef(i.source(ctx.db).value), | 132 | hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAliasDef(i.source(ctx.db()).value), |
133 | hir::AssocItem::Const(i) => ast::AssocItem::ConstDef(i.source(ctx.db).value), | 133 | hir::AssocItem::Const(i) => ast::AssocItem::ConstDef(i.source(ctx.db()).value), |
134 | }) | 134 | }) |
135 | .filter(|t| def_name(&t).is_some()) | 135 | .filter(|t| def_name(&t).is_some()) |
136 | .filter(|t| match t { | 136 | .filter(|t| match t { |
@@ -147,7 +147,7 @@ fn add_missing_impl_members_inner( | |||
147 | } | 147 | } |
148 | 148 | ||
149 | let target = impl_def.syntax().text_range(); | 149 | let target = impl_def.syntax().text_range(); |
150 | acc.add(AssistId(assist_id), label, target, |builder| { | 150 | acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| { |
151 | let n_existing_items = impl_item_list.assoc_items().count(); | 151 | let n_existing_items = impl_item_list.assoc_items().count(); |
152 | let source_scope = ctx.sema.scope_for_def(trait_); | 152 | let source_scope = ctx.sema.scope_for_def(trait_); |
153 | let target_scope = ctx.sema.scope(impl_item_list.syntax()); | 153 | let target_scope = ctx.sema.scope(impl_item_list.syntax()); |
@@ -158,6 +158,9 @@ fn add_missing_impl_members_inner( | |||
158 | .map(|it| ast_transform::apply(&*ast_transform, it)) | 158 | .map(|it| ast_transform::apply(&*ast_transform, it)) |
159 | .map(|it| match it { | 159 | .map(|it| match it { |
160 | ast::AssocItem::FnDef(def) => ast::AssocItem::FnDef(add_body(def)), | 160 | ast::AssocItem::FnDef(def) => ast::AssocItem::FnDef(add_body(def)), |
161 | ast::AssocItem::TypeAliasDef(def) => { | ||
162 | ast::AssocItem::TypeAliasDef(def.remove_bounds()) | ||
163 | } | ||
161 | _ => it, | 164 | _ => it, |
162 | }) | 165 | }) |
163 | .map(|it| edit::remove_attrs_and_docs(&it)); | 166 | .map(|it| edit::remove_attrs_and_docs(&it)); |
@@ -684,4 +687,26 @@ impl Foo<T> for S<T> { | |||
684 | }"#, | 687 | }"#, |
685 | ) | 688 | ) |
686 | } | 689 | } |
690 | |||
691 | #[test] | ||
692 | fn test_assoc_type_bounds_are_removed() { | ||
693 | check_assist( | ||
694 | add_missing_impl_members, | ||
695 | r#" | ||
696 | trait Tr { | ||
697 | type Ty: Copy + 'static; | ||
698 | } | ||
699 | |||
700 | impl Tr for ()<|> { | ||
701 | }"#, | ||
702 | r#" | ||
703 | trait Tr { | ||
704 | type Ty: Copy + 'static; | ||
705 | } | ||
706 | |||
707 | impl Tr for () { | ||
708 | $0type Ty; | ||
709 | }"#, | ||
710 | ) | ||
711 | } | ||
687 | } | 712 | } |
diff --git a/crates/ra_assists/src/handlers/add_turbo_fish.rs b/crates/ra_assists/src/handlers/add_turbo_fish.rs index 26acf81f2..f7e1a7b05 100644 --- a/crates/ra_assists/src/handlers/add_turbo_fish.rs +++ b/crates/ra_assists/src/handlers/add_turbo_fish.rs | |||
@@ -4,7 +4,7 @@ use test_utils::mark; | |||
4 | 4 | ||
5 | use crate::{ | 5 | use crate::{ |
6 | assist_context::{AssistContext, Assists}, | 6 | assist_context::{AssistContext, Assists}, |
7 | AssistId, | 7 | AssistId, AssistKind, |
8 | }; | 8 | }; |
9 | 9 | ||
10 | // Assist: add_turbo_fish | 10 | // Assist: add_turbo_fish |
@@ -45,12 +45,15 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
45 | mark::hit!(add_turbo_fish_non_generic); | 45 | mark::hit!(add_turbo_fish_non_generic); |
46 | return None; | 46 | return None; |
47 | } | 47 | } |
48 | acc.add(AssistId("add_turbo_fish"), "Add `::<>`", ident.text_range(), |builder| { | 48 | acc.add( |
49 | match ctx.config.snippet_cap { | 49 | AssistId("add_turbo_fish", AssistKind::RefactorRewrite), |
50 | "Add `::<>`", | ||
51 | ident.text_range(), | ||
52 | |builder| match ctx.config.snippet_cap { | ||
50 | Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"), | 53 | Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"), |
51 | None => builder.insert(ident.text_range().end(), "::<_>"), | 54 | None => builder.insert(ident.text_range().end(), "::<_>"), |
52 | } | 55 | }, |
53 | }) | 56 | ) |
54 | } | 57 | } |
55 | 58 | ||
56 | #[cfg(test)] | 59 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/handlers/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs index 233e8fb8e..de701f8b8 100644 --- a/crates/ra_assists/src/handlers/apply_demorgan.rs +++ b/crates/ra_assists/src/handlers/apply_demorgan.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use ra_syntax::ast::{self, AstNode}; | 1 | use ra_syntax::ast::{self, AstNode}; |
2 | 2 | ||
3 | use crate::{utils::invert_boolean_expression, AssistContext, AssistId, Assists}; | 3 | use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists}; |
4 | 4 | ||
5 | // Assist: apply_demorgan | 5 | // Assist: apply_demorgan |
6 | // | 6 | // |
@@ -39,11 +39,16 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
39 | let rhs_range = rhs.syntax().text_range(); | 39 | let rhs_range = rhs.syntax().text_range(); |
40 | let not_rhs = invert_boolean_expression(rhs); | 40 | let not_rhs = invert_boolean_expression(rhs); |
41 | 41 | ||
42 | acc.add(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| { | 42 | acc.add( |
43 | edit.replace(op_range, opposite_op); | 43 | AssistId("apply_demorgan", AssistKind::RefactorRewrite), |
44 | edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); | 44 | "Apply De Morgan's law", |
45 | edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); | 45 | op_range, |
46 | }) | 46 | |edit| { |
47 | edit.replace(op_range, opposite_op); | ||
48 | edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); | ||
49 | edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); | ||
50 | }, | ||
51 | ) | ||
47 | } | 52 | } |
48 | 53 | ||
49 | // Return the opposite text for a given logical operator, if it makes sense | 54 | // Return the opposite text for a given logical operator, if it makes sense |
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs index edf96d50e..947be3b9b 100644 --- a/crates/ra_assists/src/handlers/auto_import.rs +++ b/crates/ra_assists/src/handlers/auto_import.rs | |||
@@ -5,7 +5,7 @@ use hir::{ | |||
5 | AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, | 5 | AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, |
6 | Type, | 6 | Type, |
7 | }; | 7 | }; |
8 | use ra_ide_db::{imports_locator::ImportsLocator, RootDatabase}; | 8 | use ra_ide_db::{imports_locator, RootDatabase}; |
9 | use ra_prof::profile; | 9 | use ra_prof::profile; |
10 | use ra_syntax::{ | 10 | use ra_syntax::{ |
11 | ast::{self, AstNode}, | 11 | ast::{self, AstNode}, |
@@ -13,7 +13,9 @@ use ra_syntax::{ | |||
13 | }; | 13 | }; |
14 | use rustc_hash::FxHashSet; | 14 | use rustc_hash::FxHashSet; |
15 | 15 | ||
16 | use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, GroupLabel}; | 16 | use crate::{ |
17 | utils::insert_use_statement, AssistContext, AssistId, AssistKind, Assists, GroupLabel, | ||
18 | }; | ||
17 | 19 | ||
18 | // Assist: auto_import | 20 | // Assist: auto_import |
19 | // | 21 | // |
@@ -35,8 +37,8 @@ use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, Group | |||
35 | // # pub mod std { pub mod collections { pub struct HashMap { } } } | 37 | // # pub mod std { pub mod collections { pub struct HashMap { } } } |
36 | // ``` | 38 | // ``` |
37 | pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 39 | pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
38 | let auto_import_assets = AutoImportAssets::new(&ctx)?; | 40 | let auto_import_assets = AutoImportAssets::new(ctx)?; |
39 | let proposed_imports = auto_import_assets.search_for_imports(ctx.db); | 41 | let proposed_imports = auto_import_assets.search_for_imports(ctx); |
40 | if proposed_imports.is_empty() { | 42 | if proposed_imports.is_empty() { |
41 | return None; | 43 | return None; |
42 | } | 44 | } |
@@ -46,7 +48,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
46 | for import in proposed_imports { | 48 | for import in proposed_imports { |
47 | acc.add_group( | 49 | acc.add_group( |
48 | &group, | 50 | &group, |
49 | AssistId("auto_import"), | 51 | AssistId("auto_import", AssistKind::QuickFix), |
50 | format!("Import `{}`", &import), | 52 | format!("Import `{}`", &import), |
51 | range, | 53 | range, |
52 | |builder| { | 54 | |builder| { |
@@ -127,11 +129,11 @@ impl AutoImportAssets { | |||
127 | GroupLabel(name) | 129 | GroupLabel(name) |
128 | } | 130 | } |
129 | 131 | ||
130 | fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> { | 132 | fn search_for_imports(&self, ctx: &AssistContext) -> BTreeSet<ModPath> { |
131 | let _p = profile("auto_import::search_for_imports"); | 133 | let _p = profile("auto_import::search_for_imports"); |
134 | let db = ctx.db(); | ||
132 | let current_crate = self.module_with_name_to_import.krate(); | 135 | let current_crate = self.module_with_name_to_import.krate(); |
133 | ImportsLocator::new(db) | 136 | imports_locator::find_imports(&ctx.sema, current_crate, &self.get_search_query()) |
134 | .find_imports(&self.get_search_query()) | ||
135 | .into_iter() | 137 | .into_iter() |
136 | .filter_map(|candidate| match &self.import_candidate { | 138 | .filter_map(|candidate| match &self.import_candidate { |
137 | ImportCandidate::TraitAssocItem(assoc_item_type, _) => { | 139 | ImportCandidate::TraitAssocItem(assoc_item_type, _) => { |
@@ -488,16 +490,17 @@ mod tests { | |||
488 | check_assist( | 490 | check_assist( |
489 | auto_import, | 491 | auto_import, |
490 | r" | 492 | r" |
491 | //- /lib.rs crate:crate_with_macro | 493 | //- /lib.rs crate:crate_with_macro |
492 | #[macro_export] | 494 | #[macro_export] |
493 | macro_rules! foo { | 495 | macro_rules! foo { |
494 | () => () | 496 | () => () |
495 | } | 497 | } |
496 | 498 | ||
497 | //- /main.rs crate:main deps:crate_with_macro | 499 | //- /main.rs crate:main deps:crate_with_macro |
498 | fn main() { | 500 | fn main() { |
499 | foo<|> | 501 | foo<|> |
500 | }", | 502 | } |
503 | ", | ||
501 | r"use crate_with_macro::foo; | 504 | r"use crate_with_macro::foo; |
502 | 505 | ||
503 | fn main() { | 506 | fn main() { |
@@ -810,6 +813,146 @@ fn main() { | |||
810 | } | 813 | } |
811 | 814 | ||
812 | #[test] | 815 | #[test] |
816 | fn trait_method_cross_crate() { | ||
817 | check_assist( | ||
818 | auto_import, | ||
819 | r" | ||
820 | //- /main.rs crate:main deps:dep | ||
821 | fn main() { | ||
822 | let test_struct = dep::test_mod::TestStruct {}; | ||
823 | test_struct.test_meth<|>od() | ||
824 | } | ||
825 | //- /dep.rs crate:dep | ||
826 | pub mod test_mod { | ||
827 | pub trait TestTrait { | ||
828 | fn test_method(&self); | ||
829 | } | ||
830 | pub struct TestStruct {} | ||
831 | impl TestTrait for TestStruct { | ||
832 | fn test_method(&self) {} | ||
833 | } | ||
834 | } | ||
835 | ", | ||
836 | r" | ||
837 | use dep::test_mod::TestTrait; | ||
838 | |||
839 | fn main() { | ||
840 | let test_struct = dep::test_mod::TestStruct {}; | ||
841 | test_struct.test_method() | ||
842 | } | ||
843 | ", | ||
844 | ); | ||
845 | } | ||
846 | |||
847 | #[test] | ||
848 | fn assoc_fn_cross_crate() { | ||
849 | check_assist( | ||
850 | auto_import, | ||
851 | r" | ||
852 | //- /main.rs crate:main deps:dep | ||
853 | fn main() { | ||
854 | dep::test_mod::TestStruct::test_func<|>tion | ||
855 | } | ||
856 | //- /dep.rs crate:dep | ||
857 | pub mod test_mod { | ||
858 | pub trait TestTrait { | ||
859 | fn test_function(); | ||
860 | } | ||
861 | pub struct TestStruct {} | ||
862 | impl TestTrait for TestStruct { | ||
863 | fn test_function() {} | ||
864 | } | ||
865 | } | ||
866 | ", | ||
867 | r" | ||
868 | use dep::test_mod::TestTrait; | ||
869 | |||
870 | fn main() { | ||
871 | dep::test_mod::TestStruct::test_function | ||
872 | } | ||
873 | ", | ||
874 | ); | ||
875 | } | ||
876 | |||
877 | #[test] | ||
878 | fn assoc_const_cross_crate() { | ||
879 | check_assist( | ||
880 | auto_import, | ||
881 | r" | ||
882 | //- /main.rs crate:main deps:dep | ||
883 | fn main() { | ||
884 | dep::test_mod::TestStruct::CONST<|> | ||
885 | } | ||
886 | //- /dep.rs crate:dep | ||
887 | pub mod test_mod { | ||
888 | pub trait TestTrait { | ||
889 | const CONST: bool; | ||
890 | } | ||
891 | pub struct TestStruct {} | ||
892 | impl TestTrait for TestStruct { | ||
893 | const CONST: bool = true; | ||
894 | } | ||
895 | } | ||
896 | ", | ||
897 | r" | ||
898 | use dep::test_mod::TestTrait; | ||
899 | |||
900 | fn main() { | ||
901 | dep::test_mod::TestStruct::CONST | ||
902 | } | ||
903 | ", | ||
904 | ); | ||
905 | } | ||
906 | |||
907 | #[test] | ||
908 | fn assoc_fn_as_method_cross_crate() { | ||
909 | check_assist_not_applicable( | ||
910 | auto_import, | ||
911 | r" | ||
912 | //- /main.rs crate:main deps:dep | ||
913 | fn main() { | ||
914 | let test_struct = dep::test_mod::TestStruct {}; | ||
915 | test_struct.test_func<|>tion() | ||
916 | } | ||
917 | //- /dep.rs crate:dep | ||
918 | pub mod test_mod { | ||
919 | pub trait TestTrait { | ||
920 | fn test_function(); | ||
921 | } | ||
922 | pub struct TestStruct {} | ||
923 | impl TestTrait for TestStruct { | ||
924 | fn test_function() {} | ||
925 | } | ||
926 | } | ||
927 | ", | ||
928 | ); | ||
929 | } | ||
930 | |||
931 | #[test] | ||
932 | fn private_trait_cross_crate() { | ||
933 | check_assist_not_applicable( | ||
934 | auto_import, | ||
935 | r" | ||
936 | //- /main.rs crate:main deps:dep | ||
937 | fn main() { | ||
938 | let test_struct = dep::test_mod::TestStruct {}; | ||
939 | test_struct.test_meth<|>od() | ||
940 | } | ||
941 | //- /dep.rs crate:dep | ||
942 | pub mod test_mod { | ||
943 | trait TestTrait { | ||
944 | fn test_method(&self); | ||
945 | } | ||
946 | pub struct TestStruct {} | ||
947 | impl TestTrait for TestStruct { | ||
948 | fn test_method(&self) {} | ||
949 | } | ||
950 | } | ||
951 | ", | ||
952 | ); | ||
953 | } | ||
954 | |||
955 | #[test] | ||
813 | fn not_applicable_for_imported_trait_for_method() { | 956 | fn not_applicable_for_imported_trait_for_method() { |
814 | check_assist_not_applicable( | 957 | check_assist_not_applicable( |
815 | auto_import, | 958 | auto_import, |
@@ -841,4 +984,106 @@ fn main() { | |||
841 | ", | 984 | ", |
842 | ) | 985 | ) |
843 | } | 986 | } |
987 | |||
988 | #[test] | ||
989 | fn dep_import() { | ||
990 | check_assist( | ||
991 | auto_import, | ||
992 | r" | ||
993 | //- /lib.rs crate:dep | ||
994 | pub struct Struct; | ||
995 | |||
996 | //- /main.rs crate:main deps:dep | ||
997 | fn main() { | ||
998 | Struct<|> | ||
999 | } | ||
1000 | ", | ||
1001 | r"use dep::Struct; | ||
1002 | |||
1003 | fn main() { | ||
1004 | Struct | ||
1005 | } | ||
1006 | ", | ||
1007 | ); | ||
1008 | } | ||
1009 | |||
1010 | #[test] | ||
1011 | fn whole_segment() { | ||
1012 | // Tests that only imports whose last segment matches the identifier get suggested. | ||
1013 | check_assist( | ||
1014 | auto_import, | ||
1015 | r" | ||
1016 | //- /lib.rs crate:dep | ||
1017 | pub mod fmt { | ||
1018 | pub trait Display {} | ||
1019 | } | ||
1020 | |||
1021 | pub fn panic_fmt() {} | ||
1022 | |||
1023 | //- /main.rs crate:main deps:dep | ||
1024 | struct S; | ||
1025 | |||
1026 | impl f<|>mt::Display for S {} | ||
1027 | ", | ||
1028 | r"use dep::fmt; | ||
1029 | |||
1030 | struct S; | ||
1031 | |||
1032 | impl fmt::Display for S {} | ||
1033 | ", | ||
1034 | ); | ||
1035 | } | ||
1036 | |||
1037 | #[test] | ||
1038 | fn macro_generated() { | ||
1039 | // Tests that macro-generated items are suggested from external crates. | ||
1040 | check_assist( | ||
1041 | auto_import, | ||
1042 | r" | ||
1043 | //- /lib.rs crate:dep | ||
1044 | macro_rules! mac { | ||
1045 | () => { | ||
1046 | pub struct Cheese; | ||
1047 | }; | ||
1048 | } | ||
1049 | |||
1050 | mac!(); | ||
1051 | |||
1052 | //- /main.rs crate:main deps:dep | ||
1053 | fn main() { | ||
1054 | Cheese<|>; | ||
1055 | } | ||
1056 | ", | ||
1057 | r"use dep::Cheese; | ||
1058 | |||
1059 | fn main() { | ||
1060 | Cheese; | ||
1061 | } | ||
1062 | ", | ||
1063 | ); | ||
1064 | } | ||
1065 | |||
1066 | #[test] | ||
1067 | fn casing() { | ||
1068 | // Tests that differently cased names don't interfere and we only suggest the matching one. | ||
1069 | check_assist( | ||
1070 | auto_import, | ||
1071 | r" | ||
1072 | //- /lib.rs crate:dep | ||
1073 | pub struct FMT; | ||
1074 | pub struct fmt; | ||
1075 | |||
1076 | //- /main.rs crate:main deps:dep | ||
1077 | fn main() { | ||
1078 | FMT<|>; | ||
1079 | } | ||
1080 | ", | ||
1081 | r"use dep::FMT; | ||
1082 | |||
1083 | fn main() { | ||
1084 | FMT; | ||
1085 | } | ||
1086 | ", | ||
1087 | ); | ||
1088 | } | ||
844 | } | 1089 | } |
diff --git a/crates/ra_assists/src/handlers/change_return_type_to_result.rs b/crates/ra_assists/src/handlers/change_return_type_to_result.rs index c6baa0a57..24e5f6963 100644 --- a/crates/ra_assists/src/handlers/change_return_type_to_result.rs +++ b/crates/ra_assists/src/handlers/change_return_type_to_result.rs | |||
@@ -3,7 +3,8 @@ use ra_syntax::{ | |||
3 | AstNode, SyntaxNode, | 3 | AstNode, SyntaxNode, |
4 | }; | 4 | }; |
5 | 5 | ||
6 | use crate::{AssistContext, AssistId, Assists}; | 6 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
7 | use test_utils::mark; | ||
7 | 8 | ||
8 | // Assist: change_return_type_to_result | 9 | // Assist: change_return_type_to_result |
9 | // | 10 | // |
@@ -22,14 +23,19 @@ pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContex | |||
22 | let fn_def = ret_type.syntax().parent().and_then(ast::FnDef::cast)?; | 23 | let fn_def = ret_type.syntax().parent().and_then(ast::FnDef::cast)?; |
23 | 24 | ||
24 | let type_ref = &ret_type.type_ref()?; | 25 | let type_ref = &ret_type.type_ref()?; |
25 | if type_ref.syntax().text().to_string().starts_with("Result<") { | 26 | let ret_type_str = type_ref.syntax().text().to_string(); |
26 | return None; | 27 | let first_part_ret_type = ret_type_str.splitn(2, '<').next(); |
28 | if let Some(ret_type_first_part) = first_part_ret_type { | ||
29 | if ret_type_first_part.ends_with("Result") { | ||
30 | mark::hit!(change_return_type_to_result_simple_return_type_already_result); | ||
31 | return None; | ||
32 | } | ||
27 | } | 33 | } |
28 | 34 | ||
29 | let block_expr = &fn_def.body()?; | 35 | let block_expr = &fn_def.body()?; |
30 | 36 | ||
31 | acc.add( | 37 | acc.add( |
32 | AssistId("change_return_type_to_result"), | 38 | AssistId("change_return_type_to_result", AssistKind::RefactorRewrite), |
33 | "Change return type to Result", | 39 | "Change return type to Result", |
34 | type_ref.syntax().text_range(), | 40 | type_ref.syntax().text_range(), |
35 | |builder| { | 41 | |builder| { |
@@ -297,6 +303,29 @@ mod tests { | |||
297 | } | 303 | } |
298 | 304 | ||
299 | #[test] | 305 | #[test] |
306 | fn change_return_type_to_result_simple_return_type_already_result_std() { | ||
307 | check_assist_not_applicable( | ||
308 | change_return_type_to_result, | ||
309 | r#"fn foo() -> std::result::Result<i32<|>, String> { | ||
310 | let test = "test"; | ||
311 | return 42i32; | ||
312 | }"#, | ||
313 | ); | ||
314 | } | ||
315 | |||
316 | #[test] | ||
317 | fn change_return_type_to_result_simple_return_type_already_result() { | ||
318 | mark::check!(change_return_type_to_result_simple_return_type_already_result); | ||
319 | check_assist_not_applicable( | ||
320 | change_return_type_to_result, | ||
321 | r#"fn foo() -> Result<i32<|>, String> { | ||
322 | let test = "test"; | ||
323 | return 42i32; | ||
324 | }"#, | ||
325 | ); | ||
326 | } | ||
327 | |||
328 | #[test] | ||
300 | fn change_return_type_to_result_simple_with_cursor() { | 329 | fn change_return_type_to_result_simple_with_cursor() { |
301 | check_assist( | 330 | check_assist( |
302 | change_return_type_to_result, | 331 | change_return_type_to_result, |
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs index c21d75be0..4343b423c 100644 --- a/crates/ra_assists/src/handlers/change_visibility.rs +++ b/crates/ra_assists/src/handlers/change_visibility.rs | |||
@@ -2,14 +2,13 @@ use ra_syntax::{ | |||
2 | ast::{self, NameOwner, VisibilityOwner}, | 2 | ast::{self, NameOwner, VisibilityOwner}, |
3 | AstNode, | 3 | AstNode, |
4 | SyntaxKind::{ | 4 | SyntaxKind::{ |
5 | ATTR, COMMENT, CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY, | 5 | CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STATIC_DEF, STRUCT_DEF, TRAIT_DEF, VISIBILITY, |
6 | WHITESPACE, | ||
7 | }, | 6 | }, |
8 | SyntaxNode, TextSize, T, | 7 | T, |
9 | }; | 8 | }; |
10 | use test_utils::mark; | 9 | use test_utils::mark; |
11 | 10 | ||
12 | use crate::{AssistContext, AssistId, Assists}; | 11 | use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists}; |
13 | 12 | ||
14 | // Assist: change_visibility | 13 | // Assist: change_visibility |
15 | // | 14 | // |
@@ -30,14 +29,16 @@ pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Optio | |||
30 | } | 29 | } |
31 | 30 | ||
32 | fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 31 | fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
33 | let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { | 32 | let item_keyword = ctx.token_at_offset().find(|leaf| { |
34 | T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, | 33 | matches!( |
35 | _ => false, | 34 | leaf.kind(), |
35 | T![const] | T![static] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] | ||
36 | ) | ||
36 | }); | 37 | }); |
37 | 38 | ||
38 | let (offset, target) = if let Some(keyword) = item_keyword { | 39 | let (offset, target) = if let Some(keyword) = item_keyword { |
39 | let parent = keyword.parent(); | 40 | let parent = keyword.parent(); |
40 | let def_kws = vec![CONST_DEF, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; | 41 | let def_kws = vec![CONST_DEF, STATIC_DEF, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; |
41 | // Parent is not a definition, can't add visibility | 42 | // Parent is not a definition, can't add visibility |
42 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { | 43 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { |
43 | return None; | 44 | return None; |
@@ -66,27 +67,21 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
66 | return None; | 67 | return None; |
67 | }; | 68 | }; |
68 | 69 | ||
69 | acc.add(AssistId("change_visibility"), "Change visibility to pub(crate)", target, |edit| { | 70 | acc.add( |
70 | edit.insert(offset, "pub(crate) "); | 71 | AssistId("change_visibility", AssistKind::RefactorRewrite), |
71 | }) | 72 | "Change visibility to pub(crate)", |
72 | } | 73 | target, |
73 | 74 | |edit| { | |
74 | fn vis_offset(node: &SyntaxNode) -> TextSize { | 75 | edit.insert(offset, "pub(crate) "); |
75 | node.children_with_tokens() | 76 | }, |
76 | .skip_while(|it| match it.kind() { | 77 | ) |
77 | WHITESPACE | COMMENT | ATTR => true, | ||
78 | _ => false, | ||
79 | }) | ||
80 | .next() | ||
81 | .map(|it| it.text_range().start()) | ||
82 | .unwrap_or_else(|| node.text_range().start()) | ||
83 | } | 78 | } |
84 | 79 | ||
85 | fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> { | 80 | fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> { |
86 | if vis.syntax().text() == "pub" { | 81 | if vis.syntax().text() == "pub" { |
87 | let target = vis.syntax().text_range(); | 82 | let target = vis.syntax().text_range(); |
88 | return acc.add( | 83 | return acc.add( |
89 | AssistId("change_visibility"), | 84 | AssistId("change_visibility", AssistKind::RefactorRewrite), |
90 | "Change Visibility to pub(crate)", | 85 | "Change Visibility to pub(crate)", |
91 | target, | 86 | target, |
92 | |edit| { | 87 | |edit| { |
@@ -97,7 +92,7 @@ fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> { | |||
97 | if vis.syntax().text() == "pub(crate)" { | 92 | if vis.syntax().text() == "pub(crate)" { |
98 | let target = vis.syntax().text_range(); | 93 | let target = vis.syntax().text_range(); |
99 | return acc.add( | 94 | return acc.add( |
100 | AssistId("change_visibility"), | 95 | AssistId("change_visibility", AssistKind::RefactorRewrite), |
101 | "Change visibility to pub", | 96 | "Change visibility to pub", |
102 | target, | 97 | target, |
103 | |edit| { | 98 | |edit| { |
@@ -162,6 +157,11 @@ mod tests { | |||
162 | } | 157 | } |
163 | 158 | ||
164 | #[test] | 159 | #[test] |
160 | fn change_visibility_static() { | ||
161 | check_assist(change_visibility, "<|>static FOO = 3u8;", "pub(crate) static FOO = 3u8;"); | ||
162 | } | ||
163 | |||
164 | #[test] | ||
165 | fn change_visibility_handles_comment_attrs() { | 165 | fn change_visibility_handles_comment_attrs() { |
166 | check_assist( | 166 | check_assist( |
167 | change_visibility, | 167 | change_visibility, |
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs index 4cc75a7ce..330459f3c 100644 --- a/crates/ra_assists/src/handlers/early_return.rs +++ b/crates/ra_assists/src/handlers/early_return.rs | |||
@@ -15,7 +15,7 @@ use ra_syntax::{ | |||
15 | use crate::{ | 15 | use crate::{ |
16 | assist_context::{AssistContext, Assists}, | 16 | assist_context::{AssistContext, Assists}, |
17 | utils::invert_boolean_expression, | 17 | utils::invert_boolean_expression, |
18 | AssistId, | 18 | AssistId, AssistKind, |
19 | }; | 19 | }; |
20 | 20 | ||
21 | // Assist: convert_to_guarded_return | 21 | // Assist: convert_to_guarded_return |
@@ -99,86 +99,92 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) | |||
99 | then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; | 99 | then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; |
100 | 100 | ||
101 | let target = if_expr.syntax().text_range(); | 101 | let target = if_expr.syntax().text_range(); |
102 | acc.add(AssistId("convert_to_guarded_return"), "Convert to guarded return", target, |edit| { | 102 | acc.add( |
103 | let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); | 103 | AssistId("convert_to_guarded_return", AssistKind::RefactorRewrite), |
104 | let new_block = match if_let_pat { | 104 | "Convert to guarded return", |
105 | None => { | 105 | target, |
106 | // If. | 106 | |edit| { |
107 | let new_expr = { | 107 | let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); |
108 | let then_branch = | 108 | let new_block = match if_let_pat { |
109 | make::block_expr(once(make::expr_stmt(early_expression).into()), None); | 109 | None => { |
110 | let cond = invert_boolean_expression(cond_expr); | 110 | // If. |
111 | make::expr_if(make::condition(cond, None), then_branch).indent(if_indent_level) | 111 | let new_expr = { |
112 | }; | 112 | let then_branch = |
113 | replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) | 113 | make::block_expr(once(make::expr_stmt(early_expression).into()), None); |
114 | } | 114 | let cond = invert_boolean_expression(cond_expr); |
115 | Some((path, bound_ident)) => { | 115 | make::expr_if(make::condition(cond, None), then_branch) |
116 | // If-let. | 116 | .indent(if_indent_level) |
117 | let match_expr = { | ||
118 | let happy_arm = { | ||
119 | let pat = make::tuple_struct_pat( | ||
120 | path, | ||
121 | once(make::bind_pat(make::name("it")).into()), | ||
122 | ); | ||
123 | let expr = { | ||
124 | let name_ref = make::name_ref("it"); | ||
125 | let segment = make::path_segment(name_ref); | ||
126 | let path = make::path_unqualified(segment); | ||
127 | make::expr_path(path) | ||
128 | }; | ||
129 | make::match_arm(once(pat.into()), expr) | ||
130 | }; | 117 | }; |
118 | replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) | ||
119 | } | ||
120 | Some((path, bound_ident)) => { | ||
121 | // If-let. | ||
122 | let match_expr = { | ||
123 | let happy_arm = { | ||
124 | let pat = make::tuple_struct_pat( | ||
125 | path, | ||
126 | once(make::bind_pat(make::name("it")).into()), | ||
127 | ); | ||
128 | let expr = { | ||
129 | let name_ref = make::name_ref("it"); | ||
130 | let segment = make::path_segment(name_ref); | ||
131 | let path = make::path_unqualified(segment); | ||
132 | make::expr_path(path) | ||
133 | }; | ||
134 | make::match_arm(once(pat.into()), expr) | ||
135 | }; | ||
131 | 136 | ||
132 | let sad_arm = make::match_arm( | 137 | let sad_arm = make::match_arm( |
133 | // FIXME: would be cool to use `None` or `Err(_)` if appropriate | 138 | // FIXME: would be cool to use `None` or `Err(_)` if appropriate |
134 | once(make::placeholder_pat().into()), | 139 | once(make::placeholder_pat().into()), |
135 | early_expression, | 140 | early_expression, |
136 | ); | 141 | ); |
137 | 142 | ||
138 | make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm])) | 143 | make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm])) |
139 | }; | 144 | }; |
140 | 145 | ||
141 | let let_stmt = make::let_stmt( | 146 | let let_stmt = make::let_stmt( |
142 | make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), | 147 | make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), |
143 | Some(match_expr), | 148 | Some(match_expr), |
149 | ); | ||
150 | let let_stmt = let_stmt.indent(if_indent_level); | ||
151 | replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) | ||
152 | } | ||
153 | }; | ||
154 | edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap()); | ||
155 | |||
156 | fn replace( | ||
157 | new_expr: &SyntaxNode, | ||
158 | then_block: &ast::BlockExpr, | ||
159 | parent_block: &ast::BlockExpr, | ||
160 | if_expr: &ast::IfExpr, | ||
161 | ) -> SyntaxNode { | ||
162 | let then_block_items = then_block.dedent(IndentLevel(1)); | ||
163 | let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); | ||
164 | let end_of_then = | ||
165 | if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { | ||
166 | end_of_then.prev_sibling_or_token().unwrap() | ||
167 | } else { | ||
168 | end_of_then | ||
169 | }; | ||
170 | let mut then_statements = new_expr.children_with_tokens().chain( | ||
171 | then_block_items | ||
172 | .syntax() | ||
173 | .children_with_tokens() | ||
174 | .skip(1) | ||
175 | .take_while(|i| *i != end_of_then), | ||
144 | ); | 176 | ); |
145 | let let_stmt = let_stmt.indent(if_indent_level); | 177 | replace_children( |
146 | replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) | 178 | &parent_block.syntax(), |
179 | RangeInclusive::new( | ||
180 | if_expr.clone().syntax().clone().into(), | ||
181 | if_expr.syntax().clone().into(), | ||
182 | ), | ||
183 | &mut then_statements, | ||
184 | ) | ||
147 | } | 185 | } |
148 | }; | 186 | }, |
149 | edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap()); | 187 | ) |
150 | |||
151 | fn replace( | ||
152 | new_expr: &SyntaxNode, | ||
153 | then_block: &ast::BlockExpr, | ||
154 | parent_block: &ast::BlockExpr, | ||
155 | if_expr: &ast::IfExpr, | ||
156 | ) -> SyntaxNode { | ||
157 | let then_block_items = then_block.dedent(IndentLevel::from(1)); | ||
158 | let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); | ||
159 | let end_of_then = | ||
160 | if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { | ||
161 | end_of_then.prev_sibling_or_token().unwrap() | ||
162 | } else { | ||
163 | end_of_then | ||
164 | }; | ||
165 | let mut then_statements = new_expr.children_with_tokens().chain( | ||
166 | then_block_items | ||
167 | .syntax() | ||
168 | .children_with_tokens() | ||
169 | .skip(1) | ||
170 | .take_while(|i| *i != end_of_then), | ||
171 | ); | ||
172 | replace_children( | ||
173 | &parent_block.syntax(), | ||
174 | RangeInclusive::new( | ||
175 | if_expr.clone().syntax().clone().into(), | ||
176 | if_expr.syntax().clone().into(), | ||
177 | ), | ||
178 | &mut then_statements, | ||
179 | ) | ||
180 | } | ||
181 | }) | ||
182 | } | 188 | } |
183 | 189 | ||
184 | #[cfg(test)] | 190 | #[cfg(test)] |
diff --git a/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs new file mode 100644 index 000000000..2b8e273b3 --- /dev/null +++ b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs | |||
@@ -0,0 +1,321 @@ | |||
1 | use hir::{EnumVariant, Module, ModuleDef, Name}; | ||
2 | use ra_db::FileId; | ||
3 | use ra_fmt::leading_indent; | ||
4 | use ra_ide_db::{defs::Definition, search::Reference, RootDatabase}; | ||
5 | use ra_syntax::{ | ||
6 | algo::find_node_at_offset, | ||
7 | ast::{self, ArgListOwner, AstNode, NameOwner, VisibilityOwner}, | ||
8 | SourceFile, SyntaxNode, TextRange, TextSize, | ||
9 | }; | ||
10 | use rustc_hash::FxHashSet; | ||
11 | |||
12 | use crate::{ | ||
13 | assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId, | ||
14 | AssistKind, Assists, | ||
15 | }; | ||
16 | |||
17 | // Assist: extract_struct_from_enum_variant | ||
18 | // | ||
19 | // Extracts a struct from enum variant. | ||
20 | // | ||
21 | // ``` | ||
22 | // enum A { <|>One(u32, u32) } | ||
23 | // ``` | ||
24 | // -> | ||
25 | // ``` | ||
26 | // struct One(pub u32, pub u32); | ||
27 | // | ||
28 | // enum A { One(One) } | ||
29 | // ``` | ||
30 | pub(crate) fn extract_struct_from_enum_variant( | ||
31 | acc: &mut Assists, | ||
32 | ctx: &AssistContext, | ||
33 | ) -> Option<()> { | ||
34 | let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?; | ||
35 | let field_list = match variant.kind() { | ||
36 | ast::StructKind::Tuple(field_list) => field_list, | ||
37 | _ => return None, | ||
38 | }; | ||
39 | let variant_name = variant.name()?.to_string(); | ||
40 | let variant_hir = ctx.sema.to_def(&variant)?; | ||
41 | if existing_struct_def(ctx.db(), &variant_name, &variant_hir) { | ||
42 | return None; | ||
43 | } | ||
44 | let enum_ast = variant.parent_enum(); | ||
45 | let visibility = enum_ast.visibility(); | ||
46 | let enum_hir = ctx.sema.to_def(&enum_ast)?; | ||
47 | let variant_hir_name = variant_hir.name(ctx.db()); | ||
48 | let enum_module_def = ModuleDef::from(enum_hir); | ||
49 | let current_module = enum_hir.module(ctx.db()); | ||
50 | let target = variant.syntax().text_range(); | ||
51 | acc.add( | ||
52 | AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite), | ||
53 | "Extract struct from enum variant", | ||
54 | target, | ||
55 | |builder| { | ||
56 | let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir)); | ||
57 | let res = definition.find_usages(&ctx.sema, None); | ||
58 | let start_offset = variant.parent_enum().syntax().text_range().start(); | ||
59 | let mut visited_modules_set = FxHashSet::default(); | ||
60 | visited_modules_set.insert(current_module); | ||
61 | for reference in res { | ||
62 | let source_file = ctx.sema.parse(reference.file_range.file_id); | ||
63 | update_reference( | ||
64 | ctx, | ||
65 | builder, | ||
66 | reference, | ||
67 | &source_file, | ||
68 | &enum_module_def, | ||
69 | &variant_hir_name, | ||
70 | &mut visited_modules_set, | ||
71 | ); | ||
72 | } | ||
73 | extract_struct_def( | ||
74 | builder, | ||
75 | enum_ast.syntax(), | ||
76 | &variant_name, | ||
77 | &field_list.to_string(), | ||
78 | start_offset, | ||
79 | ctx.frange.file_id, | ||
80 | &visibility, | ||
81 | ); | ||
82 | let list_range = field_list.syntax().text_range(); | ||
83 | update_variant(builder, &variant_name, ctx.frange.file_id, list_range); | ||
84 | }, | ||
85 | ) | ||
86 | } | ||
87 | |||
88 | fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVariant) -> bool { | ||
89 | variant | ||
90 | .parent_enum(db) | ||
91 | .module(db) | ||
92 | .scope(db, None) | ||
93 | .into_iter() | ||
94 | .any(|(name, _)| name.to_string() == variant_name.to_string()) | ||
95 | } | ||
96 | |||
97 | fn insert_import( | ||
98 | ctx: &AssistContext, | ||
99 | builder: &mut AssistBuilder, | ||
100 | path: &ast::PathExpr, | ||
101 | module: &Module, | ||
102 | enum_module_def: &ModuleDef, | ||
103 | variant_hir_name: &Name, | ||
104 | ) -> Option<()> { | ||
105 | let db = ctx.db(); | ||
106 | let mod_path = module.find_use_path(db, enum_module_def.clone()); | ||
107 | if let Some(mut mod_path) = mod_path { | ||
108 | mod_path.segments.pop(); | ||
109 | mod_path.segments.push(variant_hir_name.clone()); | ||
110 | insert_use_statement(path.syntax(), &mod_path, ctx, builder.text_edit_builder()); | ||
111 | } | ||
112 | Some(()) | ||
113 | } | ||
114 | |||
115 | fn extract_struct_def( | ||
116 | builder: &mut AssistBuilder, | ||
117 | enum_ast: &SyntaxNode, | ||
118 | variant_name: &str, | ||
119 | variant_list: &str, | ||
120 | start_offset: TextSize, | ||
121 | file_id: FileId, | ||
122 | visibility: &Option<ast::Visibility>, | ||
123 | ) -> Option<()> { | ||
124 | let visibility_string = if let Some(visibility) = visibility { | ||
125 | format!("{} ", visibility.to_string()) | ||
126 | } else { | ||
127 | "".to_string() | ||
128 | }; | ||
129 | let indent = if let Some(indent) = leading_indent(enum_ast) { | ||
130 | indent.to_string() | ||
131 | } else { | ||
132 | "".to_string() | ||
133 | }; | ||
134 | let struct_def = format!( | ||
135 | r#"{}struct {}{}; | ||
136 | |||
137 | {}"#, | ||
138 | visibility_string, | ||
139 | variant_name, | ||
140 | list_with_visibility(variant_list), | ||
141 | indent | ||
142 | ); | ||
143 | builder.edit_file(file_id); | ||
144 | builder.insert(start_offset, struct_def); | ||
145 | Some(()) | ||
146 | } | ||
147 | |||
148 | fn update_variant( | ||
149 | builder: &mut AssistBuilder, | ||
150 | variant_name: &str, | ||
151 | file_id: FileId, | ||
152 | list_range: TextRange, | ||
153 | ) -> Option<()> { | ||
154 | let inside_variant_range = TextRange::new( | ||
155 | list_range.start().checked_add(TextSize::from(1))?, | ||
156 | list_range.end().checked_sub(TextSize::from(1))?, | ||
157 | ); | ||
158 | builder.edit_file(file_id); | ||
159 | builder.replace(inside_variant_range, variant_name); | ||
160 | Some(()) | ||
161 | } | ||
162 | |||
163 | fn update_reference( | ||
164 | ctx: &AssistContext, | ||
165 | builder: &mut AssistBuilder, | ||
166 | reference: Reference, | ||
167 | source_file: &SourceFile, | ||
168 | enum_module_def: &ModuleDef, | ||
169 | variant_hir_name: &Name, | ||
170 | visited_modules_set: &mut FxHashSet<Module>, | ||
171 | ) -> Option<()> { | ||
172 | let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>( | ||
173 | source_file.syntax(), | ||
174 | reference.file_range.range.start(), | ||
175 | )?; | ||
176 | let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; | ||
177 | let list = call.arg_list()?; | ||
178 | let segment = path_expr.path()?.segment()?; | ||
179 | let module = ctx.sema.scope(&path_expr.syntax()).module()?; | ||
180 | let list_range = list.syntax().text_range(); | ||
181 | let inside_list_range = TextRange::new( | ||
182 | list_range.start().checked_add(TextSize::from(1))?, | ||
183 | list_range.end().checked_sub(TextSize::from(1))?, | ||
184 | ); | ||
185 | builder.edit_file(reference.file_range.file_id); | ||
186 | if !visited_modules_set.contains(&module) { | ||
187 | if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name) | ||
188 | .is_some() | ||
189 | { | ||
190 | visited_modules_set.insert(module); | ||
191 | } | ||
192 | } | ||
193 | builder.replace(inside_list_range, format!("{}{}", segment, list)); | ||
194 | Some(()) | ||
195 | } | ||
196 | |||
197 | fn list_with_visibility(list: &str) -> String { | ||
198 | list.split(',') | ||
199 | .map(|part| { | ||
200 | let index = if part.chars().next().unwrap() == '(' { 1usize } else { 0 }; | ||
201 | let mut mod_part = part.trim().to_string(); | ||
202 | mod_part.insert_str(index, "pub "); | ||
203 | mod_part | ||
204 | }) | ||
205 | .collect::<Vec<String>>() | ||
206 | .join(", ") | ||
207 | } | ||
208 | |||
209 | #[cfg(test)] | ||
210 | mod tests { | ||
211 | |||
212 | use crate::{ | ||
213 | tests::{check_assist, check_assist_not_applicable}, | ||
214 | utils::FamousDefs, | ||
215 | }; | ||
216 | |||
217 | use super::*; | ||
218 | |||
219 | #[test] | ||
220 | fn test_extract_struct_several_fields() { | ||
221 | check_assist( | ||
222 | extract_struct_from_enum_variant, | ||
223 | "enum A { <|>One(u32, u32) }", | ||
224 | r#"struct One(pub u32, pub u32); | ||
225 | |||
226 | enum A { One(One) }"#, | ||
227 | ); | ||
228 | } | ||
229 | |||
230 | #[test] | ||
231 | fn test_extract_struct_one_field() { | ||
232 | check_assist( | ||
233 | extract_struct_from_enum_variant, | ||
234 | "enum A { <|>One(u32) }", | ||
235 | r#"struct One(pub u32); | ||
236 | |||
237 | enum A { One(One) }"#, | ||
238 | ); | ||
239 | } | ||
240 | |||
241 | #[test] | ||
242 | fn test_extract_struct_pub_visibility() { | ||
243 | check_assist( | ||
244 | extract_struct_from_enum_variant, | ||
245 | "pub enum A { <|>One(u32, u32) }", | ||
246 | r#"pub struct One(pub u32, pub u32); | ||
247 | |||
248 | pub enum A { One(One) }"#, | ||
249 | ); | ||
250 | } | ||
251 | |||
252 | #[test] | ||
253 | fn test_extract_struct_with_complex_imports() { | ||
254 | check_assist( | ||
255 | extract_struct_from_enum_variant, | ||
256 | r#"mod my_mod { | ||
257 | fn another_fn() { | ||
258 | let m = my_other_mod::MyEnum::MyField(1, 1); | ||
259 | } | ||
260 | |||
261 | pub mod my_other_mod { | ||
262 | fn another_fn() { | ||
263 | let m = MyEnum::MyField(1, 1); | ||
264 | } | ||
265 | |||
266 | pub enum MyEnum { | ||
267 | <|>MyField(u8, u8), | ||
268 | } | ||
269 | } | ||
270 | } | ||
271 | |||
272 | fn another_fn() { | ||
273 | let m = my_mod::my_other_mod::MyEnum::MyField(1, 1); | ||
274 | }"#, | ||
275 | r#"use my_mod::my_other_mod::MyField; | ||
276 | |||
277 | mod my_mod { | ||
278 | use my_other_mod::MyField; | ||
279 | |||
280 | fn another_fn() { | ||
281 | let m = my_other_mod::MyEnum::MyField(MyField(1, 1)); | ||
282 | } | ||
283 | |||
284 | pub mod my_other_mod { | ||
285 | fn another_fn() { | ||
286 | let m = MyEnum::MyField(MyField(1, 1)); | ||
287 | } | ||
288 | |||
289 | pub struct MyField(pub u8, pub u8); | ||
290 | |||
291 | pub enum MyEnum { | ||
292 | MyField(MyField), | ||
293 | } | ||
294 | } | ||
295 | } | ||
296 | |||
297 | fn another_fn() { | ||
298 | let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1)); | ||
299 | }"#, | ||
300 | ); | ||
301 | } | ||
302 | |||
303 | fn check_not_applicable(ra_fixture: &str) { | ||
304 | let fixture = | ||
305 | format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); | ||
306 | check_assist_not_applicable(extract_struct_from_enum_variant, &fixture) | ||
307 | } | ||
308 | |||
309 | #[test] | ||
310 | fn test_extract_enum_not_applicable_for_element_with_no_fields() { | ||
311 | check_not_applicable("enum A { <|>One }"); | ||
312 | } | ||
313 | |||
314 | #[test] | ||
315 | fn test_extract_enum_not_applicable_if_struct_exists() { | ||
316 | check_not_applicable( | ||
317 | r#"struct One; | ||
318 | enum A { <|>One(u8) }"#, | ||
319 | ); | ||
320 | } | ||
321 | } | ||
diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/extract_variable.rs index 31d6539f7..481baf1a4 100644 --- a/crates/ra_assists/src/handlers/introduce_variable.rs +++ b/crates/ra_assists/src/handlers/extract_variable.rs | |||
@@ -9,9 +9,9 @@ use ra_syntax::{ | |||
9 | use stdx::format_to; | 9 | use stdx::format_to; |
10 | use test_utils::mark; | 10 | use test_utils::mark; |
11 | 11 | ||
12 | use crate::{AssistContext, AssistId, Assists}; | 12 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
13 | 13 | ||
14 | // Assist: introduce_variable | 14 | // Assist: extract_variable |
15 | // | 15 | // |
16 | // Extracts subexpression into a variable. | 16 | // Extracts subexpression into a variable. |
17 | // | 17 | // |
@@ -27,13 +27,13 @@ use crate::{AssistContext, AssistId, Assists}; | |||
27 | // var_name * 4; | 27 | // var_name * 4; |
28 | // } | 28 | // } |
29 | // ``` | 29 | // ``` |
30 | pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 30 | pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
31 | if ctx.frange.range.is_empty() { | 31 | if ctx.frange.range.is_empty() { |
32 | return None; | 32 | return None; |
33 | } | 33 | } |
34 | let node = ctx.covering_element(); | 34 | let node = ctx.covering_element(); |
35 | if node.kind() == COMMENT { | 35 | if node.kind() == COMMENT { |
36 | mark::hit!(introduce_var_in_comment_is_not_applicable); | 36 | mark::hit!(extract_var_in_comment_is_not_applicable); |
37 | return None; | 37 | return None; |
38 | } | 38 | } |
39 | let expr = node.ancestors().find_map(valid_target_expr)?; | 39 | let expr = node.ancestors().find_map(valid_target_expr)?; |
@@ -43,65 +43,85 @@ pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Opti | |||
43 | return None; | 43 | return None; |
44 | } | 44 | } |
45 | let target = expr.syntax().text_range(); | 45 | let target = expr.syntax().text_range(); |
46 | acc.add(AssistId("introduce_variable"), "Extract into variable", target, move |edit| { | 46 | acc.add( |
47 | let mut buf = String::new(); | 47 | AssistId("extract_variable", AssistKind::RefactorExtract), |
48 | 48 | "Extract into variable", | |
49 | if wrap_in_block { | 49 | target, |
50 | buf.push_str("{ let var_name = "); | 50 | move |edit| { |
51 | } else { | 51 | let field_shorthand = match expr.syntax().parent().and_then(ast::RecordField::cast) { |
52 | buf.push_str("let var_name = "); | 52 | Some(field) => field.name_ref(), |
53 | }; | 53 | None => None, |
54 | format_to!(buf, "{}", expr.syntax()); | 54 | }; |
55 | 55 | ||
56 | let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); | 56 | let mut buf = String::new(); |
57 | let is_full_stmt = if let Some(expr_stmt) = &full_stmt { | 57 | |
58 | Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) | 58 | let var_name = match &field_shorthand { |
59 | } else { | 59 | Some(it) => it.to_string(), |
60 | false | 60 | None => "var_name".to_string(), |
61 | }; | 61 | }; |
62 | if is_full_stmt { | 62 | let expr_range = match &field_shorthand { |
63 | mark::hit!(test_introduce_var_expr_stmt); | 63 | Some(it) => it.syntax().text_range().cover(expr.syntax().text_range()), |
64 | if full_stmt.unwrap().semicolon_token().is_none() { | 64 | None => expr.syntax().text_range(), |
65 | buf.push_str(";"); | 65 | }; |
66 | |||
67 | if wrap_in_block { | ||
68 | format_to!(buf, "{{ let {} = ", var_name); | ||
69 | } else { | ||
70 | format_to!(buf, "let {} = ", var_name); | ||
71 | }; | ||
72 | format_to!(buf, "{}", expr.syntax()); | ||
73 | |||
74 | let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); | ||
75 | let is_full_stmt = if let Some(expr_stmt) = &full_stmt { | ||
76 | Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) | ||
77 | } else { | ||
78 | false | ||
79 | }; | ||
80 | if is_full_stmt { | ||
81 | mark::hit!(test_extract_var_expr_stmt); | ||
82 | if full_stmt.unwrap().semicolon_token().is_none() { | ||
83 | buf.push_str(";"); | ||
84 | } | ||
85 | match ctx.config.snippet_cap { | ||
86 | Some(cap) => { | ||
87 | let snip = buf | ||
88 | .replace(&format!("let {}", var_name), &format!("let $0{}", var_name)); | ||
89 | edit.replace_snippet(cap, expr_range, snip) | ||
90 | } | ||
91 | None => edit.replace(expr_range, buf), | ||
92 | } | ||
93 | return; | ||
94 | } | ||
95 | |||
96 | buf.push_str(";"); | ||
97 | |||
98 | // We want to maintain the indent level, | ||
99 | // but we do not want to duplicate possible | ||
100 | // extra newlines in the indent block | ||
101 | let text = indent.text(); | ||
102 | if text.starts_with('\n') { | ||
103 | buf.push_str("\n"); | ||
104 | buf.push_str(text.trim_start_matches('\n')); | ||
105 | } else { | ||
106 | buf.push_str(text); | ||
66 | } | 107 | } |
67 | let offset = expr.syntax().text_range(); | 108 | |
109 | edit.replace(expr_range, var_name.clone()); | ||
110 | let offset = anchor_stmt.text_range().start(); | ||
68 | match ctx.config.snippet_cap { | 111 | match ctx.config.snippet_cap { |
69 | Some(cap) => { | 112 | Some(cap) => { |
70 | let snip = buf.replace("let var_name", "let $0var_name"); | 113 | let snip = |
71 | edit.replace_snippet(cap, offset, snip) | 114 | buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name)); |
115 | edit.insert_snippet(cap, offset, snip) | ||
72 | } | 116 | } |
73 | None => edit.replace(offset, buf), | 117 | None => edit.insert(offset, buf), |
74 | } | 118 | } |
75 | return; | ||
76 | } | ||
77 | 119 | ||
78 | buf.push_str(";"); | 120 | if wrap_in_block { |
79 | 121 | edit.insert(anchor_stmt.text_range().end(), " }"); | |
80 | // We want to maintain the indent level, | ||
81 | // but we do not want to duplicate possible | ||
82 | // extra newlines in the indent block | ||
83 | let text = indent.text(); | ||
84 | if text.starts_with('\n') { | ||
85 | buf.push_str("\n"); | ||
86 | buf.push_str(text.trim_start_matches('\n')); | ||
87 | } else { | ||
88 | buf.push_str(text); | ||
89 | } | ||
90 | |||
91 | edit.replace(expr.syntax().text_range(), "var_name".to_string()); | ||
92 | let offset = anchor_stmt.text_range().start(); | ||
93 | match ctx.config.snippet_cap { | ||
94 | Some(cap) => { | ||
95 | let snip = buf.replace("let var_name", "let $0var_name"); | ||
96 | edit.insert_snippet(cap, offset, snip) | ||
97 | } | 122 | } |
98 | None => edit.insert(offset, buf), | 123 | }, |
99 | } | 124 | ) |
100 | |||
101 | if wrap_in_block { | ||
102 | edit.insert(anchor_stmt.text_range().end(), " }"); | ||
103 | } | ||
104 | }) | ||
105 | } | 125 | } |
106 | 126 | ||
107 | /// Check whether the node is a valid expression which can be extracted to a variable. | 127 | /// Check whether the node is a valid expression which can be extracted to a variable. |
@@ -118,7 +138,7 @@ fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> { | |||
118 | } | 138 | } |
119 | } | 139 | } |
120 | 140 | ||
121 | /// Returns the syntax node which will follow the freshly introduced var | 141 | /// Returns the syntax node which will follow the freshly extractd var |
122 | /// and a boolean indicating whether we have to wrap it within a { } block | 142 | /// and a boolean indicating whether we have to wrap it within a { } block |
123 | /// to produce correct code. | 143 | /// to produce correct code. |
124 | /// It can be a statement, the last in a block expression or a wanna be block | 144 | /// It can be a statement, the last in a block expression or a wanna be block |
@@ -127,7 +147,7 @@ fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> { | |||
127 | expr.syntax().ancestors().find_map(|node| { | 147 | expr.syntax().ancestors().find_map(|node| { |
128 | if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) { | 148 | if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) { |
129 | if expr.syntax() == &node { | 149 | if expr.syntax() == &node { |
130 | mark::hit!(test_introduce_var_last_expr); | 150 | mark::hit!(test_extract_var_last_expr); |
131 | return Some((node, false)); | 151 | return Some((node, false)); |
132 | } | 152 | } |
133 | } | 153 | } |
@@ -155,9 +175,9 @@ mod tests { | |||
155 | use super::*; | 175 | use super::*; |
156 | 176 | ||
157 | #[test] | 177 | #[test] |
158 | fn test_introduce_var_simple() { | 178 | fn test_extract_var_simple() { |
159 | check_assist( | 179 | check_assist( |
160 | introduce_variable, | 180 | extract_variable, |
161 | r#" | 181 | r#" |
162 | fn foo() { | 182 | fn foo() { |
163 | foo(<|>1 + 1<|>); | 183 | foo(<|>1 + 1<|>); |
@@ -171,16 +191,16 @@ fn foo() { | |||
171 | } | 191 | } |
172 | 192 | ||
173 | #[test] | 193 | #[test] |
174 | fn introduce_var_in_comment_is_not_applicable() { | 194 | fn extract_var_in_comment_is_not_applicable() { |
175 | mark::check!(introduce_var_in_comment_is_not_applicable); | 195 | mark::check!(extract_var_in_comment_is_not_applicable); |
176 | check_assist_not_applicable(introduce_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }"); | 196 | check_assist_not_applicable(extract_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }"); |
177 | } | 197 | } |
178 | 198 | ||
179 | #[test] | 199 | #[test] |
180 | fn test_introduce_var_expr_stmt() { | 200 | fn test_extract_var_expr_stmt() { |
181 | mark::check!(test_introduce_var_expr_stmt); | 201 | mark::check!(test_extract_var_expr_stmt); |
182 | check_assist( | 202 | check_assist( |
183 | introduce_variable, | 203 | extract_variable, |
184 | r#" | 204 | r#" |
185 | fn foo() { | 205 | fn foo() { |
186 | <|>1 + 1<|>; | 206 | <|>1 + 1<|>; |
@@ -191,7 +211,7 @@ fn foo() { | |||
191 | }"#, | 211 | }"#, |
192 | ); | 212 | ); |
193 | check_assist( | 213 | check_assist( |
194 | introduce_variable, | 214 | extract_variable, |
195 | " | 215 | " |
196 | fn foo() { | 216 | fn foo() { |
197 | <|>{ let x = 0; x }<|> | 217 | <|>{ let x = 0; x }<|> |
@@ -206,9 +226,9 @@ fn foo() { | |||
206 | } | 226 | } |
207 | 227 | ||
208 | #[test] | 228 | #[test] |
209 | fn test_introduce_var_part_of_expr_stmt() { | 229 | fn test_extract_var_part_of_expr_stmt() { |
210 | check_assist( | 230 | check_assist( |
211 | introduce_variable, | 231 | extract_variable, |
212 | " | 232 | " |
213 | fn foo() { | 233 | fn foo() { |
214 | <|>1<|> + 1; | 234 | <|>1<|> + 1; |
@@ -222,38 +242,42 @@ fn foo() { | |||
222 | } | 242 | } |
223 | 243 | ||
224 | #[test] | 244 | #[test] |
225 | fn test_introduce_var_last_expr() { | 245 | fn test_extract_var_last_expr() { |
226 | mark::check!(test_introduce_var_last_expr); | 246 | mark::check!(test_extract_var_last_expr); |
227 | check_assist( | 247 | check_assist( |
228 | introduce_variable, | 248 | extract_variable, |
229 | " | 249 | r#" |
230 | fn foo() { | 250 | fn foo() { |
231 | bar(<|>1 + 1<|>) | 251 | bar(<|>1 + 1<|>) |
232 | }", | 252 | } |
233 | " | 253 | "#, |
254 | r#" | ||
234 | fn foo() { | 255 | fn foo() { |
235 | let $0var_name = 1 + 1; | 256 | let $0var_name = 1 + 1; |
236 | bar(var_name) | 257 | bar(var_name) |
237 | }", | 258 | } |
259 | "#, | ||
238 | ); | 260 | ); |
239 | check_assist( | 261 | check_assist( |
240 | introduce_variable, | 262 | extract_variable, |
241 | " | 263 | r#" |
242 | fn foo() { | 264 | fn foo() { |
243 | <|>bar(1 + 1)<|> | 265 | <|>bar(1 + 1)<|> |
244 | }", | 266 | } |
245 | " | 267 | "#, |
268 | r#" | ||
246 | fn foo() { | 269 | fn foo() { |
247 | let $0var_name = bar(1 + 1); | 270 | let $0var_name = bar(1 + 1); |
248 | var_name | 271 | var_name |
249 | }", | 272 | } |
273 | "#, | ||
250 | ) | 274 | ) |
251 | } | 275 | } |
252 | 276 | ||
253 | #[test] | 277 | #[test] |
254 | fn test_introduce_var_in_match_arm_no_block() { | 278 | fn test_extract_var_in_match_arm_no_block() { |
255 | check_assist( | 279 | check_assist( |
256 | introduce_variable, | 280 | extract_variable, |
257 | " | 281 | " |
258 | fn main() { | 282 | fn main() { |
259 | let x = true; | 283 | let x = true; |
@@ -276,9 +300,9 @@ fn main() { | |||
276 | } | 300 | } |
277 | 301 | ||
278 | #[test] | 302 | #[test] |
279 | fn test_introduce_var_in_match_arm_with_block() { | 303 | fn test_extract_var_in_match_arm_with_block() { |
280 | check_assist( | 304 | check_assist( |
281 | introduce_variable, | 305 | extract_variable, |
282 | " | 306 | " |
283 | fn main() { | 307 | fn main() { |
284 | let x = true; | 308 | let x = true; |
@@ -308,9 +332,9 @@ fn main() { | |||
308 | } | 332 | } |
309 | 333 | ||
310 | #[test] | 334 | #[test] |
311 | fn test_introduce_var_in_closure_no_block() { | 335 | fn test_extract_var_in_closure_no_block() { |
312 | check_assist( | 336 | check_assist( |
313 | introduce_variable, | 337 | extract_variable, |
314 | " | 338 | " |
315 | fn main() { | 339 | fn main() { |
316 | let lambda = |x: u32| <|>x * 2<|>; | 340 | let lambda = |x: u32| <|>x * 2<|>; |
@@ -325,9 +349,9 @@ fn main() { | |||
325 | } | 349 | } |
326 | 350 | ||
327 | #[test] | 351 | #[test] |
328 | fn test_introduce_var_in_closure_with_block() { | 352 | fn test_extract_var_in_closure_with_block() { |
329 | check_assist( | 353 | check_assist( |
330 | introduce_variable, | 354 | extract_variable, |
331 | " | 355 | " |
332 | fn main() { | 356 | fn main() { |
333 | let lambda = |x: u32| { <|>x * 2<|> }; | 357 | let lambda = |x: u32| { <|>x * 2<|> }; |
@@ -342,9 +366,9 @@ fn main() { | |||
342 | } | 366 | } |
343 | 367 | ||
344 | #[test] | 368 | #[test] |
345 | fn test_introduce_var_path_simple() { | 369 | fn test_extract_var_path_simple() { |
346 | check_assist( | 370 | check_assist( |
347 | introduce_variable, | 371 | extract_variable, |
348 | " | 372 | " |
349 | fn main() { | 373 | fn main() { |
350 | let o = <|>Some(true)<|>; | 374 | let o = <|>Some(true)<|>; |
@@ -360,9 +384,9 @@ fn main() { | |||
360 | } | 384 | } |
361 | 385 | ||
362 | #[test] | 386 | #[test] |
363 | fn test_introduce_var_path_method() { | 387 | fn test_extract_var_path_method() { |
364 | check_assist( | 388 | check_assist( |
365 | introduce_variable, | 389 | extract_variable, |
366 | " | 390 | " |
367 | fn main() { | 391 | fn main() { |
368 | let v = <|>bar.foo()<|>; | 392 | let v = <|>bar.foo()<|>; |
@@ -378,9 +402,9 @@ fn main() { | |||
378 | } | 402 | } |