aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/src/call_hierarchy.rs29
-rw-r--r--crates/ra_ide/src/mock_analysis.rs123
-rw-r--r--crates/ra_ide/src/runnables.rs189
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html2
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs4
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tags.rs6
6 files changed, 294 insertions, 59 deletions
diff --git a/crates/ra_ide/src/call_hierarchy.rs b/crates/ra_ide/src/call_hierarchy.rs
index 85d1f0cb1..defd8176f 100644
--- a/crates/ra_ide/src/call_hierarchy.rs
+++ b/crates/ra_ide/src/call_hierarchy.rs
@@ -246,6 +246,35 @@ mod tests {
246 } 246 }
247 247
248 #[test] 248 #[test]
249 fn test_call_hierarchy_in_tests_mod() {
250 check_hierarchy(
251 r#"
252 //- /lib.rs cfg:test
253 fn callee() {}
254 fn caller1() {
255 call<|>ee();
256 }
257
258 #[cfg(test)]
259 mod tests {
260 use super::*;
261
262 #[test]
263 fn test_caller() {
264 callee();
265 }
266 }
267 "#,
268 "callee FN_DEF FileId(1) 0..14 3..9",
269 &[
270 "caller1 FN_DEF FileId(1) 15..45 18..25 : [34..40]",
271 "test_caller FN_DEF FileId(1) 93..147 108..119 : [132..138]",
272 ],
273 &[],
274 );
275 }
276
277 #[test]
249 fn test_call_hierarchy_in_different_files() { 278 fn test_call_hierarchy_in_different_files() {
250 check_hierarchy( 279 check_hierarchy(
251 r#" 280 r#"
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs
index 2c13f206a..ad78d2d93 100644
--- a/crates/ra_ide/src/mock_analysis.rs
+++ b/crates/ra_ide/src/mock_analysis.rs
@@ -1,21 +1,81 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use std::str::FromStr;
3use std::sync::Arc; 4use std::sync::Arc;
4 5
5use ra_cfg::CfgOptions; 6use ra_cfg::CfgOptions;
6use ra_db::{CrateName, Env, RelativePathBuf}; 7use ra_db::{CrateName, Env, RelativePathBuf};
7use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER}; 8use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER};
8 9
9use crate::{ 10use crate::{
10 Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition::Edition2018, FileId, FilePosition, 11 Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition, FileId, FilePosition, FileRange,
11 FileRange, SourceRootId, 12 SourceRootId,
12}; 13};
13 14
15#[derive(Debug)]
16enum MockFileData {
17 Plain { path: String, content: String },
18 Fixture(FixtureEntry),
19}
20
21impl MockFileData {
22 fn new(path: String, content: String) -> Self {
23 // `Self::Plain` causes a false warning: 'variant is never constructed: `Plain` '
24 // see https://github.com/rust-lang/rust/issues/69018
25 MockFileData::Plain { path, content }
26 }
27
28 fn path(&self) -> &str {
29 match self {
30 MockFileData::Plain { path, .. } => path.as_str(),
31 MockFileData::Fixture(f) => f.meta.path().as_str(),
32 }
33 }
34
35 fn content(&self) -> &str {
36 match self {
37 MockFileData::Plain { content, .. } => content,
38 MockFileData::Fixture(f) => f.text.as_str(),
39 }
40 }
41
42 fn cfg_options(&self) -> CfgOptions {
43 match self {
44 MockFileData::Fixture(f) => {
45 f.meta.cfg_options().map_or_else(Default::default, |o| o.clone())
46 }
47 _ => CfgOptions::default(),
48 }
49 }
50
51 fn edition(&self) -> Edition {
52 match self {
53 MockFileData::Fixture(f) => {
54 f.meta.edition().map_or(Edition::Edition2018, |v| Edition::from_str(v).unwrap())
55 }
56 _ => Edition::Edition2018,
57 }
58 }
59
60 fn env(&self) -> Env {
61 match self {
62 MockFileData::Fixture(f) => Env::from(f.meta.env()),
63 _ => Env::default(),
64 }
65 }
66}
67
68impl From<FixtureEntry> for MockFileData {
69 fn from(fixture: FixtureEntry) -> Self {
70 Self::Fixture(fixture)
71 }
72}
73
14/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis 74/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis
15/// from a set of in-memory files. 75/// from a set of in-memory files.
16#[derive(Debug, Default)] 76#[derive(Debug, Default)]
17pub struct MockAnalysis { 77pub struct MockAnalysis {
18 files: Vec<(String, String)>, 78 files: Vec<MockFileData>,
19} 79}
20 80
21impl MockAnalysis { 81impl MockAnalysis {
@@ -35,7 +95,7 @@ impl MockAnalysis {
35 pub fn with_files(fixture: &str) -> MockAnalysis { 95 pub fn with_files(fixture: &str) -> MockAnalysis {
36 let mut res = MockAnalysis::new(); 96 let mut res = MockAnalysis::new();
37 for entry in parse_fixture(fixture) { 97 for entry in parse_fixture(fixture) {
38 res.add_file(&entry.meta, &entry.text); 98 res.add_file_fixture(entry);
39 } 99 }
40 res 100 res
41 } 101 }
@@ -48,30 +108,44 @@ impl MockAnalysis {
48 for entry in parse_fixture(fixture) { 108 for entry in parse_fixture(fixture) {
49 if entry.text.contains(CURSOR_MARKER) { 109 if entry.text.contains(CURSOR_MARKER) {
50 assert!(position.is_none(), "only one marker (<|>) per fixture is allowed"); 110 assert!(position.is_none(), "only one marker (<|>) per fixture is allowed");
51 position = Some(res.add_file_with_position(&entry.meta, &entry.text)); 111 position = Some(res.add_file_fixture_with_position(entry));
52 } else { 112 } else {
53 res.add_file(&entry.meta, &entry.text); 113 res.add_file_fixture(entry);
54 } 114 }
55 } 115 }
56 let position = position.expect("expected a marker (<|>)"); 116 let position = position.expect("expected a marker (<|>)");
57 (res, position) 117 (res, position)
58 } 118 }
59 119
120 pub fn add_file_fixture(&mut self, fixture: FixtureEntry) -> FileId {
121 let file_id = self.next_id();
122 self.files.push(MockFileData::from(fixture));
123 file_id
124 }
125
126 pub fn add_file_fixture_with_position(&mut self, mut fixture: FixtureEntry) -> FilePosition {
127 let (offset, text) = extract_offset(&fixture.text);
128 fixture.text = text;
129 let file_id = self.next_id();
130 self.files.push(MockFileData::from(fixture));
131 FilePosition { file_id, offset }
132 }
133
60 pub fn add_file(&mut self, path: &str, text: &str) -> FileId { 134 pub fn add_file(&mut self, path: &str, text: &str) -> FileId {
61 let file_id = FileId((self.files.len() + 1) as u32); 135 let file_id = self.next_id();
62 self.files.push((path.to_string(), text.to_string())); 136 self.files.push(MockFileData::new(path.to_string(), text.to_string()));
63 file_id 137 file_id
64 } 138 }
65 pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition { 139 pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition {
66 let (offset, text) = extract_offset(text); 140 let (offset, text) = extract_offset(text);
67 let file_id = FileId((self.files.len() + 1) as u32); 141 let file_id = self.next_id();
68 self.files.push((path.to_string(), text)); 142 self.files.push(MockFileData::new(path.to_string(), text));
69 FilePosition { file_id, offset } 143 FilePosition { file_id, offset }
70 } 144 }
71 pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange { 145 pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange {
72 let (range, text) = extract_range(text); 146 let (range, text) = extract_range(text);
73 let file_id = FileId((self.files.len() + 1) as u32); 147 let file_id = self.next_id();
74 self.files.push((path.to_string(), text)); 148 self.files.push(MockFileData::new(path.to_string(), text));
75 FileRange { file_id, range } 149 FileRange { file_id, range }
76 } 150 }
77 pub fn id_of(&self, path: &str) -> FileId { 151 pub fn id_of(&self, path: &str) -> FileId {
@@ -79,7 +153,7 @@ impl MockAnalysis {
79 .files 153 .files
80 .iter() 154 .iter()
81 .enumerate() 155 .enumerate()
82 .find(|(_, (p, _text))| path == p) 156 .find(|(_, data)| path == data.path())
83 .expect("no file in this mock"); 157 .expect("no file in this mock");
84 FileId(idx as u32 + 1) 158 FileId(idx as u32 + 1)
85 } 159 }
@@ -90,18 +164,21 @@ impl MockAnalysis {
90 change.add_root(source_root, true); 164 change.add_root(source_root, true);
91 let mut crate_graph = CrateGraph::default(); 165 let mut crate_graph = CrateGraph::default();
92 let mut root_crate = None; 166 let mut root_crate = None;
93 for (i, (path, contents)) in self.files.into_iter().enumerate() { 167 for (i, data) in self.files.into_iter().enumerate() {
168 let path = data.path();
94 assert!(path.starts_with('/')); 169 assert!(path.starts_with('/'));
95 let path = RelativePathBuf::from_path(&path[1..]).unwrap(); 170 let path = RelativePathBuf::from_path(&path[1..]).unwrap();
171 let cfg_options = data.cfg_options();
96 let file_id = FileId(i as u32 + 1); 172 let file_id = FileId(i as u32 + 1);
97 let cfg_options = CfgOptions::default(); 173 let edition = data.edition();
174 let env = data.env();
98 if path == "/lib.rs" || path == "/main.rs" { 175 if path == "/lib.rs" || path == "/main.rs" {
99 root_crate = Some(crate_graph.add_crate_root( 176 root_crate = Some(crate_graph.add_crate_root(
100 file_id, 177 file_id,
101 Edition2018, 178 edition,
102 None, 179 None,
103 cfg_options, 180 cfg_options,
104 Env::default(), 181 env,
105 Default::default(), 182 Default::default(),
106 Default::default(), 183 Default::default(),
107 )); 184 ));
@@ -109,10 +186,10 @@ impl MockAnalysis {
109 let crate_name = path.parent().unwrap().file_name().unwrap(); 186 let crate_name = path.parent().unwrap().file_name().unwrap();
110 let other_crate = crate_graph.add_crate_root( 187 let other_crate = crate_graph.add_crate_root(
111 file_id, 188 file_id,
112 Edition2018, 189 edition,
113 Some(CrateName::new(crate_name).unwrap()), 190 Some(CrateName::new(crate_name).unwrap()),
114 cfg_options, 191 cfg_options,
115 Env::default(), 192 env,
116 Default::default(), 193 Default::default(),
117 Default::default(), 194 Default::default(),
118 ); 195 );
@@ -122,7 +199,7 @@ impl MockAnalysis {
122 .unwrap(); 199 .unwrap();
123 } 200 }
124 } 201 }
125 change.add_file(source_root, file_id, path, Arc::new(contents)); 202 change.add_file(source_root, file_id, path, Arc::new(data.content().to_owned()));
126 } 203 }
127 change.set_crate_graph(crate_graph); 204 change.set_crate_graph(crate_graph);
128 host.apply_change(change); 205 host.apply_change(change);
@@ -131,6 +208,10 @@ impl MockAnalysis {
131 pub fn analysis(self) -> Analysis { 208 pub fn analysis(self) -> Analysis {
132 self.analysis_host().analysis() 209 self.analysis_host().analysis()
133 } 210 }
211
212 fn next_id(&self) -> FileId {
213 FileId((self.files.len() + 1) as u32)
214 }
134} 215}
135 216
136/// Creates analysis from a multi-file fixture, returns positions marked with <|>. 217/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index 131b8f307..6e7e47199 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
3use hir::{AsAssocItem, Semantics}; 3use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics};
4use itertools::Itertools; 4use itertools::Itertools;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{
@@ -10,12 +10,14 @@ use ra_syntax::{
10 10
11use crate::FileId; 11use crate::FileId;
12use ast::DocCommentsOwner; 12use ast::DocCommentsOwner;
13use ra_cfg::CfgExpr;
13use std::fmt::Display; 14use std::fmt::Display;
14 15
15#[derive(Debug)] 16#[derive(Debug)]
16pub struct Runnable { 17pub struct Runnable {
17 pub range: TextRange, 18 pub range: TextRange,
18 pub kind: RunnableKind, 19 pub kind: RunnableKind,
20 pub cfg_exprs: Vec<CfgExpr>,
19} 21}
20 22
21#[derive(Debug)] 23#[derive(Debug)]
@@ -45,29 +47,33 @@ pub enum RunnableKind {
45pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 47pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
46 let sema = Semantics::new(db); 48 let sema = Semantics::new(db);
47 let source_file = sema.parse(file_id); 49 let source_file = sema.parse(file_id);
48 source_file.syntax().descendants().filter_map(|i| runnable(&sema, i)).collect() 50 source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect()
49} 51}
50 52
51fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { 53fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> {
52 match_ast! { 54 match_ast! {
53 match item { 55 match item {
54 ast::FnDef(it) => runnable_fn(sema, it), 56 ast::FnDef(it) => runnable_fn(sema, it, file_id),
55 ast::Module(it) => runnable_mod(sema, it), 57 ast::Module(it) => runnable_mod(sema, it, file_id),
56 _ => None, 58 _ => None,
57 } 59 }
58 } 60 }
59} 61}
60 62
61fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Runnable> { 63fn runnable_fn(
64 sema: &Semantics<RootDatabase>,
65 fn_def: ast::FnDef,
66 file_id: FileId,
67) -> Option<Runnable> {
62 let name_string = fn_def.name()?.text().to_string(); 68 let name_string = fn_def.name()?.text().to_string();
63 69
64 let kind = if name_string == "main" { 70 let kind = if name_string == "main" {
65 RunnableKind::Bin 71 RunnableKind::Bin
66 } else { 72 } else {
67 let test_id = if let Some(module) = sema.to_def(&fn_def).map(|def| def.module(sema.db)) { 73 let test_id = match sema.to_def(&fn_def).map(|def| def.module(sema.db)) {
68 let def = sema.to_def(&fn_def)?; 74 Some(module) => {
69 let impl_trait_name = 75 let def = sema.to_def(&fn_def)?;
70 def.as_assoc_item(sema.db).and_then(|assoc_item| { 76 let impl_trait_name = def.as_assoc_item(sema.db).and_then(|assoc_item| {
71 match assoc_item.container(sema.db) { 77 match assoc_item.container(sema.db) {
72 hir::AssocItemContainer::Trait(trait_item) => { 78 hir::AssocItemContainer::Trait(trait_item) => {
73 Some(trait_item.name(sema.db).to_string()) 79 Some(trait_item.name(sema.db).to_string())
@@ -79,25 +85,25 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
79 } 85 }
80 }); 86 });
81 87
82 let path_iter = module 88 let path_iter = module
83 .path_to_root(sema.db) 89 .path_to_root(sema.db)
84 .into_iter() 90 .into_iter()
85 .rev() 91 .rev()
86 .filter_map(|it| it.name(sema.db)) 92 .filter_map(|it| it.name(sema.db))
87 .map(|name| name.to_string()); 93 .map(|name| name.to_string());
88 94
89 let path = if let Some(impl_trait_name) = impl_trait_name { 95 let path = if let Some(impl_trait_name) = impl_trait_name {
90 path_iter 96 path_iter
91 .chain(std::iter::once(impl_trait_name)) 97 .chain(std::iter::once(impl_trait_name))
92 .chain(std::iter::once(name_string)) 98 .chain(std::iter::once(name_string))
93 .join("::") 99 .join("::")
94 } else { 100 } else {
95 path_iter.chain(std::iter::once(name_string)).join("::") 101 path_iter.chain(std::iter::once(name_string)).join("::")
96 }; 102 };
97 103
98 TestId::Path(path) 104 TestId::Path(path)
99 } else { 105 }
100 TestId::Name(name_string) 106 None => TestId::Name(name_string),
101 }; 107 };
102 108
103 if has_test_related_attribute(&fn_def) { 109 if has_test_related_attribute(&fn_def) {
@@ -111,7 +117,12 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
111 return None; 117 return None;
112 } 118 }
113 }; 119 };
114 Some(Runnable { range: fn_def.syntax().text_range(), kind }) 120
121 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def));
122 let cfg_exprs =
123 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
124
125 Some(Runnable { range: fn_def.syntax().text_range(), kind, cfg_exprs })
115} 126}
116 127
117#[derive(Debug)] 128#[derive(Debug)]
@@ -147,7 +158,11 @@ fn has_doc_test(fn_def: &ast::FnDef) -> bool {
147 fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```")) 158 fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```"))
148} 159}
149 160
150fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> { 161fn runnable_mod(
162 sema: &Semantics<RootDatabase>,
163 module: ast::Module,
164 file_id: FileId,
165) -> Option<Runnable> {
151 let has_test_function = module 166 let has_test_function = module
152 .item_list()? 167 .item_list()?
153 .items() 168 .items()
@@ -160,11 +175,20 @@ fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<R
160 return None; 175 return None;
161 } 176 }
162 let range = module.syntax().text_range(); 177 let range = module.syntax().text_range();
163 let module = sema.to_def(&module)?; 178 let module_def = sema.to_def(&module)?;
164 179
165 let path = 180 let path = module_def
166 module.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::"); 181 .path_to_root(sema.db)
167 Some(Runnable { range, kind: RunnableKind::TestMod { path } }) 182 .into_iter()
183 .rev()
184 .filter_map(|it| it.name(sema.db))
185 .join("::");
186
187 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module));
188 let cfg_exprs =
189 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
190
191 Some(Runnable { range, kind: RunnableKind::TestMod { path }, cfg_exprs })
168} 192}
169 193
170#[cfg(test)] 194#[cfg(test)]
@@ -196,6 +220,7 @@ mod tests {
196 Runnable { 220 Runnable {
197 range: 1..21, 221 range: 1..21,
198 kind: Bin, 222 kind: Bin,
223 cfg_exprs: [],
199 }, 224 },
200 Runnable { 225 Runnable {
201 range: 22..46, 226 range: 22..46,
@@ -207,6 +232,7 @@ mod tests {
207 ignore: false, 232 ignore: false,
208 }, 233 },
209 }, 234 },
235 cfg_exprs: [],
210 }, 236 },
211 Runnable { 237 Runnable {
212 range: 47..81, 238 range: 47..81,
@@ -218,6 +244,7 @@ mod tests {
218 ignore: true, 244 ignore: true,
219 }, 245 },
220 }, 246 },
247 cfg_exprs: [],
221 }, 248 },
222 ] 249 ]
223 "### 250 "###
@@ -245,6 +272,7 @@ mod tests {
245 Runnable { 272 Runnable {
246 range: 1..21, 273 range: 1..21,
247 kind: Bin, 274 kind: Bin,
275 cfg_exprs: [],
248 }, 276 },
249 Runnable { 277 Runnable {
250 range: 22..64, 278 range: 22..64,
@@ -253,6 +281,7 @@ mod tests {
253 "foo", 281 "foo",
254 ), 282 ),
255 }, 283 },
284 cfg_exprs: [],
256 }, 285 },
257 ] 286 ]
258 "### 287 "###
@@ -283,6 +312,7 @@ mod tests {
283 Runnable { 312 Runnable {
284 range: 1..21, 313 range: 1..21,
285 kind: Bin, 314 kind: Bin,
315 cfg_exprs: [],
286 }, 316 },
287 Runnable { 317 Runnable {
288 range: 51..105, 318 range: 51..105,
@@ -291,6 +321,7 @@ mod tests {
291 "Data::foo", 321 "Data::foo",
292 ), 322 ),
293 }, 323 },
324 cfg_exprs: [],
294 }, 325 },
295 ] 326 ]
296 "### 327 "###
@@ -318,6 +349,7 @@ mod tests {
318 kind: TestMod { 349 kind: TestMod {
319 path: "test_mod", 350 path: "test_mod",
320 }, 351 },
352 cfg_exprs: [],
321 }, 353 },
322 Runnable { 354 Runnable {
323 range: 28..57, 355 range: 28..57,
@@ -329,6 +361,7 @@ mod tests {
329 ignore: false, 361 ignore: false,
330 }, 362 },
331 }, 363 },
364 cfg_exprs: [],
332 }, 365 },
333 ] 366 ]
334 "### 367 "###
@@ -358,6 +391,7 @@ mod tests {
358 kind: TestMod { 391 kind: TestMod {
359 path: "foo::test_mod", 392 path: "foo::test_mod",
360 }, 393 },
394 cfg_exprs: [],
361 }, 395 },
362 Runnable { 396 Runnable {
363 range: 46..79, 397 range: 46..79,
@@ -369,6 +403,7 @@ mod tests {
369 ignore: false, 403 ignore: false,
370 }, 404 },
371 }, 405 },
406 cfg_exprs: [],
372 }, 407 },
373 ] 408 ]
374 "### 409 "###
@@ -400,6 +435,7 @@ mod tests {
400 kind: TestMod { 435 kind: TestMod {
401 path: "foo::bar::test_mod", 436 path: "foo::bar::test_mod",
402 }, 437 },
438 cfg_exprs: [],
403 }, 439 },
404 Runnable { 440 Runnable {
405 range: 68..105, 441 range: 68..105,
@@ -411,6 +447,89 @@ mod tests {
411 ignore: false, 447 ignore: false,
412 }, 448 },
413 }, 449 },
450 cfg_exprs: [],
451 },
452 ]
453 "###
454 );
455 }
456
457 #[test]
458 fn test_runnables_with_feature() {
459 let (analysis, pos) = analysis_and_position(
460 r#"
461 //- /lib.rs crate:foo cfg:feature=foo
462 <|> //empty
463 #[test]
464 #[cfg(feature = "foo")]
465 fn test_foo1() {}
466 "#,
467 );
468 let runnables = analysis.runnables(pos.file_id).unwrap();
469 assert_debug_snapshot!(&runnables,
470 @r###"
471 [
472 Runnable {
473 range: 1..58,
474 kind: Test {
475 test_id: Path(
476 "test_foo1",
477 ),
478 attr: TestAttr {
479 ignore: false,
480 },
481 },
482 cfg_exprs: [
483 KeyValue {
484 key: "feature",
485 value: "foo",
486 },
487 ],
488 },
489 ]
490 "###
491 );
492 }
493
494 #[test]
495 fn test_runnables_with_features() {
496 let (analysis, pos) = analysis_and_position(
497 r#"
498 //- /lib.rs crate:foo cfg:feature=foo,feature=bar
499 <|> //empty
500 #[test]
501 #[cfg(all(feature = "foo", feature = "bar"))]
502 fn test_foo1() {}
503 "#,
504 );
505 let runnables = analysis.runnables(pos.file_id).unwrap();
506 assert_debug_snapshot!(&runnables,
507 @r###"
508 [
509 Runnable {
510 range: 1..80,
511 kind: Test {
512 test_id: Path(
513 "test_foo1",
514 ),
515 attr: TestAttr {
516 ignore: false,
517 },
518 },
519 cfg_exprs: [
520 All(
521 [
522 KeyValue {
523 key: "feature",
524 value: "foo",
525 },
526 KeyValue {
527 key: "feature",
528 value: "bar",
529 },
530 ],
531 ),
532 ],
414 }, 533 },
415 ] 534 ]
416 "### 535 "###
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html
index 635fe5cf9..2ceadf2fc 100644
--- a/crates/ra_ide/src/snapshots/highlighting.html
+++ b/crates/ra_ide/src/snapshots/highlighting.html
@@ -27,7 +27,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
27.keyword.unsafe { color: #BC8383; font-weight: bold; } 27.keyword.unsafe { color: #BC8383; font-weight: bold; }
28.control { font-style: italic; } 28.control { font-style: italic; }
29</style> 29</style>
30<pre><code><span class="attribute">#[derive(Clone, Debug)]</span> 30<pre><code><span class="attribute">#[</span><span class="function attribute">derive</span><span class="attribute">(Clone, Debug)]</span>
31<span class="keyword">struct</span> <span class="struct declaration">Foo</span> { 31<span class="keyword">struct</span> <span class="struct declaration">Foo</span> {
32 <span class="keyword">pub</span> <span class="field declaration">x</span>: <span class="builtin_type">i32</span>, 32 <span class="keyword">pub</span> <span class="field declaration">x</span>: <span class="builtin_type">i32</span>,
33 <span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>, 33 <span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>,
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index be57eeb0a..b55cf748d 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -361,7 +361,9 @@ fn highlight_element(
361 } 361 }
362 362
363 // Highlight references like the definitions they resolve to 363 // Highlight references like the definitions they resolve to
364 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => return None, 364 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => {
365 Highlight::from(HighlightTag::Function) | HighlightModifier::Attribute
366 }
365 NAME_REF => { 367 NAME_REF => {
366 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); 368 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
367 match classify_name_ref(sema, &name_ref) { 369 match classify_name_ref(sema, &name_ref) {
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs
index be1a0f12b..33e6619ec 100644
--- a/crates/ra_ide/src/syntax_highlighting/tags.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tags.rs
@@ -45,8 +45,10 @@ pub enum HighlightTag {
45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
46#[repr(u8)] 46#[repr(u8)]
47pub enum HighlightModifier { 47pub enum HighlightModifier {
48 /// Used to differentiate individual elements within attributes.
49 Attribute = 0,
48 /// Used with keywords like `if` and `break`. 50 /// Used with keywords like `if` and `break`.
49 ControlFlow = 0, 51 ControlFlow,
50 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is 52 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is
51 /// not. 53 /// not.
52 Definition, 54 Definition,
@@ -95,6 +97,7 @@ impl fmt::Display for HighlightTag {
95 97
96impl HighlightModifier { 98impl HighlightModifier {
97 const ALL: &'static [HighlightModifier] = &[ 99 const ALL: &'static [HighlightModifier] = &[
100 HighlightModifier::Attribute,
98 HighlightModifier::ControlFlow, 101 HighlightModifier::ControlFlow,
99 HighlightModifier::Definition, 102 HighlightModifier::Definition,
100 HighlightModifier::Mutable, 103 HighlightModifier::Mutable,
@@ -103,6 +106,7 @@ impl HighlightModifier {
103 106
104 fn as_str(self) -> &'static str { 107 fn as_str(self) -> &'static str {
105 match self { 108 match self {
109 HighlightModifier::Attribute => "attribute",
106 HighlightModifier::ControlFlow => "control", 110 HighlightModifier::ControlFlow => "control",
107 HighlightModifier::Definition => "declaration", 111 HighlightModifier::Definition => "declaration",
108 HighlightModifier::Mutable => "mutable", 112 HighlightModifier::Mutable => "mutable",