aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_batch/src/lib.rs8
-rw-r--r--crates/ra_syntax/src/ast.rs14
-rw-r--r--crates/ra_syntax/src/ast/generated.rs2
-rw-r--r--crates/ra_syntax/src/grammar.ron4
-rw-r--r--crates/ra_syntax/src/grammar/expressions/atom.rs28
-rw-r--r--crates/ra_syntax/src/syntax_node/syntax_error.rs4
-rw-r--r--crates/ra_syntax/tests/data/parser/err/0032_match_arms_inner_attrs.rs20
-rw-r--r--crates/ra_syntax/tests/data/parser/err/0032_match_arms_inner_attrs.txt201
-rw-r--r--crates/ra_syntax/tests/data/parser/err/0033_match_arms_outer_attrs.rs7
-rw-r--r--crates/ra_syntax/tests/data/parser/err/0033_match_arms_outer_attrs.txt64
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0120_match_arms_inner_attribute.rs8
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0120_match_arms_inner_attribute.txt74
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0121_match_arms_outer_attributes.rs12
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0121_match_arms_outer_attributes.txt135
-rw-r--r--crates/ra_vfs/src/io.rs31
-rw-r--r--crates/ra_vfs/src/lib.rs248
-rw-r--r--crates/ra_vfs/src/roots.rs109
17 files changed, 773 insertions, 196 deletions
diff --git a/crates/ra_batch/src/lib.rs b/crates/ra_batch/src/lib.rs
index 69d66113e..89a234f4c 100644
--- a/crates/ra_batch/src/lib.rs
+++ b/crates/ra_batch/src/lib.rs
@@ -75,7 +75,7 @@ impl BatchDatabase {
75 let source_root = SourceRoot { files: file_map }; 75 let source_root = SourceRoot { files: file_map };
76 db.set_source_root(source_root_id, Arc::new(source_root)); 76 db.set_source_root(source_root_id, Arc::new(source_root));
77 roots_loaded.insert(source_root_id); 77 roots_loaded.insert(source_root_id);
78 if roots_loaded.len() == vfs.num_roots() { 78 if roots_loaded.len() == vfs.n_roots() {
79 done = true; 79 done = true;
80 } 80 }
81 } 81 }
@@ -137,14 +137,14 @@ mod tests {
137 path = path.parent().unwrap().to_owned(); 137 path = path.parent().unwrap().to_owned();
138 } 138 }
139 let (db, roots) = BatchDatabase::load_cargo(path).unwrap(); 139 let (db, roots) = BatchDatabase::load_cargo(path).unwrap();
140 let mut num_crates = 0; 140 let mut n_crates = 0;
141 for root in roots { 141 for root in roots {
142 for _krate in Crate::source_root_crates(&db, root) { 142 for _krate in Crate::source_root_crates(&db, root) {
143 num_crates += 1; 143 n_crates += 1;
144 } 144 }
145 } 145 }
146 146
147 // RA has quite a few crates, but the exact count doesn't matter 147 // RA has quite a few crates, but the exact count doesn't matter
148 assert!(num_crates > 20); 148 assert!(n_crates > 20);
149 } 149 }
150} 150}
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs
index 22105d6c9..350f01f33 100644
--- a/crates/ra_syntax/src/ast.rs
+++ b/crates/ra_syntax/src/ast.rs
@@ -153,6 +153,20 @@ impl FnDef {
153} 153}
154 154
155impl Attr { 155impl Attr {
156 pub fn is_inner(&self) -> bool {
157 let tt = match self.value() {
158 None => return false,
159 Some(tt) => tt,
160 };
161
162 let prev = match tt.syntax().prev_sibling() {
163 None => return false,
164 Some(prev) => prev,
165 };
166
167 prev.kind() == EXCL
168 }
169
156 pub fn as_atom(&self) -> Option<SmolStr> { 170 pub fn as_atom(&self) -> Option<SmolStr> {
157 let tt = self.value()?; 171 let tt = self.value()?;
158 let (_bra, attr, _ket) = tt.syntax().children().collect_tuple()?; 172 let (_bra, attr, _ket) = tt.syntax().children().collect_tuple()?;
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs
index dd91b5063..c7aaccc95 100644
--- a/crates/ra_syntax/src/ast/generated.rs
+++ b/crates/ra_syntax/src/ast/generated.rs
@@ -1946,6 +1946,7 @@ impl ToOwned for MatchArm {
1946} 1946}
1947 1947
1948 1948
1949impl ast::AttrsOwner for MatchArm {}
1949impl MatchArm { 1950impl MatchArm {
1950 pub fn pats(&self) -> impl Iterator<Item = &Pat> { 1951 pub fn pats(&self) -> impl Iterator<Item = &Pat> {
1951 super::children(self) 1952 super::children(self)
@@ -1986,6 +1987,7 @@ impl ToOwned for MatchArmList {
1986} 1987}
1987 1988
1988 1989
1990impl ast::AttrsOwner for MatchArmList {}
1989impl MatchArmList { 1991impl MatchArmList {
1990 pub fn arms(&self) -> impl Iterator<Item = &MatchArm> { 1992 pub fn arms(&self) -> impl Iterator<Item = &MatchArm> {
1991 super::children(self) 1993 super::children(self)
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron
index 27a123681..193b563aa 100644
--- a/crates/ra_syntax/src/grammar.ron
+++ b/crates/ra_syntax/src/grammar.ron
@@ -413,13 +413,15 @@ Grammar(
413 ), 413 ),
414 "MatchArmList": ( 414 "MatchArmList": (
415 collections: [ ["arms", "MatchArm"] ], 415 collections: [ ["arms", "MatchArm"] ],
416 traits: [ "AttrsOwner" ]
416 ), 417 ),
417 "MatchArm": ( 418 "MatchArm": (
418 options: [ 419 options: [
419 [ "guard", "MatchGuard" ], 420 [ "guard", "MatchGuard" ],
420 "Expr", 421 "Expr",
421 ], 422 ],
422 collections: [ [ "pats", "Pat" ] ] 423 collections: [ [ "pats", "Pat" ] ],
424 traits: [ "AttrsOwner" ]
423 ), 425 ),
424 "MatchGuard": (options: ["Expr"]), 426 "MatchGuard": (options: ["Expr"]),
425 "StructLit": (options: ["Path", "NamedFieldList", ["spread", "Expr"]]), 427 "StructLit": (options: ["Path", "NamedFieldList", ["spread", "Expr"]]),
diff --git a/crates/ra_syntax/src/grammar/expressions/atom.rs b/crates/ra_syntax/src/grammar/expressions/atom.rs
index 27ba87657..e74305b6a 100644
--- a/crates/ra_syntax/src/grammar/expressions/atom.rs
+++ b/crates/ra_syntax/src/grammar/expressions/atom.rs
@@ -313,11 +313,39 @@ pub(crate) fn match_arm_list(p: &mut Parser) {
313 assert!(p.at(L_CURLY)); 313 assert!(p.at(L_CURLY));
314 let m = p.start(); 314 let m = p.start();
315 p.eat(L_CURLY); 315 p.eat(L_CURLY);
316
317 // test match_arms_inner_attribute
318 // fn foo() {
319 // match () {
320 // #![doc("Inner attribute")]
321 // #![doc("Can be")]
322 // #![doc("Stacked")]
323 // _ => (),
324 // }
325 // }
326 attributes::inner_attributes(p);
327
316 while !p.at(EOF) && !p.at(R_CURLY) { 328 while !p.at(EOF) && !p.at(R_CURLY) {
317 if p.at(L_CURLY) { 329 if p.at(L_CURLY) {
318 error_block(p, "expected match arm"); 330 error_block(p, "expected match arm");
319 continue; 331 continue;
320 } 332 }
333
334 // test match_arms_outer_attributes
335 // fn foo() {
336 // match () {
337 // #[cfg(feature = "some")]
338 // _ => (),
339 // #[cfg(feature = "other")]
340 // _ => (),
341 // #[cfg(feature = "many")]
342 // #[cfg(feature = "attributes")]
343 // #[cfg(feature = "before")]
344 // _ => (),
345 // }
346 // }
347 attributes::outer_attributes(p);
348
321 // test match_arms_commas 349 // test match_arms_commas
322 // fn foo() { 350 // fn foo() {
323 // match () { 351 // match () {
diff --git a/crates/ra_syntax/src/syntax_node/syntax_error.rs b/crates/ra_syntax/src/syntax_node/syntax_error.rs
index 412cf82cc..4ff998090 100644
--- a/crates/ra_syntax/src/syntax_node/syntax_error.rs
+++ b/crates/ra_syntax/src/syntax_node/syntax_error.rs
@@ -92,6 +92,7 @@ pub enum SyntaxErrorKind {
92 UnclosedString, 92 UnclosedString,
93 InvalidSuffix, 93 InvalidSuffix,
94 InvalidBlockAttr, 94 InvalidBlockAttr,
95 InvalidMatchInnerAttr,
95} 96}
96 97
97#[derive(Debug, Clone, PartialEq, Eq, Hash)] 98#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -136,6 +137,9 @@ impl fmt::Display for SyntaxErrorKind {
136 InvalidBlockAttr => { 137 InvalidBlockAttr => {
137 write!(f, "A block in this position cannot accept inner attributes") 138 write!(f, "A block in this position cannot accept inner attributes")
138 } 139 }
140 InvalidMatchInnerAttr => {
141 write!(f, "Inner attributes are only allowed directly after the opening brace of the match expression")
142 }
139 ParseError(msg) => write!(f, "{}", msg.0), 143 ParseError(msg) => write!(f, "{}", msg.0),
140 } 144 }
141 } 145 }
diff --git a/crates/ra_syntax/tests/data/parser/err/0032_match_arms_inner_attrs.rs b/crates/ra_syntax/tests/data/parser/err/0032_match_arms_inner_attrs.rs
new file mode 100644
index 000000000..06aa47770
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/err/0032_match_arms_inner_attrs.rs
@@ -0,0 +1,20 @@
1fn foo() {
2 match () {
3 _ => (),
4 #![doc("Not allowed here")]
5 _ => (),
6 }
7
8 match () {
9 _ => (),
10 _ => (),
11 #![doc("Nor here")]
12 }
13
14 match () {
15 #[cfg(test)]
16 #![doc("Nor here")]
17 _ => (),
18 _ => (),
19 }
20}
diff --git a/crates/ra_syntax/tests/data/parser/err/0032_match_arms_inner_attrs.txt b/crates/ra_syntax/tests/data/parser/err/0032_match_arms_inner_attrs.txt
new file mode 100644
index 000000000..b7543abc9
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/err/0032_match_arms_inner_attrs.txt
@@ -0,0 +1,201 @@
1SOURCE_FILE@[0; 293)
2 FN_DEF@[0; 292)
3 FN_KW@[0; 2)
4 WHITESPACE@[2; 3)
5 NAME@[3; 6)
6 IDENT@[3; 6) "foo"
7 PARAM_LIST@[6; 8)
8 L_PAREN@[6; 7)
9 R_PAREN@[7; 8)
10 WHITESPACE@[8; 9)
11 BLOCK@[9; 292)
12 L_CURLY@[9; 10)
13 WHITESPACE@[10; 15)
14 EXPR_STMT@[15; 101)
15 MATCH_EXPR@[15; 101)
16 MATCH_KW@[15; 20)
17 WHITESPACE@[20; 21)
18 TUPLE_EXPR@[21; 23)
19 L_PAREN@[21; 22)
20 R_PAREN@[22; 23)
21 WHITESPACE@[23; 24)
22 MATCH_ARM_LIST@[24; 101)
23 L_CURLY@[24; 25)
24 WHITESPACE@[25; 34)
25 MATCH_ARM@[34; 41)
26 PLACEHOLDER_PAT@[34; 35)
27 UNDERSCORE@[34; 35)
28 WHITESPACE@[35; 36)
29 FAT_ARROW@[36; 38)
30 WHITESPACE@[38; 39)
31 TUPLE_EXPR@[39; 41)
32 L_PAREN@[39; 40)
33 R_PAREN@[40; 41)
34 COMMA@[41; 42)
35 WHITESPACE@[42; 51)
36 ATTR@[51; 52)
37 POUND@[51; 52)
38 err: `expected `[``
39 err: `expected pattern`
40 MATCH_ARM@[52; 78)
41 ERROR@[52; 53)
42 EXCL@[52; 53)
43 err: `expected FAT_ARROW`
44 ARRAY_EXPR@[53; 78)
45 L_BRACK@[53; 54)
46 CALL_EXPR@[54; 77)
47 PATH_EXPR@[54; 57)
48 PATH@[54; 57)
49 PATH_SEGMENT@[54; 57)
50 NAME_REF@[54; 57)
51 IDENT@[54; 57) "doc"
52 ARG_LIST@[57; 77)
53 L_PAREN@[57; 58)
54 LITERAL@[58; 76)
55 STRING@[58; 76)
56 R_PAREN@[76; 77)
57 R_BRACK@[77; 78)
58 err: `expected COMMA`
59 WHITESPACE@[78; 87)
60 MATCH_ARM@[87; 94)
61 PLACEHOLDER_PAT@[87; 88)
62 UNDERSCORE@[87; 88)
63 WHITESPACE@[88; 89)
64 FAT_ARROW@[89; 91)
65 WHITESPACE@[91; 92)
66 TUPLE_EXPR@[92; 94)
67 L_PAREN@[92; 93)
68 R_PAREN@[93; 94)
69 COMMA@[94; 95)
70 WHITESPACE@[95; 100)
71 R_CURLY@[100; 101)
72 WHITESPACE@[101; 107)
73 EXPR_STMT@[107; 185)
74 MATCH_EXPR@[107; 185)
75 MATCH_KW@[107; 112)
76 WHITESPACE@[112; 113)
77 TUPLE_EXPR@[113; 115)
78 L_PAREN@[113; 114)
79 R_PAREN@[114; 115)
80 WHITESPACE@[115; 116)
81 MATCH_ARM_LIST@[116; 185)
82 L_CURLY@[116; 117)
83 WHITESPACE@[117; 126)
84 MATCH_ARM@[126; 133)
85 PLACEHOLDER_PAT@[126; 127)
86 UNDERSCORE@[126; 127)
87 WHITESPACE@[127; 128)
88 FAT_ARROW@[128; 130)
89 WHITESPACE@[130; 131)
90 TUPLE_EXPR@[131; 133)
91 L_PAREN@[131; 132)
92 R_PAREN@[132; 133)
93 COMMA@[133; 134)
94 WHITESPACE@[134; 143)
95 MATCH_ARM@[143; 150)
96 PLACEHOLDER_PAT@[143; 144)
97 UNDERSCORE@[143; 144)
98 WHITESPACE@[144; 145)
99 FAT_ARROW@[145; 147)
100 WHITESPACE@[147; 148)
101 TUPLE_EXPR@[148; 150)
102 L_PAREN@[148; 149)
103 R_PAREN@[149; 150)
104 COMMA@[150; 151)
105 WHITESPACE@[151; 160)
106 ATTR@[160; 161)
107 POUND@[160; 161)
108 err: `expected `[``
109 err: `expected pattern`
110 MATCH_ARM@[161; 179)
111 ERROR@[161; 162)
112 EXCL@[161; 162)
113 err: `expected FAT_ARROW`
114 ARRAY_EXPR@[162; 179)
115 L_BRACK@[162; 163)
116 CALL_EXPR@[163; 178)
117 PATH_EXPR@[163; 166)
118 PATH@[163; 166)
119 PATH_SEGMENT@[163; 166)
120 NAME_REF@[163; 166)
121 IDENT@[163; 166) "doc"
122 ARG_LIST@[166; 178)
123 L_PAREN@[166; 167)
124 LITERAL@[167; 177)
125 STRING@[167; 177)
126 R_PAREN@[177; 178)
127 R_BRACK@[178; 179)
128 WHITESPACE@[179; 184)
129 R_CURLY@[184; 185)
130 WHITESPACE@[185; 191)
131 MATCH_EXPR@[191; 290)
132 MATCH_KW@[191; 196)
133 WHITESPACE@[196; 197)
134 TUPLE_EXPR@[197; 199)
135 L_PAREN@[197; 198)
136 R_PAREN@[198; 199)
137 WHITESPACE@[199; 200)
138 MATCH_ARM_LIST@[200; 290)
139 L_CURLY@[200; 201)
140 WHITESPACE@[201; 210)
141 ATTR@[210; 222)
142 POUND@[210; 211)
143 TOKEN_TREE@[211; 222)
144 L_BRACK@[211; 212)
145 IDENT@[212; 215) "cfg"
146 TOKEN_TREE@[215; 221)
147 L_PAREN@[215; 216)
148 IDENT@[216; 220) "test"
149 R_PAREN@[220; 221)
150 R_BRACK@[221; 222)
151 WHITESPACE@[222; 231)
152 ATTR@[231; 232)
153 POUND@[231; 232)
154 err: `expected `[``
155 err: `expected pattern`
156 MATCH_ARM@[232; 250)
157 ERROR@[232; 233)
158 EXCL@[232; 233)
159 err: `expected FAT_ARROW`
160 ARRAY_EXPR@[233; 250)
161 L_BRACK@[233; 234)
162 CALL_EXPR@[234; 249)
163 PATH_EXPR@[234; 237)
164 PATH@[234; 237)
165 PATH_SEGMENT@[234; 237)
166 NAME_REF@[234; 237)
167 IDENT@[234; 237) "doc"
168 ARG_LIST@[237; 249)
169 L_PAREN@[237; 238)
170 LITERAL@[238; 248)
171 STRING@[238; 248)
172 R_PAREN@[248; 249)
173 R_BRACK@[249; 250)
174 err: `expected COMMA`
175 WHITESPACE@[250; 259)
176 MATCH_ARM@[259; 266)
177 PLACEHOLDER_PAT@[259; 260)
178 UNDERSCORE@[259; 260)
179 WHITESPACE@[260; 261)
180 FAT_ARROW@[261; 263)
181 WHITESPACE@[263; 264)
182 TUPLE_EXPR@[264; 266)
183 L_PAREN@[264; 265)
184 R_PAREN@[265; 266)
185 COMMA@[266; 267)
186 WHITESPACE@[267; 276)
187 MATCH_ARM@[276; 283)
188 PLACEHOLDER_PAT@[276; 277)
189 UNDERSCORE@[276; 277)
190 WHITESPACE@[277; 278)
191 FAT_ARROW@[278; 280)
192 WHITESPACE@[280; 281)
193 TUPLE_EXPR@[281; 283)
194 L_PAREN@[281; 282)
195 R_PAREN@[282; 283)
196 COMMA@[283; 284)
197 WHITESPACE@[284; 289)
198 R_CURLY@[289; 290)
199 WHITESPACE@[290; 291)
200 R_CURLY@[291; 292)
201 WHITESPACE@[292; 293)
diff --git a/crates/ra_syntax/tests/data/parser/err/0033_match_arms_outer_attrs.rs b/crates/ra_syntax/tests/data/parser/err/0033_match_arms_outer_attrs.rs
new file mode 100644
index 000000000..4635222da
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/err/0033_match_arms_outer_attrs.rs
@@ -0,0 +1,7 @@
1fn foo() {
2 match () {
3 _ => (),
4 _ => (),
5 #[cfg(test)]
6 }
7}
diff --git a/crates/ra_syntax/tests/data/parser/err/0033_match_arms_outer_attrs.txt b/crates/ra_syntax/tests/data/parser/err/0033_match_arms_outer_attrs.txt
new file mode 100644
index 000000000..7f8767001
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/err/0033_match_arms_outer_attrs.txt
@@ -0,0 +1,64 @@
1SOURCE_FILE@[0; 89)
2 FN_DEF@[0; 88)
3 FN_KW@[0; 2)
4 WHITESPACE@[2; 3)
5 NAME@[3; 6)
6 IDENT@[3; 6) "foo"
7 PARAM_LIST@[6; 8)
8 L_PAREN@[6; 7)
9 R_PAREN@[7; 8)
10 WHITESPACE@[8; 9)
11 BLOCK@[9; 88)
12 L_CURLY@[9; 10)
13 WHITESPACE@[10; 15)
14 MATCH_EXPR@[15; 86)
15 MATCH_KW@[15; 20)
16 WHITESPACE@[20; 21)
17 TUPLE_EXPR@[21; 23)
18 L_PAREN@[21; 22)
19 R_PAREN@[22; 23)
20 WHITESPACE@[23; 24)
21 MATCH_ARM_LIST@[24; 86)
22 L_CURLY@[24; 25)
23 WHITESPACE@[25; 34)
24 MATCH_ARM@[34; 41)
25 PLACEHOLDER_PAT@[34; 35)
26 UNDERSCORE@[34; 35)
27 WHITESPACE@[35; 36)
28 FAT_ARROW@[36; 38)
29 WHITESPACE@[38; 39)
30 TUPLE_EXPR@[39; 41)
31 L_PAREN@[39; 40)
32 R_PAREN@[40; 41)
33 COMMA@[41; 42)
34 WHITESPACE@[42; 51)
35 MATCH_ARM@[51; 58)
36 PLACEHOLDER_PAT@[51; 52)
37 UNDERSCORE@[51; 52)
38 WHITESPACE@[52; 53)
39 FAT_ARROW@[53; 55)
40 WHITESPACE@[55; 56)
41 TUPLE_EXPR@[56; 58)
42 L_PAREN@[56; 57)
43 R_PAREN@[57; 58)
44 COMMA@[58; 59)
45 WHITESPACE@[59; 68)
46 ATTR@[68; 80)
47 POUND@[68; 69)
48 TOKEN_TREE@[69; 80)
49 L_BRACK@[69; 70)
50 IDENT@[70; 73) "cfg"
51 TOKEN_TREE@[73; 79)
52 L_PAREN@[73; 74)
53 IDENT@[74; 78) "test"
54 R_PAREN@[78; 79)
55 R_BRACK@[79; 80)
56 WHITESPACE@[80; 85)
57 err: `expected pattern`
58 err: `expected FAT_ARROW`
59 err: `expected expression`
60 MATCH_ARM@[85; 85)
61 R_CURLY@[85; 86)
62 WHITESPACE@[86; 87)
63 R_CURLY@[87; 88)
64 WHITESPACE@[88; 89)
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0120_match_arms_inner_attribute.rs b/crates/ra_syntax/tests/data/parser/inline/ok/0120_match_arms_inner_attribute.rs
new file mode 100644
index 000000000..54a67c9d7
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0120_match_arms_inner_attribute.rs
@@ -0,0 +1,8 @@
1fn foo() {
2 match () {
3 #![doc("Inner attribute")]
4 #![doc("Can be")]
5 #![doc("Stacked")]
6 _ => (),
7 }
8}
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0120_match_arms_inner_attribute.txt b/crates/ra_syntax/tests/data/parser/inline/ok/0120_match_arms_inner_attribute.txt
new file mode 100644
index 000000000..b39a217bd
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0120_match_arms_inner_attribute.txt
@@ -0,0 +1,74 @@
1SOURCE_FILE@[0; 139)
2 FN_DEF@[0; 138)
3 FN_KW@[0; 2)
4 WHITESPACE@[2; 3)
5 NAME@[3; 6)
6 IDENT@[3; 6) "foo"
7 PARAM_LIST@[6; 8)
8 L_PAREN@[6; 7)
9 R_PAREN@[7; 8)
10 WHITESPACE@[8; 9)
11 BLOCK@[9; 138)
12 L_CURLY@[9; 10)
13 WHITESPACE@[10; 15)
14 MATCH_EXPR@[15; 136)
15 MATCH_KW@[15; 20)
16 WHITESPACE@[20; 21)
17 TUPLE_EXPR@[21; 23)
18 L_PAREN@[21; 22)
19 R_PAREN@[22; 23)
20 WHITESPACE@[23; 24)
21 MATCH_ARM_LIST@[24; 136)
22 L_CURLY@[24; 25)
23 WHITESPACE@[25; 34)
24 ATTR@[34; 60)
25 POUND@[34; 35)
26 EXCL@[35; 36)
27 TOKEN_TREE@[36; 60)
28 L_BRACK@[36; 37)
29 IDENT@[37; 40) "doc"
30 TOKEN_TREE@[40; 59)
31 L_PAREN@[40; 41)
32 STRING@[41; 58)
33 R_PAREN@[58; 59)
34 R_BRACK@[59; 60)
35 WHITESPACE@[60; 69)
36 ATTR@[69; 86)
37 POUND@[69; 70)
38 EXCL@[70; 71)
39 TOKEN_TREE@[71; 86)
40 L_BRACK@[71; 72)
41 IDENT@[72; 75) "doc"
42 TOKEN_TREE@[75; 85)
43 L_PAREN@[75; 76)
44 STRING@[76; 84)
45 R_PAREN@[84; 85)
46 R_BRACK@[85; 86)
47 WHITESPACE@[86; 95)
48 ATTR@[95; 113)
49 POUND@[95; 96)
50 EXCL@[96; 97)
51 TOKEN_TREE@[97; 113)
52 L_BRACK@[97; 98)
53 IDENT@[98; 101) "doc"
54 TOKEN_TREE@[101; 112)
55 L_PAREN@[101; 102)
56 STRING@[102; 111)
57 R_PAREN@[111; 112)
58 R_BRACK@[112; 113)
59 WHITESPACE@[113; 122)
60 MATCH_ARM@[122; 129)
61 PLACEHOLDER_PAT@[122; 123)
62 UNDERSCORE@[122; 123)
63 WHITESPACE@[123; 124)
64 FAT_ARROW@[124; 126)
65 WHITESPACE@[126; 127)
66 TUPLE_EXPR@[127; 129)
67 L_PAREN@[127; 128)
68 R_PAREN@[128; 129)
69 COMMA@[129; 130)
70 WHITESPACE@[130; 135)
71 R_CURLY@[135; 136)
72 WHITESPACE@[136; 137)
73 R_CURLY@[137; 138)
74 WHITESPACE@[138; 139)
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0121_match_arms_outer_attributes.rs b/crates/ra_syntax/tests/data/parser/inline/ok/0121_match_arms_outer_attributes.rs
new file mode 100644
index 000000000..676db42d1
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0121_match_arms_outer_attributes.rs
@@ -0,0 +1,12 @@
1fn foo() {
2 match () {
3 #[cfg(feature = "some")]
4 _ => (),
5 #[cfg(feature = "other")]
6 _ => (),
7 #[cfg(feature = "many")]
8 #[cfg(feature = "attributes")]
9 #[cfg(feature = "before")]
10 _ => (),
11 }
12}
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0121_match_arms_outer_attributes.txt b/crates/ra_syntax/tests/data/parser/inline/ok/0121_match_arms_outer_attributes.txt
new file mode 100644
index 000000000..c888fb8f0
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0121_match_arms_outer_attributes.txt
@@ -0,0 +1,135 @@
1SOURCE_FILE@[0; 259)
2 FN_DEF@[0; 258)
3 FN_KW@[0; 2)
4 WHITESPACE@[2; 3)
5 NAME@[3; 6)
6 IDENT@[3; 6) "foo"
7 PARAM_LIST@[6; 8)
8 L_PAREN@[6; 7)
9 R_PAREN@[7; 8)
10 WHITESPACE@[8; 9)
11 BLOCK@[9; 258)
12 L_CURLY@[9; 10)
13 WHITESPACE@[10; 15)
14 MATCH_EXPR@[15; 256)
15 MATCH_KW@[15; 20)
16 WHITESPACE@[20; 21)
17 TUPLE_EXPR@[21; 23)
18 L_PAREN@[21; 22)
19 R_PAREN@[22; 23)
20 WHITESPACE@[23; 24)
21 MATCH_ARM_LIST@[24; 256)
22 L_CURLY@[24; 25)
23 WHITESPACE@[25; 34)
24 ATTR@[34; 58)
25 POUND@[34; 35)
26 TOKEN_TREE@[35; 58)
27 L_BRACK@[35; 36)
28 IDENT@[36; 39) "cfg"
29 TOKEN_TREE@[39; 57)
30 L_PAREN@[39; 40)
31 IDENT@[40; 47) "feature"
32 WHITESPACE@[47; 48)
33 EQ@[48; 49)
34 WHITESPACE@[49; 50)
35 STRING@[50; 56)
36 R_PAREN@[56; 57)
37 R_BRACK@[57; 58)
38 WHITESPACE@[58; 67)
39 MATCH_ARM@[67; 74)
40 PLACEHOLDER_PAT@[67; 68)
41 UNDERSCORE@[67; 68)
42 WHITESPACE@[68; 69)
43 FAT_ARROW@[69; 71)
44 WHITESPACE@[71; 72)
45 TUPLE_EXPR@[72; 74)
46 L_PAREN@[72; 73)
47 R_PAREN@[73; 74)
48 COMMA@[74; 75)
49 WHITESPACE@[75; 84)
50 ATTR@[84; 109)
51 POUND@[84; 85)
52 TOKEN_TREE@[85; 109)
53 L_BRACK@[85; 86)
54 IDENT@[86; 89) "cfg"
55 TOKEN_TREE@[89; 108)
56 L_PAREN@[89; 90)
57 IDENT@[90; 97) "feature"
58 WHITESPACE@[97; 98)
59 EQ@[98; 99)
60 WHITESPACE@[99; 100)
61 STRING@[100; 107)
62 R_PAREN@[107; 108)
63 R_BRACK@[108; 109)
64 WHITESPACE@[109; 118)
65 MATCH_ARM@[118; 125)
66 PLACEHOLDER_PAT@[118; 119)
67 UNDERSCORE@[118; 119)
68 WHITESPACE@[119; 120)
69 FAT_ARROW@[120; 122)
70 WHITESPACE@[122; 123)
71 TUPLE_EXPR@[123; 125)
72 L_PAREN@[123; 124)
73 R_PAREN@[124; 125)
74 COMMA@[125; 126)
75 WHITESPACE@[126; 135)
76 ATTR@[135; 159)
77 POUND@[135; 136)
78 TOKEN_TREE@[136; 159)
79 L_BRACK@[136; 137)
80 IDENT@[137; 140) "cfg"
81 TOKEN_TREE@[140; 158)
82 L_PAREN@[140; 141)
83 IDENT@[141; 148) "feature"
84 WHITESPACE@[148; 149)
85 EQ@[149; 150)
86 WHITESPACE@[150; 151)
87 STRING@[151; 157)
88 R_PAREN@[157; 158)
89 R_BRACK@[158; 159)
90 WHITESPACE@[159; 168)
91 ATTR@[168; 198)
92 POUND@[168; 169)
93 TOKEN_TREE@[169; 198)
94 L_BRACK@[169; 170)
95 IDENT@[170; 173) "cfg"
96 TOKEN_TREE@[173; 197)
97 L_PAREN@[173; 174)
98 IDENT@[174; 181) "feature"
99 WHITESPACE@[181; 182)
100 EQ@[182; 183)
101 WHITESPACE@[183; 184)
102 STRING@[184; 196)
103 R_PAREN@[196; 197)
104 R_BRACK@[197; 198)
105 WHITESPACE@[198; 207)
106 ATTR@[207; 233)
107 POUND@[207; 208)
108 TOKEN_TREE@[208; 233)
109 L_BRACK@[208; 209)
110 IDENT@[209; 212) "cfg"
111 TOKEN_TREE@[212; 232)
112 L_PAREN@[212; 213)
113 IDENT@[213; 220) "feature"
114 WHITESPACE@[220; 221)
115 EQ@[221; 222)
116 WHITESPACE@[222; 223)
117 STRING@[223; 231)
118 R_PAREN@[231; 232)
119 R_BRACK@[232; 233)
120 WHITESPACE@[233; 242)
121 MATCH_ARM@[242; 249)
122 PLACEHOLDER_PAT@[242; 243)
123 UNDERSCORE@[242; 243)
124 WHITESPACE@[243; 244)
125 FAT_ARROW@[244; 246)
126 WHITESPACE@[246; 247)
127 TUPLE_EXPR@[247; 249)
128 L_PAREN@[247; 248)
129 R_PAREN@[248; 249)
130 COMMA@[249; 250)
131 WHITESPACE@[250; 255)
132 R_CURLY@[255; 256)
133 WHITESPACE@[256; 257)
134 R_CURLY@[257; 258)
135 WHITESPACE@[258; 259)
diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs
index f64b4c532..0cffc03f3 100644
--- a/crates/ra_vfs/src/io.rs
+++ b/crates/ra_vfs/src/io.rs
@@ -9,10 +9,10 @@ use relative_path::RelativePathBuf;
9use walkdir::WalkDir; 9use walkdir::WalkDir;
10use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as _Watcher}; 10use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
11 11
12use crate::{RootConfig, Roots, VfsRoot}; 12use crate::{Roots, VfsRoot};
13 13
14pub(crate) enum Task { 14pub(crate) enum Task {
15 AddRoot { root: VfsRoot, config: Arc<RootConfig> }, 15 AddRoot { root: VfsRoot },
16} 16}
17 17
18/// `TaskResult` transfers files read on the IO thread to the VFS on the main 18/// `TaskResult` transfers files read on the IO thread to the VFS on the main
@@ -98,8 +98,8 @@ pub(crate) fn start(roots: Arc<Roots>) -> Worker {
98 drop(input_receiver); 98 drop(input_receiver);
99 break 99 break
100 }, 100 },
101 Ok(Task::AddRoot { root, config }) => { 101 Ok(Task::AddRoot { root }) => {
102 watch_root(watcher.as_mut(), &output_sender, root, Arc::clone(&config)); 102 watch_root(watcher.as_mut(), &output_sender, &*roots, root);
103 } 103 }
104 }, 104 },
105 // Watcher send us changes. If **this** channel is 105 // Watcher send us changes. If **this** channel is
@@ -123,20 +123,21 @@ pub(crate) fn start(roots: Arc<Roots>) -> Worker {
123fn watch_root( 123fn watch_root(
124 watcher: Option<&mut RecommendedWatcher>, 124 watcher: Option<&mut RecommendedWatcher>,
125 sender: &Sender<TaskResult>, 125 sender: &Sender<TaskResult>,
126 roots: &Roots,
126 root: VfsRoot, 127 root: VfsRoot,
127 config: Arc<RootConfig>,
128) { 128) {
129 log::debug!("loading {} ...", config.root.as_path().display()); 129 let root_path = roots.path(root);
130 let files = watch_recursive(watcher, config.root.as_path(), &*config) 130 log::debug!("loading {} ...", root_path.display());
131 let files = watch_recursive(watcher, root_path, roots, root)
131 .into_iter() 132 .into_iter()
132 .filter_map(|path| { 133 .filter_map(|path| {
133 let abs_path = path.to_path(&config.root); 134 let abs_path = path.to_path(&root_path);
134 let text = read_to_string(&abs_path)?; 135 let text = read_to_string(&abs_path)?;
135 Some((path, text)) 136 Some((path, text))
136 }) 137 })
137 .collect(); 138 .collect();
138 sender.send(TaskResult::BulkLoadRoot { root, files }).unwrap(); 139 sender.send(TaskResult::BulkLoadRoot { root, files }).unwrap();
139 log::debug!("... loaded {}", config.root.as_path().display()); 140 log::debug!("... loaded {}", root_path.display());
140} 141}
141 142
142fn convert_notify_event(event: DebouncedEvent, sender: &Sender<(PathBuf, ChangeKind)>) { 143fn convert_notify_event(event: DebouncedEvent, sender: &Sender<(PathBuf, ChangeKind)>) {
@@ -181,19 +182,18 @@ fn handle_change(
181 None => return, 182 None => return,
182 Some(it) => it, 183 Some(it) => it,
183 }; 184 };
184 let config = &roots[root];
185 match kind { 185 match kind {
186 ChangeKind::Create => { 186 ChangeKind::Create => {
187 let mut paths = Vec::new(); 187 let mut paths = Vec::new();
188 if path.is_dir() { 188 if path.is_dir() {
189 paths.extend(watch_recursive(watcher, &path, &config)); 189 paths.extend(watch_recursive(watcher, &path, roots, root));
190 } else { 190 } else {
191 paths.push(rel_path); 191 paths.push(rel_path);
192 } 192 }
193 paths 193 paths
194 .into_iter() 194 .into_iter()
195 .try_for_each(|rel_path| { 195 .try_for_each(|rel_path| {
196 let abs_path = rel_path.to_path(&config.root); 196 let abs_path = rel_path.to_path(&roots.path(root));
197 let text = read_to_string(&abs_path); 197 let text = read_to_string(&abs_path);
198 sender.send(TaskResult::SingleFile { root, path: rel_path, text }) 198 sender.send(TaskResult::SingleFile { root, path: rel_path, text })
199 }) 199 })
@@ -209,12 +209,13 @@ fn handle_change(
209fn watch_recursive( 209fn watch_recursive(
210 mut watcher: Option<&mut RecommendedWatcher>, 210 mut watcher: Option<&mut RecommendedWatcher>,
211 dir: &Path, 211 dir: &Path,
212 config: &RootConfig, 212 roots: &Roots,
213 root: VfsRoot,
213) -> Vec<RelativePathBuf> { 214) -> Vec<RelativePathBuf> {
214 let mut files = Vec::new(); 215 let mut files = Vec::new();
215 for entry in WalkDir::new(dir) 216 for entry in WalkDir::new(dir)
216 .into_iter() 217 .into_iter()
217 .filter_entry(|it| config.contains(it.path()).is_some()) 218 .filter_entry(|it| roots.contains(root, it.path()).is_some())
218 .filter_map(|it| it.map_err(|e| log::warn!("watcher error: {}", e)).ok()) 219 .filter_map(|it| it.map_err(|e| log::warn!("watcher error: {}", e)).ok())
219 { 220 {
220 if entry.file_type().is_dir() { 221 if entry.file_type().is_dir() {
@@ -222,7 +223,7 @@ fn watch_recursive(
222 watch_one(watcher, entry.path()); 223 watch_one(watcher, entry.path());
223 } 224 }
224 } else { 225 } else {
225 let path = config.contains(entry.path()).unwrap(); 226 let path = roots.contains(root, entry.path()).unwrap();
226 files.push(path.to_owned()); 227 files.push(path.to_owned());
227 } 228 }
228 } 229 }
diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs
index cfdc1275f..7f555a3c0 100644
--- a/crates/ra_vfs/src/lib.rs
+++ b/crates/ra_vfs/src/lib.rs
@@ -15,10 +15,10 @@
15//! VFS is based on a concept of roots: a set of directories on the file system 15//! VFS is based on a concept of roots: a set of directories on the file system
16//! which are watched for changes. Typically, there will be a root for each 16//! which are watched for changes. Typically, there will be a root for each
17//! Cargo package. 17//! Cargo package.
18mod roots;
18mod io; 19mod io;
19 20
20use std::{ 21use std::{
21 cmp::Reverse,
22 fmt, fs, mem, 22 fmt, fs, mem,
23 path::{Path, PathBuf}, 23 path::{Path, PathBuf},
24 sync::Arc, 24 sync::Arc,
@@ -26,106 +26,18 @@ use std::{
26 26
27use crossbeam_channel::Receiver; 27use crossbeam_channel::Receiver;
28use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap}; 28use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap};
29use relative_path::{Component, RelativePath, RelativePathBuf}; 29use relative_path::{RelativePath, RelativePathBuf};
30use rustc_hash::{FxHashMap, FxHashSet}; 30use rustc_hash::{FxHashMap, FxHashSet};
31 31
32pub use crate::io::TaskResult as VfsTask; 32use crate::{
33use io::{TaskResult, Worker}; 33 io::{TaskResult, Worker},
34 34 roots::Roots,
35#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 35};
36pub struct VfsRoot(pub RawId);
37impl_arena_id!(VfsRoot);
38
39/// Describes the contents of a single source root.
40///
41/// `RootConfig` can be thought of as a glob pattern like `src/**.rs` which
42/// specifies the source root or as a function which takes a `PathBuf` and
43/// returns `true` iff path belongs to the source root
44pub(crate) struct RootConfig {
45 root: PathBuf,
46 // result of `root.canonicalize()` if that differs from `root`; `None` otherwise.
47 canonical_root: Option<PathBuf>,
48 excluded_dirs: Vec<PathBuf>,
49}
50
51pub(crate) struct Roots {
52 roots: Arena<VfsRoot, Arc<RootConfig>>,
53}
54
55impl std::ops::Deref for Roots {
56 type Target = Arena<VfsRoot, Arc<RootConfig>>;
57 fn deref(&self) -> &Self::Target {
58 &self.roots
59 }
60}
61
62impl RootConfig {
63 fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootConfig {
64 let mut canonical_root = root.canonicalize().ok();
65 if Some(&root) == canonical_root.as_ref() {
66 canonical_root = None;
67 }
68 RootConfig { root, canonical_root, excluded_dirs }
69 }
70 /// Checks if root contains a path and returns a root-relative path.
71 pub(crate) fn contains(&self, path: &Path) -> Option<RelativePathBuf> {
72 // First, check excluded dirs
73 if self.excluded_dirs.iter().any(|it| path.starts_with(it)) {
74 return None;
75 }
76 let rel_path = path
77 .strip_prefix(&self.root)
78 .or_else(|err_payload| {
79 self.canonical_root
80 .as_ref()
81 .map_or(Err(err_payload), |canonical_root| path.strip_prefix(canonical_root))
82 })
83 .ok()?;
84 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
85
86 // Ignore some common directories.
87 //
88 // FIXME: don't hard-code, specify at source-root creation time using
89 // gitignore
90 for (i, c) in rel_path.components().enumerate() {
91 if let Component::Normal(c) = c {
92 if (i == 0 && c == "target") || c == ".git" || c == "node_modules" {
93 return None;
94 }
95 }
96 }
97
98 if path.is_file() && rel_path.extension() != Some("rs") {
99 return None;
100 }
101
102 Some(rel_path)
103 }
104}
105 36
106impl Roots { 37pub use crate::{
107 pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots { 38 io::TaskResult as VfsTask,
108 let mut roots = Arena::default(); 39 roots::VfsRoot,
109 // A hack to make nesting work. 40};
110 paths.sort_by_key(|it| Reverse(it.as_os_str().len()));
111 paths.dedup();
112 for (i, path) in paths.iter().enumerate() {
113 let nested_roots = paths[..i]
114 .iter()
115 .filter(|it| it.starts_with(path))
116 .map(|it| it.clone())
117 .collect::<Vec<_>>();
118
119 let config = Arc::new(RootConfig::new(path.clone(), nested_roots));
120
121 roots.alloc(config.clone());
122 }
123 Roots { roots }
124 }
125 pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> {
126 self.roots.iter().find_map(|(root, data)| data.contains(path).map(|it| (root, it)))
127 }
128}
129 41
130#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
131pub struct VfsFile(pub RawId); 43pub struct VfsFile(pub RawId);
@@ -162,18 +74,18 @@ impl Vfs {
162 let worker = io::start(Arc::clone(&roots)); 74 let worker = io::start(Arc::clone(&roots));
163 let mut root2files = ArenaMap::default(); 75 let mut root2files = ArenaMap::default();
164 76
165 for (root, config) in roots.iter() { 77 for root in roots.iter() {
166 root2files.insert(root, Default::default()); 78 root2files.insert(root, Default::default());
167 worker.sender().send(io::Task::AddRoot { root, config: Arc::clone(config) }).unwrap(); 79 worker.sender().send(io::Task::AddRoot { root }).unwrap();
168 } 80 }
169 let res = 81 let res =
170 Vfs { roots, files: Arena::default(), root2files, worker, pending_changes: Vec::new() }; 82 Vfs { roots, files: Arena::default(), root2files, worker, pending_changes: Vec::new() };
171 let vfs_roots = res.roots.iter().map(|(id, _)| id).collect(); 83 let vfs_roots = res.roots.iter().collect();
172 (res, vfs_roots) 84 (res, vfs_roots)
173 } 85 }
174 86
175 pub fn root2path(&self, root: VfsRoot) -> PathBuf { 87 pub fn root2path(&self, root: VfsRoot) -> PathBuf {
176 self.roots[root].root.clone() 88 self.roots.path(root).to_path_buf()
177 } 89 }
178 90
179 pub fn path2file(&self, path: &Path) -> Option<VfsFile> { 91 pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
@@ -185,18 +97,11 @@ impl Vfs {
185 97
186 pub fn file2path(&self, file: VfsFile) -> PathBuf { 98 pub fn file2path(&self, file: VfsFile) -> PathBuf {
187 let rel_path = &self.files[file].path; 99 let rel_path = &self.files[file].path;
188 let root_path = &self.roots[self.files[file].root].root; 100 let root_path = &self.roots.path(self.files[file].root);
189 rel_path.to_path(root_path) 101 rel_path.to_path(root_path)
190 } 102 }
191 103
192 pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> { 104 pub fn n_roots(&self) -> usize {
193 if let Some((_root, _path, Some(file))) = self.find_root(path) {
194 return Some(file);
195 }
196 None
197 }
198
199 pub fn num_roots(&self) -> usize {
200 self.roots.len() 105 self.roots.len()
201 } 106 }
202 107
@@ -207,7 +112,7 @@ impl Vfs {
207 } else { 112 } else {
208 let text = fs::read_to_string(path).unwrap_or_default(); 113 let text = fs::read_to_string(path).unwrap_or_default();
209 let text = Arc::new(text); 114 let text = Arc::new(text);
210 let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false); 115 let file = self.raw_add_file(root, rel_path.clone(), Arc::clone(&text), false);
211 let change = VfsChange::AddFile { file, text, root, path: rel_path }; 116 let change = VfsChange::AddFile { file, text, root, path: rel_path };
212 self.pending_changes.push(change); 117 self.pending_changes.push(change);
213 Some(file) 118 Some(file)
@@ -216,6 +121,39 @@ impl Vfs {
216 None 121 None
217 } 122 }
218 123
124 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
125 let (root, rel_path, file) = self.find_root(path)?;
126 if let Some(file) = file {
127 self.change_file_event(file, text, true);
128 Some(file)
129 } else {
130 self.add_file_event(root, rel_path, text, true)
131 }
132 }
133
134 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
135 if let Some((_root, _path, file)) = self.find_root(path) {
136 let file = file.expect("can't change a file which wasn't added");
137 self.change_file_event(file, new_text, true);
138 }
139 }
140
141 pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
142 let (root, rel_path, file) = self.find_root(path)?;
143 let file = file.expect("can't remove a file which wasn't added");
144 let full_path = rel_path.to_path(&self.roots.path(root));
145 if let Ok(text) = fs::read_to_string(&full_path) {
146 self.change_file_event(file, text, false);
147 } else {
148 self.remove_file_event(root, rel_path, file);
149 }
150 Some(file)
151 }
152
153 pub fn commit_changes(&mut self) -> Vec<VfsChange> {
154 mem::replace(&mut self.pending_changes, Vec::new())
155 }
156
219 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> { 157 pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
220 self.worker.receiver() 158 self.worker.receiver()
221 } 159 }
@@ -237,7 +175,7 @@ impl Vfs {
237 continue; 175 continue;
238 } 176 }
239 let text = Arc::new(text); 177 let text = Arc::new(text);
240 let file = self.add_file(root, path.clone(), Arc::clone(&text), false); 178 let file = self.raw_add_file(root, path.clone(), Arc::clone(&text), false);
241 cur_files.push((file, path, text)); 179 cur_files.push((file, path, text));
242 } 180 }
243 181
@@ -245,15 +183,19 @@ impl Vfs {
245 self.pending_changes.push(change); 183 self.pending_changes.push(change);
246 } 184 }
247 TaskResult::SingleFile { root, path, text } => { 185 TaskResult::SingleFile { root, path, text } => {
248 match (self.find_file(root, &path), text) { 186 let existing_file = self.find_file(root, &path);
187 if existing_file.map(|file| self.files[file].is_overlayed) == Some(true) {
188 return;
189 }
190 match (existing_file, text) {
249 (Some(file), None) => { 191 (Some(file), None) => {
250 self.do_remove_file(root, path, file, false); 192 self.remove_file_event(root, path, file);
251 } 193 }
252 (None, Some(text)) => { 194 (None, Some(text)) => {
253 self.do_add_file(root, path, text, false); 195 self.add_file_event(root, path, text, false);
254 } 196 }
255 (Some(file), Some(text)) => { 197 (Some(file), Some(text)) => {
256 self.do_change_file(file, text, false); 198 self.change_file_event(file, text, false);
257 } 199 }
258 (None, None) => (), 200 (None, None) => (),
259 } 201 }
@@ -261,7 +203,10 @@ impl Vfs {
261 } 203 }
262 } 204 }
263 205
264 fn do_add_file( 206 // *_event calls change the state of VFS and push a change onto pending
207 // changes array.
208
209 fn add_file_event(
265 &mut self, 210 &mut self,
266 root: VfsRoot, 211 root: VfsRoot,
267 path: RelativePathBuf, 212 path: RelativePathBuf,
@@ -269,74 +214,25 @@ impl Vfs {
269 is_overlay: bool, 214 is_overlay: bool,
270 ) -> Option<VfsFile> { 215 ) -> Option<VfsFile> {
271 let text = Arc::new(text); 216 let text = Arc::new(text);
272 let file = self.add_file(root, path.clone(), text.clone(), is_overlay); 217 let file = self.raw_add_file(root, path.clone(), text.clone(), is_overlay);
273 self.pending_changes.push(VfsChange::AddFile { file, root, path, text }); 218 self.pending_changes.push(VfsChange::AddFile { file, root, path, text });
274 Some(file) 219 Some(file)
275 } 220 }
276 221
277 fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) { 222 fn change_file_event(&mut self, file: VfsFile, text: String, is_overlay: bool) {
278 if !is_overlay && self.files[file].is_overlayed {
279 return;
280 }
281 let text = Arc::new(text); 223 let text = Arc::new(text);
282 self.change_file(file, text.clone(), is_overlay); 224 self.raw_change_file(file, text.clone(), is_overlay);
283 self.pending_changes.push(VfsChange::ChangeFile { file, text }); 225 self.pending_changes.push(VfsChange::ChangeFile { file, text });
284 } 226 }
285 227
286 fn do_remove_file( 228 fn remove_file_event(&mut self, root: VfsRoot, path: RelativePathBuf, file: VfsFile) {
287 &mut self, 229 self.raw_remove_file(file);
288 root: VfsRoot,
289 path: RelativePathBuf,
290 file: VfsFile,
291 is_overlay: bool,
292 ) {
293 if !is_overlay && self.files[file].is_overlayed {
294 return;
295 }
296 self.remove_file(file);
297 self.pending_changes.push(VfsChange::RemoveFile { root, path, file }); 230 self.pending_changes.push(VfsChange::RemoveFile { root, path, file });
298 } 231 }
299 232
300 pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> { 233 // raw_* calls change the state of VFS, but **do not** emit events.
301 if let Some((root, rel_path, file)) = self.find_root(path) {
302 if let Some(file) = file {
303 self.do_change_file(file, text, true);
304 Some(file)
305 } else {
306 self.do_add_file(root, rel_path, text, true)
307 }
308 } else {
309 None
310 }
311 }
312
313 pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
314 if let Some((_root, _path, file)) = self.find_root(path) {
315 let file = file.expect("can't change a file which wasn't added");
316 self.do_change_file(file, new_text, true);
317 }
318 }
319
320 pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
321 if let Some((root, path, file)) = self.find_root(path) {
322 let file = file.expect("can't remove a file which wasn't added");
323 let full_path = path.to_path(&self.roots[root].root);
324 if let Ok(text) = fs::read_to_string(&full_path) {
325 self.do_change_file(file, text, true);
326 } else {
327 self.do_remove_file(root, path, file, true);
328 }
329 Some(file)
330 } else {
331 None
332 }
333 }
334
335 pub fn commit_changes(&mut self) -> Vec<VfsChange> {
336 mem::replace(&mut self.pending_changes, Vec::new())
337 }
338 234
339 fn add_file( 235 fn raw_add_file(
340 &mut self, 236 &mut self,
341 root: VfsRoot, 237 root: VfsRoot,
342 path: RelativePathBuf, 238 path: RelativePathBuf,
@@ -349,13 +245,13 @@ impl Vfs {
349 file 245 file
350 } 246 }
351 247
352 fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) { 248 fn raw_change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
353 let mut file_data = &mut self.files[file]; 249 let mut file_data = &mut self.files[file];
354 file_data.text = new_text; 250 file_data.text = new_text;
355 file_data.is_overlayed = is_overlayed; 251 file_data.is_overlayed = is_overlayed;
356 } 252 }
357 253
358 fn remove_file(&mut self, file: VfsFile) { 254 fn raw_remove_file(&mut self, file: VfsFile) {
359 // FIXME: use arena with removal 255 // FIXME: use arena with removal
360 self.files[file].text = Default::default(); 256 self.files[file].text = Default::default();
361 self.files[file].path = Default::default(); 257 self.files[file].path = Default::default();
diff --git a/crates/ra_vfs/src/roots.rs b/crates/ra_vfs/src/roots.rs
new file mode 100644
index 000000000..5e2776a35
--- /dev/null
+++ b/crates/ra_vfs/src/roots.rs
@@ -0,0 +1,109 @@
1use std::{
2 iter,
3 sync::Arc,
4 path::{Path, PathBuf},
5};
6
7use relative_path::{ RelativePath, RelativePathBuf};
8use ra_arena::{impl_arena_id, Arena, RawId};
9
10/// VfsRoot identifies a watched directory on the file system.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct VfsRoot(pub RawId);
13impl_arena_id!(VfsRoot);
14
15/// Describes the contents of a single source root.
16///
17/// `RootConfig` can be thought of as a glob pattern like `src/**.rs` which
18/// specifies the source root or as a function which takes a `PathBuf` and
19/// returns `true` iff path belongs to the source root
20struct RootData {
21 path: PathBuf,
22 // result of `root.canonicalize()` if that differs from `root`; `None` otherwise.
23 canonical_path: Option<PathBuf>,
24 excluded_dirs: Vec<RelativePathBuf>,
25}
26
27pub(crate) struct Roots {
28 roots: Arena<VfsRoot, Arc<RootData>>,
29}
30
31impl Roots {
32 pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots {
33 let mut roots = Arena::default();
34 // A hack to make nesting work.
35 paths.sort_by_key(|it| std::cmp::Reverse(it.as_os_str().len()));
36 paths.dedup();
37 for (i, path) in paths.iter().enumerate() {
38 let nested_roots =
39 paths[..i].iter().filter_map(|it| rel_path(path, it)).collect::<Vec<_>>();
40
41 let config = Arc::new(RootData::new(path.clone(), nested_roots));
42
43 roots.alloc(config.clone());
44 }
45 Roots { roots }
46 }
47 pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> {
48 self.iter().find_map(|root| {
49 let rel_path = self.contains(root, path)?;
50 Some((root, rel_path))
51 })
52 }
53 pub(crate) fn len(&self) -> usize {
54 self.roots.len()
55 }
56 pub(crate) fn iter<'a>(&'a self) -> impl Iterator<Item = VfsRoot> + 'a {
57 self.roots.iter().map(|(id, _)| id)
58 }
59 pub(crate) fn path(&self, root: VfsRoot) -> &Path {
60 self.roots[root].path.as_path()
61 }
62 /// Checks if root contains a path and returns a root-relative path.
63 pub(crate) fn contains(&self, root: VfsRoot, path: &Path) -> Option<RelativePathBuf> {
64 let data = &self.roots[root];
65 iter::once(&data.path)
66 .chain(data.canonical_path.as_ref().into_iter())
67 .find_map(|base| rel_path(base, path))
68 .filter(|path| !data.excluded_dirs.contains(path))
69 .filter(|path| !data.is_excluded(path))
70 }
71}
72
73impl RootData {
74 fn new(path: PathBuf, excluded_dirs: Vec<RelativePathBuf>) -> RootData {
75 let mut canonical_path = path.canonicalize().ok();
76 if Some(&path) == canonical_path.as_ref() {
77 canonical_path = None;
78 }
79 RootData { path, canonical_path, excluded_dirs }
80 }
81
82 fn is_excluded(&self, path: &RelativePath) -> bool {
83 if self.excluded_dirs.iter().any(|it| it == path) {
84 return true;
85 }
86 // Ignore some common directories.
87 //
88 // FIXME: don't hard-code, specify at source-root creation time using
89 // gitignore
90 for (i, c) in path.components().enumerate() {
91 if let relative_path::Component::Normal(c) = c {
92 if (i == 0 && c == "target") || c == ".git" || c == "node_modules" {
93 return true;
94 }
95 }
96 }
97
98 match path.extension() {
99 None | Some("rs") => false,
100 _ => true,
101 }
102 }
103}
104
105fn rel_path(base: &Path, path: &Path) -> Option<RelativePathBuf> {
106 let path = path.strip_prefix(base).ok()?;
107 let path = RelativePathBuf::from_path(path).unwrap();
108 Some(path)
109}