diff options
47 files changed, 1124 insertions, 451 deletions
diff --git a/Cargo.lock b/Cargo.lock index 4412b0708..c06236692 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -2,9 +2,9 @@ | |||
2 | # It is not intended for manual editing. | 2 | # It is not intended for manual editing. |
3 | [[package]] | 3 | [[package]] |
4 | name = "addr2line" | 4 | name = "addr2line" |
5 | version = "0.12.0" | 5 | version = "0.12.1" |
6 | source = "registry+https://github.com/rust-lang/crates.io-index" | 6 | source = "registry+https://github.com/rust-lang/crates.io-index" |
7 | checksum = "456d75cbb82da1ad150c8a9d97285ffcd21c9931dcb11e995903e7d75141b38b" | 7 | checksum = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" |
8 | dependencies = [ | 8 | dependencies = [ |
9 | "gimli", | 9 | "gimli", |
10 | ] | 10 | ] |
@@ -20,9 +20,9 @@ dependencies = [ | |||
20 | 20 | ||
21 | [[package]] | 21 | [[package]] |
22 | name = "anyhow" | 22 | name = "anyhow" |
23 | version = "1.0.29" | 23 | version = "1.0.31" |
24 | source = "registry+https://github.com/rust-lang/crates.io-index" | 24 | source = "registry+https://github.com/rust-lang/crates.io-index" |
25 | checksum = "dc98824304f5513bb8f862f9e5985219003de4d730689e59d8f28818283a6fe4" | 25 | checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" |
26 | 26 | ||
27 | [[package]] | 27 | [[package]] |
28 | name = "anymap" | 28 | name = "anymap" |
@@ -55,9 +55,9 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" | |||
55 | 55 | ||
56 | [[package]] | 56 | [[package]] |
57 | name = "backtrace" | 57 | name = "backtrace" |
58 | version = "0.3.47" | 58 | version = "0.3.48" |
59 | source = "registry+https://github.com/rust-lang/crates.io-index" | 59 | source = "registry+https://github.com/rust-lang/crates.io-index" |
60 | checksum = "a5393cb2f40a6fae0014c9af00018e95846f3b241b331a6b7733c326d3e58108" | 60 | checksum = "0df2f85c8a2abbe3b7d7e748052fdd9b76a0458fdeb16ad4223f5eca78c7c130" |
61 | dependencies = [ | 61 | dependencies = [ |
62 | "addr2line", | 62 | "addr2line", |
63 | "cfg-if", | 63 | "cfg-if", |
@@ -101,9 +101,9 @@ dependencies = [ | |||
101 | 101 | ||
102 | [[package]] | 102 | [[package]] |
103 | name = "cc" | 103 | name = "cc" |
104 | version = "1.0.52" | 104 | version = "1.0.53" |
105 | source = "registry+https://github.com/rust-lang/crates.io-index" | 105 | source = "registry+https://github.com/rust-lang/crates.io-index" |
106 | checksum = "c3d87b23d6a92cd03af510a5ade527033f6aa6fa92161e2d5863a907d4c5e31d" | 106 | checksum = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c" |
107 | 107 | ||
108 | [[package]] | 108 | [[package]] |
109 | name = "cfg-if" | 109 | name = "cfg-if" |
@@ -360,9 +360,9 @@ checksum = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" | |||
360 | 360 | ||
361 | [[package]] | 361 | [[package]] |
362 | name = "fnv" | 362 | name = "fnv" |
363 | version = "1.0.6" | 363 | version = "1.0.7" |
364 | source = "registry+https://github.com/rust-lang/crates.io-index" | 364 | source = "registry+https://github.com/rust-lang/crates.io-index" |
365 | checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" | 365 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" |
366 | 366 | ||
367 | [[package]] | 367 | [[package]] |
368 | name = "fs_extra" | 368 | name = "fs_extra" |
@@ -463,9 +463,9 @@ dependencies = [ | |||
463 | 463 | ||
464 | [[package]] | 464 | [[package]] |
465 | name = "hermit-abi" | 465 | name = "hermit-abi" |
466 | version = "0.1.12" | 466 | version = "0.1.13" |
467 | source = "registry+https://github.com/rust-lang/crates.io-index" | 467 | source = "registry+https://github.com/rust-lang/crates.io-index" |
468 | checksum = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4" | 468 | checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" |
469 | dependencies = [ | 469 | dependencies = [ |
470 | "libc", | 470 | "libc", |
471 | ] | 471 | ] |
@@ -809,9 +809,9 @@ checksum = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2" | |||
809 | 809 | ||
810 | [[package]] | 810 | [[package]] |
811 | name = "once_cell" | 811 | name = "once_cell" |
812 | version = "1.3.1" | 812 | version = "1.4.0" |
813 | source = "registry+https://github.com/rust-lang/crates.io-index" | 813 | source = "registry+https://github.com/rust-lang/crates.io-index" |
814 | checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" | 814 | checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" |
815 | 815 | ||
816 | [[package]] | 816 | [[package]] |
817 | name = "ordermap" | 817 | name = "ordermap" |
@@ -895,9 +895,9 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" | |||
895 | 895 | ||
896 | [[package]] | 896 | [[package]] |
897 | name = "ppv-lite86" | 897 | name = "ppv-lite86" |
898 | version = "0.2.6" | 898 | version = "0.2.8" |
899 | source = "registry+https://github.com/rust-lang/crates.io-index" | 899 | source = "registry+https://github.com/rust-lang/crates.io-index" |
900 | checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" | 900 | checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" |
901 | 901 | ||
902 | [[package]] | 902 | [[package]] |
903 | name = "proc-macro-hack" | 903 | name = "proc-macro-hack" |
@@ -907,18 +907,18 @@ checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" | |||
907 | 907 | ||
908 | [[package]] | 908 | [[package]] |
909 | name = "proc-macro2" | 909 | name = "proc-macro2" |
910 | version = "1.0.12" | 910 | version = "1.0.13" |
911 | source = "registry+https://github.com/rust-lang/crates.io-index" | 911 | source = "registry+https://github.com/rust-lang/crates.io-index" |
912 | checksum = "8872cf6f48eee44265156c111456a700ab3483686b3f96df4cf5481c89157319" | 912 | checksum = "53f5ffe53a6b28e37c9c1ce74893477864d64f74778a93a4beb43c8fa167f639" |
913 | dependencies = [ | 913 | dependencies = [ |
914 | "unicode-xid", | 914 | "unicode-xid", |
915 | ] | 915 | ] |
916 | 916 | ||
917 | [[package]] | 917 | [[package]] |
918 | name = "quote" | 918 | name = "quote" |
919 | version = "1.0.5" | 919 | version = "1.0.6" |
920 | source = "registry+https://github.com/rust-lang/crates.io-index" | 920 | source = "registry+https://github.com/rust-lang/crates.io-index" |
921 | checksum = "42934bc9c8ab0d3b273a16d8551c8f0fcff46be73276ca083ec2414c15c4ba5e" | 921 | checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" |
922 | dependencies = [ | 922 | dependencies = [ |
923 | "proc-macro2", | 923 | "proc-macro2", |
924 | ] | 924 | ] |
@@ -1610,9 +1610,9 @@ checksum = "ab16ced94dbd8a46c82fd81e3ed9a8727dac2977ea869d217bcc4ea1f122e81f" | |||
1610 | 1610 | ||
1611 | [[package]] | 1611 | [[package]] |
1612 | name = "syn" | 1612 | name = "syn" |
1613 | version = "1.0.21" | 1613 | version = "1.0.22" |
1614 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1614 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1615 | checksum = "4696caa4048ac7ce2bcd2e484b3cef88c1004e41b8e945a277e2c25dc0b72060" | 1615 | checksum = "1425de3c33b0941002740a420b1a906a350b88d08b82b2c8a01035a3f9447bac" |
1616 | dependencies = [ | 1616 | dependencies = [ |
1617 | "proc-macro2", | 1617 | "proc-macro2", |
1618 | "quote", | 1618 | "quote", |
diff --git a/crates/ra_assists/src/assist_config.rs b/crates/ra_assists/src/assist_config.rs new file mode 100644 index 000000000..c0a0226fb --- /dev/null +++ b/crates/ra_assists/src/assist_config.rs | |||
@@ -0,0 +1,27 @@ | |||
1 | //! Settings for tweaking assists. | ||
2 | //! | ||
3 | //! The fun thing here is `SnippetCap` -- this type can only be created in this | ||
4 | //! module, and we use to statically check that we only produce snippet | ||
5 | //! assists if we are allowed to. | ||
6 | |||
7 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
8 | pub struct AssistConfig { | ||
9 | pub snippet_cap: Option<SnippetCap>, | ||
10 | } | ||
11 | |||
12 | impl AssistConfig { | ||
13 | pub fn allow_snippets(&mut self, yes: bool) { | ||
14 | self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None } | ||
15 | } | ||
16 | } | ||
17 | |||
18 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
19 | pub struct SnippetCap { | ||
20 | _private: (), | ||
21 | } | ||
22 | |||
23 | impl Default for AssistConfig { | ||
24 | fn default() -> Self { | ||
25 | AssistConfig { snippet_cap: Some(SnippetCap { _private: () }) } | ||
26 | } | ||
27 | } | ||
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs index a680f752b..005c17776 100644 --- a/crates/ra_assists/src/assist_context.rs +++ b/crates/ra_assists/src/assist_context.rs | |||
@@ -15,7 +15,10 @@ use ra_syntax::{ | |||
15 | }; | 15 | }; |
16 | use ra_text_edit::TextEditBuilder; | 16 | use ra_text_edit::TextEditBuilder; |
17 | 17 | ||
18 | use crate::{Assist, AssistId, GroupLabel, ResolvedAssist}; | 18 | use crate::{ |
19 | assist_config::{AssistConfig, SnippetCap}, | ||
20 | Assist, AssistId, GroupLabel, ResolvedAssist, | ||
21 | }; | ||
19 | 22 | ||
20 | /// `AssistContext` allows to apply an assist or check if it could be applied. | 23 | /// `AssistContext` allows to apply an assist or check if it could be applied. |
21 | /// | 24 | /// |
@@ -48,6 +51,7 @@ use crate::{Assist, AssistId, GroupLabel, ResolvedAssist}; | |||
48 | /// moment, because the LSP API is pretty awkward in this place, and it's much | 51 | /// moment, because the LSP API is pretty awkward in this place, and it's much |
49 | /// easier to just compute the edit eagerly :-) | 52 | /// easier to just compute the edit eagerly :-) |
50 | pub(crate) struct AssistContext<'a> { | 53 | pub(crate) struct AssistContext<'a> { |
54 | pub(crate) config: &'a AssistConfig, | ||
51 | pub(crate) sema: Semantics<'a, RootDatabase>, | 55 | pub(crate) sema: Semantics<'a, RootDatabase>, |
52 | pub(crate) db: &'a RootDatabase, | 56 | pub(crate) db: &'a RootDatabase, |
53 | pub(crate) frange: FileRange, | 57 | pub(crate) frange: FileRange, |
@@ -55,10 +59,14 @@ pub(crate) struct AssistContext<'a> { | |||
55 | } | 59 | } |
56 | 60 | ||
57 | impl<'a> AssistContext<'a> { | 61 | impl<'a> AssistContext<'a> { |
58 | pub fn new(sema: Semantics<'a, RootDatabase>, frange: FileRange) -> AssistContext<'a> { | 62 | pub(crate) fn new( |
63 | sema: Semantics<'a, RootDatabase>, | ||
64 | config: &'a AssistConfig, | ||
65 | frange: FileRange, | ||
66 | ) -> AssistContext<'a> { | ||
59 | let source_file = sema.parse(frange.file_id); | 67 | let source_file = sema.parse(frange.file_id); |
60 | let db = sema.db; | 68 | let db = sema.db; |
61 | AssistContext { sema, db, frange, source_file } | 69 | AssistContext { config, sema, db, frange, source_file } |
62 | } | 70 | } |
63 | 71 | ||
64 | // NB, this ignores active selection. | 72 | // NB, this ignores active selection. |
@@ -165,11 +173,17 @@ pub(crate) struct AssistBuilder { | |||
165 | edit: TextEditBuilder, | 173 | edit: TextEditBuilder, |
166 | cursor_position: Option<TextSize>, | 174 | cursor_position: Option<TextSize>, |
167 | file: FileId, | 175 | file: FileId, |
176 | is_snippet: bool, | ||
168 | } | 177 | } |
169 | 178 | ||
170 | impl AssistBuilder { | 179 | impl AssistBuilder { |
171 | pub(crate) fn new(file: FileId) -> AssistBuilder { | 180 | pub(crate) fn new(file: FileId) -> AssistBuilder { |
172 | AssistBuilder { edit: TextEditBuilder::default(), cursor_position: None, file } | 181 | AssistBuilder { |
182 | edit: TextEditBuilder::default(), | ||
183 | cursor_position: None, | ||
184 | file, | ||
185 | is_snippet: false, | ||
186 | } | ||
173 | } | 187 | } |
174 | 188 | ||
175 | /// Remove specified `range` of text. | 189 | /// Remove specified `range` of text. |
@@ -180,10 +194,30 @@ impl AssistBuilder { | |||
180 | pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) { | 194 | pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) { |
181 | self.edit.insert(offset, text.into()) | 195 | self.edit.insert(offset, text.into()) |
182 | } | 196 | } |
197 | /// Append specified `snippet` at the given `offset` | ||
198 | pub(crate) fn insert_snippet( | ||
199 | &mut self, | ||
200 | _cap: SnippetCap, | ||
201 | offset: TextSize, | ||
202 | snippet: impl Into<String>, | ||
203 | ) { | ||
204 | self.is_snippet = true; | ||
205 | self.insert(offset, snippet); | ||
206 | } | ||
183 | /// Replaces specified `range` of text with a given string. | 207 | /// Replaces specified `range` of text with a given string. |
184 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { | 208 | pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { |
185 | self.edit.replace(range, replace_with.into()) | 209 | self.edit.replace(range, replace_with.into()) |
186 | } | 210 | } |
211 | /// Replaces specified `range` of text with a given `snippet`. | ||
212 | pub(crate) fn replace_snippet( | ||
213 | &mut self, | ||
214 | _cap: SnippetCap, | ||
215 | range: TextRange, | ||
216 | snippet: impl Into<String>, | ||
217 | ) { | ||
218 | self.is_snippet = true; | ||
219 | self.replace(range, snippet); | ||
220 | } | ||
187 | pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { | 221 | pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { |
188 | algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) | 222 | algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) |
189 | } | 223 | } |
@@ -227,7 +261,12 @@ impl AssistBuilder { | |||
227 | if edit.is_empty() && self.cursor_position.is_none() { | 261 | if edit.is_empty() && self.cursor_position.is_none() { |
228 | panic!("Only call `add_assist` if the assist can be applied") | 262 | panic!("Only call `add_assist` if the assist can be applied") |
229 | } | 263 | } |
230 | SingleFileChange { label: change_label, edit, cursor_position: self.cursor_position } | 264 | let mut res = |
231 | .into_source_change(self.file) | 265 | SingleFileChange { label: change_label, edit, cursor_position: self.cursor_position } |
266 | .into_source_change(self.file); | ||
267 | if self.is_snippet { | ||
268 | res.is_snippet = true; | ||
269 | } | ||
270 | res | ||
232 | } | 271 | } |
233 | } | 272 | } |
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs index 2baeb8607..fa70c8496 100644 --- a/crates/ra_assists/src/handlers/add_custom_impl.rs +++ b/crates/ra_assists/src/handlers/add_custom_impl.rs | |||
@@ -25,7 +25,7 @@ use crate::{ | |||
25 | // struct S; | 25 | // struct S; |
26 | // | 26 | // |
27 | // impl Debug for S { | 27 | // impl Debug for S { |
28 | // | 28 | // $0 |
29 | // } | 29 | // } |
30 | // ``` | 30 | // ``` |
31 | pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 31 | pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
@@ -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, |edit| { | 55 | acc.add(AssistId("add_custom_impl"), 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() |
@@ -63,20 +63,11 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
63 | let has_more_derives = !new_attr_input.is_empty(); | 63 | let has_more_derives = !new_attr_input.is_empty(); |
64 | let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string(); | 64 | let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string(); |
65 | 65 | ||
66 | let mut buf = String::new(); | 66 | if has_more_derives { |
67 | buf.push_str("\n\nimpl "); | 67 | builder.replace(input.syntax().text_range(), new_attr_input); |
68 | buf.push_str(trait_token.text().as_str()); | ||
69 | buf.push_str(" for "); | ||
70 | buf.push_str(annotated_name.as_str()); | ||
71 | buf.push_str(" {\n"); | ||
72 | |||
73 | let cursor_delta = if has_more_derives { | ||
74 | let delta = input.syntax().text_range().len() - TextSize::of(&new_attr_input); | ||
75 | edit.replace(input.syntax().text_range(), new_attr_input); | ||
76 | delta | ||
77 | } else { | 68 | } else { |
78 | let attr_range = attr.syntax().text_range(); | 69 | let attr_range = attr.syntax().text_range(); |
79 | edit.delete(attr_range); | 70 | builder.delete(attr_range); |
80 | 71 | ||
81 | let line_break_range = attr | 72 | let line_break_range = attr |
82 | .syntax() | 73 | .syntax() |
@@ -84,14 +75,24 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
84 | .filter(|t| t.kind() == WHITESPACE) | 75 | .filter(|t| t.kind() == WHITESPACE) |
85 | .map(|t| t.text_range()) | 76 | .map(|t| t.text_range()) |
86 | .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0))); | 77 | .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0))); |
87 | edit.delete(line_break_range); | 78 | builder.delete(line_break_range); |
88 | 79 | } | |
89 | attr_range.len() + line_break_range.len() | 80 | |
90 | }; | 81 | match ctx.config.snippet_cap { |
91 | 82 | Some(cap) => { | |
92 | edit.set_cursor(start_offset + TextSize::of(&buf) - cursor_delta); | 83 | builder.insert_snippet( |
93 | buf.push_str("\n}"); | 84 | cap, |
94 | edit.insert(start_offset, buf); | 85 | start_offset, |
86 | format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name), | ||
87 | ); | ||
88 | } | ||
89 | None => { | ||
90 | builder.insert( | ||
91 | start_offset, | ||
92 | format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name), | ||
93 | ); | ||
94 | } | ||
95 | } | ||
95 | }) | 96 | }) |
96 | } | 97 | } |
97 | 98 | ||
@@ -117,7 +118,7 @@ struct Foo { | |||
117 | } | 118 | } |
118 | 119 | ||
119 | impl Debug for Foo { | 120 | impl Debug for Foo { |
120 | <|> | 121 | $0 |
121 | } | 122 | } |
122 | ", | 123 | ", |
123 | ) | 124 | ) |
@@ -139,7 +140,7 @@ pub struct Foo { | |||
139 | } | 140 | } |
140 | 141 | ||
141 | impl Debug for Foo { | 142 | impl Debug for Foo { |
142 | <|> | 143 | $0 |
143 | } | 144 | } |
144 | ", | 145 | ", |
145 | ) | 146 | ) |
@@ -158,7 +159,7 @@ struct Foo {} | |||
158 | struct Foo {} | 159 | struct Foo {} |
159 | 160 | ||
160 | impl Debug for Foo { | 161 | impl Debug for Foo { |
161 | <|> | 162 | $0 |
162 | } | 163 | } |
163 | ", | 164 | ", |
164 | ) | 165 | ) |
diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/add_derive.rs index fb08c19e9..b123b8498 100644 --- a/crates/ra_assists/src/handlers/add_derive.rs +++ b/crates/ra_assists/src/handlers/add_derive.rs | |||
@@ -18,31 +18,37 @@ use crate::{AssistContext, AssistId, Assists}; | |||
18 | // ``` | 18 | // ``` |
19 | // -> | 19 | // -> |
20 | // ``` | 20 | // ``` |
21 | // #[derive()] | 21 | // #[derive($0)] |
22 | // struct Point { | 22 | // struct Point { |
23 | // x: u32, | 23 | // x: u32, |
24 | // y: u32, | 24 | // y: u32, |
25 | // } | 25 | // } |
26 | // ``` | 26 | // ``` |
27 | pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 27 | pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
28 | let cap = ctx.config.snippet_cap?; | ||
28 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; | 29 | let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; |
29 | let node_start = derive_insertion_offset(&nominal)?; | 30 | let node_start = derive_insertion_offset(&nominal)?; |
30 | let target = nominal.syntax().text_range(); | 31 | let target = nominal.syntax().text_range(); |
31 | acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |edit| { | 32 | acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |builder| { |
32 | let derive_attr = nominal | 33 | let derive_attr = nominal |
33 | .attrs() | 34 | .attrs() |
34 | .filter_map(|x| x.as_simple_call()) | 35 | .filter_map(|x| x.as_simple_call()) |
35 | .filter(|(name, _arg)| name == "derive") | 36 | .filter(|(name, _arg)| name == "derive") |
36 | .map(|(_name, arg)| arg) | 37 | .map(|(_name, arg)| arg) |
37 | .next(); | 38 | .next(); |
38 | let offset = match derive_attr { | 39 | match derive_attr { |
39 | None => { | 40 | None => { |
40 | edit.insert(node_start, "#[derive()]\n"); | 41 | builder.insert_snippet(cap, node_start, "#[derive($0)]\n"); |
41 | node_start + TextSize::of("#[derive(") | 42 | } |
43 | Some(tt) => { | ||
44 | // Just move the cursor. | ||
45 | builder.insert_snippet( | ||
46 | cap, | ||
47 | tt.syntax().text_range().end() - TextSize::of(')'), | ||
48 | "$0", | ||
49 | ) | ||
42 | } | 50 | } |
43 | Some(tt) => tt.syntax().text_range().end() - TextSize::of(')'), | ||
44 | }; | 51 | }; |
45 | edit.set_cursor(offset) | ||
46 | }) | 52 | }) |
47 | } | 53 | } |
48 | 54 | ||
@@ -66,12 +72,12 @@ mod tests { | |||
66 | check_assist( | 72 | check_assist( |
67 | add_derive, | 73 | add_derive, |
68 | "struct Foo { a: i32, <|>}", | 74 | "struct Foo { a: i32, <|>}", |
69 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | 75 | "#[derive($0)]\nstruct Foo { a: i32, }", |
70 | ); | 76 | ); |
71 | check_assist( | 77 | check_assist( |
72 | add_derive, | 78 | add_derive, |
73 | "struct Foo { <|> a: i32, }", | 79 | "struct Foo { <|> a: i32, }", |
74 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | 80 | "#[derive($0)]\nstruct Foo { a: i32, }", |
75 | ); | 81 | ); |
76 | } | 82 | } |
77 | 83 | ||
@@ -80,7 +86,7 @@ mod tests { | |||
80 | check_assist( | 86 | check_assist( |
81 | add_derive, | 87 | add_derive, |
82 | "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", | 88 | "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", |
83 | "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", | 89 | "#[derive(Clone$0)]\nstruct Foo { a: i32, }", |
84 | ); | 90 | ); |
85 | } | 91 | } |
86 | 92 | ||
@@ -96,7 +102,7 @@ struct Foo { a: i32<|>, } | |||
96 | " | 102 | " |
97 | /// `Foo` is a pretty important struct. | 103 | /// `Foo` is a pretty important struct. |
98 | /// It does stuff. | 104 | /// It does stuff. |
99 | #[derive(<|>)] | 105 | #[derive($0)] |
100 | struct Foo { a: i32, } | 106 | struct Foo { a: i32, } |
101 | ", | 107 | ", |
102 | ); | 108 | ); |
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs index 0c7d5e355..770049212 100644 --- a/crates/ra_assists/src/handlers/add_explicit_type.rs +++ b/crates/ra_assists/src/handlers/add_explicit_type.rs | |||
@@ -25,9 +25,8 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio | |||
25 | let stmt = ctx.find_node_at_offset::<LetStmt>()?; | 25 | let stmt = ctx.find_node_at_offset::<LetStmt>()?; |
26 | let module = ctx.sema.scope(stmt.syntax()).module()?; | 26 | let module = ctx.sema.scope(stmt.syntax()).module()?; |
27 | let expr = stmt.initializer()?; | 27 | let expr = stmt.initializer()?; |
28 | let pat = stmt.pat()?; | ||
29 | // Must be a binding | 28 | // Must be a binding |
30 | let pat = match pat { | 29 | let pat = match stmt.pat()? { |
31 | ast::Pat::BindPat(bind_pat) => bind_pat, | 30 | ast::Pat::BindPat(bind_pat) => bind_pat, |
32 | _ => return None, | 31 | _ => return None, |
33 | }; | 32 | }; |
@@ -46,7 +45,7 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio | |||
46 | // Assist not applicable if the type has already been specified | 45 | // Assist not applicable if the type has already been specified |
47 | // and it has no placeholders | 46 | // and it has no placeholders |
48 | let ascribed_ty = stmt.ascribed_type(); | 47 | let ascribed_ty = stmt.ascribed_type(); |
49 | if let Some(ref ty) = ascribed_ty { | 48 | if let Some(ty) = &ascribed_ty { |
50 | if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() { | 49 | if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() { |
51 | return None; | 50 | return None; |
52 | } | 51 | } |
diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs index 6a49b7dbd..eb57b7231 100644 --- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs +++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs | |||
@@ -1,6 +1,5 @@ | |||
1 | use ra_ide_db::RootDatabase; | 1 | use ra_ide_db::RootDatabase; |
2 | use ra_syntax::ast::{self, AstNode, NameOwner}; | 2 | use ra_syntax::ast::{self, AstNode, NameOwner}; |
3 | use stdx::format_to; | ||
4 | use test_utils::tested_by; | 3 | use test_utils::tested_by; |
5 | 4 | ||
6 | use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; | 5 | use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; |
@@ -35,7 +34,7 @@ pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> | |||
35 | } | 34 | } |
36 | let field_type = field_list.fields().next()?.type_ref()?; | 35 | let field_type = field_list.fields().next()?.type_ref()?; |
37 | let path = match field_type { | 36 | let path = match field_type { |
38 | ast::TypeRef::PathType(p) => p, | 37 | ast::TypeRef::PathType(it) => it, |
39 | _ => return None, | 38 | _ => return None, |
40 | }; | 39 | }; |
41 | 40 | ||
@@ -51,9 +50,7 @@ pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> | |||
51 | target, | 50 | target, |
52 | |edit| { | 51 | |edit| { |
53 | let start_offset = variant.parent_enum().syntax().text_range().end(); | 52 | let start_offset = variant.parent_enum().syntax().text_range().end(); |
54 | let mut buf = String::new(); | 53 | let buf = format!( |
55 | format_to!( | ||
56 | buf, | ||
57 | r#" | 54 | r#" |
58 | 55 | ||
59 | impl From<{0}> for {1} {{ | 56 | impl From<{0}> for {1} {{ |
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs index de016ae4e..24f931a85 100644 --- a/crates/ra_assists/src/handlers/add_function.rs +++ b/crates/ra_assists/src/handlers/add_function.rs | |||
@@ -4,13 +4,17 @@ use ra_syntax::{ | |||
4 | ast::{ | 4 | ast::{ |
5 | self, | 5 | self, |
6 | edit::{AstNodeEdit, IndentLevel}, | 6 | edit::{AstNodeEdit, IndentLevel}, |
7 | ArgListOwner, AstNode, ModuleItemOwner, | 7 | make, ArgListOwner, AstNode, ModuleItemOwner, |
8 | }, | 8 | }, |
9 | SyntaxKind, SyntaxNode, TextSize, | 9 | SyntaxKind, SyntaxNode, TextSize, |
10 | }; | 10 | }; |
11 | use rustc_hash::{FxHashMap, FxHashSet}; | 11 | use rustc_hash::{FxHashMap, FxHashSet}; |
12 | 12 | ||
13 | use crate::{AssistContext, AssistId, Assists}; | 13 | use crate::{ |
14 | assist_config::SnippetCap, | ||
15 | utils::{render_snippet, Cursor}, | ||
16 | AssistContext, AssistId, Assists, | ||
17 | }; | ||
14 | 18 | ||
15 | // Assist: add_function | 19 | // Assist: add_function |
16 | // | 20 | // |
@@ -33,7 +37,7 @@ use crate::{AssistContext, AssistId, Assists}; | |||
33 | // } | 37 | // } |
34 | // | 38 | // |
35 | // fn bar(arg: &str, baz: Baz) { | 39 | // fn bar(arg: &str, baz: Baz) { |
36 | // todo!() | 40 | // ${0:todo!()} |
37 | // } | 41 | // } |
38 | // | 42 | // |
39 | // ``` | 43 | // ``` |
@@ -58,21 +62,40 @@ pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
58 | let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; | 62 | let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; |
59 | 63 | ||
60 | let target = call.syntax().text_range(); | 64 | let target = call.syntax().text_range(); |
61 | acc.add(AssistId("add_function"), "Add function", target, |edit| { | 65 | acc.add(AssistId("add_function"), "Add function", target, |builder| { |
62 | let function_template = function_builder.render(); | 66 | let function_template = function_builder.render(); |
63 | edit.set_file(function_template.file); | 67 | builder.set_file(function_template.file); |
64 | edit.set_cursor(function_template.cursor_offset); | 68 | let new_fn = function_template.to_string(ctx.config.snippet_cap); |
65 | edit.insert(function_template.insert_offset, function_template.fn_def.to_string()); | 69 | match ctx.config.snippet_cap { |
70 | Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn), | ||
71 | None => builder.insert(function_template.insert_offset, new_fn), | ||
72 | } | ||
66 | }) | 73 | }) |
67 | } | 74 | } |
68 | 75 | ||
69 | struct FunctionTemplate { | 76 | struct FunctionTemplate { |
70 | insert_offset: TextSize, | 77 | insert_offset: TextSize, |
71 | cursor_offset: TextSize, | 78 | placeholder_expr: ast::MacroCall, |
72 | fn_def: ast::SourceFile, | 79 | leading_ws: String, |
80 | fn_def: ast::FnDef, | ||
81 | trailing_ws: String, | ||
73 | file: FileId, | 82 | file: FileId, |
74 | } | 83 | } |
75 | 84 | ||
85 | impl FunctionTemplate { | ||
86 | fn to_string(&self, cap: Option<SnippetCap>) -> String { | ||
87 | let f = match cap { | ||
88 | Some(cap) => render_snippet( | ||
89 | cap, | ||
90 | self.fn_def.syntax(), | ||
91 | Cursor::Replace(self.placeholder_expr.syntax()), | ||
92 | ), | ||
93 | None => self.fn_def.to_string(), | ||
94 | }; | ||
95 | format!("{}{}{}", self.leading_ws, f, self.trailing_ws) | ||
96 | } | ||
97 | } | ||
98 | |||
76 | struct FunctionBuilder { | 99 | struct FunctionBuilder { |
77 | target: GeneratedFunctionTarget, | 100 | target: GeneratedFunctionTarget, |
78 | fn_name: ast::Name, | 101 | fn_name: ast::Name, |
@@ -110,35 +133,41 @@ impl FunctionBuilder { | |||
110 | } | 133 | } |
111 | 134 | ||
112 | fn render(self) -> FunctionTemplate { | 135 | fn render(self) -> FunctionTemplate { |
113 | let placeholder_expr = ast::make::expr_todo(); | 136 | let placeholder_expr = make::expr_todo(); |
114 | let fn_body = ast::make::block_expr(vec![], Some(placeholder_expr)); | 137 | let fn_body = make::block_expr(vec![], Some(placeholder_expr)); |
115 | let mut fn_def = ast::make::fn_def(self.fn_name, self.type_params, self.params, fn_body); | 138 | let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None }; |
116 | if self.needs_pub { | 139 | let mut fn_def = |
117 | fn_def = ast::make::add_pub_crate_modifier(fn_def); | 140 | make::fn_def(visibility, self.fn_name, self.type_params, self.params, fn_body); |
118 | } | 141 | let leading_ws; |
119 | 142 | let trailing_ws; | |
120 | let (fn_def, insert_offset) = match self.target { | 143 | |
144 | let insert_offset = match self.target { | ||
121 | GeneratedFunctionTarget::BehindItem(it) => { | 145 | GeneratedFunctionTarget::BehindItem(it) => { |
122 | let with_leading_blank_line = ast::make::add_leading_newlines(2, fn_def); | 146 | let indent = IndentLevel::from_node(&it); |
123 | let indented = with_leading_blank_line.indent(IndentLevel::from_node(&it)); | 147 | leading_ws = format!("\n\n{}", indent); |
124 | (indented, it.text_range().end()) | 148 | fn_def = fn_def.indent(indent); |
149 | trailing_ws = String::new(); | ||
150 | it.text_range().end() | ||
125 | } | 151 | } |
126 | GeneratedFunctionTarget::InEmptyItemList(it) => { | 152 | GeneratedFunctionTarget::InEmptyItemList(it) => { |
127 | let indent_once = IndentLevel(1); | ||
128 | let indent = IndentLevel::from_node(it.syntax()); | 153 | let indent = IndentLevel::from_node(it.syntax()); |
129 | let fn_def = ast::make::add_leading_newlines(1, fn_def); | 154 | leading_ws = format!("\n{}", indent + 1); |
130 | let fn_def = fn_def.indent(indent_once); | 155 | fn_def = fn_def.indent(indent + 1); |
131 | let fn_def = ast::make::add_trailing_newlines(1, fn_def); | 156 | trailing_ws = format!("\n{}", indent); |
132 | let fn_def = fn_def.indent(indent); | 157 | it.syntax().text_range().start() + TextSize::of('{') |
133 | (fn_def, it.syntax().text_range().start() + TextSize::of('{')) | ||
134 | } | 158 | } |
135 | }; | 159 | }; |
136 | 160 | ||
137 | let placeholder_expr = | 161 | let placeholder_expr = |
138 | fn_def.syntax().descendants().find_map(ast::MacroCall::cast).unwrap(); | 162 | fn_def.syntax().descendants().find_map(ast::MacroCall::cast).unwrap(); |
139 | let cursor_offset_from_fn_start = placeholder_expr.syntax().text_range().start(); | 163 | FunctionTemplate { |
140 | let cursor_offset = insert_offset + cursor_offset_from_fn_start; | 164 | insert_offset, |
141 | FunctionTemplate { insert_offset, cursor_offset, fn_def, file: self.file } | 165 | placeholder_expr, |
166 | leading_ws, | ||
167 | fn_def, | ||
168 | trailing_ws, | ||
169 | file: self.file, | ||
170 | } | ||
142 | } | 171 | } |
143 | } | 172 | } |
144 | 173 | ||
@@ -158,7 +187,7 @@ impl GeneratedFunctionTarget { | |||
158 | 187 | ||
159 | fn fn_name(call: &ast::Path) -> Option<ast::Name> { | 188 | fn fn_name(call: &ast::Path) -> Option<ast::Name> { |
160 | let name = call.segment()?.syntax().to_string(); | 189 | let name = call.segment()?.syntax().to_string(); |
161 | Some(ast::make::name(&name)) | 190 | Some(make::name(&name)) |
162 | } | 191 | } |
163 | 192 | ||
164 | /// Computes the type variables and arguments required for the generated function | 193 | /// Computes the type variables and arguments required for the generated function |
@@ -180,8 +209,8 @@ fn fn_args( | |||
180 | }); | 209 | }); |
181 | } | 210 | } |
182 | deduplicate_arg_names(&mut arg_names); | 211 | deduplicate_arg_names(&mut arg_names); |
183 | let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| ast::make::param(name, ty)); | 212 | let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| make::param(name, ty)); |
184 | Some((None, ast::make::param_list(params))) | 213 | Some((None, make::param_list(params))) |
185 | } | 214 | } |
186 | 215 | ||
187 | /// Makes duplicate argument names unique by appending incrementing numbers. | 216 | /// Makes duplicate argument names unique by appending incrementing numbers. |
@@ -316,7 +345,7 @@ fn foo() { | |||
316 | } | 345 | } |
317 | 346 | ||
318 | fn bar() { | 347 | fn bar() { |
319 | <|>todo!() | 348 | ${0:todo!()} |
320 | } | 349 | } |
321 | ", | 350 | ", |
322 | ) | 351 | ) |
@@ -343,7 +372,7 @@ impl Foo { | |||
343 | } | 372 | } |
344 | 373 | ||
345 | fn bar() { | 374 | fn bar() { |
346 | <|>todo!() | 375 | ${0:todo!()} |
347 | } | 376 | } |
348 | ", | 377 | ", |
349 | ) | 378 | ) |
@@ -367,7 +396,7 @@ fn foo1() { | |||
367 | } | 396 | } |
368 | 397 | ||
369 | fn bar() { | 398 | fn bar() { |
370 | <|>todo!() | 399 | ${0:todo!()} |
371 | } | 400 | } |
372 | 401 | ||
373 | fn foo2() {} | 402 | fn foo2() {} |
@@ -393,7 +422,7 @@ mod baz { | |||
393 | } | 422 | } |
394 | 423 | ||
395 | fn bar() { | 424 | fn bar() { |
396 | <|>todo!() | 425 | ${0:todo!()} |
397 | } | 426 | } |
398 | } | 427 | } |
399 | ", | 428 | ", |
@@ -419,7 +448,7 @@ fn foo() { | |||
419 | } | 448 | } |
420 | 449 | ||
421 | fn bar(baz: Baz) { | 450 | fn bar(baz: Baz) { |
422 | <|>todo!() | 451 | ${0:todo!()} |
423 | } | 452 | } |
424 | ", | 453 | ", |
425 | ); | 454 | ); |
@@ -452,7 +481,7 @@ impl Baz { | |||
452 | } | 481 | } |
453 | 482 | ||
454 | fn bar(baz: Baz) { | 483 | fn bar(baz: Baz) { |
455 | <|>todo!() | 484 | ${0:todo!()} |
456 | } | 485 | } |
457 | ", | 486 | ", |
458 | ) | 487 | ) |
@@ -473,7 +502,7 @@ fn foo() { | |||
473 | } | 502 | } |
474 | 503 | ||
475 | fn bar(arg: &str) { | 504 | fn bar(arg: &str) { |
476 | <|>todo!() | 505 | ${0:todo!()} |
477 | } | 506 | } |
478 | "#, | 507 | "#, |
479 | ) | 508 | ) |
@@ -494,7 +523,7 @@ fn foo() { | |||
494 | } | 523 | } |
495 | 524 | ||
496 | fn bar(arg: char) { | 525 | fn bar(arg: char) { |
497 | <|>todo!() | 526 | ${0:todo!()} |
498 | } | 527 | } |
499 | "#, | 528 | "#, |
500 | ) | 529 | ) |
@@ -515,7 +544,7 @@ fn foo() { | |||
515 | } | 544 | } |
516 | 545 | ||
517 | fn bar(arg: i32) { | 546 | fn bar(arg: i32) { |
518 | <|>todo!() | 547 | ${0:todo!()} |
519 | } | 548 | } |
520 | ", | 549 | ", |
521 | ) | 550 | ) |
@@ -536,7 +565,7 @@ fn foo() { | |||
536 | } | 565 | } |
537 | 566 | ||
538 | fn bar(arg: u8) { | 567 | fn bar(arg: u8) { |
539 | <|>todo!() | 568 | ${0:todo!()} |
540 | } | 569 | } |
541 | ", | 570 | ", |
542 | ) | 571 | ) |
@@ -561,7 +590,7 @@ fn foo() { | |||
561 | } | 590 | } |
562 | 591 | ||
563 | fn bar(x: u8) { | 592 | fn bar(x: u8) { |
564 | <|>todo!() | 593 | ${0:todo!()} |
565 | } | 594 | } |
566 | ", | 595 | ", |
567 | ) | 596 | ) |
@@ -584,7 +613,7 @@ fn foo() { | |||
584 | } | 613 | } |
585 | 614 | ||
586 | fn bar(worble: ()) { | 615 | fn bar(worble: ()) { |
587 | <|>todo!() | 616 | ${0:todo!()} |
588 | } | 617 | } |
589 | ", | 618 | ", |
590 | ) | 619 | ) |
@@ -613,7 +642,7 @@ fn baz() { | |||
613 | } | 642 | } |
614 | 643 | ||
615 | fn bar(foo: impl Foo) { | 644 | fn bar(foo: impl Foo) { |
616 | <|>todo!() | 645 | ${0:todo!()} |
617 | } | 646 | } |
618 | ", | 647 | ", |
619 | ) | 648 | ) |
@@ -640,7 +669,7 @@ fn foo() { | |||
640 | } | 669 | } |
641 | 670 | ||
642 | fn bar(baz: &Baz) { | 671 | fn bar(baz: &Baz) { |
643 | <|>todo!() | 672 | ${0:todo!()} |
644 | } | 673 | } |
645 | ", | 674 | ", |
646 | ) | 675 | ) |
@@ -669,7 +698,7 @@ fn foo() { | |||
669 | } | 698 | } |
670 | 699 | ||
671 | fn bar(baz: Baz::Bof) { | 700 | fn bar(baz: Baz::Bof) { |
672 | <|>todo!() | 701 | ${0:todo!()} |
673 | } | 702 | } |
674 | ", | 703 | ", |
675 | ) | 704 | ) |
@@ -692,7 +721,7 @@ fn foo<T>(t: T) { | |||
692 | } | 721 | } |
693 | 722 | ||
694 | fn bar<T>(t: T) { | 723 | fn bar<T>(t: T) { |
695 | <|>todo!() | 724 | ${0:todo!()} |
696 | } | 725 | } |
697 | ", | 726 | ", |
698 | ) | 727 | ) |
@@ -723,7 +752,7 @@ fn foo() { | |||
723 | } | 752 | } |
724 | 753 | ||
725 | fn bar(arg: fn() -> Baz) { | 754 | fn bar(arg: fn() -> Baz) { |
726 | <|>todo!() | 755 | ${0:todo!()} |
727 | } | 756 | } |
728 | ", | 757 | ", |
729 | ) | 758 | ) |
@@ -748,7 +777,7 @@ fn foo() { | |||
748 | } | 777 | } |
749 | 778 | ||
750 | fn bar(closure: impl Fn(i64) -> i64) { | 779 | fn bar(closure: impl Fn(i64) -> i64) { |
751 | <|>todo!() | 780 | ${0:todo!()} |
752 | } | 781 | } |
753 | ", | 782 | ", |
754 | ) | 783 | ) |
@@ -769,7 +798,7 @@ fn foo() { | |||
769 | } | 798 | } |
770 | 799 | ||
771 | fn bar(baz: ()) { | 800 | fn bar(baz: ()) { |
772 | <|>todo!() | 801 | ${0:todo!()} |
773 | } | 802 | } |
774 | ", | 803 | ", |
775 | ) | 804 | ) |
@@ -794,7 +823,7 @@ fn foo() { | |||
794 | } | 823 | } |
795 | 824 | ||
796 | fn bar(baz_1: Baz, baz_2: Baz) { | 825 | fn bar(baz_1: Baz, baz_2: Baz) { |
797 | <|>todo!() | 826 | ${0:todo!()} |
798 | } | 827 | } |
799 | ", | 828 | ", |
800 | ) | 829 | ) |
@@ -819,7 +848,7 @@ fn foo() { | |||
819 | } | 848 | } |
820 | 849 | ||
821 | fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) { | 850 | fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) { |
822 | <|>todo!() | 851 | ${0:todo!()} |
823 | } | 852 | } |
824 | "#, | 853 | "#, |
825 | ) | 854 | ) |
@@ -839,7 +868,7 @@ fn foo() { | |||
839 | r" | 868 | r" |
840 | mod bar { | 869 | mod bar { |
841 | pub(crate) fn my_fn() { | 870 | pub(crate) fn my_fn() { |
842 | <|>todo!() | 871 | ${0:todo!()} |
843 | } | 872 | } |
844 | } | 873 | } |
845 | 874 | ||
@@ -878,7 +907,7 @@ fn bar() { | |||
878 | } | 907 | } |
879 | 908 | ||
880 | fn baz(foo: foo::Foo) { | 909 | fn baz(foo: foo::Foo) { |
881 | <|>todo!() | 910 | ${0:todo!()} |
882 | } | 911 | } |
883 | ", | 912 | ", |
884 | ) | 913 | ) |
@@ -902,7 +931,7 @@ mod bar { | |||
902 | fn something_else() {} | 931 | fn something_else() {} |
903 | 932 | ||
904 | pub(crate) fn my_fn() { | 933 | pub(crate) fn my_fn() { |
905 | <|>todo!() | 934 | ${0:todo!()} |
906 | } | 935 | } |
907 | } | 936 | } |
908 | 937 | ||
@@ -930,7 +959,7 @@ fn foo() { | |||
930 | mod bar { | 959 | mod bar { |
931 | mod baz { | 960 | mod baz { |
932 | pub(crate) fn my_fn() { | 961 | pub(crate) fn my_fn() { |
933 | <|>todo!() | 962 | ${0:todo!()} |
934 | } | 963 | } |
935 | } | 964 | } |
936 | } | 965 | } |
@@ -959,7 +988,7 @@ fn main() { | |||
959 | 988 | ||
960 | 989 | ||
961 | pub(crate) fn bar() { | 990 | pub(crate) fn bar() { |
962 | <|>todo!() | 991 | ${0:todo!()} |
963 | }", | 992 | }", |
964 | ) | 993 | ) |
965 | } | 994 | } |
diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs index df114a0d8..eceba7d0a 100644 --- a/crates/ra_assists/src/handlers/add_impl.rs +++ b/crates/ra_assists/src/handlers/add_impl.rs | |||
@@ -1,7 +1,4 @@ | |||
1 | use ra_syntax::{ | 1 | use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner}; |
2 | ast::{self, AstNode, NameOwner, TypeParamsOwner}, | ||
3 | TextSize, | ||
4 | }; | ||
5 | use stdx::{format_to, SepBy}; | 2 | use stdx::{format_to, SepBy}; |
6 | 3 | ||
7 | use crate::{AssistContext, AssistId, Assists}; | 4 | use crate::{AssistContext, AssistId, Assists}; |
@@ -12,17 +9,17 @@ use crate::{AssistContext, AssistId, Assists}; | |||
12 | // | 9 | // |
13 | // ``` | 10 | // ``` |
14 | // struct Ctx<T: Clone> { | 11 | // struct Ctx<T: Clone> { |
15 | // data: T,<|> | 12 | // data: T,<|> |
16 | // } | 13 | // } |
17 | // ``` | 14 | // ``` |
18 | // -> | 15 | // -> |
19 | // ``` | 16 | // ``` |
20 | // struct Ctx<T: Clone> { | 17 | // struct Ctx<T: Clone> { |
21 | // data: T, | 18 | // data: T, |
22 | // } | 19 | // } |
23 | // | 20 | // |
24 | // impl<T: Clone> Ctx<T> { | 21 | // impl<T: Clone> Ctx<T> { |
25 | // | 22 | // $0 |
26 | // } | 23 | // } |
27 | // ``` | 24 | // ``` |
28 | pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 25 | pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
@@ -50,30 +47,37 @@ pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
50 | let generic_params = lifetime_params.chain(type_params).sep_by(", "); | 47 | let generic_params = lifetime_params.chain(type_params).sep_by(", "); |
51 | format_to!(buf, "<{}>", generic_params) | 48 | format_to!(buf, "<{}>", generic_params) |
52 | } | 49 | } |
53 | buf.push_str(" {\n"); | 50 | match ctx.config.snippet_cap { |
54 | edit.set_cursor(start_offset + TextSize::of(&buf)); | 51 | Some(cap) => { |
55 | buf.push_str("\n}"); | 52 | buf.push_str(" {\n $0\n}"); |
56 | edit.insert(start_offset, buf); | 53 | edit.insert_snippet(cap, start_offset, buf); |
54 | } | ||
55 | None => { | ||
56 | buf.push_str(" {\n}"); | ||
57 | edit.insert(start_offset, buf); | ||
58 | } | ||
59 | } | ||
57 | }) | 60 | }) |
58 | } | 61 | } |
59 | 62 | ||
60 | #[cfg(test)] | 63 | #[cfg(test)] |
61 | mod tests { | 64 | mod tests { |
62 | use super::*; | ||
63 | use crate::tests::{check_assist, check_assist_target}; | 65 | use crate::tests::{check_assist, check_assist_target}; |
64 | 66 | ||
67 | use super::*; | ||
68 | |||
65 | #[test] | 69 | #[test] |
66 | fn test_add_impl() { | 70 | fn test_add_impl() { |
67 | check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n<|>\n}\n"); | 71 | check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n $0\n}\n"); |
68 | check_assist( | 72 | check_assist( |
69 | add_impl, | 73 | add_impl, |
70 | "struct Foo<T: Clone> {<|>}", | 74 | "struct Foo<T: Clone> {<|>}", |
71 | "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}", | 75 | "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}", |
72 | ); | 76 | ); |
73 | check_assist( | 77 | check_assist( |
74 | add_impl, | 78 | add_impl, |
75 | "struct Foo<'a, T: Foo<'a>> {<|>}", | 79 | "struct Foo<'a, T: Foo<'a>> {<|>}", |
76 | "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", | 80 | "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}", |
77 | ); | 81 | ); |
78 | } | 82 | } |
79 | 83 | ||
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 22e1156d2..abacd4065 100644 --- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs | |||
@@ -11,7 +11,7 @@ use ra_syntax::{ | |||
11 | use crate::{ | 11 | 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, resolve_target_trait}, | 14 | utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor}, |
15 | AssistId, | 15 | AssistId, |
16 | }; | 16 | }; |
17 | 17 | ||
@@ -46,7 +46,7 @@ enum AddMissingImplMembersMode { | |||
46 | // | 46 | // |
47 | // impl Trait<u32> for () { | 47 | // impl Trait<u32> for () { |
48 | // fn foo(&self) -> u32 { | 48 | // fn foo(&self) -> u32 { |
49 | // todo!() | 49 | // ${0:todo!()} |
50 | // } | 50 | // } |
51 | // | 51 | // |
52 | // } | 52 | // } |
@@ -89,7 +89,7 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) - | |||
89 | // impl Trait for () { | 89 | // impl Trait for () { |
90 | // Type X = (); | 90 | // Type X = (); |
91 | // fn foo(&self) {} | 91 | // fn foo(&self) {} |
92 | // fn bar(&self) {} | 92 | // $0fn bar(&self) {} |
93 | // | 93 | // |
94 | // } | 94 | // } |
95 | // ``` | 95 | // ``` |
@@ -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, |edit| { | 150 | acc.add(AssistId(assist_id), 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()); |
@@ -162,13 +162,29 @@ fn add_missing_impl_members_inner( | |||
162 | }) | 162 | }) |
163 | .map(|it| edit::remove_attrs_and_docs(&it)); | 163 | .map(|it| edit::remove_attrs_and_docs(&it)); |
164 | let new_impl_item_list = impl_item_list.append_items(items); | 164 | let new_impl_item_list = impl_item_list.append_items(items); |
165 | let cursor_position = { | 165 | let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap(); |
166 | let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap(); | 166 | |
167 | first_new_item.syntax().text_range().start() | 167 | let original_range = impl_item_list.syntax().text_range(); |
168 | match ctx.config.snippet_cap { | ||
169 | None => builder.replace(original_range, new_impl_item_list.to_string()), | ||
170 | Some(cap) => { | ||
171 | let mut cursor = Cursor::Before(first_new_item.syntax()); | ||
172 | let placeholder; | ||
173 | if let ast::AssocItem::FnDef(func) = &first_new_item { | ||
174 | if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) { | ||
175 | if m.syntax().text() == "todo!()" { | ||
176 | placeholder = m; | ||
177 | cursor = Cursor::Replace(placeholder.syntax()); | ||
178 | } | ||
179 | } | ||
180 | } | ||
181 | builder.replace_snippet( | ||
182 | cap, | ||
183 | original_range, | ||
184 | render_snippet(cap, new_impl_item_list.syntax(), cursor), | ||
185 | ) | ||
186 | } | ||
168 | }; | 187 | }; |
169 | |||
170 | edit.replace_ast(impl_item_list, new_impl_item_list); | ||
171 | edit.set_cursor(cursor_position); | ||
172 | }) | 188 | }) |
173 | } | 189 | } |
174 | 190 | ||
@@ -222,7 +238,7 @@ struct S; | |||
222 | 238 | ||
223 | impl Foo for S { | 239 | impl Foo for S { |
224 | fn bar(&self) {} | 240 | fn bar(&self) {} |
225 | <|>type Output; | 241 | $0type Output; |
226 | const CONST: usize = 42; | 242 | const CONST: usize = 42; |
227 | fn foo(&self) { | 243 | fn foo(&self) { |
228 | todo!() | 244 | todo!() |
@@ -263,8 +279,8 @@ struct S; | |||
263 | 279 | ||
264 | impl Foo for S { | 280 | impl Foo for S { |
265 | fn bar(&self) {} | 281 | fn bar(&self) {} |
266 | <|>fn foo(&self) { | 282 | fn foo(&self) { |
267 | todo!() | 283 | ${0:todo!()} |
268 | } | 284 | } |
269 | 285 | ||
270 | }"#, | 286 | }"#, |
@@ -283,8 +299,8 @@ impl Foo for S { <|> }"#, | |||
283 | trait Foo { fn foo(&self); } | 299 | trait Foo { fn foo(&self); } |
284 | struct S; | 300 | struct S; |
285 | impl Foo for S { | 301 | impl Foo for S { |
286 | <|>fn foo(&self) { | 302 | fn foo(&self) { |
287 | todo!() | 303 | ${0:todo!()} |
288 | } | 304 | } |
289 | }"#, | 305 | }"#, |
290 | ); | 306 | ); |
@@ -302,8 +318,8 @@ impl Foo<u32> for S { <|> }"#, | |||
302 | trait Foo<T> { fn foo(&self, t: T) -> &T; } | 318 | trait Foo<T> { fn foo(&self, t: T) -> &T; } |
303 | struct S; | 319 | struct S; |
304 | impl Foo<u32> for S { | 320 | impl Foo<u32> for S { |
305 | <|>fn foo(&self, t: u32) -> &u32 { | 321 | fn foo(&self, t: u32) -> &u32 { |
306 | todo!() | 322 | ${0:todo!()} |
307 | } | 323 | } |
308 | }"#, | 324 | }"#, |
309 | ); | 325 | ); |
@@ -321,8 +337,8 @@ impl<U> Foo<U> for S { <|> }"#, | |||
321 | trait Foo<T> { fn foo(&self, t: T) -> &T; } | 337 | trait Foo<T> { fn foo(&self, t: T) -> &T; } |
322 | struct S; | 338 | struct S; |
323 | impl<U> Foo<U> for S { | 339 | impl<U> Foo<U> for S { |
324 | <|>fn foo(&self, t: U) -> &U { | 340 | fn foo(&self, t: U) -> &U { |
325 | todo!() | 341 | ${0:todo!()} |
326 | } | 342 | } |
327 | }"#, | 343 | }"#, |
328 | ); | 344 | ); |
@@ -340,8 +356,8 @@ impl Foo for S {}<|>"#, | |||
340 | trait Foo { fn foo(&self); } | 356 | trait Foo { fn foo(&self); } |
341 | struct S; | 357 | struct S; |
342 | impl Foo for S { | 358 | impl Foo for S { |
343 | <|>fn foo(&self) { | 359 | fn foo(&self) { |
344 | todo!() | 360 | ${0:todo!()} |
345 | } | 361 | } |
346 | }"#, | 362 | }"#, |
347 | ) | 363 | ) |
@@ -365,8 +381,8 @@ mod foo { | |||
365 | } | 381 | } |
366 | struct S; | 382 | struct S; |
367 | impl foo::Foo for S { | 383 | impl foo::Foo for S { |
368 | <|>fn foo(&self, bar: foo::Bar) { | 384 | fn foo(&self, bar: foo::Bar) { |
369 | todo!() | 385 | ${0:todo!()} |
370 | } | 386 | } |
371 | }"#, | 387 | }"#, |
372 | ); | 388 | ); |
@@ -390,8 +406,8 @@ mod foo { | |||
390 | } | 406 | } |
391 | struct S; | 407 | struct S; |
392 | impl foo::Foo for S { | 408 | impl foo::Foo for S { |
393 | <|>fn foo(&self, bar: foo::Bar<u32>) { | 409 | fn foo(&self, bar: foo::Bar<u32>) { |
394 | todo!() | 410 | ${0:todo!()} |
395 | } | 411 | } |
396 | }"#, | 412 | }"#, |
397 | ); | 413 | ); |
@@ -415,8 +431,8 @@ mod foo { | |||
415 | } | 431 | } |
416 | struct S; | 432 | struct S; |
417 | impl foo::Foo<u32> for S { | 433 | impl foo::Foo<u32> for S { |
418 | <|>fn foo(&self, bar: foo::Bar<u32>) { | 434 | fn foo(&self, bar: foo::Bar<u32>) { |
419 | todo!() | 435 | ${0:todo!()} |
420 | } | 436 | } |
421 | }"#, | 437 | }"#, |
422 | ); | 438 | ); |
@@ -443,8 +459,8 @@ mod foo { | |||
443 | struct Param; | 459 | struct Param; |
444 | struct S; | 460 | struct S; |
445 | impl foo::Foo<Param> for S { | 461 | impl foo::Foo<Param> for S { |
446 | <|>fn foo(&self, bar: Param) { | 462 | fn foo(&self, bar: Param) { |
447 | todo!() | 463 | ${0:todo!()} |
448 | } | 464 | } |
449 | }"#, | 465 | }"#, |
450 | ); | 466 | ); |
@@ -470,8 +486,8 @@ mod foo { | |||
470 | } | 486 | } |
471 | struct S; | 487 | struct S; |
472 | impl foo::Foo for S { | 488 | impl foo::Foo for S { |
473 | <|>fn foo(&self, bar: foo::Bar<u32>::Assoc) { | 489 | fn foo(&self, bar: foo::Bar<u32>::Assoc) { |
474 | todo!() | 490 | ${0:todo!()} |
475 | } | 491 | } |
476 | }"#, | 492 | }"#, |
477 | ); | 493 | ); |
@@ -497,8 +513,8 @@ mod foo { | |||
497 | } | 513 | } |
498 | struct S; | 514 | struct S; |
499 | impl foo::Foo for S { | 515 | impl foo::Foo for S { |
500 | <|>fn foo(&self, bar: foo::Bar<foo::Baz>) { | 516 | fn foo(&self, bar: foo::Bar<foo::Baz>) { |
501 | todo!() | 517 | ${0:todo!()} |
502 | } | 518 | } |
503 | }"#, | 519 | }"#, |
504 | ); | 520 | ); |
@@ -522,8 +538,8 @@ mod foo { | |||
522 | } | 538 | } |
523 | struct S; | 539 | struct S; |
524 | impl foo::Foo for S { | 540 | impl foo::Foo for S { |
525 | <|>fn foo(&self, bar: dyn Fn(u32) -> i32) { | 541 | fn foo(&self, bar: dyn Fn(u32) -> i32) { |
526 | todo!() | 542 | ${0:todo!()} |
527 | } | 543 | } |
528 | }"#, | 544 | }"#, |
529 | ); | 545 | ); |
@@ -580,7 +596,7 @@ trait Foo { | |||
580 | } | 596 | } |
581 | struct S; | 597 | struct S; |
582 | impl Foo for S { | 598 | impl Foo for S { |
583 | <|>type Output; | 599 | $0type Output; |
584 | fn foo(&self) { | 600 | fn foo(&self) { |
585 | todo!() | 601 | todo!() |
586 | } | 602 | } |
@@ -614,7 +630,7 @@ trait Foo { | |||
614 | } | 630 | } |
615 | struct S; | 631 | struct S; |
616 | impl Foo for S { | 632 | impl Foo for S { |
617 | <|>fn valid(some: u32) -> bool { false } | 633 | $0fn valid(some: u32) -> bool { false } |
618 | }"#, | 634 | }"#, |
619 | ) | 635 | ) |
620 | } | 636 | } |
@@ -637,8 +653,8 @@ trait Foo<T = Self> { | |||
637 | 653 | ||
638 | struct S; | 654 | struct S; |
639 | impl Foo for S { | 655 | impl Foo for S { |
640 | <|>fn bar(&self, other: &Self) { | 656 | fn bar(&self, other: &Self) { |
641 | todo!() | 657 | ${0:todo!()} |
642 | } | 658 | } |
643 | }"#, | 659 | }"#, |
644 | ) | 660 | ) |
@@ -662,8 +678,8 @@ trait Foo<T1, T2 = Self> { | |||
662 | 678 | ||
663 | struct S<T>; | 679 | struct S<T>; |
664 | impl Foo<T> for S<T> { | 680 | impl Foo<T> for S<T> { |
665 | <|>fn bar(&self, this: &T, that: &Self) { | 681 | fn bar(&self, this: &T, that: &Self) { |
666 | todo!() | 682 | ${0:todo!()} |
667 | } | 683 | } |
668 | }"#, | 684 | }"#, |
669 | ) | 685 | ) |
diff --git a/crates/ra_assists/src/handlers/add_turbo_fish.rs b/crates/ra_assists/src/handlers/add_turbo_fish.rs new file mode 100644 index 000000000..a0363bc78 --- /dev/null +++ b/crates/ra_assists/src/handlers/add_turbo_fish.rs | |||
@@ -0,0 +1,134 @@ | |||
1 | use ra_ide_db::defs::{classify_name_ref, Definition, NameRefClass}; | ||
2 | use ra_syntax::{ast, AstNode, SyntaxKind, T}; | ||
3 | |||
4 | use crate::{ | ||
5 | assist_context::{AssistContext, Assists}, | ||
6 | AssistId, | ||
7 | }; | ||
8 | use test_utils::tested_by; | ||
9 | |||
10 | // Assist: add_turbo_fish | ||
11 | // | ||
12 | // Adds `::<_>` to a call of a generic method or function. | ||
13 | // | ||
14 | // ``` | ||
15 | // fn make<T>() -> T { todo!() } | ||
16 | // fn main() { | ||
17 | // let x = make<|>(); | ||
18 | // } | ||
19 | // ``` | ||
20 | // -> | ||
21 | // ``` | ||
22 | // fn make<T>() -> T { todo!() } | ||
23 | // fn main() { | ||
24 | // let x = make::<${0:_}>(); | ||
25 | // } | ||
26 | // ``` | ||
27 | pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
28 | let ident = ctx.find_token_at_offset(SyntaxKind::IDENT)?; | ||
29 | let next_token = ident.next_token()?; | ||
30 | if next_token.kind() == T![::] { | ||
31 | tested_by!(add_turbo_fish_one_fish_is_enough); | ||
32 | return None; | ||
33 | } | ||
34 | let name_ref = ast::NameRef::cast(ident.parent())?; | ||
35 | let def = match classify_name_ref(&ctx.sema, &name_ref)? { | ||
36 | NameRefClass::Definition(def) => def, | ||
37 | NameRefClass::FieldShorthand { .. } => return None, | ||
38 | }; | ||
39 | let fun = match def { | ||
40 | Definition::ModuleDef(hir::ModuleDef::Function(it)) => it, | ||
41 | _ => return None, | ||
42 | }; | ||
43 | let generics = hir::GenericDef::Function(fun).params(ctx.sema.db); | ||
44 | if generics.is_empty() { | ||
45 | tested_by!(add_turbo_fish_non_generic); | ||
46 | return None; | ||
47 | } | ||
48 | acc.add(AssistId("add_turbo_fish"), "Add `::<>`", ident.text_range(), |builder| { | ||
49 | match ctx.config.snippet_cap { | ||
50 | Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"), | ||
51 | None => builder.insert(ident.text_range().end(), "::<_>"), | ||
52 | } | ||
53 | }) | ||
54 | } | ||
55 | |||
56 | #[cfg(test)] | ||
57 | mod tests { | ||
58 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
59 | |||
60 | use super::*; | ||
61 | use test_utils::covers; | ||
62 | |||
63 | #[test] | ||
64 | fn add_turbo_fish_function() { | ||
65 | check_assist( | ||
66 | add_turbo_fish, | ||
67 | r#" | ||
68 | fn make<T>() -> T {} | ||
69 | fn main() { | ||
70 | make<|>(); | ||
71 | } | ||
72 | "#, | ||
73 | r#" | ||
74 | fn make<T>() -> T {} | ||
75 | fn main() { | ||
76 | make::<${0:_}>(); | ||
77 | } | ||
78 | "#, | ||
79 | ); | ||
80 | } | ||
81 | |||
82 | #[test] | ||
83 | fn add_turbo_fish_method() { | ||
84 | check_assist( | ||
85 | add_turbo_fish, | ||
86 | r#" | ||
87 | struct S; | ||
88 | impl S { | ||
89 | fn make<T>(&self) -> T {} | ||
90 | } | ||
91 | fn main() { | ||
92 | S.make<|>(); | ||
93 | } | ||
94 | "#, | ||
95 | r#" | ||
96 | struct S; | ||
97 | impl S { | ||
98 | fn make<T>(&self) -> T {} | ||
99 | } | ||
100 | fn main() { | ||
101 | S.make::<${0:_}>(); | ||
102 | } | ||
103 | "#, | ||
104 | ); | ||
105 | } | ||
106 | |||
107 | #[test] | ||
108 | fn add_turbo_fish_one_fish_is_enough() { | ||
109 | covers!(add_turbo_fish_one_fish_is_enough); | ||
110 | check_assist_not_applicable( | ||
111 | add_turbo_fish, | ||
112 | r#" | ||
113 | fn make<T>() -> T {} | ||
114 | fn main() { | ||
115 | make<|>::<()>(); | ||
116 | } | ||
117 | "#, | ||
118 | ); | ||
119 | } | ||
120 | |||
121 | #[test] | ||
122 | fn add_turbo_fish_non_generic() { | ||
123 | covers!(add_turbo_fish_non_generic); | ||
124 | check_assist_not_applicable( | ||
125 | add_turbo_fish, | ||
126 | r#" | ||
127 | fn make() -> () {} | ||
128 | fn main() { | ||
129 | make<|>(); | ||
130 | } | ||
131 | "#, | ||
132 | ); | ||
133 | } | ||
134 | } | ||
diff --git a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs index c4b56f6e9..b379b55a8 100644 --- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs +++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs | |||
@@ -51,7 +51,7 @@ pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext) | |||
51 | let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); | 51 | let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); |
52 | let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); | 52 | let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); |
53 | 53 | ||
54 | let unreachable_call = make::unreachable_macro_call().into(); | 54 | let unreachable_call = make::expr_unreachable(); |
55 | let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); | 55 | let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); |
56 | 56 | ||
57 | let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); | 57 | let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index b6dc7cb1b..339f24100 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -10,6 +10,7 @@ macro_rules! eprintln { | |||
10 | ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; | 10 | ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; |
11 | } | 11 | } |
12 | 12 | ||
13 | mod assist_config; | ||
13 | mod assist_context; | 14 | mod assist_context; |
14 | mod marks; | 15 | mod marks; |
15 | #[cfg(test)] | 16 | #[cfg(test)] |
@@ -24,6 +25,8 @@ use ra_syntax::TextRange; | |||
24 | 25 | ||
25 | pub(crate) use crate::assist_context::{AssistContext, Assists}; | 26 | pub(crate) use crate::assist_context::{AssistContext, Assists}; |
26 | 27 | ||
28 | pub use assist_config::AssistConfig; | ||
29 | |||
27 | /// Unique identifier of the assist, should not be shown to the user | 30 | /// Unique identifier of the assist, should not be shown to the user |
28 | /// directly. | 31 | /// directly. |
29 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 32 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
@@ -54,9 +57,9 @@ impl Assist { | |||
54 | /// | 57 | /// |
55 | /// Assists are returned in the "unresolved" state, that is only labels are | 58 | /// Assists are returned in the "unresolved" state, that is only labels are |
56 | /// returned, without actual edits. | 59 | /// returned, without actual edits. |
57 | pub fn unresolved(db: &RootDatabase, range: FileRange) -> Vec<Assist> { | 60 | pub fn unresolved(db: &RootDatabase, config: &AssistConfig, range: FileRange) -> Vec<Assist> { |
58 | let sema = Semantics::new(db); | 61 | let sema = Semantics::new(db); |
59 | let ctx = AssistContext::new(sema, range); | 62 | let ctx = AssistContext::new(sema, config, range); |
60 | let mut acc = Assists::new_unresolved(&ctx); | 63 | let mut acc = Assists::new_unresolved(&ctx); |
61 | handlers::all().iter().for_each(|handler| { | 64 | handlers::all().iter().for_each(|handler| { |
62 | handler(&mut acc, &ctx); | 65 | handler(&mut acc, &ctx); |
@@ -68,9 +71,13 @@ impl Assist { | |||
68 | /// | 71 | /// |
69 | /// Assists are returned in the "resolved" state, that is with edit fully | 72 | /// Assists are returned in the "resolved" state, that is with edit fully |
70 | /// computed. | 73 | /// computed. |
71 | pub fn resolved(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssist> { | 74 | pub fn resolved( |
75 | db: &RootDatabase, | ||
76 | config: &AssistConfig, | ||
77 | range: FileRange, | ||
78 | ) -> Vec<ResolvedAssist> { | ||
72 | let sema = Semantics::new(db); | 79 | let sema = Semantics::new(db); |
73 | let ctx = AssistContext::new(sema, range); | 80 | let ctx = AssistContext::new(sema, config, range); |
74 | let mut acc = Assists::new_resolved(&ctx); | 81 | let mut acc = Assists::new_resolved(&ctx); |
75 | handlers::all().iter().for_each(|handler| { | 82 | handlers::all().iter().for_each(|handler| { |
76 | handler(&mut acc, &ctx); | 83 | handler(&mut acc, &ctx); |
@@ -103,6 +110,7 @@ mod handlers { | |||
103 | mod add_impl; | 110 | mod add_impl; |
104 | mod add_missing_impl_members; | 111 | mod add_missing_impl_members; |
105 | mod add_new; | 112 | mod add_new; |
113 | mod add_turbo_fish; | ||
106 | mod apply_demorgan; | 114 | mod apply_demorgan; |
107 | mod auto_import; | 115 | mod auto_import; |
108 | mod change_return_type_to_result; | 116 | mod change_return_type_to_result; |
@@ -140,6 +148,7 @@ mod handlers { | |||
140 | add_function::add_function, | 148 | add_function::add_function, |
141 | add_impl::add_impl, | 149 | add_impl::add_impl, |
142 | add_new::add_new, | 150 | add_new::add_new, |
151 | add_turbo_fish::add_turbo_fish, | ||
143 | apply_demorgan::apply_demorgan, | 152 | apply_demorgan::apply_demorgan, |
144 | auto_import::auto_import, | 153 | auto_import::auto_import, |
145 | change_return_type_to_result::change_return_type_to_result, | 154 | change_return_type_to_result::change_return_type_to_result, |
diff --git a/crates/ra_assists/src/marks.rs b/crates/ra_assists/src/marks.rs index 8d910205f..d579e627f 100644 --- a/crates/ra_assists/src/marks.rs +++ b/crates/ra_assists/src/marks.rs | |||
@@ -9,4 +9,6 @@ test_utils::marks![ | |||
9 | test_not_applicable_if_variable_unused | 9 | test_not_applicable_if_variable_unused |
10 | change_visibility_field_false_positive | 10 | change_visibility_field_false_positive |
11 | test_add_from_impl_already_exists | 11 | test_add_from_impl_already_exists |
12 | add_turbo_fish_one_fish_is_enough | ||
13 | add_turbo_fish_non_generic | ||
12 | ]; | 14 | ]; |
diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs index a3eacb8f1..9ba3da786 100644 --- a/crates/ra_assists/src/tests.rs +++ b/crates/ra_assists/src/tests.rs | |||
@@ -11,7 +11,7 @@ use test_utils::{ | |||
11 | RangeOrOffset, | 11 | RangeOrOffset, |
12 | }; | 12 | }; |
13 | 13 | ||
14 | use crate::{handlers::Handler, Assist, AssistContext, Assists}; | 14 | use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; |
15 | 15 | ||
16 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { | 16 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { |
17 | let (mut db, file_id) = RootDatabase::with_single_file(text); | 17 | let (mut db, file_id) = RootDatabase::with_single_file(text); |
@@ -41,14 +41,14 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) { | |||
41 | let (db, file_id) = crate::tests::with_single_file(&before); | 41 | let (db, file_id) = crate::tests::with_single_file(&before); |
42 | let frange = FileRange { file_id, range: selection.into() }; | 42 | let frange = FileRange { file_id, range: selection.into() }; |
43 | 43 | ||
44 | let mut assist = Assist::resolved(&db, frange) | 44 | let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange) |
45 | .into_iter() | 45 | .into_iter() |
46 | .find(|assist| assist.assist.id.0 == assist_id) | 46 | .find(|assist| assist.assist.id.0 == assist_id) |
47 | .unwrap_or_else(|| { | 47 | .unwrap_or_else(|| { |
48 | panic!( | 48 | panic!( |
49 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", | 49 | "\n\nAssist is not applicable: {}\nAvailable assists: {}", |
50 | assist_id, | 50 | assist_id, |
51 | Assist::resolved(&db, frange) | 51 | Assist::resolved(&db, &AssistConfig::default(), frange) |
52 | .into_iter() | 52 | .into_iter() |
53 | .map(|assist| assist.assist.id.0) | 53 | .map(|assist| assist.assist.id.0) |
54 | .collect::<Vec<_>>() | 54 | .collect::<Vec<_>>() |
@@ -90,7 +90,8 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) { | |||
90 | let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; | 90 | let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; |
91 | 91 | ||
92 | let sema = Semantics::new(&db); | 92 | let sema = Semantics::new(&db); |
93 | let ctx = AssistContext::new(sema, frange); | 93 | let config = AssistConfig::default(); |
94 | let ctx = AssistContext::new(sema, &config, frange); | ||
94 | let mut acc = Assists::new_resolved(&ctx); | 95 | let mut acc = Assists::new_resolved(&ctx); |
95 | handler(&mut acc, &ctx); | 96 | handler(&mut acc, &ctx); |
96 | let mut res = acc.finish_resolved(); | 97 | let mut res = acc.finish_resolved(); |
@@ -103,19 +104,20 @@ fn check(handler: Handler, before: &str, expected: ExpectedResult) { | |||
103 | let mut actual = db.file_text(change.file_id).as_ref().to_owned(); | 104 | let mut actual = db.file_text(change.file_id).as_ref().to_owned(); |
104 | change.edit.apply(&mut actual); | 105 | change.edit.apply(&mut actual); |
105 | 106 | ||
106 | match source_change.cursor_position { | 107 | if !source_change.is_snippet { |
107 | None => { | 108 | match source_change.cursor_position { |
108 | if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { | 109 | None => { |
109 | let off = change | 110 | if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { |
110 | .edit | 111 | let off = change |
111 | .apply_to_offset(before_cursor_pos) | 112 | .edit |
112 | .expect("cursor position is affected by the edit"); | 113 | .apply_to_offset(before_cursor_pos) |
113 | actual = add_cursor(&actual, off) | 114 | .expect("cursor position is affected by the edit"); |
115 | actual = add_cursor(&actual, off) | ||
116 | } | ||
114 | } | 117 | } |
115 | } | 118 | Some(off) => actual = add_cursor(&actual, off.offset), |
116 | Some(off) => actual = add_cursor(&actual, off.offset), | 119 | }; |
117 | }; | 120 | } |
118 | |||
119 | assert_eq_text!(after, &actual); | 121 | assert_eq_text!(after, &actual); |
120 | } | 122 | } |
121 | (Some(assist), ExpectedResult::Target(target)) => { | 123 | (Some(assist), ExpectedResult::Target(target)) => { |
@@ -136,7 +138,7 @@ fn assist_order_field_struct() { | |||
136 | let (before_cursor_pos, before) = extract_offset(before); | 138 | let (before_cursor_pos, before) = extract_offset(before); |
137 | let (db, file_id) = with_single_file(&before); | 139 | let (db, file_id) = with_single_file(&before); |
138 | let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; | 140 | let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) }; |
139 | let assists = Assist::resolved(&db, frange); | 141 | let assists = Assist::resolved(&db, &AssistConfig::default(), frange); |
140 | let mut assists = assists.iter(); | 142 | let mut assists = assists.iter(); |
141 | 143 | ||
142 | assert_eq!( | 144 | assert_eq!( |
@@ -159,7 +161,7 @@ fn assist_order_if_expr() { | |||
159 | let (range, before) = extract_range(before); | 161 | let (range, before) = extract_range(before); |
160 | let (db, file_id) = with_single_file(&before); | 162 | let (db, file_id) = with_single_file(&before); |
161 | let frange = FileRange { file_id, range }; | 163 | let frange = FileRange { file_id, range }; |
162 | let assists = Assist::resolved(&db, frange); | 164 | let assists = Assist::resolved(&db, &AssistConfig::default(), frange); |
163 | let mut assists = assists.iter(); | 165 | let mut assists = assists.iter(); |
164 | 166 | ||
165 | assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); | 167 | assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); |
diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs index 972dbd251..3808aded1 100644 --- a/crates/ra_assists/src/tests/generated.rs +++ b/crates/ra_assists/src/tests/generated.rs | |||
@@ -15,7 +15,7 @@ struct S; | |||
15 | struct S; | 15 | struct S; |
16 | 16 | ||
17 | impl Debug for S { | 17 | impl Debug for S { |
18 | 18 | $0 | |
19 | } | 19 | } |
20 | "#####, | 20 | "#####, |
21 | ) | 21 | ) |
@@ -32,7 +32,7 @@ struct Point { | |||
32 | } | 32 | } |
33 | "#####, | 33 | "#####, |
34 | r#####" | 34 | r#####" |
35 | #[derive()] | 35 | #[derive($0)] |
36 | struct Point { | 36 | struct Point { |
37 | x: u32, | 37 | x: u32, |
38 | y: u32, | 38 | y: u32, |
@@ -78,7 +78,7 @@ fn foo() { | |||
78 | } | 78 | } |
79 | 79 | ||
80 | fn bar(arg: &str, baz: Baz) { | 80 | fn bar(arg: &str, baz: Baz) { |
81 | todo!() | 81 | ${0:todo!()} |
82 | } | 82 | } |
83 | 83 | ||
84 | "#####, | 84 | "#####, |
@@ -108,16 +108,16 @@ fn doctest_add_impl() { | |||
108 | "add_impl", | 108 | "add_impl", |
109 | r#####" | 109 | r#####" |
110 | struct Ctx<T: Clone> { | 110 | struct Ctx<T: Clone> { |
111 | data: T,<|> | 111 | data: T,<|> |
112 | } | 112 | } |
113 | "#####, | 113 | "#####, |
114 | r#####" | 114 | r#####" |
115 | struct Ctx<T: Clone> { | 115 | struct Ctx<T: Clone> { |
116 | data: T, | 116 | data: T, |
117 | } | 117 | } |
118 | 118 | ||
119 | impl<T: Clone> Ctx<T> { | 119 | impl<T: Clone> Ctx<T> { |
120 | 120 | $0 | |
121 | } | 121 | } |
122 | "#####, | 122 | "#####, |
123 | ) | 123 | ) |
@@ -150,7 +150,7 @@ trait Trait { | |||
150 | impl Trait for () { | 150 | impl Trait for () { |
151 | Type X = (); | 151 | Type X = (); |
152 | fn foo(&self) {} | 152 | fn foo(&self) {} |
153 | fn bar(&self) {} | 153 | $0fn bar(&self) {} |
154 | 154 | ||
155 | } | 155 | } |
156 | "#####, | 156 | "#####, |
@@ -181,7 +181,7 @@ trait Trait<T> { | |||
181 | 181 | ||
182 | impl Trait<u32> for () { | 182 | impl Trait<u32> for () { |
183 | fn foo(&self) -> u32 { | 183 | fn foo(&self) -> u32 { |
184 | todo!() | 184 | ${0:todo!()} |
185 | } | 185 | } |
186 | 186 | ||
187 | } | 187 | } |
@@ -212,6 +212,25 @@ impl<T: Clone> Ctx<T> { | |||
212 | } | 212 | } |
213 | 213 | ||
214 | #[test] | 214 | #[test] |
215 | fn doctest_add_turbo_fish() { | ||
216 | check_doc_test( | ||
217 | "add_turbo_fish", | ||
218 | r#####" | ||
219 | fn make<T>() -> T { todo!() } | ||
220 | fn main() { | ||
221 | let x = make<|>(); | ||
222 | } | ||
223 | "#####, | ||
224 | r#####" | ||
225 | fn make<T>() -> T { todo!() } | ||
226 | fn main() { | ||
227 | let x = make::<${0:_}>(); | ||
228 | } | ||
229 | "#####, | ||
230 | ) | ||
231 | } | ||
232 | |||
233 | #[test] | ||
215 | fn doctest_apply_demorgan() { | 234 | fn doctest_apply_demorgan() { |
216 | check_doc_test( | 235 | check_doc_test( |
217 | "apply_demorgan", | 236 | "apply_demorgan", |
diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs index f3fc92ebf..9af27180b 100644 --- a/crates/ra_assists/src/utils.rs +++ b/crates/ra_assists/src/utils.rs | |||
@@ -1,18 +1,57 @@ | |||
1 | //! Assorted functions shared by several assists. | 1 | //! Assorted functions shared by several assists. |
2 | pub(crate) mod insert_use; | 2 | pub(crate) mod insert_use; |
3 | 3 | ||
4 | use std::iter; | 4 | use std::{iter, ops}; |
5 | 5 | ||
6 | use hir::{Adt, Crate, Semantics, Trait, Type}; | 6 | use hir::{Adt, Crate, Semantics, Trait, Type}; |
7 | use ra_ide_db::RootDatabase; | 7 | use ra_ide_db::RootDatabase; |
8 | use ra_syntax::{ | 8 | use ra_syntax::{ |
9 | ast::{self, make, NameOwner}, | 9 | ast::{self, make, NameOwner}, |
10 | AstNode, T, | 10 | AstNode, SyntaxNode, T, |
11 | }; | 11 | }; |
12 | use rustc_hash::FxHashSet; | 12 | use rustc_hash::FxHashSet; |
13 | 13 | ||
14 | use crate::assist_config::SnippetCap; | ||
15 | |||
14 | pub(crate) use insert_use::insert_use_statement; | 16 | pub(crate) use insert_use::insert_use_statement; |
15 | 17 | ||
18 | #[derive(Clone, Copy, Debug)] | ||
19 | pub(crate) enum Cursor<'a> { | ||
20 | Replace(&'a SyntaxNode), | ||
21 | Before(&'a SyntaxNode), | ||
22 | } | ||
23 | |||
24 | impl<'a> Cursor<'a> { | ||
25 | fn node(self) -> &'a SyntaxNode { | ||
26 | match self { | ||
27 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
28 | } | ||
29 | } | ||
30 | } | ||
31 | |||
32 | pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor) -> String { | ||
33 | assert!(cursor.node().ancestors().any(|it| it == *node)); | ||
34 | let range = cursor.node().text_range() - node.text_range().start(); | ||
35 | let range: ops::Range<usize> = range.into(); | ||
36 | |||
37 | let mut placeholder = cursor.node().to_string(); | ||
38 | escape(&mut placeholder); | ||
39 | let tab_stop = match cursor { | ||
40 | Cursor::Replace(placeholder) => format!("${{0:{}}}", placeholder), | ||
41 | Cursor::Before(placeholder) => format!("$0{}", placeholder), | ||
42 | }; | ||
43 | |||
44 | let mut buf = node.to_string(); | ||
45 | buf.replace_range(range, &tab_stop); | ||
46 | return buf; | ||
47 | |||
48 | fn escape(buf: &mut String) { | ||
49 | stdx::replace(buf, '{', r"\{"); | ||
50 | stdx::replace(buf, '}', r"\}"); | ||
51 | stdx::replace(buf, '$', r"\$"); | ||
52 | } | ||
53 | } | ||
54 | |||
16 | pub fn get_missing_assoc_items( | 55 | pub fn get_missing_assoc_items( |
17 | sema: &Semantics<RootDatabase>, | 56 | sema: &Semantics<RootDatabase>, |
18 | impl_def: &ast::ImplDef, | 57 | impl_def: &ast::ImplDef, |
diff --git a/crates/ra_hir_def/src/db.rs b/crates/ra_hir_def/src/db.rs index 498a4c917..2f71511ba 100644 --- a/crates/ra_hir_def/src/db.rs +++ b/crates/ra_hir_def/src/db.rs | |||
@@ -112,7 +112,7 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> { | |||
112 | #[salsa::invoke(Documentation::documentation_query)] | 112 | #[salsa::invoke(Documentation::documentation_query)] |
113 | fn documentation(&self, def: AttrDefId) -> Option<Documentation>; | 113 | fn documentation(&self, def: AttrDefId) -> Option<Documentation>; |
114 | 114 | ||
115 | #[salsa::invoke(find_path::importable_locations_in_crate)] | 115 | #[salsa::invoke(find_path::importable_locations_of_query)] |
116 | fn importable_locations_of( | 116 | fn importable_locations_of( |
117 | &self, | 117 | &self, |
118 | item: ItemInNs, | 118 | item: ItemInNs, |
diff --git a/crates/ra_hir_def/src/find_path.rs b/crates/ra_hir_def/src/find_path.rs index 1ca20fabd..2eb12ec8f 100644 --- a/crates/ra_hir_def/src/find_path.rs +++ b/crates/ra_hir_def/src/find_path.rs | |||
@@ -1,5 +1,11 @@ | |||
1 | //! An algorithm to find a path to refer to a certain item. | 1 | //! An algorithm to find a path to refer to a certain item. |
2 | 2 | ||
3 | use std::sync::Arc; | ||
4 | |||
5 | use hir_expand::name::{known, AsName, Name}; | ||
6 | use ra_prof::profile; | ||
7 | use test_utils::tested_by; | ||
8 | |||
3 | use crate::{ | 9 | use crate::{ |
4 | db::DefDatabase, | 10 | db::DefDatabase, |
5 | item_scope::ItemInNs, | 11 | item_scope::ItemInNs, |
@@ -7,26 +13,28 @@ use crate::{ | |||
7 | visibility::Visibility, | 13 | visibility::Visibility, |
8 | CrateId, ModuleDefId, ModuleId, | 14 | CrateId, ModuleDefId, ModuleId, |
9 | }; | 15 | }; |
10 | use hir_expand::name::{known, AsName, Name}; | 16 | |
11 | use std::sync::Arc; | 17 | // FIXME: handle local items |
12 | use test_utils::tested_by; | 18 | |
19 | /// Find a path that can be used to refer to a certain item. This can depend on | ||
20 | /// *from where* you're referring to the item, hence the `from` parameter. | ||
21 | pub fn find_path(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> { | ||
22 | let _p = profile("find_path"); | ||
23 | find_path_inner(db, item, from, MAX_PATH_LEN) | ||
24 | } | ||
13 | 25 | ||
14 | const MAX_PATH_LEN: usize = 15; | 26 | const MAX_PATH_LEN: usize = 15; |
15 | 27 | ||
16 | impl ModPath { | 28 | impl ModPath { |
17 | fn starts_with_std(&self) -> bool { | 29 | fn starts_with_std(&self) -> bool { |
18 | self.segments.first().filter(|&first_segment| first_segment == &known::std).is_some() | 30 | self.segments.first() == Some(&known::std) |
19 | } | 31 | } |
20 | 32 | ||
21 | // When std library is present, paths starting with `std::` | 33 | // When std library is present, paths starting with `std::` |
22 | // should be preferred over paths starting with `core::` and `alloc::` | 34 | // should be preferred over paths starting with `core::` and `alloc::` |
23 | fn can_start_with_std(&self) -> bool { | 35 | fn can_start_with_std(&self) -> bool { |
24 | self.segments | 36 | let first_segment = self.segments.first(); |
25 | .first() | 37 | first_segment == Some(&known::alloc) || first_segment == Some(&known::core) |
26 | .filter(|&first_segment| { | ||
27 | first_segment == &known::alloc || first_segment == &known::core | ||
28 | }) | ||
29 | .is_some() | ||
30 | } | 38 | } |
31 | 39 | ||
32 | fn len(&self) -> usize { | 40 | fn len(&self) -> usize { |
@@ -41,15 +49,6 @@ impl ModPath { | |||
41 | } | 49 | } |
42 | } | 50 | } |
43 | 51 | ||
44 | // FIXME: handle local items | ||
45 | |||
46 | /// Find a path that can be used to refer to a certain item. This can depend on | ||
47 | /// *from where* you're referring to the item, hence the `from` parameter. | ||
48 | pub fn find_path(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option<ModPath> { | ||
49 | let _p = ra_prof::profile("find_path"); | ||
50 | find_path_inner(db, item, from, MAX_PATH_LEN) | ||
51 | } | ||
52 | |||
53 | fn find_path_inner( | 52 | fn find_path_inner( |
54 | db: &dyn DefDatabase, | 53 | db: &dyn DefDatabase, |
55 | item: ItemInNs, | 54 | item: ItemInNs, |
@@ -215,11 +214,12 @@ fn find_importable_locations( | |||
215 | /// | 214 | /// |
216 | /// Note that the crate doesn't need to be the one in which the item is defined; | 215 | /// Note that the crate doesn't need to be the one in which the item is defined; |
217 | /// it might be re-exported in other crates. | 216 | /// it might be re-exported in other crates. |
218 | pub(crate) fn importable_locations_in_crate( | 217 | pub(crate) fn importable_locations_of_query( |
219 | db: &dyn DefDatabase, | 218 | db: &dyn DefDatabase, |
220 | item: ItemInNs, | 219 | item: ItemInNs, |
221 | krate: CrateId, | 220 | krate: CrateId, |
222 | ) -> Arc<[(ModuleId, Name, Visibility)]> { | 221 | ) -> Arc<[(ModuleId, Name, Visibility)]> { |
222 | let _p = profile("importable_locations_of_query"); | ||
223 | let def_map = db.crate_def_map(krate); | 223 | let def_map = db.crate_def_map(krate); |
224 | let mut result = Vec::new(); | 224 | let mut result = Vec::new(); |
225 | for (local_id, data) in def_map.modules.iter() { | 225 | for (local_id, data) in def_map.modules.iter() { |
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index 8bdc43b1a..191300704 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs | |||
@@ -59,8 +59,8 @@ pub use crate::completion::{ | |||
59 | /// with ordering of completions (currently this is done by the client). | 59 | /// with ordering of completions (currently this is done by the client). |
60 | pub(crate) fn completions( | 60 | pub(crate) fn completions( |
61 | db: &RootDatabase, | 61 | db: &RootDatabase, |
62 | position: FilePosition, | ||
63 | config: &CompletionConfig, | 62 | config: &CompletionConfig, |
63 | position: FilePosition, | ||
64 | ) -> Option<Completions> { | 64 | ) -> Option<Completions> { |
65 | let ctx = CompletionContext::new(db, position, config)?; | 65 | let ctx = CompletionContext::new(db, position, config)?; |
66 | 66 | ||
diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs index eb90b5279..bf22452a2 100644 --- a/crates/ra_ide/src/completion/test_utils.rs +++ b/crates/ra_ide/src/completion/test_utils.rs | |||
@@ -20,7 +20,7 @@ pub(crate) fn do_completion_with_options( | |||
20 | } else { | 20 | } else { |
21 | single_file_with_position(code) | 21 | single_file_with_position(code) |
22 | }; | 22 | }; |
23 | let completions = analysis.completions(position, options).unwrap().unwrap(); | 23 | let completions = analysis.completions(options, position).unwrap().unwrap(); |
24 | let completion_items: Vec<CompletionItem> = completions.into(); | 24 | let completion_items: Vec<CompletionItem> = completions.into(); |
25 | let mut kind_completions: Vec<CompletionItem> = | 25 | let mut kind_completions: Vec<CompletionItem> = |
26 | completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); | 26 | completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); |
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index 87a0b80f1..54c2bcc09 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -629,6 +629,7 @@ mod tests { | |||
629 | }, | 629 | }, |
630 | ], | 630 | ], |
631 | cursor_position: None, | 631 | cursor_position: None, |
632 | is_snippet: false, | ||
632 | }, | 633 | }, |
633 | ), | 634 | ), |
634 | severity: Error, | 635 | severity: Error, |
@@ -685,6 +686,7 @@ mod tests { | |||
685 | ], | 686 | ], |
686 | file_system_edits: [], | 687 | file_system_edits: [], |
687 | cursor_position: None, | 688 | cursor_position: None, |
689 | is_snippet: false, | ||
688 | }, | 690 | }, |
689 | ), | 691 | ), |
690 | severity: Error, | 692 | severity: Error, |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 78149ddfc..66125f2f5 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -82,7 +82,7 @@ pub use crate::{ | |||
82 | }; | 82 | }; |
83 | 83 | ||
84 | pub use hir::Documentation; | 84 | pub use hir::Documentation; |
85 | pub use ra_assists::AssistId; | 85 | pub use ra_assists::{AssistConfig, AssistId}; |
86 | pub use ra_db::{ | 86 | pub use ra_db::{ |
87 | Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, | 87 | Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, |
88 | }; | 88 | }; |
@@ -458,17 +458,17 @@ impl Analysis { | |||
458 | /// Computes completions at the given position. | 458 | /// Computes completions at the given position. |
459 | pub fn completions( | 459 | pub fn completions( |
460 | &self, | 460 | &self, |
461 | position: FilePosition, | ||
462 | config: &CompletionConfig, | 461 | config: &CompletionConfig, |
462 | position: FilePosition, | ||
463 | ) -> Cancelable<Option<Vec<CompletionItem>>> { | 463 | ) -> Cancelable<Option<Vec<CompletionItem>>> { |
464 | self.with_db(|db| completion::completions(db, position, config).map(Into::into)) | 464 | self.with_db(|db| completion::completions(db, config, position).map(Into::into)) |
465 | } | 465 | } |
466 | 466 | ||
467 | /// Computes assists (aka code actions aka intentions) for the given | 467 | /// Computes assists (aka code actions aka intentions) for the given |
468 | /// position. | 468 | /// position. |
469 | pub fn assists(&self, frange: FileRange) -> Cancelable<Vec<Assist>> { | 469 | pub fn assists(&self, config: &AssistConfig, frange: FileRange) -> Cancelable<Vec<Assist>> { |
470 | self.with_db(|db| { | 470 | self.with_db(|db| { |
471 | ra_assists::Assist::resolved(db, frange) | 471 | ra_assists::Assist::resolved(db, config, frange) |
472 | .into_iter() | 472 | .into_iter() |
473 | .map(|assist| Assist { | 473 | .map(|assist| Assist { |
474 | id: assist.assist.id, | 474 | id: assist.assist.id, |
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index 410dae75c..68a53ad4b 100644 --- a/crates/ra_ide/src/references/rename.rs +++ b/crates/ra_ide/src/references/rename.rs | |||
@@ -670,6 +670,7 @@ mod tests { | |||
670 | }, | 670 | }, |
671 | ], | 671 | ], |
672 | cursor_position: None, | 672 | cursor_position: None, |
673 | is_snippet: false, | ||
673 | }, | 674 | }, |
674 | }, | 675 | }, |
675 | ) | 676 | ) |
@@ -722,6 +723,7 @@ mod tests { | |||
722 | }, | 723 | }, |
723 | ], | 724 | ], |
724 | cursor_position: None, | 725 | cursor_position: None, |
726 | is_snippet: false, | ||
725 | }, | 727 | }, |
726 | }, | 728 | }, |
727 | ) | 729 | ) |
@@ -818,6 +820,7 @@ mod tests { | |||
818 | }, | 820 | }, |
819 | ], | 821 | ], |
820 | cursor_position: None, | 822 | cursor_position: None, |
823 | is_snippet: false, | ||
821 | }, | 824 | }, |
822 | }, | 825 | }, |
823 | ) | 826 | ) |
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index fa8a9d92c..131b8f307 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use hir::Semantics; | 3 | use hir::{AsAssocItem, Semantics}; |
4 | use itertools::Itertools; | 4 | use itertools::Itertools; |
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{ |
@@ -65,14 +65,36 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run | |||
65 | RunnableKind::Bin | 65 | RunnableKind::Bin |
66 | } else { | 66 | } else { |
67 | let test_id = if let Some(module) = sema.to_def(&fn_def).map(|def| def.module(sema.db)) { | 67 | let test_id = if let Some(module) = sema.to_def(&fn_def).map(|def| def.module(sema.db)) { |
68 | let path = module | 68 | let def = sema.to_def(&fn_def)?; |
69 | let impl_trait_name = | ||
70 | def.as_assoc_item(sema.db).and_then(|assoc_item| { | ||
71 | match assoc_item.container(sema.db) { | ||
72 | hir::AssocItemContainer::Trait(trait_item) => { | ||
73 | Some(trait_item.name(sema.db).to_string()) | ||
74 | } | ||
75 | hir::AssocItemContainer::ImplDef(impl_def) => impl_def | ||
76 | .target_ty(sema.db) | ||
77 | .as_adt() | ||
78 | .map(|adt| adt.name(sema.db).to_string()), | ||
79 | } | ||
80 | }); | ||
81 | |||
82 | let path_iter = module | ||
69 | .path_to_root(sema.db) | 83 | .path_to_root(sema.db) |
70 | .into_iter() | 84 | .into_iter() |
71 | .rev() | 85 | .rev() |
72 | .filter_map(|it| it.name(sema.db)) | 86 | .filter_map(|it| it.name(sema.db)) |
73 | .map(|name| name.to_string()) | 87 | .map(|name| name.to_string()); |
74 | .chain(std::iter::once(name_string)) | 88 | |
75 | .join("::"); | 89 | let path = if let Some(impl_trait_name) = impl_trait_name { |
90 | path_iter | ||
91 | .chain(std::iter::once(impl_trait_name)) | ||
92 | .chain(std::iter::once(name_string)) | ||
93 | .join("::") | ||
94 | } else { | ||
95 | path_iter.chain(std::iter::once(name_string)).join("::") | ||
96 | }; | ||
97 | |||
76 | TestId::Path(path) | 98 | TestId::Path(path) |
77 | } else { | 99 | } else { |
78 | TestId::Name(name_string) | 100 | TestId::Name(name_string) |
@@ -238,6 +260,44 @@ mod tests { | |||
238 | } | 260 | } |
239 | 261 | ||
240 | #[test] | 262 | #[test] |
263 | fn test_runnables_doc_test_in_impl() { | ||
264 | let (analysis, pos) = analysis_and_position( | ||
265 | r#" | ||
266 | //- /lib.rs | ||
267 | <|> //empty | ||
268 | fn main() {} | ||
269 | |||
270 | struct Data; | ||
271 | impl Data { | ||
272 | /// ``` | ||
273 | /// let x = 5; | ||
274 | /// ``` | ||
275 | fn foo() {} | ||
276 | } | ||
277 | "#, | ||
278 | ); | ||
279 | let runnables = analysis.runnables(pos.file_id).unwrap(); | ||
280 | assert_debug_snapshot!(&runnables, | ||
281 | @r###" | ||
282 | [ | ||
283 | Runnable { | ||
284 | range: 1..21, | ||
285 | kind: Bin, | ||
286 | }, | ||
287 | Runnable { | ||
288 | range: 51..105, | ||
289 | kind: DocTest { | ||
290 | test_id: Path( | ||
291 | "Data::foo", | ||
292 | ), | ||
293 | }, | ||
294 | }, | ||
295 | ] | ||
296 | "### | ||
297 | ); | ||
298 | } | ||
299 | |||
300 | #[test] | ||
241 | fn test_runnables_module() { | 301 | fn test_runnables_module() { |
242 | let (analysis, pos) = analysis_and_position( | 302 | let (analysis, pos) = analysis_and_position( |
243 | r#" | 303 | r#" |
diff --git a/crates/ra_ide_db/src/source_change.rs b/crates/ra_ide_db/src/source_change.rs index af81a91a4..c64165f3a 100644 --- a/crates/ra_ide_db/src/source_change.rs +++ b/crates/ra_ide_db/src/source_change.rs | |||
@@ -13,6 +13,7 @@ pub struct SourceChange { | |||
13 | pub source_file_edits: Vec<SourceFileEdit>, | 13 | pub source_file_edits: Vec<SourceFileEdit>, |
14 | pub file_system_edits: Vec<FileSystemEdit>, | 14 | pub file_system_edits: Vec<FileSystemEdit>, |
15 | pub cursor_position: Option<FilePosition>, | 15 | pub cursor_position: Option<FilePosition>, |
16 | pub is_snippet: bool, | ||
16 | } | 17 | } |
17 | 18 | ||
18 | impl SourceChange { | 19 | impl SourceChange { |
@@ -28,6 +29,7 @@ impl SourceChange { | |||
28 | source_file_edits, | 29 | source_file_edits, |
29 | file_system_edits, | 30 | file_system_edits, |
30 | cursor_position: None, | 31 | cursor_position: None, |
32 | is_snippet: false, | ||
31 | } | 33 | } |
32 | } | 34 | } |
33 | 35 | ||
@@ -41,6 +43,7 @@ impl SourceChange { | |||
41 | source_file_edits: edits, | 43 | source_file_edits: edits, |
42 | file_system_edits: vec![], | 44 | file_system_edits: vec![], |
43 | cursor_position: None, | 45 | cursor_position: None, |
46 | is_snippet: false, | ||
44 | } | 47 | } |
45 | } | 48 | } |
46 | 49 | ||
@@ -52,6 +55,7 @@ impl SourceChange { | |||
52 | source_file_edits: vec![], | 55 | source_file_edits: vec![], |
53 | file_system_edits: edits, | 56 | file_system_edits: edits, |
54 | cursor_position: None, | 57 | cursor_position: None, |
58 | is_snippet: false, | ||
55 | } | 59 | } |
56 | } | 60 | } |
57 | 61 | ||
@@ -115,6 +119,7 @@ impl SingleFileChange { | |||
115 | source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }], | 119 | source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }], |
116 | file_system_edits: Vec::new(), | 120 | file_system_edits: Vec::new(), |
117 | cursor_position: self.cursor_position.map(|offset| FilePosition { file_id, offset }), | 121 | cursor_position: self.cursor_position.map(|offset| FilePosition { file_id, offset }), |
122 | is_snippet: false, | ||
118 | } | 123 | } |
119 | } | 124 | } |
120 | } | 125 | } |
diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs index 2a8dac757..664894d1f 100644 --- a/crates/ra_syntax/src/algo.rs +++ b/crates/ra_syntax/src/algo.rs | |||
@@ -266,6 +266,15 @@ impl<'a> SyntaxRewriter<'a> { | |||
266 | let replacement = Replacement::Single(with.clone().into()); | 266 | let replacement = Replacement::Single(with.clone().into()); |
267 | self.replacements.insert(what, replacement); | 267 | self.replacements.insert(what, replacement); |
268 | } | 268 | } |
269 | pub fn replace_with_many<T: Clone + Into<SyntaxElement>>( | ||
270 | &mut self, | ||
271 | what: &T, | ||
272 | with: Vec<SyntaxElement>, | ||
273 | ) { | ||
274 | let what = what.clone().into(); | ||
275 | let replacement = Replacement::Many(with); | ||
276 | self.replacements.insert(what, replacement); | ||
277 | } | ||
269 | pub fn replace_ast<T: AstNode>(&mut self, what: &T, with: &T) { | 278 | pub fn replace_ast<T: AstNode>(&mut self, what: &T, with: &T) { |
270 | self.replace(what.syntax(), with.syntax()) | 279 | self.replace(what.syntax(), with.syntax()) |
271 | } | 280 | } |
@@ -302,31 +311,41 @@ impl<'a> SyntaxRewriter<'a> { | |||
302 | 311 | ||
303 | fn rewrite_children(&self, node: &SyntaxNode) -> SyntaxNode { | 312 | fn rewrite_children(&self, node: &SyntaxNode) -> SyntaxNode { |
304 | // FIXME: this could be made much faster. | 313 | // FIXME: this could be made much faster. |
305 | let new_children = | 314 | let mut new_children = Vec::new(); |
306 | node.children_with_tokens().flat_map(|it| self.rewrite_self(&it)).collect::<Vec<_>>(); | 315 | for child in node.children_with_tokens() { |
316 | self.rewrite_self(&mut new_children, &child); | ||
317 | } | ||
307 | with_children(node, new_children) | 318 | with_children(node, new_children) |
308 | } | 319 | } |
309 | 320 | ||
310 | fn rewrite_self( | 321 | fn rewrite_self( |
311 | &self, | 322 | &self, |
323 | acc: &mut Vec<NodeOrToken<rowan::GreenNode, rowan::GreenToken>>, | ||
312 | element: &SyntaxElement, | 324 | element: &SyntaxElement, |
313 | ) -> Option<NodeOrToken<rowan::GreenNode, rowan::GreenToken>> { | 325 | ) { |
314 | if let Some(replacement) = self.replacement(&element) { | 326 | if let Some(replacement) = self.replacement(&element) { |
315 | return match replacement { | 327 | match replacement { |
316 | Replacement::Single(NodeOrToken::Node(it)) => { | 328 | Replacement::Single(NodeOrToken::Node(it)) => { |
317 | Some(NodeOrToken::Node(it.green().clone())) | 329 | acc.push(NodeOrToken::Node(it.green().clone())) |
318 | } | 330 | } |
319 | Replacement::Single(NodeOrToken::Token(it)) => { | 331 | Replacement::Single(NodeOrToken::Token(it)) => { |
320 | Some(NodeOrToken::Token(it.green().clone())) | 332 | acc.push(NodeOrToken::Token(it.green().clone())) |
321 | } | 333 | } |
322 | Replacement::Delete => None, | 334 | Replacement::Many(replacements) => { |
335 | acc.extend(replacements.iter().map(|it| match it { | ||
336 | NodeOrToken::Node(it) => NodeOrToken::Node(it.green().clone()), | ||
337 | NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()), | ||
338 | })) | ||
339 | } | ||
340 | Replacement::Delete => (), | ||
323 | }; | 341 | }; |
342 | return; | ||
324 | } | 343 | } |
325 | let res = match element { | 344 | let res = match element { |
326 | NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()), | 345 | NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()), |
327 | NodeOrToken::Node(it) => NodeOrToken::Node(self.rewrite_children(it).green().clone()), | 346 | NodeOrToken::Node(it) => NodeOrToken::Node(self.rewrite_children(it).green().clone()), |
328 | }; | 347 | }; |
329 | Some(res) | 348 | acc.push(res) |
330 | } | 349 | } |
331 | } | 350 | } |
332 | 351 | ||
@@ -341,6 +360,7 @@ impl ops::AddAssign for SyntaxRewriter<'_> { | |||
341 | enum Replacement { | 360 | enum Replacement { |
342 | Delete, | 361 | Delete, |
343 | Single(SyntaxElement), | 362 | Single(SyntaxElement), |
363 | Many(Vec<SyntaxElement>), | ||
344 | } | 364 | } |
345 | 365 | ||
346 | fn with_children( | 366 | fn with_children( |
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 24a1e1d91..29eb3fcb9 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs | |||
@@ -1,7 +1,10 @@ | |||
1 | //! This module contains functions for editing syntax trees. As the trees are | 1 | //! This module contains functions for editing syntax trees. As the trees are |
2 | //! immutable, all function here return a fresh copy of the tree, instead of | 2 | //! immutable, all function here return a fresh copy of the tree, instead of |
3 | //! doing an in-place modification. | 3 | //! doing an in-place modification. |
4 | use std::{iter, ops::RangeInclusive}; | 4 | use std::{ |
5 | fmt, iter, | ||
6 | ops::{self, RangeInclusive}, | ||
7 | }; | ||
5 | 8 | ||
6 | use arrayvec::ArrayVec; | 9 | use arrayvec::ArrayVec; |
7 | 10 | ||
@@ -437,6 +440,28 @@ impl From<u8> for IndentLevel { | |||
437 | } | 440 | } |
438 | } | 441 | } |
439 | 442 | ||
443 | impl fmt::Display for IndentLevel { | ||
444 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
445 | let spaces = " "; | ||
446 | let buf; | ||
447 | let len = self.0 as usize * 4; | ||
448 | let indent = if len <= spaces.len() { | ||
449 | &spaces[..len] | ||
450 | } else { | ||
451 | buf = iter::repeat(' ').take(len).collect::<String>(); | ||
452 | &buf | ||
453 | }; | ||
454 | fmt::Display::fmt(indent, f) | ||
455 | } | ||
456 | } | ||
457 | |||
458 | impl ops::Add<u8> for IndentLevel { | ||
459 | type Output = IndentLevel; | ||
460 | fn add(self, rhs: u8) -> IndentLevel { | ||
461 | IndentLevel(self.0 + rhs) | ||
462 | } | ||
463 | } | ||
464 | |||
440 | impl IndentLevel { | 465 | impl IndentLevel { |
441 | pub fn from_node(node: &SyntaxNode) -> IndentLevel { | 466 | pub fn from_node(node: &SyntaxNode) -> IndentLevel { |
442 | let first_token = match node.first_token() { | 467 | let first_token = match node.first_token() { |
@@ -453,6 +478,14 @@ impl IndentLevel { | |||
453 | IndentLevel(0) | 478 | IndentLevel(0) |
454 | } | 479 | } |
455 | 480 | ||
481 | /// XXX: this intentionally doesn't change the indent of the very first token. | ||
482 | /// Ie, in something like | ||
483 | /// ``` | ||
484 | /// fn foo() { | ||
485 | /// 92 | ||
486 | /// } | ||
487 | /// ``` | ||
488 | /// if you indent the block, the `{` token would stay put. | ||
456 | fn increase_indent(self, node: SyntaxNode) -> SyntaxNode { | 489 | fn increase_indent(self, node: SyntaxNode) -> SyntaxNode { |
457 | let mut rewriter = SyntaxRewriter::default(); | 490 | let mut rewriter = SyntaxRewriter::default(); |
458 | node.descendants_with_tokens() | 491 | node.descendants_with_tokens() |
@@ -463,12 +496,7 @@ impl IndentLevel { | |||
463 | text.contains('\n') | 496 | text.contains('\n') |
464 | }) | 497 | }) |
465 | .for_each(|ws| { | 498 | .for_each(|ws| { |
466 | let new_ws = make::tokens::whitespace(&format!( | 499 | let new_ws = make::tokens::whitespace(&format!("{}{}", ws.syntax(), self,)); |
467 | "{}{:width$}", | ||
468 | ws.syntax().text(), | ||
469 | "", | ||
470 | width = self.0 as usize * 4 | ||
471 | )); | ||
472 | rewriter.replace(ws.syntax(), &new_ws) | 500 | rewriter.replace(ws.syntax(), &new_ws) |
473 | }); | 501 | }); |
474 | rewriter.rewrite(&node) | 502 | rewriter.rewrite(&node) |
@@ -485,7 +513,7 @@ impl IndentLevel { | |||
485 | }) | 513 | }) |
486 | .for_each(|ws| { | 514 | .for_each(|ws| { |
487 | let new_ws = make::tokens::whitespace( | 515 | let new_ws = make::tokens::whitespace( |
488 | &ws.syntax().text().replace(&format!("\n{:1$}", "", self.0 as usize * 4), "\n"), | 516 | &ws.syntax().text().replace(&format!("\n{}", self), "\n"), |
489 | ); | 517 | ); |
490 | rewriter.replace(ws.syntax(), &new_ws) | 518 | rewriter.replace(ws.syntax(), &new_ws) |
491 | }); | 519 | }); |
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index d0e960fb4..da0eb0926 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -1,5 +1,9 @@ | |||
1 | //! This module contains free-standing functions for creating AST fragments out | 1 | //! This module contains free-standing functions for creating AST fragments out |
2 | //! of smaller pieces. | 2 | //! of smaller pieces. |
3 | //! | ||
4 | //! Note that all functions here intended to be stupid constructors, which just | ||
5 | //! assemble a finish node from immediate children. If you want to do something | ||
6 | //! smarter than that, it probably doesn't belong in this module. | ||
3 | use itertools::Itertools; | 7 | use itertools::Itertools; |
4 | use stdx::format_to; | 8 | use stdx::format_to; |
5 | 9 | ||
@@ -95,6 +99,9 @@ pub fn expr_empty_block() -> ast::Expr { | |||
95 | pub fn expr_unimplemented() -> ast::Expr { | 99 | pub fn expr_unimplemented() -> ast::Expr { |
96 | expr_from_text("unimplemented!()") | 100 | expr_from_text("unimplemented!()") |
97 | } | 101 | } |
102 | pub fn expr_unreachable() -> ast::Expr { | ||
103 | expr_from_text("unreachable!()") | ||
104 | } | ||
98 | pub fn expr_todo() -> ast::Expr { | 105 | pub fn expr_todo() -> ast::Expr { |
99 | expr_from_text("todo!()") | 106 | expr_from_text("todo!()") |
100 | } | 107 | } |
@@ -264,10 +271,6 @@ pub fn token(kind: SyntaxKind) -> SyntaxToken { | |||
264 | .unwrap_or_else(|| panic!("unhandled token: {:?}", kind)) | 271 | .unwrap_or_else(|| panic!("unhandled token: {:?}", kind)) |
265 | } | 272 | } |
266 | 273 | ||
267 | pub fn unreachable_macro_call() -> ast::MacroCall { | ||
268 | ast_from_text(&format!("unreachable!()")) | ||
269 | } | ||
270 | |||
271 | pub fn param(name: String, ty: String) -> ast::Param { | 274 | pub fn param(name: String, ty: String) -> ast::Param { |
272 | ast_from_text(&format!("fn f({}: {}) {{ }}", name, ty)) | 275 | ast_from_text(&format!("fn f({}: {}) {{ }}", name, ty)) |
273 | } | 276 | } |
@@ -277,7 +280,12 @@ pub fn param_list(pats: impl IntoIterator<Item = ast::Param>) -> ast::ParamList | |||
277 | ast_from_text(&format!("fn f({}) {{ }}", args)) | 280 | ast_from_text(&format!("fn f({}) {{ }}", args)) |
278 | } | 281 | } |
279 | 282 | ||
283 | pub fn visibility_pub_crate() -> ast::Visibility { | ||
284 | ast_from_text("pub(crate) struct S") | ||
285 | } | ||
286 | |||
280 | pub fn fn_def( | 287 | pub fn fn_def( |
288 | visibility: Option<ast::Visibility>, | ||
281 | fn_name: ast::Name, | 289 | fn_name: ast::Name, |
282 | type_params: Option<ast::TypeParamList>, | 290 | type_params: Option<ast::TypeParamList>, |
283 | params: ast::ParamList, | 291 | params: ast::ParamList, |
@@ -285,21 +293,11 @@ pub fn fn_def( | |||
285 | ) -> ast::FnDef { | 293 | ) -> ast::FnDef { |
286 | let type_params = | 294 | let type_params = |
287 | if let Some(type_params) = type_params { format!("<{}>", type_params) } else { "".into() }; | 295 | if let Some(type_params) = type_params { format!("<{}>", type_params) } else { "".into() }; |
288 | ast_from_text(&format!("fn {}{}{} {}", fn_name, type_params, params, body)) | 296 | let visibility = match visibility { |
289 | } | 297 | None => String::new(), |
290 | 298 | Some(it) => format!("{} ", it), | |
291 | pub fn add_leading_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile { | 299 | }; |
292 | let newlines = "\n".repeat(amount_of_newlines); | 300 | ast_from_text(&format!("{}fn {}{}{} {}", visibility, fn_name, type_params, params, body)) |
293 | ast_from_text(&format!("{}{}", newlines, t.syntax())) | ||
294 | } | ||
295 | |||
296 | pub fn add_trailing_newlines(amount_of_newlines: usize, t: impl AstNode) -> ast::SourceFile { | ||
297 | let newlines = "\n".repeat(amount_of_newlines); | ||
298 | ast_from_text(&format!("{}{}", t.syntax(), newlines)) | ||
299 | } | ||
300 | |||
301 | pub fn add_pub_crate_modifier(fn_def: ast::FnDef) -> ast::FnDef { | ||
302 | ast_from_text(&format!("pub(crate) {}", fn_def)) | ||
303 | } | 301 | } |
304 | 302 | ||
305 | fn ast_from_text<N: AstNode>(text: &str) -> N { | 303 | fn ast_from_text<N: AstNode>(text: &str) -> N { |
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs index 6147ae207..b20efe98d 100644 --- a/crates/rust-analyzer/src/cli/analysis_bench.rs +++ b/crates/rust-analyzer/src/cli/analysis_bench.rs | |||
@@ -105,7 +105,7 @@ pub fn analysis_bench( | |||
105 | if is_completion { | 105 | if is_completion { |
106 | let options = CompletionConfig::default(); | 106 | let options = CompletionConfig::default(); |
107 | let res = do_work(&mut host, file_id, |analysis| { | 107 | let res = do_work(&mut host, file_id, |analysis| { |
108 | analysis.completions(file_position, &options) | 108 | analysis.completions(&options, file_position) |
109 | }); | 109 | }); |
110 | if verbosity.is_verbose() { | 110 | if verbosity.is_verbose() { |
111 | println!("\n{:#?}", res); | 111 | println!("\n{:#?}", res); |
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index b5dc6f0fa..d75c48597 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs | |||
@@ -11,7 +11,7 @@ use std::{ffi::OsString, path::PathBuf}; | |||
11 | 11 | ||
12 | use lsp_types::ClientCapabilities; | 12 | use lsp_types::ClientCapabilities; |
13 | use ra_flycheck::FlycheckConfig; | 13 | use ra_flycheck::FlycheckConfig; |
14 | use ra_ide::{CompletionConfig, InlayHintsConfig}; | 14 | use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig}; |
15 | use ra_project_model::CargoConfig; | 15 | use ra_project_model::CargoConfig; |
16 | use serde::Deserialize; | 16 | use serde::Deserialize; |
17 | 17 | ||
@@ -32,6 +32,7 @@ pub struct Config { | |||
32 | 32 | ||
33 | pub inlay_hints: InlayHintsConfig, | 33 | pub inlay_hints: InlayHintsConfig, |
34 | pub completion: CompletionConfig, | 34 | pub completion: CompletionConfig, |
35 | pub assist: AssistConfig, | ||
35 | pub call_info_full: bool, | 36 | pub call_info_full: bool, |
36 | pub lens: LensConfig, | 37 | pub lens: LensConfig, |
37 | } | 38 | } |
@@ -136,6 +137,7 @@ impl Default for Config { | |||
136 | add_call_argument_snippets: true, | 137 | add_call_argument_snippets: true, |
137 | ..CompletionConfig::default() | 138 | ..CompletionConfig::default() |
138 | }, | 139 | }, |
140 | assist: AssistConfig::default(), | ||
139 | call_info_full: true, | 141 | call_info_full: true, |
140 | lens: LensConfig::default(), | 142 | lens: LensConfig::default(), |
141 | } | 143 | } |
@@ -273,6 +275,7 @@ impl Config { | |||
273 | { | 275 | { |
274 | self.client_caps.code_action_literals = value; | 276 | self.client_caps.code_action_literals = value; |
275 | } | 277 | } |
278 | |||
276 | self.completion.allow_snippets(false); | 279 | self.completion.allow_snippets(false); |
277 | if let Some(completion) = &doc_caps.completion { | 280 | if let Some(completion) = &doc_caps.completion { |
278 | if let Some(completion_item) = &completion.completion_item { | 281 | if let Some(completion_item) = &completion.completion_item { |
@@ -288,5 +291,12 @@ impl Config { | |||
288 | self.client_caps.work_done_progress = value; | 291 | self.client_caps.work_done_progress = value; |
289 | } | 292 | } |
290 | } | 293 | } |
294 | |||
295 | self.assist.allow_snippets(false); | ||
296 | if let Some(experimental) = &caps.experimental { | ||
297 | let enable = | ||
298 | experimental.get("snippetTextEdit").and_then(|it| it.as_bool()) == Some(true); | ||
299 | self.assist.allow_snippets(enable); | ||
300 | } | ||
291 | } | 301 | } |
292 | } | 302 | } |
diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs index 4bdd45a7d..25856c543 100644 --- a/crates/rust-analyzer/src/diagnostics.rs +++ b/crates/rust-analyzer/src/diagnostics.rs | |||
@@ -3,9 +3,11 @@ pub(crate) mod to_proto; | |||
3 | 3 | ||
4 | use std::{collections::HashMap, sync::Arc}; | 4 | use std::{collections::HashMap, sync::Arc}; |
5 | 5 | ||
6 | use lsp_types::{CodeActionOrCommand, Diagnostic, Range}; | 6 | use lsp_types::{Diagnostic, Range}; |
7 | use ra_ide::FileId; | 7 | use ra_ide::FileId; |
8 | 8 | ||
9 | use crate::lsp_ext; | ||
10 | |||
9 | pub type CheckFixes = Arc<HashMap<FileId, Vec<Fix>>>; | 11 | pub type CheckFixes = Arc<HashMap<FileId, Vec<Fix>>>; |
10 | 12 | ||
11 | #[derive(Debug, Default, Clone)] | 13 | #[derive(Debug, Default, Clone)] |
@@ -18,13 +20,13 @@ pub struct DiagnosticCollection { | |||
18 | #[derive(Debug, Clone)] | 20 | #[derive(Debug, Clone)] |
19 | pub struct Fix { | 21 | pub struct Fix { |
20 | pub range: Range, | 22 | pub range: Range, |
21 | pub action: CodeActionOrCommand, | 23 | pub action: lsp_ext::CodeAction, |
22 | } | 24 | } |
23 | 25 | ||
24 | #[derive(Debug)] | 26 | #[derive(Debug)] |
25 | pub enum DiagnosticTask { | 27 | pub enum DiagnosticTask { |
26 | ClearCheck, | 28 | ClearCheck, |
27 | AddCheck(FileId, Diagnostic, Vec<CodeActionOrCommand>), | 29 | AddCheck(FileId, Diagnostic, Vec<lsp_ext::CodeAction>), |
28 | SetNative(FileId, Vec<Diagnostic>), | 30 | SetNative(FileId, Vec<Diagnostic>), |
29 | } | 31 | } |
30 | 32 | ||
@@ -38,7 +40,7 @@ impl DiagnosticCollection { | |||
38 | &mut self, | 40 | &mut self, |
39 | file_id: FileId, | 41 | file_id: FileId, |
40 | diagnostic: Diagnostic, | 42 | diagnostic: Diagnostic, |
41 | fixes: Vec<CodeActionOrCommand>, | 43 | fixes: Vec<lsp_ext::CodeAction>, |
42 | ) { | 44 | ) { |
43 | let diagnostics = self.check.entry(file_id).or_default(); | 45 | let diagnostics = self.check.entry(file_id).or_default(); |
44 | for existing_diagnostic in diagnostics.iter() { | 46 | for existing_diagnostic in diagnostics.iter() { |
diff --git a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap index 076b3cf27..96466b5c9 100644 --- a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap +++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap | |||
@@ -68,9 +68,9 @@ expression: diag | |||
68 | kind: Some( | 68 | kind: Some( |
69 | "quickfix", | 69 | "quickfix", |
70 | ), | 70 | ), |
71 | diagnostics: None, | 71 | command: None, |
72 | edit: Some( | 72 | edit: Some( |
73 | WorkspaceEdit { | 73 | SnippetWorkspaceEdit { |
74 | changes: Some( | 74 | changes: Some( |
75 | { | 75 | { |
76 | "file:///test/src/main.rs": [ | 76 | "file:///test/src/main.rs": [ |
@@ -106,8 +106,6 @@ expression: diag | |||
106 | document_changes: None, | 106 | document_changes: None, |
107 | }, | 107 | }, |
108 | ), | 108 | ), |
109 | command: None, | ||
110 | is_preferred: None, | ||
111 | }, | 109 | }, |
112 | ], | 110 | ], |
113 | }, | 111 | }, |
diff --git a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap index 69138c15b..8f962277f 100644 --- a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap +++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap | |||
@@ -53,9 +53,9 @@ expression: diag | |||
53 | kind: Some( | 53 | kind: Some( |
54 | "quickfix", | 54 | "quickfix", |
55 | ), | 55 | ), |
56 | diagnostics: None, | 56 | command: None, |
57 | edit: Some( | 57 | edit: Some( |
58 | WorkspaceEdit { | 58 | SnippetWorkspaceEdit { |
59 | changes: Some( | 59 | changes: Some( |
60 | { | 60 | { |
61 | "file:///test/driver/subcommand/repl.rs": [ | 61 | "file:///test/driver/subcommand/repl.rs": [ |
@@ -78,8 +78,6 @@ expression: diag | |||
78 | document_changes: None, | 78 | document_changes: None, |
79 | }, | 79 | }, |
80 | ), | 80 | ), |
81 | command: None, | ||
82 | is_preferred: None, | ||
83 | }, | 81 | }, |
84 | ], | 82 | ], |
85 | }, | 83 | }, |
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs index eabf4908f..afea59525 100644 --- a/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs | |||
@@ -7,13 +7,13 @@ use std::{ | |||
7 | }; | 7 | }; |
8 | 8 | ||
9 | use lsp_types::{ | 9 | use lsp_types::{ |
10 | CodeAction, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, | 10 | Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location, |
11 | Location, NumberOrString, Position, Range, TextEdit, Url, WorkspaceEdit, | 11 | NumberOrString, Position, Range, TextEdit, Url, |
12 | }; | 12 | }; |
13 | use ra_flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion}; | 13 | use ra_flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion}; |
14 | use stdx::format_to; | 14 | use stdx::format_to; |
15 | 15 | ||
16 | use crate::Result; | 16 | use crate::{lsp_ext, Result}; |
17 | 17 | ||
18 | /// Converts a Rust level string to a LSP severity | 18 | /// Converts a Rust level string to a LSP severity |
19 | fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> { | 19 | fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> { |
@@ -110,7 +110,7 @@ fn is_deprecated(rd: &ra_flycheck::Diagnostic) -> bool { | |||
110 | 110 | ||
111 | enum MappedRustChildDiagnostic { | 111 | enum MappedRustChildDiagnostic { |
112 | Related(DiagnosticRelatedInformation), | 112 | Related(DiagnosticRelatedInformation), |
113 | SuggestedFix(CodeAction), | 113 | SuggestedFix(lsp_ext::CodeAction), |
114 | MessageLine(String), | 114 | MessageLine(String), |
115 | } | 115 | } |
116 | 116 | ||
@@ -143,13 +143,15 @@ fn map_rust_child_diagnostic( | |||
143 | message: rd.message.clone(), | 143 | message: rd.message.clone(), |
144 | }) | 144 | }) |
145 | } else { | 145 | } else { |
146 | MappedRustChildDiagnostic::SuggestedFix(CodeAction { | 146 | MappedRustChildDiagnostic::SuggestedFix(lsp_ext::CodeAction { |
147 | title: rd.message.clone(), | 147 | title: rd.message.clone(), |
148 | kind: Some("quickfix".to_string()), | 148 | kind: Some("quickfix".to_string()), |
149 | diagnostics: None, | 149 | edit: Some(lsp_ext::SnippetWorkspaceEdit { |
150 | edit: Some(WorkspaceEdit::new(edit_map)), | 150 | // FIXME: there's no good reason to use edit_map here.... |
151 | changes: Some(edit_map), | ||
152 | document_changes: None, | ||
153 | }), | ||
151 | command: None, | 154 | command: None, |
152 | is_preferred: None, | ||
153 | }) | 155 | }) |
154 | } | 156 | } |
155 | } | 157 | } |
@@ -158,7 +160,7 @@ fn map_rust_child_diagnostic( | |||
158 | pub(crate) struct MappedRustDiagnostic { | 160 | pub(crate) struct MappedRustDiagnostic { |
159 | pub location: Location, | 161 | pub location: Location, |
160 | pub diagnostic: Diagnostic, | 162 | pub diagnostic: Diagnostic, |
161 | pub fixes: Vec<CodeAction>, | 163 | pub fixes: Vec<lsp_ext::CodeAction>, |
162 | } | 164 | } |
163 | 165 | ||
164 | /// Converts a Rust root diagnostic to LSP form | 166 | /// Converts a Rust root diagnostic to LSP form |
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index 313a8c769..f75a26eb7 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | //! rust-analyzer extensions to the LSP. | 1 | //! rust-analyzer extensions to the LSP. |
2 | 2 | ||
3 | use std::path::PathBuf; | 3 | use std::{collections::HashMap, path::PathBuf}; |
4 | 4 | ||
5 | use lsp_types::request::Request; | 5 | use lsp_types::request::Request; |
6 | use lsp_types::{Location, Position, Range, TextDocumentIdentifier}; | 6 | use lsp_types::{Location, Position, Range, TextDocumentIdentifier}; |
@@ -137,7 +137,7 @@ pub struct Runnable { | |||
137 | #[serde(rename_all = "camelCase")] | 137 | #[serde(rename_all = "camelCase")] |
138 | pub struct SourceChange { | 138 | pub struct SourceChange { |
139 | pub label: String, | 139 | pub label: String, |
140 | pub workspace_edit: lsp_types::WorkspaceEdit, | 140 | pub workspace_edit: SnippetWorkspaceEdit, |
141 | pub cursor_position: Option<lsp_types::TextDocumentPositionParams>, | 141 | pub cursor_position: Option<lsp_types::TextDocumentPositionParams>, |
142 | } | 142 | } |
143 | 143 | ||
@@ -183,3 +183,54 @@ pub struct SsrParams { | |||
183 | pub query: String, | 183 | pub query: String, |
184 | pub parse_only: bool, | 184 | pub parse_only: bool, |
185 | } | 185 | } |
186 | |||
187 | pub enum CodeActionRequest {} | ||
188 | |||
189 | impl Request for CodeActionRequest { | ||
190 | type Params = lsp_types::CodeActionParams; | ||
191 | type Result = Option<Vec<CodeAction>>; | ||
192 | const METHOD: &'static str = "textDocument/codeAction"; | ||
193 | } | ||
194 | |||
195 | #[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)] | ||
196 | pub struct CodeAction { | ||
197 | pub title: String, | ||
198 | #[serde(skip_serializing_if = "Option::is_none")] | ||
199 | pub kind: Option<String>, | ||
200 | #[serde(skip_serializing_if = "Option::is_none")] | ||
201 | pub command: Option<lsp_types::Command>, | ||
202 | #[serde(skip_serializing_if = "Option::is_none")] | ||
203 | pub edit: Option<SnippetWorkspaceEdit>, | ||
204 | } | ||
205 | |||
206 | #[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] | ||
207 | #[serde(rename_all = "camelCase")] | ||
208 | pub struct SnippetWorkspaceEdit { | ||
209 | #[serde(skip_serializing_if = "Option::is_none")] | ||
210 | pub changes: Option<HashMap<lsp_types::Url, Vec<lsp_types::TextEdit>>>, | ||
211 | #[serde(skip_serializing_if = "Option::is_none")] | ||
212 | pub document_changes: Option<Vec<SnippetDocumentChangeOperation>>, | ||
213 | } | ||
214 | |||
215 | #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] | ||
216 | #[serde(untagged, rename_all = "lowercase")] | ||
217 | pub enum SnippetDocumentChangeOperation { | ||
218 | Op(lsp_types::ResourceOp), | ||
219 | Edit(SnippetTextDocumentEdit), | ||
220 | } | ||
221 | |||
222 | #[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] | ||
223 | #[serde(rename_all = "camelCase")] | ||
224 | pub struct SnippetTextDocumentEdit { | ||
225 | pub text_document: lsp_types::VersionedTextDocumentIdentifier, | ||
226 | pub edits: Vec<SnippetTextEdit>, | ||
227 | } | ||
228 | |||
229 | #[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)] | ||
230 | #[serde(rename_all = "camelCase")] | ||
231 | pub struct SnippetTextEdit { | ||
232 | pub range: Range, | ||
233 | pub new_text: String, | ||
234 | #[serde(skip_serializing_if = "Option::is_none")] | ||
235 | pub insert_text_format: Option<lsp_types::InsertTextFormat>, | ||
236 | } | ||
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 15e5bb354..87795fffb 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -518,6 +518,7 @@ fn on_request( | |||
518 | .on::<lsp_ext::ParentModule>(handlers::handle_parent_module)? | 518 | .on::<lsp_ext::ParentModule>(handlers::handle_parent_module)? |
519 | .on::<lsp_ext::Runnables>(handlers::handle_runnables)? | 519 | .on::<lsp_ext::Runnables>(handlers::handle_runnables)? |
520 | .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)? | 520 | .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)? |
521 | .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)? | ||
521 | .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)? | 522 | .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)? |
522 | .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)? | 523 | .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)? |
523 | .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)? | 524 | .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)? |
@@ -525,7 +526,6 @@ fn on_request( | |||
525 | .on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation)? | 526 | .on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation)? |
526 | .on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition)? | 527 | .on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition)? |
527 | .on::<lsp_types::request::Completion>(handlers::handle_completion)? | 528 | .on::<lsp_types::request::Completion>(handlers::handle_completion)? |
528 | .on::<lsp_types::request::CodeActionRequest>(handlers::handle_code_action)? | ||
529 | .on::<lsp_types::request::CodeLensRequest>(handlers::handle_code_lens)? | 529 | .on::<lsp_types::request::CodeLensRequest>(handlers::handle_code_lens)? |
530 | .on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve)? | 530 | .on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve)? |
531 | .on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range)? | 531 | .on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range)? |
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index e67556752..4ff8fa69e 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs | |||
@@ -11,12 +11,11 @@ use lsp_server::ErrorCode; | |||
11 | use lsp_types::{ | 11 | use lsp_types::{ |
12 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, | 12 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, |
13 | CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, | 13 | CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, |
14 | CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic, | 14 | CodeLens, Command, CompletionItem, Diagnostic, DocumentFormattingParams, DocumentHighlight, |
15 | DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams, | 15 | DocumentSymbol, FoldingRange, FoldingRangeParams, Hover, HoverContents, Location, |
16 | Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse, | 16 | MarkupContent, MarkupKind, Position, PrepareRenameResponse, Range, RenameParams, |
17 | Range, RenameParams, SemanticTokensParams, SemanticTokensRangeParams, | 17 | SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult, |
18 | SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, | 18 | SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, TextEdit, Url, WorkspaceEdit, |
19 | TextEdit, Url, WorkspaceEdit, | ||
20 | }; | 19 | }; |
21 | use ra_ide::{ | 20 | use ra_ide::{ |
22 | Assist, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, SearchScope, | 21 | Assist, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, SearchScope, |
@@ -476,7 +475,7 @@ pub fn handle_completion( | |||
476 | return Ok(None); | 475 | return Ok(None); |
477 | } | 476 | } |
478 | 477 | ||
479 | let items = match world.analysis().completions(position, &world.config.completion)? { | 478 | let items = match world.analysis().completions(&world.config.completion, position)? { |
480 | None => return Ok(None), | 479 | None => return Ok(None), |
481 | Some(items) => items, | 480 | Some(items) => items, |
482 | }; | 481 | }; |
@@ -585,9 +584,8 @@ pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result<Optio | |||
585 | None => return Ok(None), | 584 | None => return Ok(None), |
586 | Some(it) => it.info, | 585 | Some(it) => it.info, |
587 | }; | 586 | }; |
588 | 587 | let workspace_edit = to_proto::workspace_edit(&world, source_change)?; | |
589 | let source_change = to_proto::source_change(&world, source_change)?; | 588 | Ok(Some(workspace_edit)) |
590 | Ok(Some(source_change.workspace_edit)) | ||
591 | } | 589 | } |
592 | 590 | ||
593 | pub fn handle_references( | 591 | pub fn handle_references( |
@@ -696,14 +694,21 @@ pub fn handle_formatting( | |||
696 | pub fn handle_code_action( | 694 | pub fn handle_code_action( |
697 | world: WorldSnapshot, | 695 | world: WorldSnapshot, |
698 | params: lsp_types::CodeActionParams, | 696 | params: lsp_types::CodeActionParams, |
699 | ) -> Result<Option<CodeActionResponse>> { | 697 | ) -> Result<Option<Vec<lsp_ext::CodeAction>>> { |
700 | let _p = profile("handle_code_action"); | 698 | let _p = profile("handle_code_action"); |
699 | // We intentionally don't support command-based actions, as those either | ||
700 | // requires custom client-code anyway, or requires server-initiated edits. | ||
701 | // Server initiated edits break causality, so we avoid those as well. | ||
702 | if !world.config.client_caps.code_action_literals { | ||
703 | return Ok(None); | ||
704 | } | ||
705 | |||
701 | let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; | 706 | let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; |
702 | let line_index = world.analysis().file_line_index(file_id)?; | 707 | let line_index = world.analysis().file_line_index(file_id)?; |
703 | let range = from_proto::text_range(&line_index, params.range); | 708 | let range = from_proto::text_range(&line_index, params.range); |
704 | 709 | ||
705 | let diagnostics = world.analysis().diagnostics(file_id)?; | 710 | let diagnostics = world.analysis().diagnostics(file_id)?; |
706 | let mut res = CodeActionResponse::default(); | 711 | let mut res: Vec<lsp_ext::CodeAction> = Vec::new(); |
707 | 712 | ||
708 | let fixes_from_diagnostics = diagnostics | 713 | let fixes_from_diagnostics = diagnostics |
709 | .into_iter() | 714 | .into_iter() |
@@ -713,22 +718,9 @@ pub fn handle_code_action( | |||
713 | 718 | ||
714 | for source_edit in fixes_from_diagnostics { | 719 | for source_edit in fixes_from_diagnostics { |
715 | let title = source_edit.label.clone(); | 720 | let title = source_edit.label.clone(); |
716 | let edit = to_proto::source_change(&world, source_edit)?; | 721 | let edit = to_proto::snippet_workspace_edit(&world, source_edit)?; |
717 | 722 | let action = lsp_ext::CodeAction { title, kind: None, edit: Some(edit), command: None }; | |
718 | let command = Command { | 723 | res.push(action); |
719 | title, | ||
720 | command: "rust-analyzer.applySourceChange".to_string(), | ||
721 | arguments: Some(vec![to_value(edit).unwrap()]), | ||
722 | }; | ||
723 | let action = CodeAction { | ||
724 | title: command.title.clone(), | ||
725 | kind: None, | ||
726 | diagnostics: None, | ||
727 | edit: None, | ||
728 | command: Some(command), | ||
729 | is_preferred: None, | ||
730 | }; | ||
731 | res.push(action.into()); | ||
732 | } | 724 | } |
733 | 725 | ||
734 | for fix in world.check_fixes.get(&file_id).into_iter().flatten() { | 726 | for fix in world.check_fixes.get(&file_id).into_iter().flatten() { |
@@ -740,14 +732,21 @@ pub fn handle_code_action( | |||
740 | } | 732 | } |
741 | 733 | ||
742 | let mut grouped_assists: FxHashMap<String, (usize, Vec<Assist>)> = FxHashMap::default(); | 734 | let mut grouped_assists: FxHashMap<String, (usize, Vec<Assist>)> = FxHashMap::default(); |
743 | for assist in world.analysis().assists(FileRange { file_id, range })?.into_iter() { | 735 | for assist in |
736 | world.analysis().assists(&world.config.assist, FileRange { file_id, range })?.into_iter() | ||
737 | { | ||
744 | match &assist.group_label { | 738 | match &assist.group_label { |
745 | Some(label) => grouped_assists | 739 | Some(label) => grouped_assists |
746 | .entry(label.to_owned()) | 740 | .entry(label.to_owned()) |
747 | .or_insert_with(|| { | 741 | .or_insert_with(|| { |
748 | let idx = res.len(); | 742 | let idx = res.len(); |
749 | let dummy = Command::new(String::new(), String::new(), None); | 743 | let dummy = lsp_ext::CodeAction { |
750 | res.push(dummy.into()); | 744 | title: String::new(), |
745 | kind: None, | ||
746 | command: None, | ||
747 | edit: None, | ||
748 | }; | ||
749 | res.push(dummy); | ||
751 | (idx, Vec::new()) | 750 | (idx, Vec::new()) |
752 | }) | 751 | }) |
753 | .1 | 752 | .1 |
@@ -775,35 +774,10 @@ pub fn handle_code_action( | |||
775 | command: "rust-analyzer.selectAndApplySourceChange".to_string(), | 774 | command: "rust-analyzer.selectAndApplySourceChange".to_string(), |
776 | arguments: Some(vec![serde_json::Value::Array(arguments)]), | 775 | arguments: Some(vec![serde_json::Value::Array(arguments)]), |
777 | }); | 776 | }); |
778 | res[idx] = CodeAction { | 777 | res[idx] = lsp_ext::CodeAction { title, kind: None, edit: None, command }; |
779 | title, | ||
780 | kind: None, | ||
781 | diagnostics: None, | ||
782 | edit: None, | ||
783 | command, | ||
784 | is_preferred: None, | ||
785 | } | ||
786 | .into(); | ||
787 | } | 778 | } |
788 | } | 779 | } |
789 | 780 | ||
790 | // If the client only supports commands then filter the list | ||
791 | // and remove and actions that depend on edits. | ||
792 | if !world.config.client_caps.code_action_literals { | ||
793 | // FIXME: use drain_filter once it hits stable. | ||
794 | res = res | ||
795 | .into_iter() | ||
796 | .filter_map(|it| match it { | ||
797 | cmd @ lsp_types::CodeActionOrCommand::Command(_) => Some(cmd), | ||
798 | lsp_types::CodeActionOrCommand::CodeAction(action) => match action.command { | ||
799 | Some(cmd) if action.edit.is_none() => { | ||
800 | Some(lsp_types::CodeActionOrCommand::Command(cmd)) | ||
801 | } | ||
802 | _ => None, | ||
803 | }, | ||
804 | }) | ||
805 | .collect(); | ||
806 | } | ||
807 | Ok(Some(res)) | 781 | Ok(Some(res)) |
808 | } | 782 | } |
809 | 783 | ||
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index a8e2e535f..af54f81b7 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -112,6 +112,22 @@ pub(crate) fn text_edit( | |||
112 | lsp_types::TextEdit { range, new_text } | 112 | lsp_types::TextEdit { range, new_text } |
113 | } | 113 | } |
114 | 114 | ||
115 | pub(crate) fn snippet_text_edit( | ||
116 | line_index: &LineIndex, | ||
117 | line_endings: LineEndings, | ||
118 | is_snippet: bool, | ||
119 | indel: Indel, | ||
120 | ) -> lsp_ext::SnippetTextEdit { | ||
121 | let text_edit = text_edit(line_index, line_endings, indel); | ||
122 | let insert_text_format = | ||
123 | if is_snippet { Some(lsp_types::InsertTextFormat::Snippet) } else { None }; | ||
124 | lsp_ext::SnippetTextEdit { | ||
125 | range: text_edit.range, | ||
126 | new_text: text_edit.new_text, | ||
127 | insert_text_format, | ||
128 | } | ||
129 | } | ||
130 | |||
115 | pub(crate) fn text_edit_vec( | 131 | pub(crate) fn text_edit_vec( |
116 | line_index: &LineIndex, | 132 | line_index: &LineIndex, |
117 | line_endings: LineEndings, | 133 | line_endings: LineEndings, |
@@ -441,10 +457,11 @@ pub(crate) fn goto_definition_response( | |||
441 | } | 457 | } |
442 | } | 458 | } |
443 | 459 | ||
444 | pub(crate) fn text_document_edit( | 460 | pub(crate) fn snippet_text_document_edit( |
445 | world: &WorldSnapshot, | 461 | world: &WorldSnapshot, |
462 | is_snippet: bool, | ||
446 | source_file_edit: SourceFileEdit, | 463 | source_file_edit: SourceFileEdit, |
447 | ) -> Result<lsp_types::TextDocumentEdit> { | 464 | ) -> Result<lsp_ext::SnippetTextDocumentEdit> { |
448 | let text_document = versioned_text_document_identifier(world, source_file_edit.file_id, None)?; | 465 | let text_document = versioned_text_document_identifier(world, source_file_edit.file_id, None)?; |
449 | let line_index = world.analysis().file_line_index(source_file_edit.file_id)?; | 466 | let line_index = world.analysis().file_line_index(source_file_edit.file_id)?; |
450 | let line_endings = world.file_line_endings(source_file_edit.file_id); | 467 | let line_endings = world.file_line_endings(source_file_edit.file_id); |
@@ -452,9 +469,9 @@ pub(crate) fn text_document_edit( | |||
452 | .edit | 469 | .edit |
453 | .as_indels() | 470 | .as_indels() |
454 | .iter() | 471 | .iter() |
455 | .map(|it| text_edit(&line_index, line_endings, it.clone())) | 472 | .map(|it| snippet_text_edit(&line_index, line_endings, is_snippet, it.clone())) |
456 | .collect(); | 473 | .collect(); |
457 | Ok(lsp_types::TextDocumentEdit { text_document, edits }) | 474 | Ok(lsp_ext::SnippetTextDocumentEdit { text_document, edits }) |
458 | } | 475 | } |
459 | 476 | ||
460 | pub(crate) fn resource_op( | 477 | pub(crate) fn resource_op( |
@@ -500,20 +517,70 @@ pub(crate) fn source_change( | |||
500 | }) | 517 | }) |
501 | } | 518 | } |
502 | }; | 519 | }; |
503 | let mut document_changes: Vec<lsp_types::DocumentChangeOperation> = Vec::new(); | 520 | let label = source_change.label.clone(); |
521 | let workspace_edit = self::snippet_workspace_edit(world, source_change)?; | ||
522 | Ok(lsp_ext::SourceChange { label, workspace_edit, cursor_position }) | ||
523 | } | ||
524 | |||
525 | pub(crate) fn snippet_workspace_edit( | ||
526 | world: &WorldSnapshot, | ||
527 | source_change: SourceChange, | ||
528 | ) -> Result<lsp_ext::SnippetWorkspaceEdit> { | ||
529 | let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new(); | ||
504 | for op in source_change.file_system_edits { | 530 | for op in source_change.file_system_edits { |
505 | let op = resource_op(&world, op)?; | 531 | let op = resource_op(&world, op)?; |
506 | document_changes.push(lsp_types::DocumentChangeOperation::Op(op)); | 532 | document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Op(op)); |
507 | } | 533 | } |
508 | for edit in source_change.source_file_edits { | 534 | for edit in source_change.source_file_edits { |
509 | let edit = text_document_edit(&world, edit)?; | 535 | let edit = snippet_text_document_edit(&world, source_change.is_snippet, edit)?; |
510 | document_changes.push(lsp_types::DocumentChangeOperation::Edit(edit)); | 536 | document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit)); |
537 | } | ||
538 | let workspace_edit = | ||
539 | lsp_ext::SnippetWorkspaceEdit { changes: None, document_changes: Some(document_changes) }; | ||
540 | Ok(workspace_edit) | ||
541 | } | ||
542 | |||
543 | pub(crate) fn workspace_edit( | ||
544 | world: &WorldSnapshot, | ||
545 | source_change: SourceChange, | ||
546 | ) -> Result<lsp_types::WorkspaceEdit> { | ||
547 | assert!(!source_change.is_snippet); | ||
548 | snippet_workspace_edit(world, source_change).map(|it| it.into()) | ||
549 | } | ||
550 | |||
551 | impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit { | ||
552 | fn from(snippet_workspace_edit: lsp_ext::SnippetWorkspaceEdit) -> lsp_types::WorkspaceEdit { | ||
553 | lsp_types::WorkspaceEdit { | ||
554 | changes: None, | ||
555 | document_changes: snippet_workspace_edit.document_changes.map(|changes| { | ||
556 | lsp_types::DocumentChanges::Operations( | ||
557 | changes | ||
558 | .into_iter() | ||
559 | .map(|change| match change { | ||
560 | lsp_ext::SnippetDocumentChangeOperation::Op(op) => { | ||
561 | lsp_types::DocumentChangeOperation::Op(op) | ||
562 | } | ||
563 | lsp_ext::SnippetDocumentChangeOperation::Edit(edit) => { | ||
564 | lsp_types::DocumentChangeOperation::Edit( | ||
565 | lsp_types::TextDocumentEdit { | ||
566 | text_document: edit.text_document, | ||
567 | edits: edit | ||
568 | .edits | ||
569 | .into_iter() | ||
570 | .map(|edit| lsp_types::TextEdit { | ||
571 | range: edit.range, | ||
572 | new_text: edit.new_text, | ||
573 | }) | ||
574 | .collect(), | ||
575 | }, | ||
576 | ) | ||
577 | } | ||
578 | }) | ||
579 | .collect(), | ||
580 | ) | ||
581 | }), | ||
582 | } | ||
511 | } | 583 | } |
512 | let workspace_edit = lsp_types::WorkspaceEdit { | ||
513 | changes: None, | ||
514 | document_changes: Some(lsp_types::DocumentChanges::Operations(document_changes)), | ||
515 | }; | ||
516 | Ok(lsp_ext::SourceChange { label: source_change.label, workspace_edit, cursor_position }) | ||
517 | } | 584 | } |
518 | 585 | ||
519 | pub fn call_hierarchy_item( | 586 | pub fn call_hierarchy_item( |
@@ -571,22 +638,26 @@ fn main() <fold>{ | |||
571 | } | 638 | } |
572 | } | 639 | } |
573 | 640 | ||
574 | pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result<lsp_types::CodeAction> { | 641 | pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result<lsp_ext::CodeAction> { |
575 | let source_change = source_change(&world, assist.source_change)?; | 642 | let res = if assist.source_change.cursor_position.is_none() { |
576 | let arg = serde_json::to_value(source_change)?; | 643 | lsp_ext::CodeAction { |
577 | let title = assist.label; | 644 | title: assist.label, |
578 | let command = lsp_types::Command { | 645 | kind: Some(String::new()), |
579 | title: title.clone(), | 646 | edit: Some(snippet_workspace_edit(world, assist.source_change)?), |
580 | command: "rust-analyzer.applySourceChange".to_string(), | 647 | command: None, |
581 | arguments: Some(vec![arg]), | 648 | } |
582 | }; | 649 | } else { |
650 | assert!(!assist.source_change.is_snippet); | ||
651 | let source_change = source_change(&world, assist.source_change)?; | ||
652 | let arg = serde_json::to_value(source_change)?; | ||
653 | let title = assist.label; | ||
654 | let command = lsp_types::Command { | ||
655 | title: title.clone(), | ||
656 | command: "rust-analyzer.applySourceChange".to_string(), | ||
657 | arguments: Some(vec![arg]), | ||
658 | }; | ||
583 | 659 | ||
584 | Ok(lsp_types::CodeAction { | 660 | lsp_ext::CodeAction { title, kind: Some(String::new()), edit: None, command: Some(command) } |
585 | title, | 661 | }; |
586 | kind: Some(String::new()), | 662 | Ok(res) |
587 | diagnostics: None, | ||
588 | edit: None, | ||
589 | command: Some(command), | ||
590 | is_preferred: None, | ||
591 | }) | ||
592 | } | 663 | } |
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs index 5011cc273..74676b3ee 100644 --- a/crates/rust-analyzer/tests/heavy_tests/main.rs +++ b/crates/rust-analyzer/tests/heavy_tests/main.rs | |||
@@ -333,29 +333,17 @@ fn main() {} | |||
333 | partial_result_params: PartialResultParams::default(), | 333 | partial_result_params: PartialResultParams::default(), |
334 | work_done_progress_params: WorkDoneProgressParams::default(), | 334 | work_done_progress_params: WorkDoneProgressParams::default(), |
335 | }, | 335 | }, |
336 | json!([ | 336 | json!([{ |
337 | { | 337 | "edit": { |
338 | "command": { | 338 | "documentChanges": [ |
339 | "arguments": [ | ||
340 | { | 339 | { |
341 | "cursorPosition": null, | 340 | "kind": "create", |
342 | "label": "Create module", | 341 | "uri": "file:///[..]/src/bar.rs" |
343 | "workspaceEdit": { | ||
344 | "documentChanges": [ | ||
345 | { | ||
346 | "kind": "create", | ||
347 | "uri": "file:///[..]/src/bar.rs" | ||
348 | } | ||
349 | ] | ||
350 | } | ||
351 | } | 342 | } |
352 | ], | 343 | ] |
353 | "command": "rust-analyzer.applySourceChange", | ||
354 | "title": "Create module" | ||
355 | }, | 344 | }, |
356 | "title": "Create module" | 345 | "title": "Create module" |
357 | } | 346 | }]), |
358 | ]), | ||
359 | ); | 347 | ); |
360 | 348 | ||
361 | server.request::<CodeActionRequest>( | 349 | server.request::<CodeActionRequest>( |
@@ -416,29 +404,17 @@ fn main() {{}} | |||
416 | partial_result_params: PartialResultParams::default(), | 404 | partial_result_params: PartialResultParams::default(), |
417 | work_done_progress_params: WorkDoneProgressParams::default(), | 405 | work_done_progress_params: WorkDoneProgressParams::default(), |
418 | }, | 406 | }, |
419 | json!([ | 407 | json!([{ |
420 | { | 408 | "edit": { |
421 | "command": { | 409 | "documentChanges": [ |
422 | "arguments": [ | ||
423 | { | 410 | { |
424 | "cursorPosition": null, | 411 | "kind": "create", |
425 | "label": "Create module", | 412 | "uri": "file://[..]/src/bar.rs" |
426 | "workspaceEdit": { | ||
427 | "documentChanges": [ | ||
428 | { | ||
429 | "kind": "create", | ||
430 | "uri": "file:///[..]/src/bar.rs" | ||
431 | } | ||
432 | ] | ||
433 | } | ||
434 | } | 413 | } |
435 | ], | 414 | ] |
436 | "command": "rust-analyzer.applySourceChange", | ||
437 | "title": "Create module" | ||
438 | }, | 415 | }, |
439 | "title": "Create module" | 416 | "title": "Create module" |
440 | } | 417 | }]), |
441 | ]), | ||
442 | ); | 418 | ); |
443 | 419 | ||
444 | server.request::<CodeActionRequest>( | 420 | server.request::<CodeActionRequest>( |
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs index 0f34ce70e..71a57fba2 100644 --- a/crates/stdx/src/lib.rs +++ b/crates/stdx/src/lib.rs | |||
@@ -116,3 +116,11 @@ pub fn to_lower_snake_case(s: &str) -> String { | |||
116 | } | 116 | } |
117 | buf | 117 | buf |
118 | } | 118 | } |
119 | |||
120 | pub fn replace(buf: &mut String, from: char, to: &str) { | ||
121 | if !buf.contains(from) { | ||
122 | return; | ||
123 | } | ||
124 | // FIXME: do this in place. | ||
125 | *buf = buf.replace(from, to) | ||
126 | } | ||
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md new file mode 100644 index 000000000..d2ec6c021 --- /dev/null +++ b/docs/dev/lsp-extensions.md | |||
@@ -0,0 +1,34 @@ | |||
1 | # LSP Extensions | ||
2 | |||
3 | This document describes LSP extensions used by rust-analyzer. | ||
4 | It's a best effort document, when in doubt, consult the source (and send a PR with clarification ;-) ). | ||
5 | We aim to upstream all non Rust-specific extensions to the protocol, but this is not a top priority. | ||
6 | All capabilities are enabled via `experimental` field of `ClientCapabilities`. | ||
7 | |||
8 | ## `SnippetTextEdit` | ||
9 | |||
10 | **Capability** | ||
11 | |||
12 | ```typescript | ||
13 | { | ||
14 | "snippetTextEdit": boolean | ||
15 | } | ||
16 | ``` | ||
17 | |||
18 | If this capability is set, `WorkspaceEdit`s returned from `codeAction` requests might contain `SnippetTextEdit`s instead of usual `TextEdit`s: | ||
19 | |||
20 | ```typescript | ||
21 | interface SnippetTextEdit extends TextEdit { | ||
22 | insertTextFormat?: InsertTextFormat; | ||
23 | } | ||
24 | ``` | ||
25 | |||
26 | ```typescript | ||
27 | export interface TextDocumentEdit { | ||
28 | textDocument: VersionedTextDocumentIdentifier; | ||
29 | edits: (TextEdit | SnippetTextEdit)[]; | ||
30 | } | ||
31 | ``` | ||
32 | |||
33 | When applying such code action, the editor should insert snippet, with tab stops and placeholder. | ||
34 | At the moment, rust-analyzer guarantees that only a single edit will have `InsertTextFormat.Snippet`. | ||
diff --git a/docs/user/assists.md b/docs/user/assists.md index 692fd4f52..f329fcc10 100644 --- a/docs/user/assists.md +++ b/docs/user/assists.md | |||
@@ -17,7 +17,7 @@ struct S; | |||
17 | struct S; | 17 | struct S; |
18 | 18 | ||
19 | impl Debug for S { | 19 | impl Debug for S { |
20 | 20 | $0 | |
21 | } | 21 | } |
22 | ``` | 22 | ``` |
23 | 23 | ||
@@ -33,7 +33,7 @@ struct Point { | |||
33 | } | 33 | } |
34 | 34 | ||
35 | // AFTER | 35 | // AFTER |
36 | #[derive()] | 36 | #[derive($0)] |
37 | struct Point { | 37 | struct Point { |
38 | x: u32, | 38 | x: u32, |
39 | y: u32, | 39 | y: u32, |
@@ -77,7 +77,7 @@ fn foo() { | |||
77 | } | 77 | } |
78 | 78 | ||
79 | fn bar(arg: &str, baz: Baz) { | 79 | fn bar(arg: &str, baz: Baz) { |
80 | todo!() | 80 | ${0:todo!()} |
81 | } | 81 | } |
82 | 82 | ||
83 | ``` | 83 | ``` |
@@ -105,16 +105,16 @@ Adds a new inherent impl for a type. | |||
105 | ```rust | 105 | ```rust |
106 | // BEFORE | 106 | // BEFORE |
107 | struct Ctx<T: Clone> { | 107 | struct Ctx<T: Clone> { |
108 | data: T,┃ | 108 | data: T,┃ |
109 | } | 109 | } |
110 | 110 | ||
111 | // AFTER | 111 | // AFTER |
112 | struct Ctx<T: Clone> { | 112 | struct Ctx<T: Clone> { |
113 | data: T, | 113 | data: T, |
114 | } | 114 | } |
115 | 115 | ||
116 | impl<T: Clone> Ctx<T> { | 116 | impl<T: Clone> Ctx<T> { |
117 | 117 | $0 | |
118 | } | 118 | } |
119 | ``` | 119 | ``` |
120 | 120 | ||
@@ -146,7 +146,7 @@ trait Trait { | |||
146 | impl Trait for () { | 146 | impl Trait for () { |
147 | Type X = (); | 147 | Type X = (); |
148 | fn foo(&self) {} | 148 | fn foo(&self) {} |
149 | fn bar(&self) {} | 149 | $0fn bar(&self) {} |
150 | 150 | ||
151 | } | 151 | } |
152 | ``` | 152 | ``` |
@@ -176,7 +176,7 @@ trait Trait<T> { | |||
176 | 176 | ||
177 | impl Trait<u32> for () { | 177 | impl Trait<u32> for () { |
178 | fn foo(&self) -> u32 { | 178 | fn foo(&self) -> u32 { |
179 | todo!() | 179 | ${0:todo!()} |
180 | } | 180 | } |
181 | 181 | ||
182 | } | 182 | } |
@@ -203,6 +203,24 @@ impl<T: Clone> Ctx<T> { | |||
203 | 203 | ||
204 | ``` | 204 | ``` |
205 | 205 | ||
206 | ## `add_turbo_fish` | ||
207 | |||
208 | Adds `::<_>` to a call of a generic method or function. | ||
209 | |||
210 | ```rust | ||
211 | // BEFORE | ||
212 | fn make<T>() -> T { todo!() } | ||
213 | fn main() { | ||
214 | let x = make┃(); | ||
215 | } | ||
216 | |||
217 | // AFTER | ||
218 | fn make<T>() -> T { todo!() } | ||
219 | fn main() { | ||
220 | let x = make::<${0:_}>(); | ||
221 | } | ||
222 | ``` | ||
223 | |||
206 | ## `apply_demorgan` | 224 | ## `apply_demorgan` |
207 | 225 | ||
208 | Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws). | 226 | Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws). |
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index cffdcf11a..fac1a0be3 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts | |||
@@ -31,24 +31,79 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient | |||
31 | const res = await next(document, token); | 31 | const res = await next(document, token); |
32 | if (res === undefined) throw new Error('busy'); | 32 | if (res === undefined) throw new Error('busy'); |
33 | return res; | 33 | return res; |
34 | }, | ||
35 | async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) { | ||
36 | const params: lc.CodeActionParams = { | ||
37 | textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), | ||
38 | range: client.code2ProtocolConverter.asRange(range), | ||
39 | context: client.code2ProtocolConverter.asCodeActionContext(context) | ||
40 | }; | ||
41 | return client.sendRequest(lc.CodeActionRequest.type, params, token).then((values) => { | ||
42 | if (values === null) return undefined; | ||
43 | const result: (vscode.CodeAction | vscode.Command)[] = []; | ||
44 | for (const item of values) { | ||
45 | if (lc.CodeAction.is(item)) { | ||
46 | const action = client.protocol2CodeConverter.asCodeAction(item); | ||
47 | if (isSnippetEdit(item)) { | ||
48 | action.command = { | ||
49 | command: "rust-analyzer.applySnippetWorkspaceEdit", | ||
50 | title: "", | ||
51 | arguments: [action.edit], | ||
52 | }; | ||
53 | action.edit = undefined; | ||
54 | } | ||
55 | result.push(action); | ||
56 | } else { | ||
57 | const command = client.protocol2CodeConverter.asCommand(item); | ||
58 | result.push(command); | ||
59 | } | ||
60 | } | ||
61 | return result; | ||
62 | }, | ||
63 | (_error) => undefined | ||
64 | ); | ||
34 | } | 65 | } |
66 | |||
35 | } as any | 67 | } as any |
36 | }; | 68 | }; |
37 | 69 | ||
38 | const res = new lc.LanguageClient( | 70 | const client = new lc.LanguageClient( |
39 | 'rust-analyzer', | 71 | 'rust-analyzer', |
40 | 'Rust Analyzer Language Server', | 72 | 'Rust Analyzer Language Server', |
41 | serverOptions, | 73 | serverOptions, |
42 | clientOptions, | 74 | clientOptions, |
43 | ); | 75 | ); |
44 | 76 | ||
45 | // To turn on all proposed features use: res.registerProposedFeatures(); | 77 | // To turn on all proposed features use: client.registerProposedFeatures(); |
46 | // Here we want to enable CallHierarchyFeature and SemanticTokensFeature | 78 | // Here we want to enable CallHierarchyFeature and SemanticTokensFeature |
47 | // since they are available on stable. | 79 | // since they are available on stable. |
48 | // Note that while these features are stable in vscode their LSP protocol | 80 | // Note that while these features are stable in vscode their LSP protocol |
49 | // implementations are still in the "proposed" category for 3.16. | 81 | // implementations are still in the "proposed" category for 3.16. |
50 | res.registerFeature(new CallHierarchyFeature(res)); | 82 | client.registerFeature(new CallHierarchyFeature(client)); |
51 | res.registerFeature(new SemanticTokensFeature(res)); | 83 | client.registerFeature(new SemanticTokensFeature(client)); |
84 | client.registerFeature(new SnippetTextEditFeature()); | ||
85 | |||
86 | return client; | ||
87 | } | ||
52 | 88 | ||
53 | return res; | 89 | class SnippetTextEditFeature implements lc.StaticFeature { |
90 | fillClientCapabilities(capabilities: lc.ClientCapabilities): void { | ||
91 | const caps: any = capabilities.experimental ?? {}; | ||
92 | caps.snippetTextEdit = true; | ||
93 | capabilities.experimental = caps; | ||
94 | } | ||
95 | initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { | ||
96 | } | ||
97 | } | ||
98 | |||
99 | function isSnippetEdit(action: lc.CodeAction): boolean { | ||
100 | const documentChanges = action.edit?.documentChanges ?? []; | ||
101 | for (const edit of documentChanges) { | ||
102 | if (lc.TextDocumentEdit.is(edit)) { | ||
103 | if (edit.edits.some((indel) => (indel as any).insertTextFormat === lc.InsertTextFormat.Snippet)) { | ||
104 | return true; | ||
105 | } | ||
106 | } | ||
107 | } | ||
108 | return false; | ||
54 | } | 109 | } |
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts index bdb7fc3b0..0937b495c 100644 --- a/editors/code/src/commands/index.ts +++ b/editors/code/src/commands/index.ts | |||
@@ -4,6 +4,7 @@ import * as ra from '../rust-analyzer-api'; | |||
4 | 4 | ||
5 | import { Ctx, Cmd } from '../ctx'; | 5 | import { Ctx, Cmd } from '../ctx'; |
6 | import * as sourceChange from '../source_change'; | 6 | import * as sourceChange from '../source_change'; |
7 | import { assert } from '../util'; | ||
7 | 8 | ||
8 | export * from './analyzer_status'; | 9 | export * from './analyzer_status'; |
9 | export * from './matching_brace'; | 10 | export * from './matching_brace'; |
@@ -51,3 +52,37 @@ export function selectAndApplySourceChange(ctx: Ctx): Cmd { | |||
51 | } | 52 | } |
52 | }; | 53 | }; |
53 | } | 54 | } |
55 | |||
56 | export function applySnippetWorkspaceEdit(_ctx: Ctx): Cmd { | ||
57 | return async (edit: vscode.WorkspaceEdit) => { | ||
58 | assert(edit.entries().length === 1, `bad ws edit: ${JSON.stringify(edit)}`); | ||
59 | const [uri, edits] = edit.entries()[0]; | ||
60 | |||
61 | const editor = vscode.window.visibleTextEditors.find((it) => it.document.uri.toString() === uri.toString()); | ||
62 | if (!editor) return; | ||
63 | |||
64 | let editWithSnippet: vscode.TextEdit | undefined = undefined; | ||
65 | let lineDelta = 0; | ||
66 | await editor.edit((builder) => { | ||
67 | for (const indel of edits) { | ||
68 | const isSnippet = indel.newText.indexOf('$0') !== -1 || indel.newText.indexOf('${') !== -1; | ||
69 | if (isSnippet) { | ||
70 | editWithSnippet = indel; | ||
71 | } else { | ||
72 | if (!editWithSnippet) { | ||
73 | lineDelta = (indel.newText.match(/\n/g) || []).length - (indel.range.end.line - indel.range.start.line); | ||
74 | } | ||
75 | builder.replace(indel.range, indel.newText); | ||
76 | } | ||
77 | } | ||
78 | }); | ||
79 | if (editWithSnippet) { | ||
80 | const snip = editWithSnippet as vscode.TextEdit; | ||
81 | const range = snip.range.with( | ||
82 | snip.range.start.with(snip.range.start.line + lineDelta), | ||
83 | snip.range.end.with(snip.range.end.line + lineDelta), | ||
84 | ); | ||
85 | await editor.insertSnippet(new vscode.SnippetString(snip.newText), range); | ||
86 | } | ||
87 | }; | ||
88 | } | ||
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index c015460b8..ac3bb365e 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -91,6 +91,7 @@ export async function activate(context: vscode.ExtensionContext) { | |||
91 | ctx.registerCommand('debugSingle', commands.debugSingle); | 91 | ctx.registerCommand('debugSingle', commands.debugSingle); |
92 | ctx.registerCommand('showReferences', commands.showReferences); | 92 | ctx.registerCommand('showReferences', commands.showReferences); |
93 | ctx.registerCommand('applySourceChange', commands.applySourceChange); | 93 | ctx.registerCommand('applySourceChange', commands.applySourceChange); |
94 | ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEdit); | ||
94 | ctx.registerCommand('selectAndApplySourceChange', commands.selectAndApplySourceChange); | 95 | ctx.registerCommand('selectAndApplySourceChange', commands.selectAndApplySourceChange); |
95 | 96 | ||
96 | ctx.pushCleanup(activateTaskProvider(workspaceFolder)); | 97 | ctx.pushCleanup(activateTaskProvider(workspaceFolder)); |
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs index b8e8860ba..2e9fcf07c 100644 --- a/xtask/tests/tidy.rs +++ b/xtask/tests/tidy.rs | |||
@@ -57,6 +57,7 @@ fn check_todo(path: &Path, text: &str) { | |||
57 | "tests/generated.rs", | 57 | "tests/generated.rs", |
58 | "handlers/add_missing_impl_members.rs", | 58 | "handlers/add_missing_impl_members.rs", |
59 | "handlers/add_function.rs", | 59 | "handlers/add_function.rs", |
60 | "handlers/add_turbo_fish.rs", | ||
60 | // To support generating `todo!()` in assists, we have `expr_todo()` in ast::make. | 61 | // To support generating `todo!()` in assists, we have `expr_todo()` in ast::make. |
61 | "ast/make.rs", | 62 | "ast/make.rs", |
62 | ]; | 63 | ]; |