diff options
24 files changed, 485 insertions, 114 deletions
diff --git a/crates/paths/src/lib.rs b/crates/paths/src/lib.rs index 190c50913..32267f2e6 100644 --- a/crates/paths/src/lib.rs +++ b/crates/paths/src/lib.rs | |||
@@ -95,6 +95,80 @@ impl AbsPath { | |||
95 | pub fn normalize(&self) -> AbsPathBuf { | 95 | pub fn normalize(&self) -> AbsPathBuf { |
96 | AbsPathBuf(normalize_path(&self.0)) | 96 | AbsPathBuf(normalize_path(&self.0)) |
97 | } | 97 | } |
98 | pub fn to_path_buf(&self) -> AbsPathBuf { | ||
99 | AbsPathBuf::try_from(self.0.to_path_buf()).unwrap() | ||
100 | } | ||
101 | pub fn strip_prefix(&self, base: &AbsPath) -> Option<&RelPath> { | ||
102 | self.0.strip_prefix(base).ok().map(RelPath::new_unchecked) | ||
103 | } | ||
104 | } | ||
105 | |||
106 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] | ||
107 | pub struct RelPathBuf(PathBuf); | ||
108 | |||
109 | impl From<RelPathBuf> for PathBuf { | ||
110 | fn from(RelPathBuf(path_buf): RelPathBuf) -> PathBuf { | ||
111 | path_buf | ||
112 | } | ||
113 | } | ||
114 | |||
115 | impl ops::Deref for RelPathBuf { | ||
116 | type Target = RelPath; | ||
117 | fn deref(&self) -> &RelPath { | ||
118 | self.as_path() | ||
119 | } | ||
120 | } | ||
121 | |||
122 | impl AsRef<Path> for RelPathBuf { | ||
123 | fn as_ref(&self) -> &Path { | ||
124 | self.0.as_path() | ||
125 | } | ||
126 | } | ||
127 | |||
128 | impl TryFrom<PathBuf> for RelPathBuf { | ||
129 | type Error = PathBuf; | ||
130 | fn try_from(path_buf: PathBuf) -> Result<RelPathBuf, PathBuf> { | ||
131 | if !path_buf.is_relative() { | ||
132 | return Err(path_buf); | ||
133 | } | ||
134 | Ok(RelPathBuf(path_buf)) | ||
135 | } | ||
136 | } | ||
137 | |||
138 | impl TryFrom<&str> for RelPathBuf { | ||
139 | type Error = PathBuf; | ||
140 | fn try_from(path: &str) -> Result<RelPathBuf, PathBuf> { | ||
141 | RelPathBuf::try_from(PathBuf::from(path)) | ||
142 | } | ||
143 | } | ||
144 | |||
145 | impl RelPathBuf { | ||
146 | pub fn as_path(&self) -> &RelPath { | ||
147 | RelPath::new_unchecked(self.0.as_path()) | ||
148 | } | ||
149 | } | ||
150 | |||
151 | #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] | ||
152 | #[repr(transparent)] | ||
153 | pub struct RelPath(Path); | ||
154 | |||
155 | impl ops::Deref for RelPath { | ||
156 | type Target = Path; | ||
157 | fn deref(&self) -> &Path { | ||
158 | &self.0 | ||
159 | } | ||
160 | } | ||
161 | |||
162 | impl AsRef<Path> for RelPath { | ||
163 | fn as_ref(&self) -> &Path { | ||
164 | &self.0 | ||
165 | } | ||
166 | } | ||
167 | |||
168 | impl RelPath { | ||
169 | pub fn new_unchecked(path: &Path) -> &RelPath { | ||
170 | unsafe { &*(path as *const Path as *const RelPath) } | ||
171 | } | ||
98 | } | 172 | } |
99 | 173 | ||
100 | // https://github.com/rust-lang/cargo/blob/79c769c3d7b4c2cf6a93781575b7f592ef974255/src/cargo/util/paths.rs#L60-L85 | 174 | // https://github.com/rust-lang/cargo/blob/79c769c3d7b4c2cf6a93781575b7f592ef974255/src/cargo/util/paths.rs#L60-L85 |
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 776bddf91..b0e56e1b5 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 | |||
@@ -128,7 +128,7 @@ impl From<foo::bar::baz::Boo> for A { | |||
128 | 128 | ||
129 | fn check_not_applicable(ra_fixture: &str) { | 129 | fn check_not_applicable(ra_fixture: &str) { |
130 | let fixture = | 130 | let fixture = |
131 | format!("//- main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); | 131 | format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); |
132 | check_assist_not_applicable(add_from_impl_for_enum, &fixture) | 132 | check_assist_not_applicable(add_from_impl_for_enum, &fixture) |
133 | } | 133 | } |
134 | 134 | ||
diff --git a/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs index 44db7917a..43b4584b4 100644 --- a/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs | |||
@@ -301,7 +301,7 @@ fn another_fn() { | |||
301 | 301 | ||
302 | fn check_not_applicable(ra_fixture: &str) { | 302 | fn check_not_applicable(ra_fixture: &str) { |
303 | let fixture = | 303 | let fixture = |
304 | format!("//- main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); | 304 | format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); |
305 | check_assist_not_applicable(extract_struct_from_enum_variant, &fixture) | 305 | check_assist_not_applicable(extract_struct_from_enum_variant, &fixture) |
306 | } | 306 | } |
307 | 307 | ||
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs index 569efb768..3c12c1daa 100644 --- a/crates/ra_assists/src/handlers/fill_match_arms.rs +++ b/crates/ra_assists/src/handlers/fill_match_arms.rs | |||
@@ -765,7 +765,7 @@ fn foo(opt: Option<i32>) { | |||
765 | } | 765 | } |
766 | }"#; | 766 | }"#; |
767 | let before = | 767 | let before = |
768 | &format!("//- main.rs crate:main deps:core\n{}{}", before, FamousDefs::FIXTURE); | 768 | &format!("//- /main.rs crate:main deps:core\n{}{}", before, FamousDefs::FIXTURE); |
769 | 769 | ||
770 | check_assist( | 770 | check_assist( |
771 | fill_match_arms, | 771 | fill_match_arms, |
diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs index 482a2f3e6..af8fe11ec 100644 --- a/crates/ra_db/src/fixture.rs +++ b/crates/ra_db/src/fixture.rs | |||
@@ -164,7 +164,7 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit | |||
164 | 164 | ||
165 | let mut source_root = SourceRoot::new_local(); | 165 | let mut source_root = SourceRoot::new_local(); |
166 | let mut source_root_id = WORKSPACE; | 166 | let mut source_root_id = WORKSPACE; |
167 | let mut source_root_prefix: RelativePathBuf = "/".into(); | 167 | let mut source_root_prefix = "/".to_string(); |
168 | let mut file_id = FileId(0); | 168 | let mut file_id = FileId(0); |
169 | 169 | ||
170 | let mut file_position = None; | 170 | let mut file_position = None; |
@@ -212,9 +212,9 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit | |||
212 | }; | 212 | }; |
213 | 213 | ||
214 | db.set_file_text(file_id, Arc::new(text)); | 214 | db.set_file_text(file_id, Arc::new(text)); |
215 | db.set_file_relative_path(file_id, meta.path.clone()); | 215 | db.set_file_relative_path(file_id, meta.path.clone().into()); |
216 | db.set_file_source_root(file_id, source_root_id); | 216 | db.set_file_source_root(file_id, source_root_id); |
217 | source_root.insert_file(meta.path, file_id); | 217 | source_root.insert_file(meta.path.into(), file_id); |
218 | 218 | ||
219 | file_id.0 += 1; | 219 | file_id.0 += 1; |
220 | } | 220 | } |
@@ -245,12 +245,12 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit | |||
245 | } | 245 | } |
246 | 246 | ||
247 | enum ParsedMeta { | 247 | enum ParsedMeta { |
248 | Root { path: RelativePathBuf }, | 248 | Root { path: String }, |
249 | File(FileMeta), | 249 | File(FileMeta), |
250 | } | 250 | } |
251 | 251 | ||
252 | struct FileMeta { | 252 | struct FileMeta { |
253 | path: RelativePathBuf, | 253 | path: String, |
254 | krate: Option<String>, | 254 | krate: Option<String>, |
255 | deps: Vec<String>, | 255 | deps: Vec<String>, |
256 | cfg: CfgOptions, | 256 | cfg: CfgOptions, |
diff --git a/crates/ra_hir/src/source_analyzer.rs b/crates/ra_hir/src/source_analyzer.rs index 757d1e397..1d6c47103 100644 --- a/crates/ra_hir/src/source_analyzer.rs +++ b/crates/ra_hir/src/source_analyzer.rs | |||
@@ -216,13 +216,43 @@ impl SourceAnalyzer { | |||
216 | if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_expr(expr_id) { | 216 | if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_expr(expr_id) { |
217 | return Some(PathResolution::AssocItem(assoc.into())); | 217 | return Some(PathResolution::AssocItem(assoc.into())); |
218 | } | 218 | } |
219 | if let Some(VariantId::EnumVariantId(variant)) = | ||
220 | self.infer.as_ref()?.variant_resolution_for_expr(expr_id) | ||
221 | { | ||
222 | return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into()))); | ||
223 | } | ||
219 | } | 224 | } |
225 | |||
220 | if let Some(path_pat) = path.syntax().parent().and_then(ast::PathPat::cast) { | 226 | if let Some(path_pat) = path.syntax().parent().and_then(ast::PathPat::cast) { |
221 | let pat_id = self.pat_id(&path_pat.into())?; | 227 | let pat_id = self.pat_id(&path_pat.into())?; |
222 | if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_pat(pat_id) { | 228 | if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_pat(pat_id) { |
223 | return Some(PathResolution::AssocItem(assoc.into())); | 229 | return Some(PathResolution::AssocItem(assoc.into())); |
224 | } | 230 | } |
231 | if let Some(VariantId::EnumVariantId(variant)) = | ||
232 | self.infer.as_ref()?.variant_resolution_for_pat(pat_id) | ||
233 | { | ||
234 | return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into()))); | ||
235 | } | ||
236 | } | ||
237 | |||
238 | if let Some(rec_lit) = path.syntax().parent().and_then(ast::RecordLit::cast) { | ||
239 | let expr_id = self.expr_id(db, &rec_lit.into())?; | ||
240 | if let Some(VariantId::EnumVariantId(variant)) = | ||
241 | self.infer.as_ref()?.variant_resolution_for_expr(expr_id) | ||
242 | { | ||
243 | return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into()))); | ||
244 | } | ||
225 | } | 245 | } |
246 | |||
247 | if let Some(rec_pat) = path.syntax().parent().and_then(ast::RecordPat::cast) { | ||
248 | let pat_id = self.pat_id(&rec_pat.into())?; | ||
249 | if let Some(VariantId::EnumVariantId(variant)) = | ||
250 | self.infer.as_ref()?.variant_resolution_for_pat(pat_id) | ||
251 | { | ||
252 | return Some(PathResolution::Def(ModuleDef::EnumVariant(variant.into()))); | ||
253 | } | ||
254 | } | ||
255 | |||
226 | // This must be a normal source file rather than macro file. | 256 | // This must be a normal source file rather than macro file. |
227 | let hir_path = | 257 | let hir_path = |
228 | crate::Path::from_src(path.clone(), &Hygiene::new(db.upcast(), self.file_id))?; | 258 | crate::Path::from_src(path.clone(), &Hygiene::new(db.upcast(), self.file_id))?; |
diff --git a/crates/ra_hir_ty/src/tests/method_resolution.rs b/crates/ra_hir_ty/src/tests/method_resolution.rs index 804297315..a98efb1cc 100644 --- a/crates/ra_hir_ty/src/tests/method_resolution.rs +++ b/crates/ra_hir_ty/src/tests/method_resolution.rs | |||
@@ -933,7 +933,7 @@ fn method_resolution_overloaded_method() { | |||
933 | test_utils::mark::check!(impl_self_type_match_without_receiver); | 933 | test_utils::mark::check!(impl_self_type_match_without_receiver); |
934 | let t = type_at( | 934 | let t = type_at( |
935 | r#" | 935 | r#" |
936 | //- main.rs | 936 | //- /main.rs |
937 | struct Wrapper<T>(T); | 937 | struct Wrapper<T>(T); |
938 | struct Foo<T>(T); | 938 | struct Foo<T>(T); |
939 | struct Bar<T>(T); | 939 | struct Bar<T>(T); |
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index a88a978d7..9bde1db8e 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -167,6 +167,9 @@ fn missing_struct_field_fix( | |||
167 | }; | 167 | }; |
168 | 168 | ||
169 | let new_field_type = sema.type_of_expr(&record_expr.expr()?)?; | 169 | let new_field_type = sema.type_of_expr(&record_expr.expr()?)?; |
170 | if new_field_type.is_unknown() { | ||
171 | return None; | ||
172 | } | ||
170 | let new_field = make::record_field_def( | 173 | let new_field = make::record_field_def( |
171 | record_expr.field_name()?, | 174 | record_expr.field_name()?, |
172 | make::type_ref(&new_field_type.display_source_code(sema.db, module.into()).ok()?), | 175 | make::type_ref(&new_field_type.display_source_code(sema.db, module.into()).ok()?), |
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index 0798d2c36..450ce0ba7 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs | |||
@@ -908,4 +908,84 @@ mod tests { | |||
908 | "x: i32|x", | 908 | "x: i32|x", |
909 | ); | 909 | ); |
910 | } | 910 | } |
911 | |||
912 | #[test] | ||
913 | fn goto_def_for_enum_variant_self_pattern_const() { | ||
914 | check_goto( | ||
915 | " | ||
916 | //- /lib.rs | ||
917 | enum Foo { | ||
918 | Bar, | ||
919 | } | ||
920 | impl Foo { | ||
921 | fn baz(self) { | ||
922 | match self { | ||
923 | Self::Bar<|> => {} | ||
924 | } | ||
925 | } | ||
926 | } | ||
927 | ", | ||
928 | "Bar ENUM_VARIANT FileId(1) 15..18 15..18", | ||
929 | "Bar|Bar", | ||
930 | ); | ||
931 | } | ||
932 | |||
933 | #[test] | ||
934 | fn goto_def_for_enum_variant_self_pattern_record() { | ||
935 | check_goto( | ||
936 | " | ||
937 | //- /lib.rs | ||
938 | enum Foo { | ||
939 | Bar { val: i32 }, | ||
940 | } | ||
941 | impl Foo { | ||
942 | fn baz(self) -> i32 { | ||
943 | match self { | ||
944 | Self::Bar<|> { val } => {} | ||
945 | } | ||
946 | } | ||
947 | } | ||
948 | ", | ||
949 | "Bar ENUM_VARIANT FileId(1) 15..31 15..18", | ||
950 | "Bar { val: i32 }|Bar", | ||
951 | ); | ||
952 | } | ||
953 | |||
954 | #[test] | ||
955 | fn goto_def_for_enum_variant_self_expr_const() { | ||
956 | check_goto( | ||
957 | " | ||
958 | //- /lib.rs | ||
959 | enum Foo { | ||
960 | Bar, | ||
961 | } | ||
962 | impl Foo { | ||
963 | fn baz(self) { | ||
964 | Self::Bar<|>; | ||
965 | } | ||
966 | } | ||
967 | ", | ||
968 | "Bar ENUM_VARIANT FileId(1) 15..18 15..18", | ||
969 | "Bar|Bar", | ||
970 | ); | ||
971 | } | ||
972 | |||
973 | #[test] | ||
974 | fn goto_def_for_enum_variant_self_expr_record() { | ||
975 | check_goto( | ||
976 | " | ||
977 | //- /lib.rs | ||
978 | enum Foo { | ||
979 | Bar { val: i32 }, | ||
980 | } | ||
981 | impl Foo { | ||
982 | fn baz(self) { | ||
983 | Self::Bar<|> {val: 4}; | ||
984 | } | ||
985 | } | ||
986 | ", | ||
987 | "Bar ENUM_VARIANT FileId(1) 15..31 15..18", | ||
988 | "Bar { val: i32 }|Bar", | ||
989 | ); | ||
990 | } | ||
911 | } | 991 | } |
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs index ad78d2d93..76910d09b 100644 --- a/crates/ra_ide/src/mock_analysis.rs +++ b/crates/ra_ide/src/mock_analysis.rs | |||
@@ -4,7 +4,7 @@ use std::str::FromStr; | |||
4 | use std::sync::Arc; | 4 | use std::sync::Arc; |
5 | 5 | ||
6 | use ra_cfg::CfgOptions; | 6 | use ra_cfg::CfgOptions; |
7 | use ra_db::{CrateName, Env, RelativePathBuf}; | 7 | use ra_db::{CrateName, Env}; |
8 | use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER}; | 8 | use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER}; |
9 | 9 | ||
10 | use crate::{ | 10 | use crate::{ |
@@ -28,7 +28,7 @@ impl MockFileData { | |||
28 | fn path(&self) -> &str { | 28 | fn path(&self) -> &str { |
29 | match self { | 29 | match self { |
30 | MockFileData::Plain { path, .. } => path.as_str(), | 30 | MockFileData::Plain { path, .. } => path.as_str(), |
31 | MockFileData::Fixture(f) => f.meta.path().as_str(), | 31 | MockFileData::Fixture(f) => f.meta.path(), |
32 | } | 32 | } |
33 | } | 33 | } |
34 | 34 | ||
@@ -167,7 +167,6 @@ impl MockAnalysis { | |||
167 | for (i, data) in self.files.into_iter().enumerate() { | 167 | for (i, data) in self.files.into_iter().enumerate() { |
168 | let path = data.path(); | 168 | let path = data.path(); |
169 | assert!(path.starts_with('/')); | 169 | assert!(path.starts_with('/')); |
170 | let path = RelativePathBuf::from_path(&path[1..]).unwrap(); | ||
171 | let cfg_options = data.cfg_options(); | 170 | let cfg_options = data.cfg_options(); |
172 | let file_id = FileId(i as u32 + 1); | 171 | let file_id = FileId(i as u32 + 1); |
173 | let edition = data.edition(); | 172 | let edition = data.edition(); |
@@ -183,7 +182,8 @@ impl MockAnalysis { | |||
183 | Default::default(), | 182 | Default::default(), |
184 | )); | 183 | )); |
185 | } else if path.ends_with("/lib.rs") { | 184 | } else if path.ends_with("/lib.rs") { |
186 | let crate_name = path.parent().unwrap().file_name().unwrap(); | 185 | let base = &path[..path.len() - "/lib.rs".len()]; |
186 | let crate_name = &base[base.rfind('/').unwrap() + '/'.len_utf8()..]; | ||
187 | let other_crate = crate_graph.add_crate_root( | 187 | let other_crate = crate_graph.add_crate_root( |
188 | file_id, | 188 | file_id, |
189 | edition, | 189 | edition, |
@@ -199,7 +199,7 @@ impl MockAnalysis { | |||
199 | .unwrap(); | 199 | .unwrap(); |
200 | } | 200 | } |
201 | } | 201 | } |
202 | change.add_file(source_root, file_id, path, Arc::new(data.content().to_owned())); | 202 | change.add_file(source_root, file_id, path.into(), Arc::new(data.content().to_owned())); |
203 | } | 203 | } |
204 | change.set_crate_graph(crate_graph); | 204 | change.set_crate_graph(crate_graph); |
205 | host.apply_change(change); | 205 | host.apply_change(change); |
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index 59c230f6c..03f18c617 100644 --- a/crates/ra_ide/src/ssr.rs +++ b/crates/ra_ide/src/ssr.rs | |||
@@ -9,6 +9,7 @@ use ra_ssr::{MatchFinder, SsrError, SsrRule}; | |||
9 | // Search and replace with named wildcards that will match any expression, type, path, pattern or item. | 9 | // Search and replace with named wildcards that will match any expression, type, path, pattern or item. |
10 | // The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. | 10 | // The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. |
11 | // A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement. | 11 | // A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement. |
12 | // Within a macro call, a placeholder will match up until whatever token follows the placeholder. | ||
12 | // Available via the command `rust-analyzer.ssr`. | 13 | // Available via the command `rust-analyzer.ssr`. |
13 | // | 14 | // |
14 | // ```rust | 15 | // ```rust |
diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs index fc716ae82..da26ee669 100644 --- a/crates/ra_ssr/src/lib.rs +++ b/crates/ra_ssr/src/lib.rs | |||
@@ -91,14 +91,16 @@ impl<'db> MatchFinder<'db> { | |||
91 | if let Ok(mut m) = matching::get_match(false, rule, &code, restrict_range, &self.sema) { | 91 | if let Ok(mut m) = matching::get_match(false, rule, &code, restrict_range, &self.sema) { |
92 | // Continue searching in each of our placeholders. | 92 | // Continue searching in each of our placeholders. |
93 | for placeholder_value in m.placeholder_values.values_mut() { | 93 | for placeholder_value in m.placeholder_values.values_mut() { |
94 | // Don't search our placeholder if it's the entire matched node, otherwise we'd | 94 | if let Some(placeholder_node) = &placeholder_value.node { |
95 | // find the same match over and over until we got a stack overflow. | 95 | // Don't search our placeholder if it's the entire matched node, otherwise we'd |
96 | if placeholder_value.node != *code { | 96 | // find the same match over and over until we got a stack overflow. |
97 | self.find_matches( | 97 | if placeholder_node != code { |
98 | &placeholder_value.node, | 98 | self.find_matches( |
99 | restrict_range, | 99 | placeholder_node, |
100 | &mut placeholder_value.inner_matches, | 100 | restrict_range, |
101 | ); | 101 | &mut placeholder_value.inner_matches, |
102 | ); | ||
103 | } | ||
102 | } | 104 | } |
103 | } | 105 | } |
104 | matches_out.matches.push(m); | 106 | matches_out.matches.push(m); |
diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs index 265b6d793..bdaba9f1b 100644 --- a/crates/ra_ssr/src/matching.rs +++ b/crates/ra_ssr/src/matching.rs | |||
@@ -61,8 +61,9 @@ pub(crate) struct Var(pub String); | |||
61 | /// Information about a placeholder bound in a match. | 61 | /// Information about a placeholder bound in a match. |
62 | #[derive(Debug)] | 62 | #[derive(Debug)] |
63 | pub(crate) struct PlaceholderMatch { | 63 | pub(crate) struct PlaceholderMatch { |
64 | /// The node that the placeholder matched to. | 64 | /// The node that the placeholder matched to. If set, then we'll search for further matches |
65 | pub(crate) node: SyntaxNode, | 65 | /// within this node. It isn't set when we match tokens within a macro call's token tree. |
66 | pub(crate) node: Option<SyntaxNode>, | ||
66 | pub(crate) range: FileRange, | 67 | pub(crate) range: FileRange, |
67 | /// More matches, found within `node`. | 68 | /// More matches, found within `node`. |
68 | pub(crate) inner_matches: SsrMatches, | 69 | pub(crate) inner_matches: SsrMatches, |
@@ -195,6 +196,7 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
195 | SyntaxKind::RECORD_FIELD_LIST => { | 196 | SyntaxKind::RECORD_FIELD_LIST => { |
196 | self.attempt_match_record_field_list(match_inputs, pattern, code) | 197 | self.attempt_match_record_field_list(match_inputs, pattern, code) |
197 | } | 198 | } |
199 | SyntaxKind::TOKEN_TREE => self.attempt_match_token_tree(match_inputs, pattern, code), | ||
198 | _ => self.attempt_match_node_children(match_inputs, pattern, code), | 200 | _ => self.attempt_match_node_children(match_inputs, pattern, code), |
199 | } | 201 | } |
200 | } | 202 | } |
@@ -340,6 +342,90 @@ impl<'db, 'sema> MatchState<'db, 'sema> { | |||
340 | Ok(()) | 342 | Ok(()) |
341 | } | 343 | } |
342 | 344 | ||
345 | /// Outside of token trees, a placeholder can only match a single AST node, whereas in a token | ||
346 | /// tree it can match a sequence of tokens. | ||
347 | fn attempt_match_token_tree( | ||
348 | &mut self, | ||
349 | match_inputs: &MatchInputs, | ||
350 | pattern: &SyntaxNode, | ||
351 | code: &ra_syntax::SyntaxNode, | ||
352 | ) -> Result<(), MatchFailed> { | ||
353 | let mut pattern = PatternIterator::new(pattern).peekable(); | ||
354 | let mut children = code.children_with_tokens(); | ||
355 | while let Some(child) = children.next() { | ||
356 | if let Some(placeholder) = pattern.peek().and_then(|p| match_inputs.get_placeholder(p)) | ||
357 | { | ||
358 | pattern.next(); | ||
359 | let next_pattern_token = pattern | ||
360 | .peek() | ||
361 | .and_then(|p| match p { | ||
362 | SyntaxElement::Token(t) => Some(t.clone()), | ||
363 | SyntaxElement::Node(n) => n.first_token(), | ||
364 | }) | ||
365 | .map(|p| p.text().to_string()); | ||
366 | let first_matched_token = child.clone(); | ||
367 | let mut last_matched_token = child; | ||
368 | // Read code tokens util we reach one equal to the next token from our pattern | ||
369 | // or we reach the end of the token tree. | ||
370 | while let Some(next) = children.next() { | ||
371 | match &next { | ||
372 | SyntaxElement::Token(t) => { | ||
373 | if Some(t.to_string()) == next_pattern_token { | ||
374 | pattern.next(); | ||
375 | break; | ||
376 | } | ||
377 | } | ||
378 | SyntaxElement::Node(n) => { | ||
379 | if let Some(first_token) = n.first_token() { | ||
380 | if Some(first_token.to_string()) == next_pattern_token { | ||
381 | if let Some(SyntaxElement::Node(p)) = pattern.next() { | ||
382 | // We have a subtree that starts with the next token in our pattern. | ||
383 | self.attempt_match_token_tree(match_inputs, &p, &n)?; | ||
384 | break; | ||
385 | } | ||
386 | } | ||
387 | } | ||
388 | } | ||
389 | }; | ||
390 | last_matched_token = next; | ||
391 | } | ||
392 | if let Some(match_out) = &mut self.match_out { | ||
393 | match_out.placeholder_values.insert( | ||
394 | Var(placeholder.ident.to_string()), | ||
395 | PlaceholderMatch::from_range(FileRange { | ||
396 | file_id: self.sema.original_range(code).file_id, | ||
397 | range: first_matched_token | ||
398 | .text_range() | ||
399 | .cover(last_matched_token.text_range()), | ||
400 | }), | ||
401 | ); | ||
402 | } | ||
403 | continue; | ||
404 | } | ||
405 | // Match literal (non-placeholder) tokens. | ||
406 | match child { | ||
407 | SyntaxElement::Token(token) => { | ||
408 | self.attempt_match_token(&mut pattern, &token)?; | ||
409 | } | ||
410 | SyntaxElement::Node(node) => match pattern.next() { | ||
411 | Some(SyntaxElement::Node(p)) => { | ||
412 | self.attempt_match_token_tree(match_inputs, &p, &node)?; | ||
413 | } | ||
414 | Some(SyntaxElement::Token(p)) => fail_match!( | ||
415 | "Pattern has token '{}', code has subtree '{}'", | ||
416 | p.text(), | ||
417 | node.text() | ||
418 | ), | ||
419 | None => fail_match!("Pattern has nothing, code has '{}'", node.text()), | ||
420 | }, | ||
421 | } | ||
422 | } | ||
423 | if let Some(p) = pattern.next() { | ||
424 | fail_match!("Reached end of token tree in code, but pattern still has {:?}", p); | ||
425 | } | ||
426 | Ok(()) | ||
427 | } | ||
428 | |||
343 | fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> { | 429 | fn next_non_trivial(&mut self, code_it: &mut SyntaxElementChildren) -> Option<SyntaxElement> { |
344 | loop { | 430 | loop { |
345 | let c = code_it.next(); | 431 | let c = code_it.next(); |
@@ -399,7 +485,11 @@ fn recording_match_fail_reasons() -> bool { | |||
399 | 485 | ||
400 | impl PlaceholderMatch { | 486 | impl PlaceholderMatch { |
401 | fn new(node: &SyntaxNode, range: FileRange) -> Self { | 487 | fn new(node: &SyntaxNode, range: FileRange) -> Self { |
402 | Self { node: node.clone(), range, inner_matches: SsrMatches::default() } | 488 | Self { node: Some(node.clone()), range, inner_matches: SsrMatches::default() } |
489 | } | ||
490 | |||
491 | fn from_range(range: FileRange) -> Self { | ||
492 | Self { node: None, range, inner_matches: SsrMatches::default() } | ||
403 | } | 493 | } |
404 | } | 494 | } |
405 | 495 | ||
@@ -484,7 +574,14 @@ mod tests { | |||
484 | assert_eq!(matches.matches.len(), 1); | 574 | assert_eq!(matches.matches.len(), 1); |
485 | assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)"); | 575 | assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)"); |
486 | assert_eq!(matches.matches[0].placeholder_values.len(), 1); | 576 | assert_eq!(matches.matches[0].placeholder_values.len(), 1); |
487 | assert_eq!(matches.matches[0].placeholder_values[&Var("x".to_string())].node.text(), "1+2"); | 577 | assert_eq!( |
578 | matches.matches[0].placeholder_values[&Var("x".to_string())] | ||
579 | .node | ||
580 | .as_ref() | ||
581 | .unwrap() | ||
582 | .text(), | ||
583 | "1+2" | ||
584 | ); | ||
488 | 585 | ||
489 | let edit = crate::replacing::matches_to_edit(&matches); | 586 | let edit = crate::replacing::matches_to_edit(&matches); |
490 | let mut after = input.to_string(); | 587 | let mut after = input.to_string(); |
diff --git a/crates/ra_ssr/src/replacing.rs b/crates/ra_ssr/src/replacing.rs index 81a5e06a9..5dcde82a2 100644 --- a/crates/ra_ssr/src/replacing.rs +++ b/crates/ra_ssr/src/replacing.rs | |||
@@ -24,6 +24,7 @@ fn matches_to_edit_at_offset(matches: &SsrMatches, relative_start: TextSize) -> | |||
24 | 24 | ||
25 | fn render_replace(match_info: &Match) -> String { | 25 | fn render_replace(match_info: &Match) -> String { |
26 | let mut out = String::new(); | 26 | let mut out = String::new(); |
27 | let match_start = match_info.matched_node.text_range().start(); | ||
27 | for r in &match_info.template.tokens { | 28 | for r in &match_info.template.tokens { |
28 | match r { | 29 | match r { |
29 | PatternElement::Token(t) => out.push_str(t.text.as_str()), | 30 | PatternElement::Token(t) => out.push_str(t.text.as_str()), |
@@ -32,7 +33,14 @@ fn render_replace(match_info: &Match) -> String { | |||
32 | match_info.placeholder_values.get(&Var(p.ident.to_string())) | 33 | match_info.placeholder_values.get(&Var(p.ident.to_string())) |
33 | { | 34 | { |
34 | let range = &placeholder_value.range.range; | 35 | let range = &placeholder_value.range.range; |
35 | let mut matched_text = placeholder_value.node.text().to_string(); | 36 | let mut matched_text = if let Some(node) = &placeholder_value.node { |
37 | node.text().to_string() | ||
38 | } else { | ||
39 | let relative_range = range.checked_sub(match_start).unwrap(); | ||
40 | match_info.matched_node.text().to_string() | ||
41 | [usize::from(relative_range.start())..usize::from(relative_range.end())] | ||
42 | .to_string() | ||
43 | }; | ||
36 | let edit = | 44 | let edit = |
37 | matches_to_edit_at_offset(&placeholder_value.inner_matches, range.start()); | 45 | matches_to_edit_at_offset(&placeholder_value.inner_matches, range.start()); |
38 | edit.apply(&mut matched_text); | 46 | edit.apply(&mut matched_text); |
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index 4b747fe18..3ee1e74e9 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs | |||
@@ -427,6 +427,45 @@ fn match_reordered_struct_instantiation() { | |||
427 | } | 427 | } |
428 | 428 | ||
429 | #[test] | 429 | #[test] |
430 | fn match_macro_invocation() { | ||
431 | assert_matches("foo!($a)", "fn() {foo(foo!(foo()))}", &["foo!(foo())"]); | ||
432 | assert_matches("foo!(41, $a, 43)", "fn() {foo!(41, 42, 43)}", &["foo!(41, 42, 43)"]); | ||
433 | assert_no_match("foo!(50, $a, 43)", "fn() {foo!(41, 42, 43}"); | ||
434 | assert_no_match("foo!(41, $a, 50)", "fn() {foo!(41, 42, 43}"); | ||
435 | assert_matches("foo!($a())", "fn() {foo!(bar())}", &["foo!(bar())"]); | ||
436 | } | ||
437 | |||
438 | // When matching within a macro expansion, we only allow matches of nodes that originated from | ||
439 | // the macro call, not from the macro definition. | ||
440 | #[test] | ||
441 | fn no_match_expression_from_macro() { | ||
442 | assert_no_match( | ||
443 | "$a.clone()", | ||
444 | r#" | ||
445 | macro_rules! m1 { | ||
446 | () => {42.clone()} | ||
447 | } | ||
448 | fn f1() {m1!()} | ||
449 | "#, | ||
450 | ); | ||
451 | } | ||
452 | |||
453 | // We definitely don't want to allow matching of an expression that part originates from the | ||
454 | // macro call `42` and part from the macro definition `.clone()`. | ||
455 | #[test] | ||
456 | fn no_match_split_expression() { | ||
457 | assert_no_match( | ||
458 | "$a.clone()", | ||
459 | r#" | ||
460 | macro_rules! m1 { | ||
461 | ($x:expr) => {$x.clone()} | ||
462 | } | ||
463 | fn f1() {m1!(42)} | ||
464 | "#, | ||
465 | ); | ||
466 | } | ||
467 | |||
468 | #[test] | ||
430 | fn replace_function_call() { | 469 | fn replace_function_call() { |
431 | assert_ssr_transform("foo() ==>> bar()", "fn f1() {foo(); foo();}", "fn f1() {bar(); bar();}"); | 470 | assert_ssr_transform("foo() ==>> bar()", "fn f1() {foo(); foo();}", "fn f1() {bar(); bar();}"); |
432 | } | 471 | } |
@@ -468,6 +507,20 @@ fn replace_struct_init() { | |||
468 | } | 507 | } |
469 | 508 | ||
470 | #[test] | 509 | #[test] |
510 | fn replace_macro_invocations() { | ||
511 | assert_ssr_transform( | ||
512 | "try!($a) ==>> $a?", | ||
513 | "fn f1() -> Result<(), E> {bar(try!(foo()));}", | ||
514 | "fn f1() -> Result<(), E> {bar(foo()?);}", | ||
515 | ); | ||
516 | assert_ssr_transform( | ||
517 | "foo!($a($b)) ==>> foo($b, $a)", | ||
518 | "fn f1() {foo!(abc(def() + 2));}", | ||
519 | "fn f1() {foo(def() + 2, abc);}", | ||
520 | ); | ||
521 | } | ||
522 | |||
523 | #[test] | ||
471 | fn replace_binary_op() { | 524 | fn replace_binary_op() { |
472 | assert_ssr_transform( | 525 | assert_ssr_transform( |
473 | "$a + $b ==>> $b + $a", | 526 | "$a + $b ==>> $b + $a", |
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs index 0e2a83c6a..48ce831af 100644 --- a/crates/rust-analyzer/tests/heavy_tests/main.rs +++ b/crates/rust-analyzer/tests/heavy_tests/main.rs | |||
@@ -29,12 +29,12 @@ fn completes_items_from_standard_library() { | |||
29 | let project_start = Instant::now(); | 29 | let project_start = Instant::now(); |
30 | let server = Project::with_fixture( | 30 | let server = Project::with_fixture( |
31 | r#" | 31 | r#" |
32 | //- Cargo.toml | 32 | //- /Cargo.toml |
33 | [package] | 33 | [package] |
34 | name = "foo" | 34 | name = "foo" |
35 | version = "0.0.0" | 35 | version = "0.0.0" |
36 | 36 | ||
37 | //- src/lib.rs | 37 | //- /src/lib.rs |
38 | use std::collections::Spam; | 38 | use std::collections::Spam; |
39 | "#, | 39 | "#, |
40 | ) | 40 | ) |
@@ -63,24 +63,24 @@ fn test_runnables_project() { | |||
63 | } | 63 | } |
64 | 64 | ||
65 | let code = r#" | 65 | let code = r#" |
66 | //- foo/Cargo.toml | 66 | //- /foo/Cargo.toml |
67 | [package] | 67 | [package] |
68 | name = "foo" | 68 | name = "foo" |
69 | version = "0.0.0" | 69 | version = "0.0.0" |
70 | 70 | ||
71 | //- foo/src/lib.rs | 71 | //- /foo/src/lib.rs |
72 | pub fn foo() {} | 72 | pub fn foo() {} |
73 | 73 | ||
74 | //- foo/tests/spam.rs | 74 | //- /foo/tests/spam.rs |
75 | #[test] | 75 | #[test] |
76 | fn test_eggs() {} | 76 | fn test_eggs() {} |
77 | 77 | ||
78 | //- bar/Cargo.toml | 78 | //- /bar/Cargo.toml |
79 | [package] | 79 | [package] |
80 | name = "bar" | 80 | name = "bar" |
81 | version = "0.0.0" | 81 | version = "0.0.0" |
82 | 82 | ||
83 | //- bar/src/main.rs | 83 | //- /bar/src/main.rs |
84 | fn main() {} | 84 | fn main() {} |
85 | "#; | 85 | "#; |
86 | 86 | ||
@@ -140,12 +140,12 @@ fn test_format_document() { | |||
140 | 140 | ||
141 | let server = project( | 141 | let server = project( |
142 | r#" | 142 | r#" |
143 | //- Cargo.toml | 143 | //- /Cargo.toml |
144 | [package] | 144 | [package] |
145 | name = "foo" | 145 | name = "foo" |
146 | version = "0.0.0" | 146 | version = "0.0.0" |
147 | 147 | ||
148 | //- src/lib.rs | 148 | //- /src/lib.rs |
149 | mod bar; | 149 | mod bar; |
150 | 150 | ||
151 | fn main() { | 151 | fn main() { |
@@ -200,13 +200,13 @@ fn test_format_document_2018() { | |||
200 | 200 | ||
201 | let server = project( | 201 | let server = project( |
202 | r#" | 202 | r#" |
203 | //- Cargo.toml | 203 | //- /Cargo.toml |
204 | [package] | 204 | [package] |
205 | name = "foo" | 205 | name = "foo" |
206 | version = "0.0.0" | 206 | version = "0.0.0" |
207 | edition = "2018" | 207 | edition = "2018" |
208 | 208 | ||
209 | //- src/lib.rs | 209 | //- /src/lib.rs |
210 | mod bar; | 210 | mod bar; |
211 | 211 | ||
212 | async fn test() { | 212 | async fn test() { |
@@ -266,12 +266,12 @@ fn test_missing_module_code_action() { | |||
266 | 266 | ||
267 | let server = project( | 267 | let server = project( |
268 | r#" | 268 | r#" |
269 | //- Cargo.toml | 269 | //- /Cargo.toml |
270 | [package] | 270 | [package] |
271 | name = "foo" | 271 | name = "foo" |
272 | version = "0.0.0" | 272 | version = "0.0.0" |
273 | 273 | ||
274 | //- src/lib.rs | 274 | //- /src/lib.rs |
275 | mod bar; | 275 | mod bar; |
276 | 276 | ||
277 | fn main() {} | 277 | fn main() {} |
@@ -335,10 +335,10 @@ fn test_missing_module_code_action_in_json_project() { | |||
335 | 335 | ||
336 | let code = format!( | 336 | let code = format!( |
337 | r#" | 337 | r#" |
338 | //- rust-project.json | 338 | //- /rust-project.json |
339 | {PROJECT} | 339 | {PROJECT} |
340 | 340 | ||
341 | //- src/lib.rs | 341 | //- /src/lib.rs |
342 | mod bar; | 342 | mod bar; |
343 | 343 | ||
344 | fn main() {{}} | 344 | fn main() {{}} |
@@ -391,15 +391,15 @@ fn diagnostics_dont_block_typing() { | |||
391 | } | 391 | } |
392 | 392 | ||
393 | let librs: String = (0..10).map(|i| format!("mod m{};", i)).collect(); | 393 | let librs: String = (0..10).map(|i| format!("mod m{};", i)).collect(); |
394 | let libs: String = (0..10).map(|i| format!("//- src/m{}.rs\nfn foo() {{}}\n\n", i)).collect(); | 394 | let libs: String = (0..10).map(|i| format!("//- /src/m{}.rs\nfn foo() {{}}\n\n", i)).collect(); |
395 | let server = Project::with_fixture(&format!( | 395 | let server = Project::with_fixture(&format!( |
396 | r#" | 396 | r#" |
397 | //- Cargo.toml | 397 | //- /Cargo.toml |
398 | [package] | 398 | [package] |
399 | name = "foo" | 399 | name = "foo" |
400 | version = "0.0.0" | 400 | version = "0.0.0" |
401 | 401 | ||
402 | //- src/lib.rs | 402 | //- /src/lib.rs |
403 | {} | 403 | {} |
404 | 404 | ||
405 | {} | 405 | {} |
@@ -449,12 +449,12 @@ fn preserves_dos_line_endings() { | |||
449 | 449 | ||
450 | let server = Project::with_fixture( | 450 | let server = Project::with_fixture( |
451 | &" | 451 | &" |
452 | //- Cargo.toml | 452 | //- /Cargo.toml |
453 | [package] | 453 | [package] |
454 | name = \"foo\" | 454 | name = \"foo\" |
455 | version = \"0.0.0\" | 455 | version = \"0.0.0\" |
456 | 456 | ||
457 | //- src/main.rs | 457 | //- /src/main.rs |
458 | /// Some Docs\r\nfn main() {} | 458 | /// Some Docs\r\nfn main() {} |
459 | ", | 459 | ", |
460 | ) | 460 | ) |
@@ -484,12 +484,12 @@ fn out_dirs_check() { | |||
484 | 484 | ||
485 | let server = Project::with_fixture( | 485 | let server = Project::with_fixture( |
486 | r###" | 486 | r###" |
487 | //- Cargo.toml | 487 | //- /Cargo.toml |
488 | [package] | 488 | [package] |
489 | name = "foo" | 489 | name = "foo" |
490 | version = "0.0.0" | 490 | version = "0.0.0" |
491 | 491 | ||
492 | //- build.rs | 492 | //- /build.rs |
493 | use std::{env, fs, path::Path}; | 493 | use std::{env, fs, path::Path}; |
494 | 494 | ||
495 | fn main() { | 495 | fn main() { |
@@ -504,7 +504,7 @@ fn main() { | |||
504 | println!("cargo:rustc-cfg=featlike=\"set\""); | 504 | println!("cargo:rustc-cfg=featlike=\"set\""); |
505 | println!("cargo:rerun-if-changed=build.rs"); | 505 | println!("cargo:rerun-if-changed=build.rs"); |
506 | } | 506 | } |
507 | //- src/main.rs | 507 | //- /src/main.rs |
508 | #[rustc_builtin_macro] macro_rules! include {} | 508 | #[rustc_builtin_macro] macro_rules! include {} |
509 | #[rustc_builtin_macro] macro_rules! concat {} | 509 | #[rustc_builtin_macro] macro_rules! concat {} |
510 | #[rustc_builtin_macro] macro_rules! env {} | 510 | #[rustc_builtin_macro] macro_rules! env {} |
@@ -599,7 +599,7 @@ fn resolve_proc_macro() { | |||
599 | } | 599 | } |
600 | let server = Project::with_fixture( | 600 | let server = Project::with_fixture( |
601 | r###" | 601 | r###" |
602 | //- foo/Cargo.toml | 602 | //- /foo/Cargo.toml |
603 | [package] | 603 | [package] |
604 | name = "foo" | 604 | name = "foo" |
605 | version = "0.0.0" | 605 | version = "0.0.0" |
@@ -607,7 +607,7 @@ edition = "2018" | |||
607 | [dependencies] | 607 | [dependencies] |
608 | bar = {path = "../bar"} | 608 | bar = {path = "../bar"} |
609 | 609 | ||
610 | //- foo/src/main.rs | 610 | //- /foo/src/main.rs |
611 | use bar::Bar; | 611 | use bar::Bar; |
612 | trait Bar { | 612 | trait Bar { |
613 | fn bar(); | 613 | fn bar(); |
@@ -618,7 +618,7 @@ fn main() { | |||
618 | Foo::bar(); | 618 | Foo::bar(); |
619 | } | 619 | } |
620 | 620 | ||
621 | //- bar/Cargo.toml | 621 | //- /bar/Cargo.toml |
622 | [package] | 622 | [package] |
623 | name = "bar" | 623 | name = "bar" |
624 | version = "0.0.0" | 624 | version = "0.0.0" |
@@ -627,7 +627,7 @@ edition = "2018" | |||
627 | [lib] | 627 | [lib] |
628 | proc-macro = true | 628 | proc-macro = true |
629 | 629 | ||
630 | //- bar/src/lib.rs | 630 | //- /bar/src/lib.rs |
631 | extern crate proc_macro; | 631 | extern crate proc_macro; |
632 | use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; | 632 | use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; |
633 | macro_rules! t { | 633 | macro_rules! t { |
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs index 30d03b622..f58790ded 100644 --- a/crates/rust-analyzer/tests/heavy_tests/support.rs +++ b/crates/rust-analyzer/tests/heavy_tests/support.rs | |||
@@ -69,7 +69,7 @@ impl<'a> Project<'a> { | |||
69 | let mut paths = vec![]; | 69 | let mut paths = vec![]; |
70 | 70 | ||
71 | for entry in parse_fixture(self.fixture) { | 71 | for entry in parse_fixture(self.fixture) { |
72 | let path = tmp_dir.path().join(entry.meta.path().as_str()); | 72 | let path = tmp_dir.path().join(&entry.meta.path()['/'.len_utf8()..]); |
73 | fs::create_dir_all(path.parent().unwrap()).unwrap(); | 73 | fs::create_dir_all(path.parent().unwrap()).unwrap(); |
74 | fs::write(path.as_path(), entry.text.as_bytes()).unwrap(); | 74 | fs::write(path.as_path(), entry.text.as_bytes()).unwrap(); |
75 | paths.push((path, entry.text)); | 75 | paths.push((path, entry.text)); |
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index 981565cd7..b1333cf15 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs | |||
@@ -168,13 +168,13 @@ pub struct FixtureEntry { | |||
168 | 168 | ||
169 | #[derive(Debug, Eq, PartialEq)] | 169 | #[derive(Debug, Eq, PartialEq)] |
170 | pub enum FixtureMeta { | 170 | pub enum FixtureMeta { |
171 | Root { path: RelativePathBuf }, | 171 | Root { path: String }, |
172 | File(FileMeta), | 172 | File(FileMeta), |
173 | } | 173 | } |
174 | 174 | ||
175 | #[derive(Debug, Eq, PartialEq)] | 175 | #[derive(Debug, Eq, PartialEq)] |
176 | pub struct FileMeta { | 176 | pub struct FileMeta { |
177 | pub path: RelativePathBuf, | 177 | pub path: String, |
178 | pub crate_name: Option<String>, | 178 | pub crate_name: Option<String>, |
179 | pub deps: Vec<String>, | 179 | pub deps: Vec<String>, |
180 | pub cfg: CfgOptions, | 180 | pub cfg: CfgOptions, |
@@ -183,7 +183,7 @@ pub struct FileMeta { | |||
183 | } | 183 | } |
184 | 184 | ||
185 | impl FixtureMeta { | 185 | impl FixtureMeta { |
186 | pub fn path(&self) -> &RelativePath { | 186 | pub fn path(&self) -> &str { |
187 | match self { | 187 | match self { |
188 | FixtureMeta::Root { path } => &path, | 188 | FixtureMeta::Root { path } => &path, |
189 | FixtureMeta::File(f) => &f.path, | 189 | FixtureMeta::File(f) => &f.path, |
@@ -292,12 +292,12 @@ fn parse_meta(meta: &str) -> FixtureMeta { | |||
292 | let components = meta.split_ascii_whitespace().collect::<Vec<_>>(); | 292 | let components = meta.split_ascii_whitespace().collect::<Vec<_>>(); |
293 | 293 | ||
294 | if components[0] == "root" { | 294 | if components[0] == "root" { |
295 | let path: RelativePathBuf = components[1].into(); | 295 | let path = components[1].to_string(); |
296 | assert!(path.starts_with("/") && path.ends_with("/")); | 296 | assert!(path.starts_with("/") && path.ends_with("/")); |
297 | return FixtureMeta::Root { path }; | 297 | return FixtureMeta::Root { path }; |
298 | } | 298 | } |
299 | 299 | ||
300 | let path: RelativePathBuf = components[0].into(); | 300 | let path = components[0].to_string(); |
301 | assert!(path.starts_with("/")); | 301 | assert!(path.starts_with("/")); |
302 | 302 | ||
303 | let mut krate = None; | 303 | let mut krate = None; |
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc index ea714f49a..f1b7ed7fc 100644 --- a/docs/user/manual.adoc +++ b/docs/user/manual.adoc | |||
@@ -269,6 +269,10 @@ Gnome Builder currently has support for RLS, and there's no way to configure the | |||
269 | 1. Rename, symlink or copy the `rust-analyzer` binary to `rls` and place it somewhere Builder can find (in `PATH`, or under `~/.cargo/bin`). | 269 | 1. Rename, symlink or copy the `rust-analyzer` binary to `rls` and place it somewhere Builder can find (in `PATH`, or under `~/.cargo/bin`). |
270 | 2. Enable the Rust Builder plugin. | 270 | 2. Enable the Rust Builder plugin. |
271 | 271 | ||
272 | ==== GNOME Builder (Nightly) | ||
273 | |||
274 | https://nightly.gnome.org/repo/appstream/org.gnome.Builder.flatpakref[GNOME Builder (Nightly)] has now native support for `rust-analyzer` out of the box. If the `rust-analyzer` binary is not available, GNOME Builder can install it when opening a Rust source file. | ||
275 | |||
272 | == Non-Cargo Based Projects | 276 | == Non-Cargo Based Projects |
273 | 277 | ||
274 | rust-analyzer does not require Cargo. | 278 | rust-analyzer does not require Cargo. |
diff --git a/editors/code/package.json b/editors/code/package.json index e6ceb235f..68484a370 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -426,7 +426,7 @@ | |||
426 | "Full log" | 426 | "Full log" |
427 | ], | 427 | ], |
428 | "default": "off", | 428 | "default": "off", |
429 | "description": "Trace requests to the rust-analyzer" | 429 | "description": "Trace requests to the rust-analyzer (this is usually overly verbose and not recommended for regular users)" |
430 | }, | 430 | }, |
431 | "rust-analyzer.trace.extension": { | 431 | "rust-analyzer.trace.extension": { |
432 | "description": "Enable logging of VS Code extensions itself", | 432 | "description": "Enable logging of VS Code extensions itself", |
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 670f2ebfd..12b4d0510 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -43,12 +43,16 @@ export async function activate(context: vscode.ExtensionContext) { | |||
43 | const config = new Config(context); | 43 | const config = new Config(context); |
44 | const state = new PersistentState(context.globalState); | 44 | const state = new PersistentState(context.globalState); |
45 | const serverPath = await bootstrap(config, state).catch(err => { | 45 | const serverPath = await bootstrap(config, state).catch(err => { |
46 | let message = "Failed to bootstrap rust-analyzer."; | 46 | let message = "bootstrap error. "; |
47 | |||
47 | if (err.code === "EBUSY" || err.code === "ETXTBSY") { | 48 | if (err.code === "EBUSY" || err.code === "ETXTBSY") { |
48 | message += " Other vscode windows might be using rust-analyzer, " + | 49 | message += "Other vscode windows might be using rust-analyzer, "; |
49 | "you should close them and reload this window to retry."; | 50 | message += "you should close them and reload this window to retry. "; |
50 | } | 51 | } |
51 | message += " Open \"Help > Toggle Developer Tools > Console\" to see the logs"; | 52 | |
53 | message += 'Open "Help > Toggle Developer Tools > Console" to see the logs '; | ||
54 | message += '(enable verbose logs with "rust-analyzer.trace.extension")'; | ||
55 | |||
52 | log.error("Bootstrap error", err); | 56 | log.error("Bootstrap error", err); |
53 | throw new Error(message); | 57 | throw new Error(message); |
54 | }); | 58 | }); |
@@ -178,7 +182,11 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi | |||
178 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); | 182 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); |
179 | 183 | ||
180 | const dest = path.join(config.globalStoragePath, "rust-analyzer.vsix"); | 184 | const dest = path.join(config.globalStoragePath, "rust-analyzer.vsix"); |
181 | await download(artifact.browser_download_url, dest, "Downloading rust-analyzer extension"); | 185 | await download({ |
186 | url: artifact.browser_download_url, | ||
187 | dest, | ||
188 | progressTitle: "Downloading rust-analyzer extension", | ||
189 | }); | ||
182 | 190 | ||
183 | await vscode.commands.executeCommand("workbench.extensions.installExtension", vscode.Uri.file(dest)); | 191 | await vscode.commands.executeCommand("workbench.extensions.installExtension", vscode.Uri.file(dest)); |
184 | await fs.unlink(dest); | 192 | await fs.unlink(dest); |
@@ -299,7 +307,12 @@ async function getServer(config: Config, state: PersistentState): Promise<string | |||
299 | if (err.code !== "ENOENT") throw err; | 307 | if (err.code !== "ENOENT") throw err; |
300 | }); | 308 | }); |
301 | 309 | ||
302 | await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 }); | 310 | await download({ |
311 | url: artifact.browser_download_url, | ||
312 | dest, | ||
313 | progressTitle: "Downloading rust-analyzer server", | ||
314 | mode: 0o755 | ||
315 | }); | ||
303 | 316 | ||
304 | // Patching executable if that's NixOS. | 317 | // Patching executable if that's NixOS. |
305 | if (await fs.stat("/etc/nixos").then(_ => true).catch(_ => false)) { | 318 | if (await fs.stat("/etc/nixos").then(_ => true).catch(_ => false)) { |
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts index 0e7dd29c2..e02fd6d4f 100644 --- a/editors/code/src/net.ts +++ b/editors/code/src/net.ts | |||
@@ -60,32 +60,40 @@ export interface GithubRelease { | |||
60 | }>; | 60 | }>; |
61 | } | 61 | } |
62 | 62 | ||
63 | interface DownloadOpts { | ||
64 | progressTitle: string; | ||
65 | url: string; | ||
66 | dest: string; | ||
67 | mode?: number; | ||
68 | } | ||
63 | 69 | ||
64 | export async function download( | 70 | export async function download(opts: DownloadOpts) { |
65 | downloadUrl: string, | 71 | // Put the artifact into a temporary folder to prevent partially downloaded files when user kills vscode |
66 | destinationPath: string, | 72 | await withTempDir(async tempDir => { |
67 | progressTitle: string, | 73 | const tempFile = path.join(tempDir, path.basename(opts.dest)); |
68 | { mode }: { mode?: number } = {}, | 74 | |
69 | ) { | 75 | await vscode.window.withProgress( |
70 | await vscode.window.withProgress( | 76 | { |
71 | { | 77 | location: vscode.ProgressLocation.Notification, |
72 | location: vscode.ProgressLocation.Notification, | 78 | cancellable: false, |
73 | cancellable: false, | 79 | title: opts.progressTitle |
74 | title: progressTitle | 80 | }, |
75 | }, | 81 | async (progress, _cancellationToken) => { |
76 | async (progress, _cancellationToken) => { | 82 | let lastPercentage = 0; |
77 | let lastPercentage = 0; | 83 | await downloadFile(opts.url, tempFile, opts.mode, (readBytes, totalBytes) => { |
78 | await downloadFile(downloadUrl, destinationPath, mode, (readBytes, totalBytes) => { | 84 | const newPercentage = (readBytes / totalBytes) * 100; |
79 | const newPercentage = (readBytes / totalBytes) * 100; | 85 | progress.report({ |
80 | progress.report({ | 86 | message: newPercentage.toFixed(0) + "%", |
81 | message: newPercentage.toFixed(0) + "%", | 87 | increment: newPercentage - lastPercentage |
82 | increment: newPercentage - lastPercentage | 88 | }); |
89 | |||
90 | lastPercentage = newPercentage; | ||
83 | }); | 91 | }); |
92 | } | ||
93 | ); | ||
84 | 94 | ||
85 | lastPercentage = newPercentage; | 95 | await moveFile(tempFile, opts.dest); |
86 | }); | 96 | }); |
87 | } | ||
88 | ); | ||
89 | } | 97 | } |
90 | 98 | ||
91 | /** | 99 | /** |
@@ -120,21 +128,17 @@ async function downloadFile( | |||
120 | onProgress(readBytes, totalBytes); | 128 | onProgress(readBytes, totalBytes); |
121 | }); | 129 | }); |
122 | 130 | ||
123 | // Put the artifact into a temporary folder to prevent partially downloaded files when user kills vscode | 131 | const destFileStream = fs.createWriteStream(destFilePath, { mode }); |
124 | await withTempFile(async tempFilePath => { | 132 | await pipeline(res.body, destFileStream); |
125 | const destFileStream = fs.createWriteStream(tempFilePath, { mode }); | 133 | await new Promise<void>(resolve => { |
126 | await pipeline(res.body, destFileStream); | 134 | destFileStream.on("close", resolve); |
127 | await new Promise<void>(resolve => { | 135 | destFileStream.destroy(); |
128 | destFileStream.on("close", resolve); | 136 | // This workaround is awaiting to be removed when vscode moves to newer nodejs version: |
129 | destFileStream.destroy(); | 137 | // https://github.com/rust-analyzer/rust-analyzer/issues/3167 |
130 | // This workaround is awaiting to be removed when vscode moves to newer nodejs version: | ||
131 | // https://github.com/rust-analyzer/rust-analyzer/issues/3167 | ||
132 | }); | ||
133 | await moveFile(tempFilePath, destFilePath); | ||
134 | }); | 138 | }); |
135 | } | 139 | } |
136 | 140 | ||
137 | async function withTempFile(scope: (tempFilePath: string) => Promise<void>) { | 141 | async function withTempDir(scope: (tempDirPath: string) => Promise<void>) { |
138 | // Based on the great article: https://advancedweb.hu/secure-tempfiles-in-nodejs-without-dependencies/ | 142 | // Based on the great article: https://advancedweb.hu/secure-tempfiles-in-nodejs-without-dependencies/ |
139 | 143 | ||
140 | // `.realpath()` should handle the cases where os.tmpdir() contains symlinks | 144 | // `.realpath()` should handle the cases where os.tmpdir() contains symlinks |
@@ -143,7 +147,7 @@ async function withTempFile(scope: (tempFilePath: string) => Promise<void>) { | |||
143 | const tempDir = await fs.promises.mkdtemp(path.join(osTempDir, "rust-analyzer")); | 147 | const tempDir = await fs.promises.mkdtemp(path.join(osTempDir, "rust-analyzer")); |
144 | 148 | ||
145 | try { | 149 | try { |
146 | return await scope(path.join(tempDir, "file")); | 150 | return await scope(tempDir); |
147 | } finally { | 151 | } finally { |
148 | // We are good citizens :D | 152 | // We are good citizens :D |
149 | void fs.promises.rmdir(tempDir, { recursive: true }).catch(log.error); | 153 | void fs.promises.rmdir(tempDir, { recursive: true }).catch(log.error); |
@@ -160,6 +164,7 @@ async function moveFile(src: fs.PathLike, dest: fs.PathLike) { | |||
160 | await fs.promises.unlink(src); | 164 | await fs.promises.unlink(src); |
161 | } else { | 165 | } else { |
162 | log.error(`Failed to rename the file ${src} -> ${dest}`, err); | 166 | log.error(`Failed to rename the file ${src} -> ${dest}`, err); |
167 | throw err; | ||
163 | } | 168 | } |
164 | } | 169 | } |
165 | } | 170 | } |
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts index fe3fb71cd..fec4c3295 100644 --- a/editors/code/src/util.ts +++ b/editors/code/src/util.ts | |||
@@ -26,7 +26,6 @@ export const log = new class { | |||
26 | } | 26 | } |
27 | 27 | ||
28 | error(message?: any, ...optionalParams: any[]): void { | 28 | error(message?: any, ...optionalParams: any[]): void { |
29 | if (!log.enabled) return; | ||
30 | debugger; | 29 | debugger; |
31 | // eslint-disable-next-line no-console | 30 | // eslint-disable-next-line no-console |
32 | console.error(message, ...optionalParams); | 31 | console.error(message, ...optionalParams); |
diff --git a/xtask/src/release.rs b/xtask/src/release.rs index 36c912184..b6502b952 100644 --- a/xtask/src/release.rs +++ b/xtask/src/release.rs | |||
@@ -28,21 +28,23 @@ impl ReleaseCmd { | |||
28 | 28 | ||
29 | let contents = format!( | 29 | let contents = format!( |
30 | "\ | 30 | "\ |
31 | = Changelog #{} | 31 | = Changelog #{} |
32 | :sectanchors: | 32 | :sectanchors: |
33 | :page-layout: post | 33 | :page-layout: post |
34 | 34 | ||
35 | Commit: commit:{}[] + | 35 | Commit: commit:{}[] + |
36 | Release: release:{}[] | 36 | Release: release:{}[] |
37 | 37 | ||
38 | == New Features | 38 | == Sponsors |
39 | 39 | ||
40 | * pr:[] . | 40 | == New Features |
41 | 41 | ||
42 | == Fixes | 42 | * pr:[] . |
43 | 43 | ||
44 | == Internal Improvements | 44 | == Fixes |
45 | ", | 45 | |
46 | == Internal Improvements | ||
47 | ", | ||
46 | changelog_n, commit, today | 48 | changelog_n, commit, today |
47 | ); | 49 | ); |
48 | 50 | ||