diff options
46 files changed, 1026 insertions, 999 deletions
diff --git a/Cargo.lock b/Cargo.lock index c91236fc6..f2069c55e 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -354,9 +354,9 @@ checksum = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674" | |||
354 | 354 | ||
355 | [[package]] | 355 | [[package]] |
356 | name = "fsevent" | 356 | name = "fsevent" |
357 | version = "0.4.0" | 357 | version = "2.0.2" |
358 | source = "registry+https://github.com/rust-lang/crates.io-index" | 358 | source = "registry+https://github.com/rust-lang/crates.io-index" |
359 | checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" | 359 | checksum = "97f347202c95c98805c216f9e1df210e8ebaec9fdb2365700a43c10797a35e63" |
360 | dependencies = [ | 360 | dependencies = [ |
361 | "bitflags", | 361 | "bitflags", |
362 | "fsevent-sys", | 362 | "fsevent-sys", |
@@ -364,9 +364,9 @@ dependencies = [ | |||
364 | 364 | ||
365 | [[package]] | 365 | [[package]] |
366 | name = "fsevent-sys" | 366 | name = "fsevent-sys" |
367 | version = "2.0.1" | 367 | version = "3.0.2" |
368 | source = "registry+https://github.com/rust-lang/crates.io-index" | 368 | source = "registry+https://github.com/rust-lang/crates.io-index" |
369 | checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" | 369 | checksum = "77a29c77f1ca394c3e73a9a5d24cfcabb734682d9634fc398f2204a63c994120" |
370 | dependencies = [ | 370 | dependencies = [ |
371 | "libc", | 371 | "libc", |
372 | ] | 372 | ] |
@@ -483,9 +483,9 @@ dependencies = [ | |||
483 | 483 | ||
484 | [[package]] | 484 | [[package]] |
485 | name = "inotify" | 485 | name = "inotify" |
486 | version = "0.7.1" | 486 | version = "0.8.3" |
487 | source = "registry+https://github.com/rust-lang/crates.io-index" | 487 | source = "registry+https://github.com/rust-lang/crates.io-index" |
488 | checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" | 488 | checksum = "46dd0a94b393c730779ccfd2a872b67b1eb67be3fc33082e733bdb38b5fde4d4" |
489 | dependencies = [ | 489 | dependencies = [ |
490 | "bitflags", | 490 | "bitflags", |
491 | "inotify-sys", | 491 | "inotify-sys", |
@@ -766,11 +766,13 @@ dependencies = [ | |||
766 | 766 | ||
767 | [[package]] | 767 | [[package]] |
768 | name = "notify" | 768 | name = "notify" |
769 | version = "4.0.15" | 769 | version = "5.0.0-pre.3" |
770 | source = "registry+https://github.com/rust-lang/crates.io-index" | 770 | source = "registry+https://github.com/rust-lang/crates.io-index" |
771 | checksum = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd" | 771 | checksum = "77d03607cf88b4b160ba0e9ed425fff3cee3b55ac813f0c685b3a3772da37d0e" |
772 | dependencies = [ | 772 | dependencies = [ |
773 | "anymap", | ||
773 | "bitflags", | 774 | "bitflags", |
775 | "crossbeam-channel", | ||
774 | "filetime", | 776 | "filetime", |
775 | "fsevent", | 777 | "fsevent", |
776 | "fsevent-sys", | 778 | "fsevent-sys", |
@@ -952,7 +954,9 @@ dependencies = [ | |||
952 | "relative-path", | 954 | "relative-path", |
953 | "rustc-hash", | 955 | "rustc-hash", |
954 | "salsa", | 956 | "salsa", |
957 | "stdx", | ||
955 | "test_utils", | 958 | "test_utils", |
959 | "vfs", | ||
956 | ] | 960 | ] |
957 | 961 | ||
958 | [[package]] | 962 | [[package]] |
@@ -1233,22 +1237,6 @@ dependencies = [ | |||
1233 | ] | 1237 | ] |
1234 | 1238 | ||
1235 | [[package]] | 1239 | [[package]] |
1236 | name = "ra_vfs" | ||
1237 | version = "0.6.1" | ||
1238 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1239 | checksum = "cbf31a173fc77ec59c27cf39af6baa137b40f4dbd45a8b3eccb1b2e4cfc922c1" | ||
1240 | dependencies = [ | ||
1241 | "crossbeam-channel", | ||
1242 | "jod-thread", | ||
1243 | "log", | ||
1244 | "notify", | ||
1245 | "parking_lot", | ||
1246 | "relative-path", | ||
1247 | "rustc-hash", | ||
1248 | "walkdir", | ||
1249 | ] | ||
1250 | |||
1251 | [[package]] | ||
1252 | name = "rand" | 1240 | name = "rand" |
1253 | version = "0.7.3" | 1241 | version = "0.7.3" |
1254 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -1405,7 +1393,6 @@ dependencies = [ | |||
1405 | "ra_syntax", | 1393 | "ra_syntax", |
1406 | "ra_text_edit", | 1394 | "ra_text_edit", |
1407 | "ra_tt", | 1395 | "ra_tt", |
1408 | "ra_vfs", | ||
1409 | "rand", | 1396 | "rand", |
1410 | "rustc-hash", | 1397 | "rustc-hash", |
1411 | "serde", | 1398 | "serde", |
@@ -1414,6 +1401,8 @@ dependencies = [ | |||
1414 | "tempfile", | 1401 | "tempfile", |
1415 | "test_utils", | 1402 | "test_utils", |
1416 | "threadpool", | 1403 | "threadpool", |
1404 | "vfs", | ||
1405 | "vfs-notify", | ||
1417 | "winapi 0.3.8", | 1406 | "winapi 0.3.8", |
1418 | ] | 1407 | ] |
1419 | 1408 | ||
@@ -1764,11 +1753,22 @@ dependencies = [ | |||
1764 | name = "vfs" | 1753 | name = "vfs" |
1765 | version = "0.1.0" | 1754 | version = "0.1.0" |
1766 | dependencies = [ | 1755 | dependencies = [ |
1756 | "paths", | ||
1757 | "rustc-hash", | ||
1758 | ] | ||
1759 | |||
1760 | [[package]] | ||
1761 | name = "vfs-notify" | ||
1762 | version = "0.1.0" | ||
1763 | dependencies = [ | ||
1767 | "crossbeam-channel", | 1764 | "crossbeam-channel", |
1768 | "globset", | 1765 | "globset", |
1769 | "jod-thread", | 1766 | "jod-thread", |
1767 | "log", | ||
1768 | "notify", | ||
1770 | "paths", | 1769 | "paths", |
1771 | "rustc-hash", | 1770 | "rustc-hash", |
1771 | "vfs", | ||
1772 | "walkdir", | 1772 | "walkdir", |
1773 | ] | 1773 | ] |
1774 | 1774 | ||
diff --git a/crates/paths/src/lib.rs b/crates/paths/src/lib.rs index 32267f2e6..45b19c45a 100644 --- a/crates/paths/src/lib.rs +++ b/crates/paths/src/lib.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | //! relative paths. | 2 | //! relative paths. |
3 | use std::{ | 3 | use std::{ |
4 | convert::{TryFrom, TryInto}, | 4 | convert::{TryFrom, TryInto}, |
5 | io, ops, | 5 | ops, |
6 | path::{Component, Path, PathBuf}, | 6 | path::{Component, Path, PathBuf}, |
7 | }; | 7 | }; |
8 | 8 | ||
@@ -46,9 +46,6 @@ impl TryFrom<&str> for AbsPathBuf { | |||
46 | } | 46 | } |
47 | 47 | ||
48 | impl AbsPathBuf { | 48 | impl AbsPathBuf { |
49 | pub fn canonicalized(path: &Path) -> io::Result<AbsPathBuf> { | ||
50 | path.canonicalize().map(|it| AbsPathBuf::try_from(it).unwrap()) | ||
51 | } | ||
52 | pub fn as_path(&self) -> &AbsPath { | 49 | pub fn as_path(&self) -> &AbsPath { |
53 | AbsPath::new_unchecked(self.0.as_path()) | 50 | AbsPath::new_unchecked(self.0.as_path()) |
54 | } | 51 | } |
diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs index 62dd3547f..55576813f 100644 --- a/crates/ra_assists/src/tests.rs +++ b/crates/ra_assists/src/tests.rs | |||
@@ -1,10 +1,8 @@ | |||
1 | mod generated; | 1 | mod generated; |
2 | 2 | ||
3 | use std::sync::Arc; | ||
4 | |||
5 | use hir::Semantics; | 3 | use hir::Semantics; |
6 | use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; | 4 | use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; |
7 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; | 5 | use ra_ide_db::RootDatabase; |
8 | use ra_syntax::TextRange; | 6 | use ra_syntax::TextRange; |
9 | use test_utils::{ | 7 | use test_utils::{ |
10 | assert_eq_text, extract_offset, extract_range, extract_range_or_offset, RangeOrOffset, | 8 | assert_eq_text, extract_offset, extract_range, extract_range_or_offset, RangeOrOffset, |
@@ -13,11 +11,7 @@ use test_utils::{ | |||
13 | use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; | 11 | use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; |
14 | 12 | ||
15 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { | 13 | pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { |
16 | let (mut db, file_id) = RootDatabase::with_single_file(text); | 14 | RootDatabase::with_single_file(text) |
17 | // FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`, | ||
18 | // but it looks like this might need specialization? :( | ||
19 | db.set_local_roots(Arc::new(vec![db.file_source_root(file_id)])); | ||
20 | (db, file_id) | ||
21 | } | 15 | } |
22 | 16 | ||
23 | pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) { | 17 | pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) { |
@@ -72,8 +66,7 @@ enum ExpectedResult<'a> { | |||
72 | 66 | ||
73 | fn check(handler: Handler, before: &str, expected: ExpectedResult) { | 67 | fn check(handler: Handler, before: &str, expected: ExpectedResult) { |
74 | let (text_without_caret, file_with_caret_id, range_or_offset, db) = if before.contains("//-") { | 68 | let (text_without_caret, file_with_caret_id, range_or_offset, db) = if before.contains("//-") { |
75 | let (mut db, position) = RootDatabase::with_position(before); | 69 | let (db, position) = RootDatabase::with_position(before); |
76 | db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)])); | ||
77 | ( | 70 | ( |
78 | db.file_text(position.file_id).as_ref().to_owned(), | 71 | db.file_text(position.file_id).as_ref().to_owned(), |
79 | position.file_id, | 72 | position.file_id, |
diff --git a/crates/ra_db/Cargo.toml b/crates/ra_db/Cargo.toml index 8ab409158..372fb242b 100644 --- a/crates/ra_db/Cargo.toml +++ b/crates/ra_db/Cargo.toml | |||
@@ -17,3 +17,5 @@ ra_cfg = { path = "../ra_cfg" } | |||
17 | ra_prof = { path = "../ra_prof" } | 17 | ra_prof = { path = "../ra_prof" } |
18 | ra_tt = { path = "../ra_tt" } | 18 | ra_tt = { path = "../ra_tt" } |
19 | test_utils = { path = "../test_utils" } | 19 | test_utils = { path = "../test_utils" } |
20 | vfs = { path = "../vfs" } | ||
21 | stdx = { path = "../stdx" } | ||
diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs index af8fe11ec..f7d9118a9 100644 --- a/crates/ra_db/src/fixture.rs +++ b/crates/ra_db/src/fixture.rs | |||
@@ -57,17 +57,16 @@ | |||
57 | //! fn insert_source_code_here() {} | 57 | //! fn insert_source_code_here() {} |
58 | //! " | 58 | //! " |
59 | //! ``` | 59 | //! ``` |
60 | 60 | use std::{str::FromStr, sync::Arc}; | |
61 | use std::str::FromStr; | ||
62 | use std::sync::Arc; | ||
63 | 61 | ||
64 | use ra_cfg::CfgOptions; | 62 | use ra_cfg::CfgOptions; |
65 | use rustc_hash::FxHashMap; | 63 | use rustc_hash::FxHashMap; |
66 | use test_utils::{extract_offset, parse_fixture, parse_single_fixture, FixtureMeta, CURSOR_MARKER}; | 64 | use test_utils::{extract_offset, parse_fixture, parse_single_fixture, FixtureMeta, CURSOR_MARKER}; |
65 | use vfs::{file_set::FileSet, VfsPath}; | ||
67 | 66 | ||
68 | use crate::{ | 67 | use crate::{ |
69 | input::CrateName, CrateGraph, CrateId, Edition, Env, FileId, FilePosition, RelativePathBuf, | 68 | input::CrateName, CrateGraph, CrateId, Edition, Env, FileId, FilePosition, SourceDatabaseExt, |
70 | SourceDatabaseExt, SourceRoot, SourceRootId, | 69 | SourceRoot, SourceRootId, |
71 | }; | 70 | }; |
72 | 71 | ||
73 | pub const WORKSPACE: SourceRootId = SourceRootId(0); | 72 | pub const WORKSPACE: SourceRootId = SourceRootId(0); |
@@ -105,10 +104,10 @@ impl<DB: SourceDatabaseExt + Default + 'static> WithFixture for DB {} | |||
105 | 104 | ||
106 | fn with_single_file(db: &mut dyn SourceDatabaseExt, ra_fixture: &str) -> FileId { | 105 | fn with_single_file(db: &mut dyn SourceDatabaseExt, ra_fixture: &str) -> FileId { |
107 | let file_id = FileId(0); | 106 | let file_id = FileId(0); |
108 | let rel_path: RelativePathBuf = "/main.rs".into(); | 107 | let mut file_set = vfs::file_set::FileSet::default(); |
108 | file_set.insert(file_id, vfs::VfsPath::new_virtual_path("/main.rs".to_string())); | ||
109 | 109 | ||
110 | let mut source_root = SourceRoot::new_local(); | 110 | let source_root = SourceRoot::new_local(file_set); |
111 | source_root.insert_file(rel_path.clone(), file_id); | ||
112 | 111 | ||
113 | let fixture = parse_single_fixture(ra_fixture); | 112 | let fixture = parse_single_fixture(ra_fixture); |
114 | 113 | ||
@@ -128,7 +127,6 @@ fn with_single_file(db: &mut dyn SourceDatabaseExt, ra_fixture: &str) -> FileId | |||
128 | meta.cfg, | 127 | meta.cfg, |
129 | meta.env, | 128 | meta.env, |
130 | Default::default(), | 129 | Default::default(), |
131 | Default::default(), | ||
132 | ); | 130 | ); |
133 | crate_graph | 131 | crate_graph |
134 | } else { | 132 | } else { |
@@ -140,13 +138,11 @@ fn with_single_file(db: &mut dyn SourceDatabaseExt, ra_fixture: &str) -> FileId | |||
140 | CfgOptions::default(), | 138 | CfgOptions::default(), |
141 | Env::default(), | 139 | Env::default(), |
142 | Default::default(), | 140 | Default::default(), |
143 | Default::default(), | ||
144 | ); | 141 | ); |
145 | crate_graph | 142 | crate_graph |
146 | }; | 143 | }; |
147 | 144 | ||
148 | db.set_file_text(file_id, Arc::new(ra_fixture.to_string())); | 145 | db.set_file_text(file_id, Arc::new(ra_fixture.to_string())); |
149 | db.set_file_relative_path(file_id, rel_path); | ||
150 | db.set_file_source_root(file_id, WORKSPACE); | 146 | db.set_file_source_root(file_id, WORKSPACE); |
151 | db.set_source_root(WORKSPACE, Arc::new(source_root)); | 147 | db.set_source_root(WORKSPACE, Arc::new(source_root)); |
152 | db.set_crate_graph(Arc::new(crate_graph)); | 148 | db.set_crate_graph(Arc::new(crate_graph)); |
@@ -162,7 +158,7 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit | |||
162 | let mut crate_deps = Vec::new(); | 158 | let mut crate_deps = Vec::new(); |
163 | let mut default_crate_root: Option<FileId> = None; | 159 | let mut default_crate_root: Option<FileId> = None; |
164 | 160 | ||
165 | let mut source_root = SourceRoot::new_local(); | 161 | let mut file_set = FileSet::default(); |
166 | let mut source_root_id = WORKSPACE; | 162 | let mut source_root_id = WORKSPACE; |
167 | let mut source_root_prefix = "/".to_string(); | 163 | let mut source_root_prefix = "/".to_string(); |
168 | let mut file_id = FileId(0); | 164 | let mut file_id = FileId(0); |
@@ -172,8 +168,8 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit | |||
172 | for entry in fixture.iter() { | 168 | for entry in fixture.iter() { |
173 | let meta = match ParsedMeta::from(&entry.meta) { | 169 | let meta = match ParsedMeta::from(&entry.meta) { |
174 | ParsedMeta::Root { path } => { | 170 | ParsedMeta::Root { path } => { |
175 | let source_root = std::mem::replace(&mut source_root, SourceRoot::new_local()); | 171 | let file_set = std::mem::replace(&mut file_set, FileSet::default()); |
176 | db.set_source_root(source_root_id, Arc::new(source_root)); | 172 | db.set_source_root(source_root_id, Arc::new(SourceRoot::new_local(file_set))); |
177 | source_root_id.0 += 1; | 173 | source_root_id.0 += 1; |
178 | source_root_prefix = path; | 174 | source_root_prefix = path; |
179 | continue; | 175 | continue; |
@@ -190,7 +186,6 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit | |||
190 | meta.cfg, | 186 | meta.cfg, |
191 | meta.env, | 187 | meta.env, |
192 | Default::default(), | 188 | Default::default(), |
193 | Default::default(), | ||
194 | ); | 189 | ); |
195 | let prev = crates.insert(krate.clone(), crate_id); | 190 | let prev = crates.insert(krate.clone(), crate_id); |
196 | assert!(prev.is_none()); | 191 | assert!(prev.is_none()); |
@@ -212,9 +207,9 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit | |||
212 | }; | 207 | }; |
213 | 208 | ||
214 | db.set_file_text(file_id, Arc::new(text)); | 209 | db.set_file_text(file_id, Arc::new(text)); |
215 | db.set_file_relative_path(file_id, meta.path.clone().into()); | ||
216 | db.set_file_source_root(file_id, source_root_id); | 210 | db.set_file_source_root(file_id, source_root_id); |
217 | source_root.insert_file(meta.path.into(), file_id); | 211 | let path = VfsPath::new_virtual_path(meta.path); |
212 | file_set.insert(file_id, path.into()); | ||
218 | 213 | ||
219 | file_id.0 += 1; | 214 | file_id.0 += 1; |
220 | } | 215 | } |
@@ -228,7 +223,6 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit | |||
228 | CfgOptions::default(), | 223 | CfgOptions::default(), |
229 | Env::default(), | 224 | Env::default(), |
230 | Default::default(), | 225 | Default::default(), |
231 | Default::default(), | ||
232 | ); | 226 | ); |
233 | } else { | 227 | } else { |
234 | for (from, to) in crate_deps { | 228 | for (from, to) in crate_deps { |
@@ -238,7 +232,7 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit | |||
238 | } | 232 | } |
239 | } | 233 | } |
240 | 234 | ||
241 | db.set_source_root(source_root_id, Arc::new(source_root)); | 235 | db.set_source_root(source_root_id, Arc::new(SourceRoot::new_local(file_set))); |
242 | db.set_crate_graph(Arc::new(crate_graph)); | 236 | db.set_crate_graph(Arc::new(crate_graph)); |
243 | 237 | ||
244 | file_position | 238 | file_position |
diff --git a/crates/ra_db/src/input.rs b/crates/ra_db/src/input.rs index e6af99035..7f3660118 100644 --- a/crates/ra_db/src/input.rs +++ b/crates/ra_db/src/input.rs | |||
@@ -6,27 +6,15 @@ | |||
6 | //! actual IO. See `vfs` and `project_model` in the `rust-analyzer` crate for how | 6 | //! actual IO. See `vfs` and `project_model` in the `rust-analyzer` crate for how |
7 | //! actual IO is done and lowered to input. | 7 | //! actual IO is done and lowered to input. |
8 | 8 | ||
9 | use std::{ | 9 | use std::{fmt, ops, str::FromStr, sync::Arc}; |
10 | fmt, ops, | ||
11 | path::{Path, PathBuf}, | ||
12 | str::FromStr, | ||
13 | sync::Arc, | ||
14 | }; | ||
15 | 10 | ||
16 | use ra_cfg::CfgOptions; | 11 | use ra_cfg::CfgOptions; |
17 | use ra_syntax::SmolStr; | 12 | use ra_syntax::SmolStr; |
18 | use ra_tt::TokenExpander; | 13 | use ra_tt::TokenExpander; |
19 | use rustc_hash::{FxHashMap, FxHashSet}; | 14 | use rustc_hash::{FxHashMap, FxHashSet}; |
15 | use vfs::file_set::FileSet; | ||
20 | 16 | ||
21 | use crate::{RelativePath, RelativePathBuf}; | 17 | pub use vfs::FileId; |
22 | |||
23 | /// `FileId` is an integer which uniquely identifies a file. File paths are | ||
24 | /// messy and system-dependent, so most of the code should work directly with | ||
25 | /// `FileId`, without inspecting the path. The mapping between `FileId` and path | ||
26 | /// and `SourceRoot` is constant. A file rename is represented as a pair of | ||
27 | /// deletion/creation. | ||
28 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
29 | pub struct FileId(pub u32); | ||
30 | 18 | ||
31 | /// Files are grouped into source roots. A source root is a directory on the | 19 | /// Files are grouped into source roots. A source root is a directory on the |
32 | /// file systems which is watched for changes. Typically it corresponds to a | 20 | /// file systems which is watched for changes. Typically it corresponds to a |
@@ -45,27 +33,18 @@ pub struct SourceRoot { | |||
45 | /// Libraries are considered mostly immutable, this assumption is used to | 33 | /// Libraries are considered mostly immutable, this assumption is used to |
46 | /// optimize salsa's query structure | 34 | /// optimize salsa's query structure |
47 | pub is_library: bool, | 35 | pub is_library: bool, |
48 | files: FxHashMap<RelativePathBuf, FileId>, | 36 | pub(crate) file_set: FileSet, |
49 | } | 37 | } |
50 | 38 | ||
51 | impl SourceRoot { | 39 | impl SourceRoot { |
52 | pub fn new_local() -> SourceRoot { | 40 | pub fn new_local(file_set: FileSet) -> SourceRoot { |
53 | SourceRoot { is_library: false, files: Default::default() } | 41 | SourceRoot { is_library: false, file_set } |
54 | } | ||
55 | pub fn new_library() -> SourceRoot { | ||
56 | SourceRoot { is_library: true, files: Default::default() } | ||
57 | } | ||
58 | pub fn insert_file(&mut self, path: RelativePathBuf, file_id: FileId) { | ||
59 | self.files.insert(path, file_id); | ||
60 | } | 42 | } |
61 | pub fn remove_file(&mut self, path: &RelativePath) { | 43 | pub fn new_library(file_set: FileSet) -> SourceRoot { |
62 | self.files.remove(path); | 44 | SourceRoot { is_library: true, file_set } |
63 | } | 45 | } |
64 | pub fn walk(&self) -> impl Iterator<Item = FileId> + '_ { | 46 | pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ { |
65 | self.files.values().copied() | 47 | self.file_set.iter() |
66 | } | ||
67 | pub fn file_by_relative_path(&self, path: &RelativePath) -> Option<FileId> { | ||
68 | self.files.get(path).copied() | ||
69 | } | 48 | } |
70 | } | 49 | } |
71 | 50 | ||
@@ -141,7 +120,6 @@ pub struct CrateData { | |||
141 | pub display_name: Option<CrateName>, | 120 | pub display_name: Option<CrateName>, |
142 | pub cfg_options: CfgOptions, | 121 | pub cfg_options: CfgOptions, |
143 | pub env: Env, | 122 | pub env: Env, |
144 | pub extern_source: ExternSource, | ||
145 | pub dependencies: Vec<Dependency>, | 123 | pub dependencies: Vec<Dependency>, |
146 | pub proc_macro: Vec<ProcMacro>, | 124 | pub proc_macro: Vec<ProcMacro>, |
147 | } | 125 | } |
@@ -152,22 +130,11 @@ pub enum Edition { | |||
152 | Edition2015, | 130 | Edition2015, |
153 | } | 131 | } |
154 | 132 | ||
155 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
156 | pub struct ExternSourceId(pub u32); | ||
157 | |||
158 | #[derive(Default, Debug, Clone, PartialEq, Eq)] | 133 | #[derive(Default, Debug, Clone, PartialEq, Eq)] |
159 | pub struct Env { | 134 | pub struct Env { |
160 | entries: FxHashMap<String, String>, | 135 | entries: FxHashMap<String, String>, |
161 | } | 136 | } |
162 | 137 | ||
163 | // FIXME: Redesign vfs for solve the following limitation ? | ||
164 | // Note: Some env variables (e.g. OUT_DIR) are located outside of the | ||
165 | // crate. We store a map to allow remap it to ExternSourceId | ||
166 | #[derive(Default, Debug, Clone, PartialEq, Eq)] | ||
167 | pub struct ExternSource { | ||
168 | extern_paths: FxHashMap<PathBuf, ExternSourceId>, | ||
169 | } | ||
170 | |||
171 | #[derive(Debug, Clone, PartialEq, Eq)] | 138 | #[derive(Debug, Clone, PartialEq, Eq)] |
172 | pub struct Dependency { | 139 | pub struct Dependency { |
173 | pub crate_id: CrateId, | 140 | pub crate_id: CrateId, |
@@ -182,7 +149,6 @@ impl CrateGraph { | |||
182 | display_name: Option<CrateName>, | 149 | display_name: Option<CrateName>, |
183 | cfg_options: CfgOptions, | 150 | cfg_options: CfgOptions, |
184 | env: Env, | 151 | env: Env, |
185 | extern_source: ExternSource, | ||
186 | proc_macro: Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)>, | 152 | proc_macro: Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)>, |
187 | ) -> CrateId { | 153 | ) -> CrateId { |
188 | let proc_macro = | 154 | let proc_macro = |
@@ -194,7 +160,6 @@ impl CrateGraph { | |||
194 | display_name, | 160 | display_name, |
195 | cfg_options, | 161 | cfg_options, |
196 | env, | 162 | env, |
197 | extern_source, | ||
198 | proc_macro, | 163 | proc_macro, |
199 | dependencies: Vec::new(), | 164 | dependencies: Vec::new(), |
200 | }; | 165 | }; |
@@ -334,20 +299,6 @@ impl Env { | |||
334 | } | 299 | } |
335 | } | 300 | } |
336 | 301 | ||
337 | impl ExternSource { | ||
338 | pub fn extern_path(&self, path: &Path) -> Option<(ExternSourceId, RelativePathBuf)> { | ||
339 | self.extern_paths.iter().find_map(|(root_path, id)| { | ||
340 | let rel_path = path.strip_prefix(root_path).ok()?; | ||
341 | let rel_path = RelativePathBuf::from_path(rel_path).ok()?; | ||
342 | Some((*id, rel_path)) | ||
343 | }) | ||
344 | } | ||
345 | |||
346 | pub fn set_extern_path(&mut self, root_path: &Path, root: ExternSourceId) { | ||
347 | self.extern_paths.insert(root_path.to_path_buf(), root); | ||
348 | } | ||
349 | } | ||
350 | |||
351 | #[derive(Debug)] | 302 | #[derive(Debug)] |
352 | pub struct ParseEditionError { | 303 | pub struct ParseEditionError { |
353 | invalid_input: String, | 304 | invalid_input: String, |
@@ -378,7 +329,6 @@ mod tests { | |||
378 | CfgOptions::default(), | 329 | CfgOptions::default(), |
379 | Env::default(), | 330 | Env::default(), |
380 | Default::default(), | 331 | Default::default(), |
381 | Default::default(), | ||
382 | ); | 332 | ); |
383 | let crate2 = graph.add_crate_root( | 333 | let crate2 = graph.add_crate_root( |
384 | FileId(2u32), | 334 | FileId(2u32), |
@@ -387,7 +337,6 @@ mod tests { | |||
387 | CfgOptions::default(), | 337 | CfgOptions::default(), |
388 | Env::default(), | 338 | Env::default(), |
389 | Default::default(), | 339 | Default::default(), |
390 | Default::default(), | ||
391 | ); | 340 | ); |
392 | let crate3 = graph.add_crate_root( | 341 | let crate3 = graph.add_crate_root( |
393 | FileId(3u32), | 342 | FileId(3u32), |
@@ -396,7 +345,6 @@ mod tests { | |||
396 | CfgOptions::default(), | 345 | CfgOptions::default(), |
397 | Env::default(), | 346 | Env::default(), |
398 | Default::default(), | 347 | Default::default(), |
399 | Default::default(), | ||
400 | ); | 348 | ); |
401 | assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok()); | 349 | assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok()); |
402 | assert!(graph.add_dep(crate2, CrateName::new("crate3").unwrap(), crate3).is_ok()); | 350 | assert!(graph.add_dep(crate2, CrateName::new("crate3").unwrap(), crate3).is_ok()); |
@@ -413,7 +361,6 @@ mod tests { | |||
413 | CfgOptions::default(), | 361 | CfgOptions::default(), |
414 | Env::default(), | 362 | Env::default(), |
415 | Default::default(), | 363 | Default::default(), |
416 | Default::default(), | ||
417 | ); | 364 | ); |
418 | let crate2 = graph.add_crate_root( | 365 | let crate2 = graph.add_crate_root( |
419 | FileId(2u32), | 366 | FileId(2u32), |
@@ -422,7 +369,6 @@ mod tests { | |||
422 | CfgOptions::default(), | 369 | CfgOptions::default(), |
423 | Env::default(), | 370 | Env::default(), |
424 | Default::default(), | 371 | Default::default(), |
425 | Default::default(), | ||
426 | ); | 372 | ); |
427 | assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok()); | 373 | assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok()); |
428 | assert!(graph.add_dep(crate2, CrateName::new("crate2").unwrap(), crate2).is_err()); | 374 | assert!(graph.add_dep(crate2, CrateName::new("crate2").unwrap(), crate2).is_err()); |
@@ -438,7 +384,6 @@ mod tests { | |||
438 | CfgOptions::default(), | 384 | CfgOptions::default(), |
439 | Env::default(), | 385 | Env::default(), |
440 | Default::default(), | 386 | Default::default(), |
441 | Default::default(), | ||
442 | ); | 387 | ); |
443 | let crate2 = graph.add_crate_root( | 388 | let crate2 = graph.add_crate_root( |
444 | FileId(2u32), | 389 | FileId(2u32), |
@@ -447,7 +392,6 @@ mod tests { | |||
447 | CfgOptions::default(), | 392 | CfgOptions::default(), |
448 | Env::default(), | 393 | Env::default(), |
449 | Default::default(), | 394 | Default::default(), |
450 | Default::default(), | ||
451 | ); | 395 | ); |
452 | let crate3 = graph.add_crate_root( | 396 | let crate3 = graph.add_crate_root( |
453 | FileId(3u32), | 397 | FileId(3u32), |
@@ -456,7 +400,6 @@ mod tests { | |||
456 | CfgOptions::default(), | 400 | CfgOptions::default(), |
457 | Env::default(), | 401 | Env::default(), |
458 | Default::default(), | 402 | Default::default(), |
459 | Default::default(), | ||
460 | ); | 403 | ); |
461 | assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok()); | 404 | assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok()); |
462 | assert!(graph.add_dep(crate2, CrateName::new("crate3").unwrap(), crate3).is_ok()); | 405 | assert!(graph.add_dep(crate2, CrateName::new("crate3").unwrap(), crate3).is_ok()); |
@@ -472,7 +415,6 @@ mod tests { | |||
472 | CfgOptions::default(), | 415 | CfgOptions::default(), |
473 | Env::default(), | 416 | Env::default(), |
474 | Default::default(), | 417 | Default::default(), |
475 | Default::default(), | ||
476 | ); | 418 | ); |
477 | let crate2 = graph.add_crate_root( | 419 | let crate2 = graph.add_crate_root( |
478 | FileId(2u32), | 420 | FileId(2u32), |
@@ -481,7 +423,6 @@ mod tests { | |||
481 | CfgOptions::default(), | 423 | CfgOptions::default(), |
482 | Env::default(), | 424 | Env::default(), |
483 | Default::default(), | 425 | Default::default(), |
484 | Default::default(), | ||
485 | ); | 426 | ); |
486 | assert!(graph | 427 | assert!(graph |
487 | .add_dep(crate1, CrateName::normalize_dashes("crate-name-with-dashes"), crate2) | 428 | .add_dep(crate1, CrateName::normalize_dashes("crate-name-with-dashes"), crate2) |
diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs index 80ddb6058..875290259 100644 --- a/crates/ra_db/src/lib.rs +++ b/crates/ra_db/src/lib.rs | |||
@@ -12,12 +12,13 @@ use rustc_hash::FxHashSet; | |||
12 | pub use crate::{ | 12 | pub use crate::{ |
13 | cancellation::Canceled, | 13 | cancellation::Canceled, |
14 | input::{ | 14 | input::{ |
15 | CrateData, CrateGraph, CrateId, CrateName, Dependency, Edition, Env, ExternSource, | 15 | CrateData, CrateGraph, CrateId, CrateName, Dependency, Edition, Env, FileId, ProcMacroId, |
16 | ExternSourceId, FileId, ProcMacroId, SourceRoot, SourceRootId, | 16 | SourceRoot, SourceRootId, |
17 | }, | 17 | }, |
18 | }; | 18 | }; |
19 | pub use relative_path::{RelativePath, RelativePathBuf}; | 19 | pub use relative_path::{RelativePath, RelativePathBuf}; |
20 | pub use salsa; | 20 | pub use salsa; |
21 | pub use vfs::{file_set::FileSet, AbsPathBuf, VfsPath}; | ||
21 | 22 | ||
22 | #[macro_export] | 23 | #[macro_export] |
23 | macro_rules! impl_intern_key { | 24 | macro_rules! impl_intern_key { |
@@ -125,8 +126,6 @@ pub trait SourceDatabaseExt: SourceDatabase { | |||
125 | #[salsa::input] | 126 | #[salsa::input] |
126 | fn file_text(&self, file_id: FileId) -> Arc<String>; | 127 | fn file_text(&self, file_id: FileId) -> Arc<String>; |
127 | /// Path to a file, relative to the root of its source root. | 128 | /// Path to a file, relative to the root of its source root. |
128 | #[salsa::input] | ||
129 | fn file_relative_path(&self, file_id: FileId) -> RelativePathBuf; | ||
130 | /// Source root of the file. | 129 | /// Source root of the file. |
131 | #[salsa::input] | 130 | #[salsa::input] |
132 | fn file_source_root(&self, file_id: FileId) -> SourceRootId; | 131 | fn file_source_root(&self, file_id: FileId) -> SourceRootId; |
@@ -161,24 +160,9 @@ impl<T: SourceDatabaseExt> FileLoader for FileLoaderDelegate<&'_ T> { | |||
161 | } | 160 | } |
162 | fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> { | 161 | fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> { |
163 | // FIXME: this *somehow* should be platform agnostic... | 162 | // FIXME: this *somehow* should be platform agnostic... |
164 | if std::path::Path::new(path).is_absolute() { | 163 | let source_root = self.0.file_source_root(anchor); |
165 | let krate = *self.relevant_crates(anchor).iter().next()?; | 164 | let source_root = self.0.source_root(source_root); |
166 | let (extern_source_id, relative_file) = | 165 | source_root.file_set.resolve_path(anchor, path) |
167 | self.0.crate_graph()[krate].extern_source.extern_path(path.as_ref())?; | ||
168 | |||
169 | let source_root = self.0.source_root(SourceRootId(extern_source_id.0)); | ||
170 | source_root.file_by_relative_path(&relative_file) | ||
171 | } else { | ||
172 | let rel_path = { | ||
173 | let mut rel_path = self.0.file_relative_path(anchor); | ||
174 | assert!(rel_path.pop()); | ||
175 | rel_path.push(path); | ||
176 | rel_path.normalize() | ||
177 | }; | ||
178 | let source_root = self.0.file_source_root(anchor); | ||
179 | let source_root = self.0.source_root(source_root); | ||
180 | source_root.file_by_relative_path(&rel_path) | ||
181 | } | ||
182 | } | 166 | } |
183 | 167 | ||
184 | fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> { | 168 | fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> { |
diff --git a/crates/ra_hir/src/has_source.rs b/crates/ra_hir/src/has_source.rs index 63b8fd369..76c32fc17 100644 --- a/crates/ra_hir/src/has_source.rs +++ b/crates/ra_hir/src/has_source.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | use either::Either; | 3 | use either::Either; |
4 | use hir_def::{ | 4 | use hir_def::{ |
5 | nameres::ModuleSource, | 5 | nameres::{ModuleOrigin, ModuleSource}, |
6 | src::{HasChildSource, HasSource as _}, | 6 | src::{HasChildSource, HasSource as _}, |
7 | Lookup, VariantId, | 7 | Lookup, VariantId, |
8 | }; | 8 | }; |
@@ -29,6 +29,14 @@ impl Module { | |||
29 | def_map[self.id.local_id].definition_source(db.upcast()) | 29 | def_map[self.id.local_id].definition_source(db.upcast()) |
30 | } | 30 | } |
31 | 31 | ||
32 | pub fn is_mod_rs(self, db: &dyn HirDatabase) -> bool { | ||
33 | let def_map = db.crate_def_map(self.id.krate); | ||
34 | match def_map[self.id.local_id].origin { | ||
35 | ModuleOrigin::File { is_mod_rs, .. } => is_mod_rs, | ||
36 | _ => false, | ||
37 | } | ||
38 | } | ||
39 | |||
32 | /// Returns a node which declares this module, either a `mod foo;` or a `mod foo {}`. | 40 | /// Returns a node which declares this module, either a `mod foo;` or a `mod foo {}`. |
33 | /// `None` for the crate root. | 41 | /// `None` for the crate root. |
34 | pub fn declaration_source(self, db: &dyn HirDatabase) -> Option<InFile<ast::Module>> { | 42 | pub fn declaration_source(self, db: &dyn HirDatabase) -> Option<InFile<ast::Module>> { |
diff --git a/crates/ra_hir_def/src/nameres.rs b/crates/ra_hir_def/src/nameres.rs index b8560fdc9..060273db4 100644 --- a/crates/ra_hir_def/src/nameres.rs +++ b/crates/ra_hir_def/src/nameres.rs | |||
@@ -104,6 +104,7 @@ pub enum ModuleOrigin { | |||
104 | }, | 104 | }, |
105 | /// Note that non-inline modules, by definition, live inside non-macro file. | 105 | /// Note that non-inline modules, by definition, live inside non-macro file. |
106 | File { | 106 | File { |
107 | is_mod_rs: bool, | ||
107 | declaration: AstId<ast::Module>, | 108 | declaration: AstId<ast::Module>, |
108 | definition: FileId, | 109 | definition: FileId, |
109 | }, | 110 | }, |
diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs index b8f6aac8f..cbce04315 100644 --- a/crates/ra_hir_def/src/nameres/collector.rs +++ b/crates/ra_hir_def/src/nameres/collector.rs | |||
@@ -777,11 +777,11 @@ impl ModCollector<'_, '_> { | |||
777 | name, | 777 | name, |
778 | path_attr, | 778 | path_attr, |
779 | ) { | 779 | ) { |
780 | Ok((file_id, mod_dir)) => { | 780 | Ok((file_id, is_mod_rs, mod_dir)) => { |
781 | let module_id = self.push_child_module( | 781 | let module_id = self.push_child_module( |
782 | name.clone(), | 782 | name.clone(), |
783 | ast_id, | 783 | ast_id, |
784 | Some(file_id), | 784 | Some((file_id, is_mod_rs)), |
785 | &visibility, | 785 | &visibility, |
786 | ); | 786 | ); |
787 | let raw_items = self.def_collector.db.raw_items(file_id.into()); | 787 | let raw_items = self.def_collector.db.raw_items(file_id.into()); |
@@ -814,7 +814,7 @@ impl ModCollector<'_, '_> { | |||
814 | &mut self, | 814 | &mut self, |
815 | name: Name, | 815 | name: Name, |
816 | declaration: AstId<ast::Module>, | 816 | declaration: AstId<ast::Module>, |
817 | definition: Option<FileId>, | 817 | definition: Option<(FileId, bool)>, |
818 | visibility: &crate::visibility::RawVisibility, | 818 | visibility: &crate::visibility::RawVisibility, |
819 | ) -> LocalModuleId { | 819 | ) -> LocalModuleId { |
820 | let vis = self | 820 | let vis = self |
@@ -827,7 +827,9 @@ impl ModCollector<'_, '_> { | |||
827 | modules[res].parent = Some(self.module_id); | 827 | modules[res].parent = Some(self.module_id); |
828 | modules[res].origin = match definition { | 828 | modules[res].origin = match definition { |
829 | None => ModuleOrigin::Inline { definition: declaration }, | 829 | None => ModuleOrigin::Inline { definition: declaration }, |
830 | Some(definition) => ModuleOrigin::File { declaration, definition }, | 830 | Some((definition, is_mod_rs)) => { |
831 | ModuleOrigin::File { declaration, definition, is_mod_rs } | ||
832 | } | ||
831 | }; | 833 | }; |
832 | for (name, mac) in modules[self.module_id].scope.collect_legacy_macros() { | 834 | for (name, mac) in modules[self.module_id].scope.collect_legacy_macros() { |
833 | modules[res].scope.define_legacy_macro(name, mac) | 835 | modules[res].scope.define_legacy_macro(name, mac) |
diff --git a/crates/ra_hir_def/src/nameres/mod_resolution.rs b/crates/ra_hir_def/src/nameres/mod_resolution.rs index 19fe0615a..39e9a6d97 100644 --- a/crates/ra_hir_def/src/nameres/mod_resolution.rs +++ b/crates/ra_hir_def/src/nameres/mod_resolution.rs | |||
@@ -44,7 +44,7 @@ impl ModDir { | |||
44 | file_id: HirFileId, | 44 | file_id: HirFileId, |
45 | name: &Name, | 45 | name: &Name, |
46 | attr_path: Option<&SmolStr>, | 46 | attr_path: Option<&SmolStr>, |
47 | ) -> Result<(FileId, ModDir), String> { | 47 | ) -> Result<(FileId, bool, ModDir), String> { |
48 | let file_id = file_id.original_file(db.upcast()); | 48 | let file_id = file_id.original_file(db.upcast()); |
49 | 49 | ||
50 | let mut candidate_files = Vec::new(); | 50 | let mut candidate_files = Vec::new(); |
@@ -64,11 +64,12 @@ impl ModDir { | |||
64 | if let Some(file_id) = db.resolve_path(file_id, candidate.as_str()) { | 64 | if let Some(file_id) = db.resolve_path(file_id, candidate.as_str()) { |
65 | let mut root_non_dir_owner = false; | 65 | let mut root_non_dir_owner = false; |
66 | let mut mod_path = RelativePathBuf::new(); | 66 | let mut mod_path = RelativePathBuf::new(); |
67 | if !(candidate.ends_with("mod.rs") || attr_path.is_some()) { | 67 | let is_mod_rs = candidate.ends_with("mod.rs"); |
68 | if !(is_mod_rs || attr_path.is_some()) { | ||
68 | root_non_dir_owner = true; | 69 | root_non_dir_owner = true; |
69 | mod_path.push(&name.to_string()); | 70 | mod_path.push(&name.to_string()); |
70 | } | 71 | } |
71 | return Ok((file_id, ModDir { path: mod_path, root_non_dir_owner })); | 72 | return Ok((file_id, is_mod_rs, ModDir { path: mod_path, root_non_dir_owner })); |
72 | } | 73 | } |
73 | } | 74 | } |
74 | Err(candidate_files.remove(0)) | 75 | Err(candidate_files.remove(0)) |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 47823718f..ecac5134e 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -47,7 +47,7 @@ use std::sync::Arc; | |||
47 | use ra_cfg::CfgOptions; | 47 | use ra_cfg::CfgOptions; |
48 | use ra_db::{ | 48 | use ra_db::{ |
49 | salsa::{self, ParallelDatabase}, | 49 | salsa::{self, ParallelDatabase}, |
50 | CheckCanceled, Env, FileLoader, SourceDatabase, | 50 | CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath, |
51 | }; | 51 | }; |
52 | use ra_ide_db::{ | 52 | use ra_ide_db::{ |
53 | symbol_index::{self, FileSymbol}, | 53 | symbol_index::{self, FileSymbol}, |
@@ -78,7 +78,8 @@ pub use crate::{ | |||
78 | pub use hir::Documentation; | 78 | pub use hir::Documentation; |
79 | pub use ra_assists::{Assist, AssistConfig, AssistId, ResolvedAssist}; | 79 | pub use ra_assists::{Assist, AssistConfig, AssistId, ResolvedAssist}; |
80 | pub use ra_db::{ | 80 | pub use ra_db::{ |
81 | Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, | 81 | Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRoot, |
82 | SourceRootId, | ||
82 | }; | 83 | }; |
83 | pub use ra_ide_db::{ | 84 | pub use ra_ide_db::{ |
84 | change::AnalysisChange, | 85 | change::AnalysisChange, |
@@ -212,11 +213,14 @@ impl Analysis { | |||
212 | // `AnalysisHost` for creating a fully-featured analysis. | 213 | // `AnalysisHost` for creating a fully-featured analysis. |
213 | pub fn from_single_file(text: String) -> (Analysis, FileId) { | 214 | pub fn from_single_file(text: String) -> (Analysis, FileId) { |
214 | let mut host = AnalysisHost::default(); | 215 | let mut host = AnalysisHost::default(); |
215 | let source_root = SourceRootId(0); | 216 | let file_id = FileId(0); |
217 | let mut file_set = FileSet::default(); | ||
218 | file_set.insert(file_id, VfsPath::new_virtual_path("/main.rs".to_string())); | ||
219 | let source_root = SourceRoot::new_local(file_set); | ||
220 | |||
216 | let mut change = AnalysisChange::new(); | 221 | let mut change = AnalysisChange::new(); |
217 | change.add_root(source_root, true); | 222 | change.set_roots(vec![source_root]); |
218 | let mut crate_graph = CrateGraph::default(); | 223 | let mut crate_graph = CrateGraph::default(); |
219 | let file_id = FileId(0); | ||
220 | // FIXME: cfg options | 224 | // FIXME: cfg options |
221 | // Default to enable test for single file. | 225 | // Default to enable test for single file. |
222 | let mut cfg_options = CfgOptions::default(); | 226 | let mut cfg_options = CfgOptions::default(); |
@@ -228,9 +232,8 @@ impl Analysis { | |||
228 | cfg_options, | 232 | cfg_options, |
229 | Env::default(), | 233 | Env::default(), |
230 | Default::default(), | 234 | Default::default(), |
231 | Default::default(), | ||
232 | ); | 235 | ); |
233 | change.add_file(source_root, file_id, "main.rs".into(), Arc::new(text)); | 236 | change.change_file(file_id, Some(Arc::new(text))); |
234 | change.set_crate_graph(crate_graph); | 237 | change.set_crate_graph(crate_graph); |
235 | host.apply_change(change); | 238 | host.apply_change(change); |
236 | (host.analysis(), file_id) | 239 | (host.analysis(), file_id) |
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs index 76910d09b..58fafecab 100644 --- a/crates/ra_ide/src/mock_analysis.rs +++ b/crates/ra_ide/src/mock_analysis.rs | |||
@@ -1,15 +1,12 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | use std::{str::FromStr, sync::Arc}; | |
3 | use std::str::FromStr; | ||
4 | use std::sync::Arc; | ||
5 | 3 | ||
6 | use ra_cfg::CfgOptions; | 4 | use ra_cfg::CfgOptions; |
7 | use ra_db::{CrateName, Env}; | 5 | use ra_db::{CrateName, Env, FileSet, SourceRoot, VfsPath}; |
8 | use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER}; | 6 | use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER}; |
9 | 7 | ||
10 | use crate::{ | 8 | use crate::{ |
11 | Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition, FileId, FilePosition, FileRange, | 9 | Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition, FileId, FilePosition, FileRange, |
12 | SourceRootId, | ||
13 | }; | 10 | }; |
14 | 11 | ||
15 | #[derive(Debug)] | 12 | #[derive(Debug)] |
@@ -159,9 +156,8 @@ impl MockAnalysis { | |||
159 | } | 156 | } |
160 | pub fn analysis_host(self) -> AnalysisHost { | 157 | pub fn analysis_host(self) -> AnalysisHost { |
161 | let mut host = AnalysisHost::default(); | 158 | let mut host = AnalysisHost::default(); |
162 | let source_root = SourceRootId(0); | ||
163 | let mut change = AnalysisChange::new(); | 159 | let mut change = AnalysisChange::new(); |
164 | change.add_root(source_root, true); | 160 | let mut file_set = FileSet::default(); |
165 | let mut crate_graph = CrateGraph::default(); | 161 | let mut crate_graph = CrateGraph::default(); |
166 | let mut root_crate = None; | 162 | let mut root_crate = None; |
167 | for (i, data) in self.files.into_iter().enumerate() { | 163 | for (i, data) in self.files.into_iter().enumerate() { |
@@ -179,7 +175,6 @@ impl MockAnalysis { | |||
179 | cfg_options, | 175 | cfg_options, |
180 | env, | 176 | env, |
181 | Default::default(), | 177 | Default::default(), |
182 | Default::default(), | ||
183 | )); | 178 | )); |
184 | } else if path.ends_with("/lib.rs") { | 179 | } else if path.ends_with("/lib.rs") { |
185 | let base = &path[..path.len() - "/lib.rs".len()]; | 180 | let base = &path[..path.len() - "/lib.rs".len()]; |
@@ -191,7 +186,6 @@ impl MockAnalysis { | |||
191 | cfg_options, | 186 | cfg_options, |
192 | env, | 187 | env, |
193 | Default::default(), | 188 | Default::default(), |
194 | Default::default(), | ||
195 | ); | 189 | ); |
196 | if let Some(root_crate) = root_crate { | 190 | if let Some(root_crate) = root_crate { |
197 | crate_graph | 191 | crate_graph |
@@ -199,9 +193,12 @@ impl MockAnalysis { | |||
199 | .unwrap(); | 193 | .unwrap(); |
200 | } | 194 | } |
201 | } | 195 | } |
202 | change.add_file(source_root, file_id, path.into(), Arc::new(data.content().to_owned())); | 196 | let path = VfsPath::new_virtual_path(path.to_string()); |
197 | file_set.insert(file_id, path); | ||
198 | change.change_file(file_id, Some(Arc::new(data.content().to_owned()))); | ||
203 | } | 199 | } |
204 | change.set_crate_graph(crate_graph); | 200 | change.set_crate_graph(crate_graph); |
201 | change.set_roots(vec![SourceRoot::new_local(file_set)]); | ||
205 | host.apply_change(change); | 202 | host.apply_change(change); |
206 | host | 203 | host |
207 | } | 204 | } |
diff --git a/crates/ra_ide/src/parent_module.rs b/crates/ra_ide/src/parent_module.rs index fa1535da5..bc7f65470 100644 --- a/crates/ra_ide/src/parent_module.rs +++ b/crates/ra_ide/src/parent_module.rs | |||
@@ -145,7 +145,6 @@ mod tests { | |||
145 | CfgOptions::default(), | 145 | CfgOptions::default(), |
146 | Env::default(), | 146 | Env::default(), |
147 | Default::default(), | 147 | Default::default(), |
148 | Default::default(), | ||
149 | ); | 148 | ); |
150 | let mut change = AnalysisChange::new(); | 149 | let mut change = AnalysisChange::new(); |
151 | change.set_crate_graph(crate_graph); | 150 | change.set_crate_graph(crate_graph); |
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index 99c2581b7..6edf565b5 100644 --- a/crates/ra_ide/src/references/rename.rs +++ b/crates/ra_ide/src/references/rename.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; | 3 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; |
4 | use ra_db::{RelativePathBuf, SourceDatabaseExt}; | 4 | use ra_db::SourceDatabaseExt; |
5 | use ra_ide_db::{ | 5 | use ra_ide_db::{ |
6 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, | 6 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, |
7 | RootDatabase, | 7 | RootDatabase, |
@@ -109,9 +109,8 @@ fn rename_mod( | |||
109 | let file_id = src.file_id.original_file(db); | 109 | let file_id = src.file_id.original_file(db); |
110 | match src.value { | 110 | match src.value { |
111 | ModuleSource::SourceFile(..) => { | 111 | ModuleSource::SourceFile(..) => { |
112 | let mod_path: RelativePathBuf = db.file_relative_path(file_id); | ||
113 | // mod is defined in path/to/dir/mod.rs | 112 | // mod is defined in path/to/dir/mod.rs |
114 | let dst = if mod_path.file_stem() == Some("mod") { | 113 | let dst = if module.is_mod_rs(db) { |
115 | format!("../{}/mod.rs", new_name) | 114 | format!("../{}/mod.rs", new_name) |
116 | } else { | 115 | } else { |
117 | format!("{}.rs", new_name) | 116 | format!("{}.rs", new_name) |
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index 03f18c617..6cb96608b 100644 --- a/crates/ra_ide/src/ssr.rs +++ b/crates/ra_ide/src/ssr.rs | |||
@@ -41,7 +41,7 @@ pub fn parse_search_replace( | |||
41 | match_finder.add_rule(rule); | 41 | match_finder.add_rule(rule); |
42 | for &root in db.local_roots().iter() { | 42 | for &root in db.local_roots().iter() { |
43 | let sr = db.source_root(root); | 43 | let sr = db.source_root(root); |
44 | for file_id in sr.walk() { | 44 | for file_id in sr.iter() { |
45 | if let Some(edit) = match_finder.edits_for_file(file_id) { | 45 | if let Some(edit) = match_finder.edits_for_file(file_id) { |
46 | edits.push(SourceFileEdit { file_id, edit }); | 46 | edits.push(SourceFileEdit { file_id, edit }); |
47 | } | 47 | } |
diff --git a/crates/ra_ide_db/src/change.rs b/crates/ra_ide_db/src/change.rs index 98993d571..a95f6c13c 100644 --- a/crates/ra_ide_db/src/change.rs +++ b/crates/ra_ide_db/src/change.rs | |||
@@ -9,26 +9,22 @@ use ra_db::{ | |||
9 | SourceRootId, | 9 | SourceRootId, |
10 | }; | 10 | }; |
11 | use ra_prof::{memory_usage, profile, Bytes}; | 11 | use ra_prof::{memory_usage, profile, Bytes}; |
12 | use rustc_hash::FxHashMap; | 12 | use rustc_hash::FxHashSet; |
13 | 13 | ||
14 | use crate::{symbol_index::SymbolsDatabase, RootDatabase}; | 14 | use crate::{symbol_index::SymbolsDatabase, RootDatabase}; |
15 | 15 | ||
16 | #[derive(Default)] | 16 | #[derive(Default)] |
17 | pub struct AnalysisChange { | 17 | pub struct AnalysisChange { |
18 | new_roots: Vec<(SourceRootId, bool)>, | 18 | roots: Option<Vec<SourceRoot>>, |
19 | roots_changed: FxHashMap<SourceRootId, RootChange>, | 19 | files_changed: Vec<(FileId, Option<Arc<String>>)>, |
20 | files_changed: Vec<(FileId, Arc<String>)>, | ||
21 | crate_graph: Option<CrateGraph>, | 20 | crate_graph: Option<CrateGraph>, |
22 | } | 21 | } |
23 | 22 | ||
24 | impl fmt::Debug for AnalysisChange { | 23 | impl fmt::Debug for AnalysisChange { |
25 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | 24 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
26 | let mut d = fmt.debug_struct("AnalysisChange"); | 25 | let mut d = fmt.debug_struct("AnalysisChange"); |
27 | if !self.new_roots.is_empty() { | 26 | if let Some(roots) = &self.roots { |
28 | d.field("new_roots", &self.new_roots); | 27 | d.field("roots", roots); |
29 | } | ||
30 | if !self.roots_changed.is_empty() { | ||
31 | d.field("roots_changed", &self.roots_changed); | ||
32 | } | 28 | } |
33 | if !self.files_changed.is_empty() { | 29 | if !self.files_changed.is_empty() { |
34 | d.field("files_changed", &self.files_changed.len()); | 30 | d.field("files_changed", &self.files_changed.len()); |
@@ -45,30 +41,14 @@ impl AnalysisChange { | |||
45 | AnalysisChange::default() | 41 | AnalysisChange::default() |
46 | } | 42 | } |
47 | 43 | ||
48 | pub fn add_root(&mut self, root_id: SourceRootId, is_local: bool) { | 44 | pub fn set_roots(&mut self, roots: Vec<SourceRoot>) { |
49 | self.new_roots.push((root_id, is_local)); | 45 | self.roots = Some(roots); |
50 | } | ||
51 | |||
52 | pub fn add_file( | ||
53 | &mut self, | ||
54 | root_id: SourceRootId, | ||
55 | file_id: FileId, | ||
56 | path: RelativePathBuf, | ||
57 | text: Arc<String>, | ||
58 | ) { | ||
59 | let file = AddFile { file_id, path, text }; | ||
60 | self.roots_changed.entry(root_id).or_default().added.push(file); | ||
61 | } | 46 | } |
62 | 47 | ||
63 | pub fn change_file(&mut self, file_id: FileId, new_text: Arc<String>) { | 48 | pub fn change_file(&mut self, file_id: FileId, new_text: Option<Arc<String>>) { |
64 | self.files_changed.push((file_id, new_text)) | 49 | self.files_changed.push((file_id, new_text)) |
65 | } | 50 | } |
66 | 51 | ||
67 | pub fn remove_file(&mut self, root_id: SourceRootId, file_id: FileId, path: RelativePathBuf) { | ||
68 | let file = RemoveFile { file_id, path }; | ||
69 | self.roots_changed.entry(root_id).or_default().removed.push(file); | ||
70 | } | ||
71 | |||
72 | pub fn set_crate_graph(&mut self, graph: CrateGraph) { | 52 | pub fn set_crate_graph(&mut self, graph: CrateGraph) { |
73 | self.crate_graph = Some(graph); | 53 | self.crate_graph = Some(graph); |
74 | } | 54 | } |
@@ -114,31 +94,32 @@ impl RootDatabase { | |||
114 | let _p = profile("RootDatabase::apply_change"); | 94 | let _p = profile("RootDatabase::apply_change"); |
115 | self.request_cancellation(); | 95 | self.request_cancellation(); |
116 | log::info!("apply_change {:?}", change); | 96 | log::info!("apply_change {:?}", change); |
117 | if !change.new_roots.is_empty() { | 97 | if let Some(roots) = change.roots { |
118 | let mut local_roots = Vec::clone(&self.local_roots()); | 98 | let mut local_roots = FxHashSet::default(); |
119 | let mut libraries = Vec::clone(&self.library_roots()); | 99 | let mut library_roots = FxHashSet::default(); |
120 | for (root_id, is_local) in change.new_roots { | 100 | for (idx, root) in roots.into_iter().enumerate() { |
121 | let root = | 101 | let root_id = SourceRootId(idx as u32); |
122 | if is_local { SourceRoot::new_local() } else { SourceRoot::new_library() }; | ||
123 | let durability = durability(&root); | 102 | let durability = durability(&root); |
124 | self.set_source_root_with_durability(root_id, Arc::new(root), durability); | 103 | if root.is_library { |
125 | if is_local { | 104 | library_roots.insert(root_id); |
126 | local_roots.push(root_id); | ||
127 | } else { | 105 | } else { |
128 | libraries.push(root_id) | 106 | local_roots.insert(root_id); |
107 | } | ||
108 | for file_id in root.iter() { | ||
109 | self.set_file_source_root_with_durability(file_id, root_id, durability); | ||
129 | } | 110 | } |
111 | self.set_source_root_with_durability(root_id, Arc::new(root), durability); | ||
130 | } | 112 | } |
131 | self.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); | 113 | self.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); |
132 | self.set_library_roots_with_durability(Arc::new(libraries), Durability::HIGH); | 114 | self.set_library_roots_with_durability(Arc::new(library_roots), Durability::HIGH); |
133 | } | 115 | } |
134 | 116 | ||
135 | for (root_id, root_change) in change.roots_changed { | ||
136 | self.apply_root_change(root_id, root_change); | ||
137 | } | ||
138 | for (file_id, text) in change.files_changed { | 117 | for (file_id, text) in change.files_changed { |
139 | let source_root_id = self.file_source_root(file_id); | 118 | let source_root_id = self.file_source_root(file_id); |
140 | let source_root = self.source_root(source_root_id); | 119 | let source_root = self.source_root(source_root_id); |
141 | let durability = durability(&source_root); | 120 | let durability = durability(&source_root); |
121 | // XXX: can't actually remove the file, just reset the text | ||
122 | let text = text.unwrap_or_default(); | ||
142 | self.set_file_text_with_durability(file_id, text, durability) | 123 | self.set_file_text_with_durability(file_id, text, durability) |
143 | } | 124 | } |
144 | if let Some(crate_graph) = change.crate_graph { | 125 | if let Some(crate_graph) = change.crate_graph { |
@@ -146,26 +127,6 @@ impl RootDatabase { | |||
146 | } | 127 | } |
147 | } | 128 | } |
148 | 129 | ||
149 | fn apply_root_change(&mut self, root_id: SourceRootId, root_change: RootChange) { | ||
150 | let mut source_root = SourceRoot::clone(&self.source_root(root_id)); | ||
151 | let durability = durability(&source_root); | ||
152 | for add_file in root_change.added { | ||
153 | self.set_file_text_with_durability(add_file.file_id, add_file.text, durability); | ||
154 | self.set_file_relative_path_with_durability( | ||
155 | add_file.file_id, | ||
156 | add_file.path.clone(), | ||
157 | durability, | ||
158 | ); | ||
159 | self.set_file_source_root_with_durability(add_file.file_id, root_id, durability); | ||
160 | source_root.insert_file(add_file.path, add_file.file_id); | ||
161 | } | ||
162 | for remove_file in root_change.removed { | ||
163 | self.set_file_text_with_durability(remove_file.file_id, Default::default(), durability); | ||
164 | source_root.remove_file(&remove_file.path); | ||
165 | } | ||
166 | self.set_source_root_with_durability(root_id, Arc::new(source_root), durability); | ||
167 | } | ||
168 | |||
169 | pub fn maybe_collect_garbage(&mut self) { | 130 | pub fn maybe_collect_garbage(&mut self) { |
170 | if cfg!(feature = "wasm") { | 131 | if cfg!(feature = "wasm") { |
171 | return; | 132 | return; |
diff --git a/crates/ra_ide_db/src/search.rs b/crates/ra_ide_db/src/search.rs index 335a1ad03..44d5c35e6 100644 --- a/crates/ra_ide_db/src/search.rs +++ b/crates/ra_ide_db/src/search.rs | |||
@@ -157,14 +157,14 @@ impl Definition { | |||
157 | if let Some(Visibility::Public) = vis { | 157 | if let Some(Visibility::Public) = vis { |
158 | let source_root_id = db.file_source_root(file_id); | 158 | let source_root_id = db.file_source_root(file_id); |
159 | let source_root = db.source_root(source_root_id); | 159 | let source_root = db.source_root(source_root_id); |
160 | let mut res = source_root.walk().map(|id| (id, None)).collect::<FxHashMap<_, _>>(); | 160 | let mut res = source_root.iter().map(|id| (id, None)).collect::<FxHashMap<_, _>>(); |
161 | 161 | ||
162 | let krate = module.krate(); | 162 | let krate = module.krate(); |
163 | for rev_dep in krate.reverse_dependencies(db) { | 163 | for rev_dep in krate.reverse_dependencies(db) { |
164 | let root_file = rev_dep.root_file(db); | 164 | let root_file = rev_dep.root_file(db); |
165 | let source_root_id = db.file_source_root(root_file); | 165 | let source_root_id = db.file_source_root(root_file); |
166 | let source_root = db.source_root(source_root_id); | 166 | let source_root = db.source_root(source_root_id); |
167 | res.extend(source_root.walk().map(|id| (id, None))); | 167 | res.extend(source_root.iter().map(|id| (id, None))); |
168 | } | 168 | } |
169 | return SearchScope::new(res); | 169 | return SearchScope::new(res); |
170 | } | 170 | } |
diff --git a/crates/ra_ide_db/src/symbol_index.rs b/crates/ra_ide_db/src/symbol_index.rs index 25c99813f..6929055b2 100644 --- a/crates/ra_ide_db/src/symbol_index.rs +++ b/crates/ra_ide_db/src/symbol_index.rs | |||
@@ -42,7 +42,7 @@ use ra_syntax::{ | |||
42 | SyntaxNode, SyntaxNodePtr, TextRange, WalkEvent, | 42 | SyntaxNode, SyntaxNodePtr, TextRange, WalkEvent, |
43 | }; | 43 | }; |
44 | use rayon::prelude::*; | 44 | use rayon::prelude::*; |
45 | use rustc_hash::FxHashMap; | 45 | use rustc_hash::{FxHashMap, FxHashSet}; |
46 | 46 | ||
47 | use crate::RootDatabase; | 47 | use crate::RootDatabase; |
48 | 48 | ||
@@ -93,11 +93,11 @@ pub trait SymbolsDatabase: hir::db::HirDatabase + SourceDatabaseExt + ParallelDa | |||
93 | /// The set of "local" (that is, from the current workspace) roots. | 93 | /// The set of "local" (that is, from the current workspace) roots. |
94 | /// Files in local roots are assumed to change frequently. | 94 | /// Files in local roots are assumed to change frequently. |
95 | #[salsa::input] | 95 | #[salsa::input] |
96 | fn local_roots(&self) -> Arc<Vec<SourceRootId>>; | 96 | fn local_roots(&self) -> Arc<FxHashSet<SourceRootId>>; |
97 | /// The set of roots for crates.io libraries. | 97 | /// The set of roots for crates.io libraries. |
98 | /// Files in libraries are assumed to never change. | 98 | /// Files in libraries are assumed to never change. |
99 | #[salsa::input] | 99 | #[salsa::input] |
100 | fn library_roots(&self) -> Arc<Vec<SourceRootId>>; | 100 | fn library_roots(&self) -> Arc<FxHashSet<SourceRootId>>; |
101 | } | 101 | } |
102 | 102 | ||
103 | fn library_symbols( | 103 | fn library_symbols( |
@@ -111,7 +111,7 @@ fn library_symbols( | |||
111 | .map(|&root_id| { | 111 | .map(|&root_id| { |
112 | let root = db.source_root(root_id); | 112 | let root = db.source_root(root_id); |
113 | let files = root | 113 | let files = root |
114 | .walk() | 114 | .iter() |
115 | .map(|it| (it, SourceDatabaseExt::file_text(db, it))) | 115 | .map(|it| (it, SourceDatabaseExt::file_text(db, it))) |
116 | .collect::<Vec<_>>(); | 116 | .collect::<Vec<_>>(); |
117 | let symbol_index = SymbolIndex::for_files( | 117 | let symbol_index = SymbolIndex::for_files( |
@@ -175,7 +175,7 @@ pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> { | |||
175 | let mut files = Vec::new(); | 175 | let mut files = Vec::new(); |
176 | for &root in db.local_roots().iter() { | 176 | for &root in db.local_roots().iter() { |
177 | let sr = db.source_root(root); | 177 | let sr = db.source_root(root); |
178 | files.extend(sr.walk()) | 178 | files.extend(sr.iter()) |
179 | } | 179 | } |
180 | 180 | ||
181 | let snap = Snap(db.snapshot()); | 181 | let snap = Snap(db.snapshot()); |
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index 9541362f5..fe3e81689 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs | |||
@@ -13,7 +13,7 @@ use std::{ | |||
13 | 13 | ||
14 | use anyhow::{bail, Context, Result}; | 14 | use anyhow::{bail, Context, Result}; |
15 | use ra_cfg::CfgOptions; | 15 | use ra_cfg::CfgOptions; |
16 | use ra_db::{CrateGraph, CrateName, Edition, Env, ExternSource, ExternSourceId, FileId}; | 16 | use ra_db::{CrateGraph, CrateName, Edition, Env, FileId}; |
17 | use rustc_hash::{FxHashMap, FxHashSet}; | 17 | use rustc_hash::{FxHashMap, FxHashSet}; |
18 | use serde_json::from_reader; | 18 | use serde_json::from_reader; |
19 | 19 | ||
@@ -246,7 +246,6 @@ impl ProjectWorkspace { | |||
246 | pub fn to_crate_graph( | 246 | pub fn to_crate_graph( |
247 | &self, | 247 | &self, |
248 | target: Option<&str>, | 248 | target: Option<&str>, |
249 | extern_source_roots: &FxHashMap<PathBuf, ExternSourceId>, | ||
250 | proc_macro_client: &ProcMacroClient, | 249 | proc_macro_client: &ProcMacroClient, |
251 | load: &mut dyn FnMut(&Path) -> Option<FileId>, | 250 | load: &mut dyn FnMut(&Path) -> Option<FileId>, |
252 | ) -> CrateGraph { | 251 | ) -> CrateGraph { |
@@ -280,15 +279,11 @@ impl ProjectWorkspace { | |||
280 | }; | 279 | }; |
281 | 280 | ||
282 | let mut env = Env::default(); | 281 | let mut env = Env::default(); |
283 | let mut extern_source = ExternSource::default(); | ||
284 | if let Some(out_dir) = &krate.out_dir { | 282 | if let Some(out_dir) = &krate.out_dir { |
285 | // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() | 283 | // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() |
286 | if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { | 284 | if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { |
287 | env.set("OUT_DIR", out_dir); | 285 | env.set("OUT_DIR", out_dir); |
288 | } | 286 | } |
289 | if let Some(&extern_source_id) = extern_source_roots.get(out_dir) { | ||
290 | extern_source.set_extern_path(&out_dir, extern_source_id); | ||
291 | } | ||
292 | } | 287 | } |
293 | let proc_macro = krate | 288 | let proc_macro = krate |
294 | .proc_macro_dylib_path | 289 | .proc_macro_dylib_path |
@@ -304,7 +299,6 @@ impl ProjectWorkspace { | |||
304 | None, | 299 | None, |
305 | cfg_options, | 300 | cfg_options, |
306 | env, | 301 | env, |
307 | extern_source, | ||
308 | proc_macro.unwrap_or_default(), | 302 | proc_macro.unwrap_or_default(), |
309 | ), | 303 | ), |
310 | )) | 304 | )) |
@@ -341,7 +335,6 @@ impl ProjectWorkspace { | |||
341 | let file_id = load(&sysroot[krate].root)?; | 335 | let file_id = load(&sysroot[krate].root)?; |
342 | 336 | ||
343 | let env = Env::default(); | 337 | let env = Env::default(); |
344 | let extern_source = ExternSource::default(); | ||
345 | let proc_macro = vec![]; | 338 | let proc_macro = vec![]; |
346 | let crate_name = CrateName::new(&sysroot[krate].name) | 339 | let crate_name = CrateName::new(&sysroot[krate].name) |
347 | .expect("Sysroot crate names should not contain dashes"); | 340 | .expect("Sysroot crate names should not contain dashes"); |
@@ -352,7 +345,6 @@ impl ProjectWorkspace { | |||
352 | Some(crate_name), | 345 | Some(crate_name), |
353 | cfg_options.clone(), | 346 | cfg_options.clone(), |
354 | env, | 347 | env, |
355 | extern_source, | ||
356 | proc_macro, | 348 | proc_macro, |
357 | ); | 349 | ); |
358 | Some((krate, crate_id)) | 350 | Some((krate, crate_id)) |
@@ -409,15 +401,11 @@ impl ProjectWorkspace { | |||
409 | opts | 401 | opts |
410 | }; | 402 | }; |
411 | let mut env = Env::default(); | 403 | let mut env = Env::default(); |
412 | let mut extern_source = ExternSource::default(); | ||
413 | if let Some(out_dir) = &cargo[pkg].out_dir { | 404 | if let Some(out_dir) = &cargo[pkg].out_dir { |
414 | // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() | 405 | // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!() |
415 | if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { | 406 | if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) { |
416 | env.set("OUT_DIR", out_dir); | 407 | env.set("OUT_DIR", out_dir); |
417 | } | 408 | } |
418 | if let Some(&extern_source_id) = extern_source_roots.get(out_dir) { | ||
419 | extern_source.set_extern_path(&out_dir, extern_source_id); | ||
420 | } | ||
421 | } | 409 | } |
422 | let proc_macro = cargo[pkg] | 410 | let proc_macro = cargo[pkg] |
423 | .proc_macro_dylib_path | 411 | .proc_macro_dylib_path |
@@ -431,7 +419,6 @@ impl ProjectWorkspace { | |||
431 | Some(CrateName::normalize_dashes(&cargo[pkg].name)), | 419 | Some(CrateName::normalize_dashes(&cargo[pkg].name)), |
432 | cfg_options, | 420 | cfg_options, |
433 | env, | 421 | env, |
434 | extern_source, | ||
435 | proc_macro.clone(), | 422 | proc_macro.clone(), |
436 | ); | 423 | ); |
437 | if cargo[tgt].kind == TargetKind::Lib { | 424 | if cargo[tgt].kind == TargetKind::Lib { |
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index f289a02f6..68d04f3e3 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml | |||
@@ -38,7 +38,8 @@ ra_prof = { path = "../ra_prof" } | |||
38 | ra_project_model = { path = "../ra_project_model" } | 38 | ra_project_model = { path = "../ra_project_model" } |
39 | ra_syntax = { path = "../ra_syntax" } | 39 | ra_syntax = { path = "../ra_syntax" } |
40 | ra_text_edit = { path = "../ra_text_edit" } | 40 | ra_text_edit = { path = "../ra_text_edit" } |
41 | ra_vfs = "0.6.0" | 41 | vfs = { path = "../vfs" } |
42 | vfs-notify = { path = "../vfs-notify" } | ||
42 | ra_cfg = { path = "../ra_cfg"} | 43 | ra_cfg = { path = "../ra_cfg"} |
43 | 44 | ||
44 | # This should only be used in CLI | 45 | # This should only be used in CLI |
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs index 5c22dce0d..65f90c83c 100644 --- a/crates/rust-analyzer/src/cargo_target_spec.rs +++ b/crates/rust-analyzer/src/cargo_target_spec.rs | |||
@@ -1,5 +1,7 @@ | |||
1 | //! See `CargoTargetSpec` | 1 | //! See `CargoTargetSpec` |
2 | 2 | ||
3 | use std::path::PathBuf; | ||
4 | |||
3 | use ra_cfg::CfgExpr; | 5 | use ra_cfg::CfgExpr; |
4 | use ra_ide::{FileId, RunnableKind, TestId}; | 6 | use ra_ide::{FileId, RunnableKind, TestId}; |
5 | use ra_project_model::{self, TargetKind}; | 7 | use ra_project_model::{self, TargetKind}; |
@@ -12,6 +14,7 @@ use crate::{global_state::GlobalStateSnapshot, Result}; | |||
12 | /// build/test/run the target. | 14 | /// build/test/run the target. |
13 | #[derive(Clone)] | 15 | #[derive(Clone)] |
14 | pub(crate) struct CargoTargetSpec { | 16 | pub(crate) struct CargoTargetSpec { |
17 | pub(crate) workspace_root: PathBuf, | ||
15 | pub(crate) package: String, | 18 | pub(crate) package: String, |
16 | pub(crate) target: String, | 19 | pub(crate) target: String, |
17 | pub(crate) target_kind: TargetKind, | 20 | pub(crate) target_kind: TargetKind, |
@@ -101,6 +104,7 @@ impl CargoTargetSpec { | |||
101 | None => return Ok(None), | 104 | None => return Ok(None), |
102 | }; | 105 | }; |
103 | let res = CargoTargetSpec { | 106 | let res = CargoTargetSpec { |
107 | workspace_root: cargo_ws.workspace_root().to_path_buf(), | ||
104 | package: cargo_ws.package_flag(&cargo_ws[cargo_ws[target].package]), | 108 | package: cargo_ws.package_flag(&cargo_ws[cargo_ws[target].package]), |
105 | target: cargo_ws[target].name.clone(), | 109 | target: cargo_ws[target].name.clone(), |
106 | target_kind: cargo_ws[target].kind, | 110 | target_kind: cargo_ws[target].kind, |
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs index b20efe98d..4fe99ff68 100644 --- a/crates/rust-analyzer/src/cli/analysis_bench.rs +++ b/crates/rust-analyzer/src/cli/analysis_bench.rs | |||
@@ -1,6 +1,7 @@ | |||
1 | //! Benchmark operations like highlighting or goto definition. | 1 | //! Benchmark operations like highlighting or goto definition. |
2 | 2 | ||
3 | use std::{ | 3 | use std::{ |
4 | convert::TryFrom, | ||
4 | path::{Path, PathBuf}, | 5 | path::{Path, PathBuf}, |
5 | str::FromStr, | 6 | str::FromStr, |
6 | sync::Arc, | 7 | sync::Arc, |
@@ -10,7 +11,7 @@ use std::{ | |||
10 | use anyhow::{format_err, Result}; | 11 | use anyhow::{format_err, Result}; |
11 | use ra_db::{ | 12 | use ra_db::{ |
12 | salsa::{Database, Durability}, | 13 | salsa::{Database, Durability}, |
13 | FileId, SourceDatabaseExt, | 14 | AbsPathBuf, FileId, |
14 | }; | 15 | }; |
15 | use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CompletionConfig, FilePosition, LineCol}; | 16 | use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CompletionConfig, FilePosition, LineCol}; |
16 | 17 | ||
@@ -53,8 +54,7 @@ pub fn analysis_bench( | |||
53 | 54 | ||
54 | let start = Instant::now(); | 55 | let start = Instant::now(); |
55 | eprint!("loading: "); | 56 | eprint!("loading: "); |
56 | let (mut host, roots) = load_cargo(path, load_output_dirs, with_proc_macro)?; | 57 | let (mut host, vfs) = load_cargo(path, load_output_dirs, with_proc_macro)?; |
57 | let db = host.raw_database(); | ||
58 | eprintln!("{:?}\n", start.elapsed()); | 58 | eprintln!("{:?}\n", start.elapsed()); |
59 | 59 | ||
60 | let file_id = { | 60 | let file_id = { |
@@ -62,22 +62,9 @@ pub fn analysis_bench( | |||
62 | BenchWhat::Highlight { path } => path, | 62 | BenchWhat::Highlight { path } => path, |
63 | BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => &pos.path, | 63 | BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => &pos.path, |
64 | }; | 64 | }; |
65 | let path = std::env::current_dir()?.join(path).canonicalize()?; | 65 | let path = AbsPathBuf::try_from(path.clone()).unwrap(); |
66 | roots | 66 | let path = path.into(); |
67 | .iter() | 67 | vfs.file_id(&path).ok_or_else(|| format_err!("Can't find {}", path))? |
68 | .find_map(|(source_root_id, project_root)| { | ||
69 | if project_root.is_member() { | ||
70 | for file_id in db.source_root(*source_root_id).walk() { | ||
71 | let rel_path = db.file_relative_path(file_id); | ||
72 | let abs_path = rel_path.to_path(project_root.path()); | ||
73 | if abs_path == path { | ||
74 | return Some(file_id); | ||
75 | } | ||
76 | } | ||
77 | } | ||
78 | None | ||
79 | }) | ||
80 | .ok_or_else(|| format_err!("Can't find {}", path.display()))? | ||
81 | }; | 68 | }; |
82 | 69 | ||
83 | match &what { | 70 | match &what { |
@@ -149,7 +136,7 @@ fn do_work<F: Fn(&Analysis) -> T, T>(host: &mut AnalysisHost, file_id: FileId, w | |||
149 | let mut text = host.analysis().file_text(file_id).unwrap().to_string(); | 136 | let mut text = host.analysis().file_text(file_id).unwrap().to_string(); |
150 | text.push_str("\n/* Hello world */\n"); | 137 | text.push_str("\n/* Hello world */\n"); |
151 | let mut change = AnalysisChange::new(); | 138 | let mut change = AnalysisChange::new(); |
152 | change.change_file(file_id, Arc::new(text)); | 139 | change.change_file(file_id, Some(Arc::new(text))); |
153 | host.apply_change(change); | 140 | host.apply_change(change); |
154 | } | 141 | } |
155 | work(&host.analysis()); | 142 | work(&host.analysis()); |
diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 72183da15..90868760b 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs | |||
@@ -28,26 +28,14 @@ pub fn analysis_stats( | |||
28 | with_proc_macro: bool, | 28 | with_proc_macro: bool, |
29 | ) -> Result<()> { | 29 | ) -> Result<()> { |
30 | let db_load_time = Instant::now(); | 30 | let db_load_time = Instant::now(); |
31 | let (mut host, roots) = load_cargo(path, load_output_dirs, with_proc_macro)?; | 31 | let (mut host, vfs) = load_cargo(path, load_output_dirs, with_proc_macro)?; |
32 | let db = host.raw_database(); | 32 | let db = host.raw_database(); |
33 | println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed()); | 33 | println!("Database loaded {:?}", db_load_time.elapsed()); |
34 | let analysis_time = Instant::now(); | 34 | let analysis_time = Instant::now(); |
35 | let mut num_crates = 0; | 35 | let mut num_crates = 0; |
36 | let mut visited_modules = HashSet::new(); | 36 | let mut visited_modules = HashSet::new(); |
37 | let mut visit_queue = Vec::new(); | 37 | let mut visit_queue = Vec::new(); |
38 | 38 | ||
39 | let members = | ||
40 | roots | ||
41 | .into_iter() | ||
42 | .filter_map(|(source_root_id, project_root)| { | ||
43 | if with_deps || project_root.is_member() { | ||
44 | Some(source_root_id) | ||
45 | } else { | ||
46 | None | ||
47 | } | ||
48 | }) | ||
49 | .collect::<HashSet<_>>(); | ||
50 | |||
51 | let mut krates = Crate::all(db); | 39 | let mut krates = Crate::all(db); |
52 | if randomize { | 40 | if randomize { |
53 | krates.shuffle(&mut thread_rng()); | 41 | krates.shuffle(&mut thread_rng()); |
@@ -55,7 +43,10 @@ pub fn analysis_stats( | |||
55 | for krate in krates { | 43 | for krate in krates { |
56 | let module = krate.root_module(db).expect("crate without root module"); | 44 | let module = krate.root_module(db).expect("crate without root module"); |
57 | let file_id = module.definition_source(db).file_id; | 45 | let file_id = module.definition_source(db).file_id; |
58 | if members.contains(&db.file_source_root(file_id.original_file(db))) { | 46 | let file_id = file_id.original_file(db); |
47 | let source_root = db.file_source_root(file_id); | ||
48 | let source_root = db.source_root(source_root); | ||
49 | if !source_root.is_library || with_deps { | ||
59 | num_crates += 1; | 50 | num_crates += 1; |
60 | visit_queue.push(module); | 51 | visit_queue.push(module); |
61 | } | 52 | } |
@@ -128,7 +119,7 @@ pub fn analysis_stats( | |||
128 | if verbosity.is_verbose() { | 119 | if verbosity.is_verbose() { |
129 | let src = f.source(db); | 120 | let src = f.source(db); |
130 | let original_file = src.file_id.original_file(db); | 121 | let original_file = src.file_id.original_file(db); |
131 | let path = db.file_relative_path(original_file); | 122 | let path = vfs.file_path(original_file); |
132 | let syntax_range = src.value.syntax().text_range(); | 123 | let syntax_range = src.value.syntax().text_range(); |
133 | format_to!(msg, " ({:?} {:?})", path, syntax_range); | 124 | format_to!(msg, " ({:?} {:?})", path, syntax_range); |
134 | } | 125 | } |
@@ -196,7 +187,7 @@ pub fn analysis_stats( | |||
196 | let root = db.parse_or_expand(src.file_id).unwrap(); | 187 | let root = db.parse_or_expand(src.file_id).unwrap(); |
197 | let node = src.map(|e| e.to_node(&root).syntax().clone()); | 188 | let node = src.map(|e| e.to_node(&root).syntax().clone()); |
198 | let original_range = original_range(db, node.as_ref()); | 189 | let original_range = original_range(db, node.as_ref()); |
199 | let path = db.file_relative_path(original_range.file_id); | 190 | let path = vfs.file_path(original_range.file_id); |
200 | let line_index = | 191 | let line_index = |
201 | host.analysis().file_line_index(original_range.file_id).unwrap(); | 192 | host.analysis().file_line_index(original_range.file_id).unwrap(); |
202 | let text_range = original_range.range; | 193 | let text_range = original_range.range; |
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs index 60daefa3e..82b3a8a53 100644 --- a/crates/rust-analyzer/src/cli/diagnostics.rs +++ b/crates/rust-analyzer/src/cli/diagnostics.rs | |||
@@ -2,68 +2,57 @@ | |||
2 | //! code if any errors are found. | 2 | //! code if any errors are found. |
3 | 3 | ||
4 | use anyhow::anyhow; | 4 | use anyhow::anyhow; |
5 | use hir::Crate; | ||
5 | use ra_db::SourceDatabaseExt; | 6 | use ra_db::SourceDatabaseExt; |
6 | use ra_ide::Severity; | 7 | use ra_ide::Severity; |
7 | use std::{collections::HashSet, path::Path}; | 8 | use std::{collections::HashSet, path::Path}; |
8 | 9 | ||
9 | use crate::cli::{load_cargo::load_cargo, Result}; | 10 | use crate::cli::{load_cargo::load_cargo, Result}; |
10 | use hir::Semantics; | ||
11 | 11 | ||
12 | pub fn diagnostics( | 12 | pub fn diagnostics( |
13 | path: &Path, | 13 | path: &Path, |
14 | load_output_dirs: bool, | 14 | load_output_dirs: bool, |
15 | with_proc_macro: bool, | 15 | with_proc_macro: bool, |
16 | all: bool, | 16 | _all: bool, |
17 | ) -> Result<()> { | 17 | ) -> Result<()> { |
18 | let (host, roots) = load_cargo(path, load_output_dirs, with_proc_macro)?; | 18 | let (host, _vfs) = load_cargo(path, load_output_dirs, with_proc_macro)?; |
19 | let db = host.raw_database(); | 19 | let db = host.raw_database(); |
20 | let analysis = host.analysis(); | 20 | let analysis = host.analysis(); |
21 | let semantics = Semantics::new(db); | ||
22 | let members = roots | ||
23 | .into_iter() | ||
24 | .filter_map(|(source_root_id, project_root)| { | ||
25 | // filter out dependencies | ||
26 | if project_root.is_member() { | ||
27 | Some(source_root_id) | ||
28 | } else { | ||
29 | None | ||
30 | } | ||
31 | }) | ||
32 | .collect::<HashSet<_>>(); | ||
33 | 21 | ||
34 | let mut found_error = false; | 22 | let mut found_error = false; |
35 | let mut visited_files = HashSet::new(); | 23 | let mut visited_files = HashSet::new(); |
36 | for source_root_id in members { | ||
37 | for file_id in db.source_root(source_root_id).walk() { | ||
38 | // Filter out files which are not actually modules (unless `--all` flag is | ||
39 | // passed). In the rust-analyzer repository this filters out the parser test files. | ||
40 | if semantics.to_module_def(file_id).is_some() || all { | ||
41 | if !visited_files.contains(&file_id) { | ||
42 | let crate_name = if let Some(module) = semantics.to_module_def(file_id) { | ||
43 | if let Some(name) = module.krate().display_name(db) { | ||
44 | format!("{}", name) | ||
45 | } else { | ||
46 | String::from("unknown") | ||
47 | } | ||
48 | } else { | ||
49 | String::from("unknown") | ||
50 | }; | ||
51 | println!( | ||
52 | "processing crate: {}, module: {}", | ||
53 | crate_name, | ||
54 | db.file_relative_path(file_id) | ||
55 | ); | ||
56 | for diagnostic in analysis.diagnostics(file_id).unwrap() { | ||
57 | if matches!(diagnostic.severity, Severity::Error) { | ||
58 | found_error = true; | ||
59 | } | ||
60 | 24 | ||
61 | println!("{:?}", diagnostic); | 25 | let mut work = Vec::new(); |
62 | } | 26 | let krates = Crate::all(db); |
27 | for krate in krates { | ||
28 | let module = krate.root_module(db).expect("crate without root module"); | ||
29 | let file_id = module.definition_source(db).file_id; | ||
30 | let file_id = file_id.original_file(db); | ||
31 | let source_root = db.file_source_root(file_id); | ||
32 | let source_root = db.source_root(source_root); | ||
33 | if !source_root.is_library { | ||
34 | work.push(module); | ||
35 | } | ||
36 | } | ||
63 | 37 | ||
64 | visited_files.insert(file_id); | 38 | for module in work { |
39 | let file_id = module.definition_source(db).file_id.original_file(db); | ||
40 | if !visited_files.contains(&file_id) { | ||
41 | let crate_name = if let Some(name) = module.krate().display_name(db) { | ||
42 | format!("{}", name) | ||
43 | } else { | ||
44 | String::from("unknown") | ||
45 | }; | ||
46 | println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id)); | ||
47 | for diagnostic in analysis.diagnostics(file_id).unwrap() { | ||
48 | if matches!(diagnostic.severity, Severity::Error) { | ||
49 | found_error = true; | ||
65 | } | 50 | } |
51 | |||
52 | println!("{:?}", diagnostic); | ||
66 | } | 53 | } |
54 | |||
55 | visited_files.insert(file_id); | ||
67 | } | 56 | } |
68 | } | 57 | } |
69 | 58 | ||
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs index 97367d7c6..00bbbaf40 100644 --- a/crates/rust-analyzer/src/cli/load_cargo.rs +++ b/crates/rust-analyzer/src/cli/load_cargo.rs | |||
@@ -1,32 +1,21 @@ | |||
1 | //! Loads a Cargo project into a static instance of analysis, without support | 1 | //! Loads a Cargo project into a static instance of analysis, without support |
2 | //! for incorporating changes. | 2 | //! for incorporating changes. |
3 | 3 | use std::{convert::TryFrom, path::Path, sync::Arc}; | |
4 | use std::path::{Path, PathBuf}; | ||
5 | 4 | ||
6 | use anyhow::Result; | 5 | use anyhow::Result; |
7 | use crossbeam_channel::{unbounded, Receiver}; | 6 | use crossbeam_channel::{unbounded, Receiver}; |
8 | use ra_db::{ExternSourceId, FileId, SourceRootId}; | 7 | use ra_db::{AbsPathBuf, CrateGraph}; |
9 | use ra_ide::{AnalysisChange, AnalysisHost}; | 8 | use ra_ide::{AnalysisChange, AnalysisHost}; |
10 | use ra_project_model::{ | 9 | use ra_project_model::{CargoConfig, ProcMacroClient, ProjectManifest, ProjectWorkspace}; |
11 | CargoConfig, PackageRoot, ProcMacroClient, ProjectManifest, ProjectWorkspace, | 10 | use vfs::loader::Handle; |
12 | }; | ||
13 | use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; | ||
14 | use rustc_hash::{FxHashMap, FxHashSet}; | ||
15 | |||
16 | use crate::vfs_glob::RustPackageFilterBuilder; | ||
17 | 11 | ||
18 | fn vfs_file_to_id(f: ra_vfs::VfsFile) -> FileId { | 12 | use crate::global_state::{ProjectFolders, SourceRootConfig}; |
19 | FileId(f.0) | ||
20 | } | ||
21 | fn vfs_root_to_id(r: ra_vfs::VfsRoot) -> SourceRootId { | ||
22 | SourceRootId(r.0) | ||
23 | } | ||
24 | 13 | ||
25 | pub fn load_cargo( | 14 | pub fn load_cargo( |
26 | root: &Path, | 15 | root: &Path, |
27 | load_out_dirs_from_check: bool, | 16 | load_out_dirs_from_check: bool, |
28 | with_proc_macro: bool, | 17 | with_proc_macro: bool, |
29 | ) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> { | 18 | ) -> Result<(AnalysisHost, vfs::Vfs)> { |
30 | let root = std::env::current_dir()?.join(root); | 19 | let root = std::env::current_dir()?.join(root); |
31 | let root = ProjectManifest::discover_single(&root)?; | 20 | let root = ProjectManifest::discover_single(&root)?; |
32 | let ws = ProjectWorkspace::load( | 21 | let ws = ProjectWorkspace::load( |
@@ -35,123 +24,74 @@ pub fn load_cargo( | |||
35 | true, | 24 | true, |
36 | )?; | 25 | )?; |
37 | 26 | ||
38 | let mut extern_dirs = FxHashSet::default(); | ||
39 | |||
40 | let (sender, receiver) = unbounded(); | 27 | let (sender, receiver) = unbounded(); |
41 | let sender = Box::new(move |t| sender.send(t).unwrap()); | 28 | let mut vfs = vfs::Vfs::default(); |
42 | 29 | let mut loader = { | |
43 | let mut roots = Vec::new(); | 30 | let loader = |
44 | let project_roots = ws.to_roots(); | 31 | vfs_notify::LoaderHandle::spawn(Box::new(move |msg| sender.send(msg).unwrap())); |
45 | for root in &project_roots { | 32 | Box::new(loader) |
46 | roots.push(RootEntry::new( | 33 | }; |
47 | root.path().to_owned(), | ||
48 | RustPackageFilterBuilder::default().set_member(root.is_member()).into_vfs_filter(), | ||
49 | )); | ||
50 | |||
51 | if let Some(out_dir) = root.out_dir() { | ||
52 | extern_dirs.insert(out_dir.to_path_buf()); | ||
53 | roots.push(RootEntry::new( | ||
54 | out_dir.to_owned(), | ||
55 | RustPackageFilterBuilder::default().set_member(root.is_member()).into_vfs_filter(), | ||
56 | )) | ||
57 | } | ||
58 | } | ||
59 | |||
60 | let (mut vfs, roots) = Vfs::new(roots, sender, Watch(false)); | ||
61 | |||
62 | let source_roots = roots | ||
63 | .into_iter() | ||
64 | .map(|vfs_root| { | ||
65 | let source_root_id = vfs_root_to_id(vfs_root); | ||
66 | let project_root = project_roots | ||
67 | .iter() | ||
68 | .find(|it| it.path() == vfs.root2path(vfs_root)) | ||
69 | .unwrap() | ||
70 | .clone(); | ||
71 | (source_root_id, project_root) | ||
72 | }) | ||
73 | .collect::<FxHashMap<_, _>>(); | ||
74 | 34 | ||
75 | let proc_macro_client = if !with_proc_macro { | 35 | let proc_macro_client = if with_proc_macro { |
76 | ProcMacroClient::dummy() | ||
77 | } else { | ||
78 | let path = std::env::current_exe()?; | 36 | let path = std::env::current_exe()?; |
79 | ProcMacroClient::extern_process(path, &["proc-macro"]).unwrap() | 37 | ProcMacroClient::extern_process(path, &["proc-macro"]).unwrap() |
38 | } else { | ||
39 | ProcMacroClient::dummy() | ||
80 | }; | 40 | }; |
81 | let host = load(&source_roots, ws, &mut vfs, receiver, extern_dirs, &proc_macro_client); | 41 | |
82 | Ok((host, source_roots)) | 42 | let crate_graph = ws.to_crate_graph(None, &proc_macro_client, &mut |path: &Path| { |
43 | let path = AbsPathBuf::try_from(path.to_path_buf()).unwrap(); | ||
44 | let contents = loader.load_sync(&path); | ||
45 | let path = vfs::VfsPath::from(path); | ||
46 | vfs.set_file_contents(path.clone(), contents); | ||
47 | vfs.file_id(&path) | ||
48 | }); | ||
49 | |||
50 | let project_folders = ProjectFolders::new(&[ws]); | ||
51 | loader.set_config(vfs::loader::Config { load: project_folders.load, watch: vec![] }); | ||
52 | |||
53 | log::debug!("crate graph: {:?}", crate_graph); | ||
54 | let host = load(crate_graph, project_folders.source_root_config, &mut vfs, &receiver); | ||
55 | Ok((host, vfs)) | ||
83 | } | 56 | } |
84 | 57 | ||
85 | pub(crate) fn load( | 58 | pub(crate) fn load( |
86 | source_roots: &FxHashMap<SourceRootId, PackageRoot>, | 59 | crate_graph: CrateGraph, |
87 | ws: ProjectWorkspace, | 60 | source_root_config: SourceRootConfig, |
88 | vfs: &mut Vfs, | 61 | vfs: &mut vfs::Vfs, |
89 | receiver: Receiver<VfsTask>, | 62 | receiver: &Receiver<vfs::loader::Message>, |
90 | extern_dirs: FxHashSet<PathBuf>, | ||
91 | proc_macro_client: &ProcMacroClient, | ||
92 | ) -> AnalysisHost { | 63 | ) -> AnalysisHost { |
93 | let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok()); | 64 | let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok()); |
94 | let mut host = AnalysisHost::new(lru_cap); | 65 | let mut host = AnalysisHost::new(lru_cap); |
95 | let mut analysis_change = AnalysisChange::new(); | 66 | let mut analysis_change = AnalysisChange::new(); |
96 | 67 | ||
97 | // wait until Vfs has loaded all roots | 68 | // wait until Vfs has loaded all roots |
98 | let mut roots_loaded = FxHashSet::default(); | ||
99 | let mut extern_source_roots = FxHashMap::default(); | ||
100 | for task in receiver { | 69 | for task in receiver { |
101 | vfs.handle_task(task); | 70 | match task { |
102 | let mut done = false; | 71 | vfs::loader::Message::Progress { n_entries_done, n_entries_total } => { |
103 | for change in vfs.commit_changes() { | 72 | if n_entries_done == n_entries_total { |
104 | match change { | 73 | break; |
105 | VfsChange::AddRoot { root, files } => { | ||
106 | let source_root_id = vfs_root_to_id(root); | ||
107 | let is_local = source_roots[&source_root_id].is_member(); | ||
108 | log::debug!( | ||
109 | "loaded source root {:?} with path {:?}", | ||
110 | source_root_id, | ||
111 | vfs.root2path(root) | ||
112 | ); | ||
113 | analysis_change.add_root(source_root_id, is_local); | ||
114 | |||
115 | let vfs_root_path = vfs.root2path(root); | ||
116 | if extern_dirs.contains(&vfs_root_path) { | ||
117 | extern_source_roots.insert(vfs_root_path, ExternSourceId(root.0)); | ||
118 | } | ||
119 | |||
120 | let mut file_map = FxHashMap::default(); | ||
121 | for (vfs_file, path, text) in files { | ||
122 | let file_id = vfs_file_to_id(vfs_file); | ||
123 | analysis_change.add_file(source_root_id, file_id, path.clone(), text); | ||
124 | file_map.insert(path, file_id); | ||
125 | } | ||
126 | roots_loaded.insert(source_root_id); | ||
127 | if roots_loaded.len() == vfs.n_roots() { | ||
128 | done = true; | ||
129 | } | ||
130 | } | ||
131 | VfsChange::AddFile { root, file, path, text } => { | ||
132 | let source_root_id = vfs_root_to_id(root); | ||
133 | let file_id = vfs_file_to_id(file); | ||
134 | analysis_change.add_file(source_root_id, file_id, path, text); | ||
135 | } | 74 | } |
136 | VfsChange::RemoveFile { .. } | VfsChange::ChangeFile { .. } => { | 75 | } |
137 | // We just need the first scan, so just ignore these | 76 | vfs::loader::Message::Loaded { files } => { |
77 | for (path, contents) in files { | ||
78 | vfs.set_file_contents(path.into(), contents) | ||
138 | } | 79 | } |
139 | } | 80 | } |
140 | } | 81 | } |
141 | if done { | 82 | } |
142 | break; | 83 | let changes = vfs.take_changes(); |
84 | for file in changes { | ||
85 | if file.exists() { | ||
86 | let contents = vfs.file_contents(file.file_id).to_vec(); | ||
87 | if let Ok(text) = String::from_utf8(contents) { | ||
88 | analysis_change.change_file(file.file_id, Some(Arc::new(text))) | ||
89 | } | ||
143 | } | 90 | } |
144 | } | 91 | } |
92 | let source_roots = source_root_config.partition(&vfs); | ||
93 | analysis_change.set_roots(source_roots); | ||
145 | 94 | ||
146 | let crate_graph = | ||
147 | ws.to_crate_graph(None, &extern_source_roots, proc_macro_client, &mut |path: &Path| { | ||
148 | // Some path from metadata will be non canonicalized, e.g. /foo/../bar/lib.rs | ||
149 | let path = path.canonicalize().ok()?; | ||
150 | let vfs_file = vfs.load(&path); | ||
151 | log::debug!("vfs file {:?} -> {:?}", path, vfs_file); | ||
152 | vfs_file.map(vfs_file_to_id) | ||
153 | }); | ||
154 | log::debug!("crate graph: {:?}", crate_graph); | ||
155 | analysis_change.set_crate_graph(crate_graph); | 95 | analysis_change.set_crate_graph(crate_graph); |
156 | 96 | ||
157 | host.apply_change(analysis_change); | 97 | host.apply_change(analysis_change); |
@@ -167,7 +107,7 @@ mod tests { | |||
167 | #[test] | 107 | #[test] |
168 | fn test_loading_rust_analyzer() { | 108 | fn test_loading_rust_analyzer() { |
169 | let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap(); | 109 | let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap(); |
170 | let (host, _roots) = load_cargo(path, false, false).unwrap(); | 110 | let (host, _vfs) = load_cargo(path, false, false).unwrap(); |
171 | let n_crates = Crate::all(host.raw_database()).len(); | 111 | let n_crates = Crate::all(host.raw_database()).len(); |
172 | // RA has quite a few crates, but the exact count doesn't matter | 112 | // RA has quite a few crates, but the exact count doesn't matter |
173 | assert!(n_crates > 20); | 113 | assert!(n_crates > 20); |
diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs index 40f856e6e..40d440c67 100644 --- a/crates/rust-analyzer/src/from_proto.rs +++ b/crates/rust-analyzer/src/from_proto.rs | |||
@@ -1,10 +1,22 @@ | |||
1 | //! Conversion lsp_types types to rust-analyzer specific ones. | 1 | //! Conversion lsp_types types to rust-analyzer specific ones. |
2 | use std::convert::TryFrom; | ||
3 | |||
2 | use ra_db::{FileId, FilePosition, FileRange}; | 4 | use ra_db::{FileId, FilePosition, FileRange}; |
3 | use ra_ide::{LineCol, LineIndex}; | 5 | use ra_ide::{LineCol, LineIndex}; |
4 | use ra_syntax::{TextRange, TextSize}; | 6 | use ra_syntax::{TextRange, TextSize}; |
7 | use vfs::AbsPathBuf; | ||
5 | 8 | ||
6 | use crate::{global_state::GlobalStateSnapshot, Result}; | 9 | use crate::{global_state::GlobalStateSnapshot, Result}; |
7 | 10 | ||
11 | pub(crate) fn abs_path(url: &lsp_types::Url) -> Result<AbsPathBuf> { | ||
12 | let path = url.to_file_path().map_err(|()| "url is not a file")?; | ||
13 | Ok(AbsPathBuf::try_from(path).unwrap()) | ||
14 | } | ||
15 | |||
16 | pub(crate) fn vfs_path(url: &lsp_types::Url) -> Result<vfs::VfsPath> { | ||
17 | abs_path(url).map(vfs::VfsPath::from) | ||
18 | } | ||
19 | |||
8 | pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> TextSize { | 20 | pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> TextSize { |
9 | let line_col = LineCol { line: position.line as u32, col_utf16: position.character as u32 }; | 21 | let line_col = LineCol { line: position.line as u32, col_utf16: position.character as u32 }; |
10 | line_index.offset(line_col) | 22 | line_index.offset(line_col) |
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index d04ef4c61..e2ddb7933 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs | |||
@@ -3,30 +3,28 @@ | |||
3 | //! | 3 | //! |
4 | //! Each tick provides an immutable snapshot of the state as `WorldSnapshot`. | 4 | //! Each tick provides an immutable snapshot of the state as `WorldSnapshot`. |
5 | 5 | ||
6 | use std::{ | 6 | use std::{convert::TryFrom, path::Path, sync::Arc}; |
7 | path::{Path, PathBuf}, | ||
8 | sync::Arc, | ||
9 | }; | ||
10 | 7 | ||
11 | use crossbeam_channel::{unbounded, Receiver}; | 8 | use crossbeam_channel::{unbounded, Receiver}; |
12 | use lsp_types::Url; | 9 | use lsp_types::Url; |
13 | use parking_lot::RwLock; | 10 | use parking_lot::RwLock; |
11 | use ra_db::{CrateId, SourceRoot, VfsPath}; | ||
14 | use ra_flycheck::{Flycheck, FlycheckConfig}; | 12 | use ra_flycheck::{Flycheck, FlycheckConfig}; |
15 | use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, SourceRootId}; | 13 | use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId}; |
16 | use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target}; | 14 | use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target}; |
17 | use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsTask, Watch}; | ||
18 | use stdx::format_to; | 15 | use stdx::format_to; |
16 | use vfs::{file_set::FileSetConfig, loader::Handle, AbsPathBuf}; | ||
19 | 17 | ||
20 | use crate::{ | 18 | use crate::{ |
21 | config::{Config, FilesWatcher}, | 19 | config::{Config, FilesWatcher}, |
22 | diagnostics::{CheckFixes, DiagnosticCollection}, | 20 | diagnostics::{CheckFixes, DiagnosticCollection}, |
21 | from_proto, | ||
22 | line_endings::LineEndings, | ||
23 | main_loop::request_metrics::{LatestRequests, RequestMetrics}, | 23 | main_loop::request_metrics::{LatestRequests, RequestMetrics}, |
24 | to_proto::url_from_abs_path, | 24 | to_proto::url_from_abs_path, |
25 | vfs_glob::{Glob, RustPackageFilterBuilder}, | 25 | Result, |
26 | LspError, Result, | ||
27 | }; | 26 | }; |
28 | use ra_db::{CrateId, ExternSourceId}; | 27 | use rustc_hash::FxHashMap; |
29 | use rustc_hash::{FxHashMap, FxHashSet}; | ||
30 | 28 | ||
31 | fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> { | 29 | fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> { |
32 | // FIXME: Figure out the multi-workspace situation | 30 | // FIXME: Figure out the multi-workspace situation |
@@ -50,15 +48,16 @@ fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> | |||
50 | #[derive(Debug)] | 48 | #[derive(Debug)] |
51 | pub struct GlobalState { | 49 | pub struct GlobalState { |
52 | pub config: Config, | 50 | pub config: Config, |
53 | pub local_roots: Vec<PathBuf>, | ||
54 | pub workspaces: Arc<Vec<ProjectWorkspace>>, | 51 | pub workspaces: Arc<Vec<ProjectWorkspace>>, |
55 | pub analysis_host: AnalysisHost, | 52 | pub analysis_host: AnalysisHost, |
56 | pub vfs: Arc<RwLock<Vfs>>, | 53 | pub loader: Box<dyn vfs::loader::Handle>, |
57 | pub task_receiver: Receiver<VfsTask>, | 54 | pub task_receiver: Receiver<vfs::loader::Message>, |
58 | pub flycheck: Option<Flycheck>, | 55 | pub flycheck: Option<Flycheck>, |
59 | pub diagnostics: DiagnosticCollection, | 56 | pub diagnostics: DiagnosticCollection, |
60 | pub proc_macro_client: ProcMacroClient, | 57 | pub proc_macro_client: ProcMacroClient, |
58 | pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, | ||
61 | pub(crate) latest_requests: Arc<RwLock<LatestRequests>>, | 59 | pub(crate) latest_requests: Arc<RwLock<LatestRequests>>, |
60 | source_root_config: SourceRootConfig, | ||
62 | } | 61 | } |
63 | 62 | ||
64 | /// An immutable snapshot of the world's state at a point in time. | 63 | /// An immutable snapshot of the world's state at a point in time. |
@@ -68,62 +67,21 @@ pub struct GlobalStateSnapshot { | |||
68 | pub analysis: Analysis, | 67 | pub analysis: Analysis, |
69 | pub check_fixes: CheckFixes, | 68 | pub check_fixes: CheckFixes, |
70 | pub(crate) latest_requests: Arc<RwLock<LatestRequests>>, | 69 | pub(crate) latest_requests: Arc<RwLock<LatestRequests>>, |
71 | vfs: Arc<RwLock<Vfs>>, | 70 | vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, |
72 | } | 71 | } |
73 | 72 | ||
74 | impl GlobalState { | 73 | impl GlobalState { |
75 | pub fn new( | 74 | pub fn new( |
76 | workspaces: Vec<ProjectWorkspace>, | 75 | workspaces: Vec<ProjectWorkspace>, |
77 | lru_capacity: Option<usize>, | 76 | lru_capacity: Option<usize>, |
78 | exclude_globs: &[Glob], | ||
79 | config: Config, | 77 | config: Config, |
80 | ) -> GlobalState { | 78 | ) -> GlobalState { |
81 | let mut change = AnalysisChange::new(); | 79 | let mut change = AnalysisChange::new(); |
82 | 80 | ||
83 | let mut extern_dirs: FxHashSet<PathBuf> = FxHashSet::default(); | 81 | let project_folders = ProjectFolders::new(&workspaces); |
84 | |||
85 | let mut local_roots = Vec::new(); | ||
86 | let roots: Vec<_> = { | ||
87 | let create_filter = |is_member| { | ||
88 | RustPackageFilterBuilder::default() | ||
89 | .set_member(is_member) | ||
90 | .exclude(exclude_globs.iter().cloned()) | ||
91 | .into_vfs_filter() | ||
92 | }; | ||
93 | let mut roots = Vec::new(); | ||
94 | for root in workspaces.iter().flat_map(ProjectWorkspace::to_roots) { | ||
95 | let path = root.path().to_owned(); | ||
96 | if root.is_member() { | ||
97 | local_roots.push(path.clone()); | ||
98 | } | ||
99 | roots.push(RootEntry::new(path, create_filter(root.is_member()))); | ||
100 | if let Some(out_dir) = root.out_dir() { | ||
101 | extern_dirs.insert(out_dir.to_path_buf()); | ||
102 | roots.push(RootEntry::new( | ||
103 | out_dir.to_path_buf(), | ||
104 | create_filter(root.is_member()), | ||
105 | )) | ||
106 | } | ||
107 | } | ||
108 | roots | ||
109 | }; | ||
110 | |||
111 | let (task_sender, task_receiver) = unbounded(); | ||
112 | let task_sender = Box::new(move |t| task_sender.send(t).unwrap()); | ||
113 | let watch = Watch(matches!(config.files.watcher, FilesWatcher::Notify)); | ||
114 | let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch); | ||
115 | 82 | ||
116 | let mut extern_source_roots = FxHashMap::default(); | 83 | let (task_sender, task_receiver) = unbounded::<vfs::loader::Message>(); |
117 | for r in vfs_roots { | 84 | let mut vfs = vfs::Vfs::default(); |
118 | let vfs_root_path = vfs.root2path(r); | ||
119 | let is_local = local_roots.iter().any(|it| vfs_root_path.starts_with(it)); | ||
120 | change.add_root(SourceRootId(r.0), is_local); | ||
121 | |||
122 | // FIXME: add path2root in vfs to simpily this logic | ||
123 | if extern_dirs.contains(&vfs_root_path) { | ||
124 | extern_source_roots.insert(vfs_root_path, ExternSourceId(r.0)); | ||
125 | } | ||
126 | } | ||
127 | 85 | ||
128 | let proc_macro_client = match &config.proc_macro_srv { | 86 | let proc_macro_client = match &config.proc_macro_srv { |
129 | None => ProcMacroClient::dummy(), | 87 | None => ProcMacroClient::dummy(), |
@@ -140,18 +98,30 @@ impl GlobalState { | |||
140 | }, | 98 | }, |
141 | }; | 99 | }; |
142 | 100 | ||
101 | let mut loader = { | ||
102 | let loader = vfs_notify::LoaderHandle::spawn(Box::new(move |msg| { | ||
103 | task_sender.send(msg).unwrap() | ||
104 | })); | ||
105 | Box::new(loader) | ||
106 | }; | ||
107 | let watch = match config.files.watcher { | ||
108 | FilesWatcher::Client => vec![], | ||
109 | FilesWatcher::Notify => project_folders.watch, | ||
110 | }; | ||
111 | loader.set_config(vfs::loader::Config { load: project_folders.load, watch }); | ||
112 | |||
143 | // Create crate graph from all the workspaces | 113 | // Create crate graph from all the workspaces |
144 | let mut crate_graph = CrateGraph::default(); | 114 | let mut crate_graph = CrateGraph::default(); |
145 | let mut load = |path: &Path| { | 115 | let mut load = |path: &Path| { |
146 | // Some path from metadata will be non canonicalized, e.g. /foo/../bar/lib.rs | 116 | let path = AbsPathBuf::try_from(path.to_path_buf()).ok()?; |
147 | let path = path.canonicalize().ok()?; | 117 | let contents = loader.load_sync(&path); |
148 | let vfs_file = vfs.load(&path); | 118 | let path = vfs::VfsPath::from(path); |
149 | vfs_file.map(|f| FileId(f.0)) | 119 | vfs.set_file_contents(path.clone(), contents); |
120 | vfs.file_id(&path) | ||
150 | }; | 121 | }; |
151 | for ws in workspaces.iter() { | 122 | for ws in workspaces.iter() { |
152 | crate_graph.extend(ws.to_crate_graph( | 123 | crate_graph.extend(ws.to_crate_graph( |
153 | config.cargo.target.as_deref(), | 124 | config.cargo.target.as_deref(), |
154 | &extern_source_roots, | ||
155 | &proc_macro_client, | 125 | &proc_macro_client, |
156 | &mut load, | 126 | &mut load, |
157 | )); | 127 | )); |
@@ -162,18 +132,21 @@ impl GlobalState { | |||
162 | 132 | ||
163 | let mut analysis_host = AnalysisHost::new(lru_capacity); | 133 | let mut analysis_host = AnalysisHost::new(lru_capacity); |
164 | analysis_host.apply_change(change); | 134 | analysis_host.apply_change(change); |
165 | GlobalState { | 135 | let mut res = GlobalState { |
166 | config, | 136 | config, |
167 | local_roots, | ||
168 | workspaces: Arc::new(workspaces), | 137 | workspaces: Arc::new(workspaces), |
169 | analysis_host, | 138 | analysis_host, |
170 | vfs: Arc::new(RwLock::new(vfs)), | 139 | loader, |
140 | vfs: Arc::new(RwLock::new((vfs, FxHashMap::default()))), | ||
171 | task_receiver, | 141 | task_receiver, |
172 | latest_requests: Default::default(), | 142 | latest_requests: Default::default(), |
173 | flycheck, | 143 | flycheck, |
174 | diagnostics: Default::default(), | 144 | diagnostics: Default::default(), |
175 | proc_macro_client, | 145 | proc_macro_client, |
176 | } | 146 | source_root_config: project_folders.source_root_config, |
147 | }; | ||
148 | res.process_changes(); | ||
149 | res | ||
177 | } | 150 | } |
178 | 151 | ||
179 | pub fn update_configuration(&mut self, config: Config) { | 152 | pub fn update_configuration(&mut self, config: Config) { |
@@ -186,33 +159,40 @@ impl GlobalState { | |||
186 | self.config = config; | 159 | self.config = config; |
187 | } | 160 | } |
188 | 161 | ||
189 | /// Returns a vec of libraries | 162 | pub fn process_changes(&mut self) -> bool { |
190 | /// FIXME: better API here | 163 | let change = { |
191 | pub fn process_changes(&mut self, roots_scanned: &mut usize) -> bool { | 164 | let mut change = AnalysisChange::new(); |
192 | let changes = self.vfs.write().commit_changes(); | 165 | let (vfs, line_endings_map) = &mut *self.vfs.write(); |
193 | if changes.is_empty() { | 166 | let changed_files = vfs.take_changes(); |
194 | return false; | 167 | if changed_files.is_empty() { |
195 | } | 168 | return false; |
196 | let mut change = AnalysisChange::new(); | 169 | } |
197 | for c in changes { | 170 | |
198 | match c { | 171 | let fs_op = changed_files.iter().any(|it| it.is_created_or_deleted()); |
199 | VfsChange::AddRoot { root, files } => { | 172 | if fs_op { |
200 | *roots_scanned += 1; | 173 | let roots = self.source_root_config.partition(&vfs); |
201 | for (file, path, text) in files { | 174 | change.set_roots(roots) |
202 | change.add_file(SourceRootId(root.0), FileId(file.0), path, text); | 175 | } |
176 | |||
177 | for file in changed_files { | ||
178 | let text = if file.exists() { | ||
179 | let bytes = vfs.file_contents(file.file_id).to_vec(); | ||
180 | match String::from_utf8(bytes).ok() { | ||
181 | Some(text) => { | ||
182 | let (text, line_endings) = LineEndings::normalize(text); | ||
183 | line_endings_map.insert(file.file_id, line_endings); | ||
184 | Some(Arc::new(text)) | ||
185 | } | ||
186 | None => None, | ||
203 | } | 187 | } |
204 | } | 188 | } else { |
205 | VfsChange::AddFile { root, file, path, text } => { | 189 | None |
206 | change.add_file(SourceRootId(root.0), FileId(file.0), path, text); | 190 | }; |
207 | } | 191 | change.change_file(file.file_id, text); |
208 | VfsChange::RemoveFile { root, file, path } => { | ||
209 | change.remove_file(SourceRootId(root.0), FileId(file.0), path) | ||
210 | } | ||
211 | VfsChange::ChangeFile { file, text } => { | ||
212 | change.change_file(FileId(file.0), text); | ||
213 | } | ||
214 | } | 192 | } |
215 | } | 193 | change |
194 | }; | ||
195 | |||
216 | self.analysis_host.apply_change(change); | 196 | self.analysis_host.apply_change(change); |
217 | true | 197 | true |
218 | } | 198 | } |
@@ -242,35 +222,31 @@ impl GlobalState { | |||
242 | } | 222 | } |
243 | 223 | ||
244 | impl GlobalStateSnapshot { | 224 | impl GlobalStateSnapshot { |
245 | pub fn analysis(&self) -> &Analysis { | 225 | pub(crate) fn analysis(&self) -> &Analysis { |
246 | &self.analysis | 226 | &self.analysis |
247 | } | 227 | } |
248 | 228 | ||
249 | pub fn url_to_file_id(&self, url: &Url) -> Result<FileId> { | 229 | pub(crate) fn url_to_file_id(&self, url: &Url) -> Result<FileId> { |
250 | let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?; | 230 | let path = from_proto::abs_path(url)?; |
251 | let file = self.vfs.read().path2file(&path).ok_or_else(|| { | 231 | let path = path.into(); |
252 | // Show warning as this file is outside current workspace | 232 | let res = |
253 | // FIXME: just handle such files, and remove `LspError::UNKNOWN_FILE`. | 233 | self.vfs.read().0.file_id(&path).ok_or_else(|| format!("file not found: {}", path))?; |
254 | LspError { | 234 | Ok(res) |
255 | code: LspError::UNKNOWN_FILE, | ||
256 | message: "Rust file outside current workspace is not supported yet.".to_string(), | ||
257 | } | ||
258 | })?; | ||
259 | Ok(FileId(file.0)) | ||
260 | } | 235 | } |
261 | 236 | ||
262 | pub fn file_id_to_url(&self, id: FileId) -> Url { | 237 | pub(crate) fn file_id_to_url(&self, id: FileId) -> Url { |
263 | file_id_to_url(&self.vfs.read(), id) | 238 | file_id_to_url(&self.vfs.read().0, id) |
264 | } | 239 | } |
265 | 240 | ||
266 | pub fn file_line_endings(&self, id: FileId) -> LineEndings { | 241 | pub(crate) fn file_line_endings(&self, id: FileId) -> LineEndings { |
267 | self.vfs.read().file_line_endings(VfsFile(id.0)) | 242 | self.vfs.read().1[&id] |
268 | } | 243 | } |
269 | 244 | ||
270 | pub fn anchored_path(&self, file_id: FileId, path: &str) -> Url { | 245 | pub fn anchored_path(&self, file_id: FileId, path: &str) -> Url { |
271 | let mut base = self.vfs.read().file2path(VfsFile(file_id.0)); | 246 | let mut base = self.vfs.read().0.file_path(file_id); |
272 | base.pop(); | 247 | base.pop(); |
273 | let path = base.join(path); | 248 | let path = base.join(path); |
249 | let path = path.as_path().unwrap(); | ||
274 | url_from_abs_path(&path) | 250 | url_from_abs_path(&path) |
275 | } | 251 | } |
276 | 252 | ||
@@ -279,7 +255,8 @@ impl GlobalStateSnapshot { | |||
279 | crate_id: CrateId, | 255 | crate_id: CrateId, |
280 | ) -> Option<(&CargoWorkspace, Target)> { | 256 | ) -> Option<(&CargoWorkspace, Target)> { |
281 | let file_id = self.analysis().crate_root(crate_id).ok()?; | 257 | let file_id = self.analysis().crate_root(crate_id).ok()?; |
282 | let path = self.vfs.read().file2path(VfsFile(file_id.0)); | 258 | let path = self.vfs.read().0.file_path(file_id); |
259 | let path = path.as_path()?; | ||
283 | self.workspaces.iter().find_map(|ws| match ws { | 260 | self.workspaces.iter().find_map(|ws| match ws { |
284 | ProjectWorkspace::Cargo { cargo, .. } => { | 261 | ProjectWorkspace::Cargo { cargo, .. } => { |
285 | cargo.target_by_root(&path).map(|it| (cargo, it)) | 262 | cargo.target_by_root(&path).map(|it| (cargo, it)) |
@@ -307,14 +284,86 @@ impl GlobalStateSnapshot { | |||
307 | ); | 284 | ); |
308 | buf | 285 | buf |
309 | } | 286 | } |
287 | } | ||
288 | |||
289 | pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url { | ||
290 | let path = vfs.file_path(id); | ||
291 | let path = path.as_path().unwrap(); | ||
292 | url_from_abs_path(&path) | ||
293 | } | ||
294 | |||
295 | #[derive(Default)] | ||
296 | pub(crate) struct ProjectFolders { | ||
297 | pub(crate) load: Vec<vfs::loader::Entry>, | ||
298 | pub(crate) watch: Vec<usize>, | ||
299 | pub(crate) source_root_config: SourceRootConfig, | ||
300 | } | ||
301 | |||
302 | impl ProjectFolders { | ||
303 | pub(crate) fn new(workspaces: &[ProjectWorkspace]) -> ProjectFolders { | ||
304 | let mut res = ProjectFolders::default(); | ||
305 | let mut fsc = FileSetConfig::builder(); | ||
306 | let mut local_filesets = vec![]; | ||
307 | |||
308 | for root in workspaces.iter().flat_map(|it| it.to_roots()) { | ||
309 | let path = root.path().to_owned(); | ||
310 | |||
311 | let mut file_set_roots: Vec<VfsPath> = vec![]; | ||
312 | |||
313 | let path = AbsPathBuf::try_from(path).unwrap(); | ||
314 | let entry = if root.is_member() { | ||
315 | vfs::loader::Entry::local_cargo_package(path.clone()) | ||
316 | } else { | ||
317 | vfs::loader::Entry::cargo_package_dependency(path.clone()) | ||
318 | }; | ||
319 | res.load.push(entry); | ||
320 | if root.is_member() { | ||
321 | res.watch.push(res.load.len() - 1); | ||
322 | } | ||
323 | |||
324 | if let Some(out_dir) = root.out_dir() { | ||
325 | let out_dir = AbsPathBuf::try_from(out_dir.to_path_buf()).unwrap(); | ||
326 | res.load.push(vfs::loader::Entry::rs_files_recursively(out_dir.clone())); | ||
327 | if root.is_member() { | ||
328 | res.watch.push(res.load.len() - 1); | ||
329 | } | ||
330 | file_set_roots.push(out_dir.into()); | ||
331 | } | ||
332 | file_set_roots.push(path.into()); | ||
333 | |||
334 | if root.is_member() { | ||
335 | local_filesets.push(fsc.len()); | ||
336 | } | ||
337 | fsc.add_file_set(file_set_roots) | ||
338 | } | ||
339 | |||
340 | let fsc = fsc.build(); | ||
341 | res.source_root_config = SourceRootConfig { fsc, local_filesets }; | ||
310 | 342 | ||
311 | pub fn workspace_root_for(&self, file_id: FileId) -> Option<&Path> { | 343 | res |
312 | let path = self.vfs.read().file2path(VfsFile(file_id.0)); | ||
313 | self.workspaces.iter().find_map(|ws| ws.workspace_root_for(&path)) | ||
314 | } | 344 | } |
315 | } | 345 | } |
316 | 346 | ||
317 | pub(crate) fn file_id_to_url(vfs: &Vfs, id: FileId) -> Url { | 347 | #[derive(Default, Debug)] |
318 | let path = vfs.file2path(VfsFile(id.0)); | 348 | pub(crate) struct SourceRootConfig { |
319 | url_from_abs_path(&path) | 349 | pub(crate) fsc: FileSetConfig, |
350 | pub(crate) local_filesets: Vec<usize>, | ||
351 | } | ||
352 | |||
353 | impl SourceRootConfig { | ||
354 | pub fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> { | ||
355 | self.fsc | ||
356 | .partition(vfs) | ||
357 | .into_iter() | ||
358 | .enumerate() | ||
359 | .map(|(idx, file_set)| { | ||
360 | let is_local = self.local_filesets.contains(&idx); | ||
361 | if is_local { | ||
362 | SourceRoot::new_local(file_set) | ||
363 | } else { | ||
364 | SourceRoot::new_library(file_set) | ||
365 | } | ||
366 | }) | ||
367 | .collect() | ||
368 | } | ||
320 | } | 369 | } |
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs index 64e70955f..b38067079 100644 --- a/crates/rust-analyzer/src/lib.rs +++ b/crates/rust-analyzer/src/lib.rs | |||
@@ -17,7 +17,6 @@ macro_rules! eprintln { | |||
17 | ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; | 17 | ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; |
18 | } | 18 | } |
19 | 19 | ||
20 | mod vfs_glob; | ||
21 | mod caps; | 20 | mod caps; |
22 | mod cargo_target_spec; | 21 | mod cargo_target_spec; |
23 | mod to_proto; | 22 | mod to_proto; |
@@ -29,6 +28,7 @@ pub mod config; | |||
29 | mod global_state; | 28 | mod global_state; |
30 | mod diagnostics; | 29 | mod diagnostics; |
31 | mod semantic_tokens; | 30 | mod semantic_tokens; |
31 | mod line_endings; | ||
32 | 32 | ||
33 | use serde::de::DeserializeOwned; | 33 | use serde::de::DeserializeOwned; |
34 | 34 | ||
diff --git a/crates/rust-analyzer/src/line_endings.rs b/crates/rust-analyzer/src/line_endings.rs new file mode 100644 index 000000000..7e6db954e --- /dev/null +++ b/crates/rust-analyzer/src/line_endings.rs | |||
@@ -0,0 +1,64 @@ | |||
1 | //! We maintain invariant that all internal strings use `\n` as line separator. | ||
2 | //! This module does line ending conversion and detection (so that we can | ||
3 | //! convert back to `\r\n` on the way out). | ||
4 | |||
5 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
6 | pub(crate) enum LineEndings { | ||
7 | Unix, | ||
8 | Dos, | ||
9 | } | ||
10 | |||
11 | impl LineEndings { | ||
12 | /// Replaces `\r\n` with `\n` in-place in `src`. | ||
13 | pub(crate) fn normalize(src: String) -> (String, LineEndings) { | ||
14 | if !src.as_bytes().contains(&b'\r') { | ||
15 | return (src, LineEndings::Unix); | ||
16 | } | ||
17 | |||
18 | // We replace `\r\n` with `\n` in-place, which doesn't break utf-8 encoding. | ||
19 | // While we *can* call `as_mut_vec` and do surgery on the live string | ||
20 | // directly, let's rather steal the contents of `src`. This makes the code | ||
21 | // safe even if a panic occurs. | ||
22 | |||
23 | let mut buf = src.into_bytes(); | ||
24 | let mut gap_len = 0; | ||
25 | let mut tail = buf.as_mut_slice(); | ||
26 | loop { | ||
27 | let idx = match find_crlf(&tail[gap_len..]) { | ||
28 | None => tail.len(), | ||
29 | Some(idx) => idx + gap_len, | ||
30 | }; | ||
31 | tail.copy_within(gap_len..idx, 0); | ||
32 | tail = &mut tail[idx - gap_len..]; | ||
33 | if tail.len() == gap_len { | ||
34 | break; | ||
35 | } | ||
36 | gap_len += 1; | ||
37 | } | ||
38 | |||
39 | // Account for removed `\r`. | ||
40 | // After `set_len`, `buf` is guaranteed to contain utf-8 again. | ||
41 | let new_len = buf.len() - gap_len; | ||
42 | let src = unsafe { | ||
43 | buf.set_len(new_len); | ||
44 | String::from_utf8_unchecked(buf) | ||
45 | }; | ||
46 | return (src, LineEndings::Dos); | ||
47 | |||
48 | fn find_crlf(src: &[u8]) -> Option<usize> { | ||
49 | let mut search_idx = 0; | ||
50 | while let Some(idx) = find_cr(&src[search_idx..]) { | ||
51 | if src[search_idx..].get(idx + 1) != Some(&b'\n') { | ||
52 | search_idx += idx + 1; | ||
53 | continue; | ||
54 | } | ||
55 | return Some(search_idx + idx); | ||
56 | } | ||
57 | None | ||
58 | } | ||
59 | |||
60 | fn find_cr(src: &[u8]) -> Option<usize> { | ||
61 | src.iter().enumerate().find_map(|(idx, &b)| if b == b'\r' { Some(idx) } else { None }) | ||
62 | } | ||
63 | } | ||
64 | } | ||
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 674b1323b..b9d296856 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -2,11 +2,9 @@ | |||
2 | //! requests/replies and notifications back to the client. | 2 | //! requests/replies and notifications back to the client. |
3 | 3 | ||
4 | mod handlers; | 4 | mod handlers; |
5 | mod subscriptions; | ||
6 | pub(crate) mod request_metrics; | 5 | pub(crate) mod request_metrics; |
7 | 6 | ||
8 | use std::{ | 7 | use std::{ |
9 | borrow::Cow, | ||
10 | env, | 8 | env, |
11 | error::Error, | 9 | error::Error, |
12 | fmt, | 10 | fmt, |
@@ -20,16 +18,12 @@ use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; | |||
20 | use lsp_server::{ | 18 | use lsp_server::{ |
21 | Connection, ErrorCode, Message, Notification, ReqQueue, Request, RequestId, Response, | 19 | Connection, ErrorCode, Message, Notification, ReqQueue, Request, RequestId, Response, |
22 | }; | 20 | }; |
23 | use lsp_types::{ | 21 | use lsp_types::{request::Request as _, NumberOrString, TextDocumentContentChangeEvent}; |
24 | request::Request as _, DidChangeTextDocumentParams, NumberOrString, | 22 | use ra_flycheck::CheckTask; |
25 | TextDocumentContentChangeEvent, WorkDoneProgress, WorkDoneProgressBegin, | ||
26 | WorkDoneProgressCreateParams, WorkDoneProgressEnd, WorkDoneProgressReport, | ||
27 | }; | ||
28 | use ra_flycheck::{CheckTask, Status}; | ||
29 | use ra_ide::{Canceled, FileId, LineIndex}; | 23 | use ra_ide::{Canceled, FileId, LineIndex}; |
30 | use ra_prof::profile; | 24 | use ra_prof::profile; |
31 | use ra_project_model::{PackageRoot, ProjectWorkspace}; | 25 | use ra_project_model::{PackageRoot, ProjectWorkspace}; |
32 | use ra_vfs::VfsTask; | 26 | use rustc_hash::FxHashSet; |
33 | use serde::{de::DeserializeOwned, Serialize}; | 27 | use serde::{de::DeserializeOwned, Serialize}; |
34 | use threadpool::ThreadPool; | 28 | use threadpool::ThreadPool; |
35 | 29 | ||
@@ -39,9 +33,10 @@ use crate::{ | |||
39 | from_proto, | 33 | from_proto, |
40 | global_state::{file_id_to_url, GlobalState, GlobalStateSnapshot}, | 34 | global_state::{file_id_to_url, GlobalState, GlobalStateSnapshot}, |
41 | lsp_ext, | 35 | lsp_ext, |
42 | main_loop::{request_metrics::RequestMetrics, subscriptions::Subscriptions}, | 36 | main_loop::request_metrics::RequestMetrics, |
43 | Result, | 37 | Result, |
44 | }; | 38 | }; |
39 | use ra_db::VfsPath; | ||
45 | 40 | ||
46 | #[derive(Debug)] | 41 | #[derive(Debug)] |
47 | pub struct LspError { | 42 | pub struct LspError { |
@@ -128,13 +123,6 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { | |||
128 | .collect::<Vec<_>>() | 123 | .collect::<Vec<_>>() |
129 | }; | 124 | }; |
130 | 125 | ||
131 | let globs = config | ||
132 | .files | ||
133 | .exclude | ||
134 | .iter() | ||
135 | .map(|glob| crate::vfs_glob::Glob::new(glob)) | ||
136 | .collect::<std::result::Result<Vec<_>, _>>()?; | ||
137 | |||
138 | if let FilesWatcher::Client = config.files.watcher { | 126 | if let FilesWatcher::Client = config.files.watcher { |
139 | let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions { | 127 | let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions { |
140 | watchers: workspaces | 128 | watchers: workspaces |
@@ -159,11 +147,9 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { | |||
159 | connection.sender.send(request.into()).unwrap(); | 147 | connection.sender.send(request.into()).unwrap(); |
160 | } | 148 | } |
161 | 149 | ||
162 | GlobalState::new(workspaces, config.lru_capacity, &globs, config) | 150 | GlobalState::new(workspaces, config.lru_capacity, config) |
163 | }; | 151 | }; |
164 | 152 | ||
165 | loop_state.roots_total = global_state.vfs.read().n_roots(); | ||
166 | |||
167 | let pool = ThreadPool::default(); | 153 | let pool = ThreadPool::default(); |
168 | let (task_sender, task_receiver) = unbounded::<Task>(); | 154 | let (task_sender, task_receiver) = unbounded::<Task>(); |
169 | 155 | ||
@@ -192,7 +178,9 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { | |||
192 | break; | 178 | break; |
193 | }; | 179 | }; |
194 | } | 180 | } |
181 | assert!(!global_state.vfs.read().0.has_changes()); | ||
195 | loop_turn(&pool, &task_sender, &connection, &mut global_state, &mut loop_state, event)?; | 182 | loop_turn(&pool, &task_sender, &connection, &mut global_state, &mut loop_state, event)?; |
183 | assert!(!global_state.vfs.read().0.has_changes()); | ||
196 | } | 184 | } |
197 | } | 185 | } |
198 | global_state.analysis_host.request_cancellation(); | 186 | global_state.analysis_host.request_cancellation(); |
@@ -222,7 +210,7 @@ enum Task { | |||
222 | enum Event { | 210 | enum Event { |
223 | Msg(Message), | 211 | Msg(Message), |
224 | Task(Task), | 212 | Task(Task), |
225 | Vfs(VfsTask), | 213 | Vfs(vfs::loader::Message), |
226 | CheckWatcher(CheckTask), | 214 | CheckWatcher(CheckTask), |
227 | } | 215 | } |
228 | 216 | ||
@@ -270,11 +258,20 @@ type Incoming = lsp_server::Incoming<(&'static str, Instant)>; | |||
270 | #[derive(Default)] | 258 | #[derive(Default)] |
271 | struct LoopState { | 259 | struct LoopState { |
272 | req_queue: ReqQueue<(&'static str, Instant), ReqHandler>, | 260 | req_queue: ReqQueue<(&'static str, Instant), ReqHandler>, |
273 | subscriptions: Subscriptions, | 261 | mem_docs: FxHashSet<VfsPath>, |
274 | workspace_loaded: bool, | 262 | status: Status, |
275 | roots_progress_reported: Option<usize>, | 263 | } |
276 | roots_scanned: usize, | 264 | |
277 | roots_total: usize, | 265 | #[derive(Eq, PartialEq)] |
266 | enum Status { | ||
267 | Loading, | ||
268 | Ready, | ||
269 | } | ||
270 | |||
271 | impl Default for Status { | ||
272 | fn default() -> Self { | ||
273 | Status::Loading | ||
274 | } | ||
278 | } | 275 | } |
279 | 276 | ||
280 | fn loop_turn( | 277 | fn loop_turn( |
@@ -295,14 +292,36 @@ fn loop_turn( | |||
295 | log::info!("queued count = {}", queue_count); | 292 | log::info!("queued count = {}", queue_count); |
296 | } | 293 | } |
297 | 294 | ||
295 | let mut became_ready = false; | ||
298 | match event { | 296 | match event { |
299 | Event::Task(task) => { | 297 | Event::Task(task) => { |
300 | on_task(task, &connection.sender, &mut loop_state.req_queue.incoming, global_state); | 298 | on_task(task, &connection.sender, &mut loop_state.req_queue.incoming, global_state); |
301 | global_state.maybe_collect_garbage(); | 299 | global_state.maybe_collect_garbage(); |
302 | } | 300 | } |
303 | Event::Vfs(task) => { | 301 | Event::Vfs(task) => match task { |
304 | global_state.vfs.write().handle_task(task); | 302 | vfs::loader::Message::Loaded { files } => { |
305 | } | 303 | let vfs = &mut global_state.vfs.write().0; |
304 | for (path, contents) in files { | ||
305 | let path = VfsPath::from(path); | ||
306 | if !loop_state.mem_docs.contains(&path) { | ||
307 | vfs.set_file_contents(path, contents) | ||
308 | } | ||
309 | } | ||
310 | } | ||
311 | vfs::loader::Message::Progress { n_entries_total, n_entries_done } => { | ||
312 | if n_entries_done == n_entries_done { | ||
313 | loop_state.status = Status::Ready; | ||
314 | became_ready = true; | ||
315 | } | ||
316 | report_progress( | ||
317 | loop_state, | ||
318 | &connection.sender, | ||
319 | n_entries_done, | ||
320 | n_entries_total, | ||
321 | "roots scanned", | ||
322 | ) | ||
323 | } | ||
324 | }, | ||
306 | Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?, | 325 | Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?, |
307 | Event::Msg(msg) => match msg { | 326 | Event::Msg(msg) => match msg { |
308 | Message::Request(req) => on_request( | 327 | Message::Request(req) => on_request( |
@@ -324,32 +343,29 @@ fn loop_turn( | |||
324 | }, | 343 | }, |
325 | }; | 344 | }; |
326 | 345 | ||
327 | let mut state_changed = global_state.process_changes(&mut loop_state.roots_scanned); | 346 | let state_changed = global_state.process_changes(); |
328 | 347 | ||
329 | let show_progress = | 348 | if became_ready { |
330 | !loop_state.workspace_loaded && global_state.config.client_caps.work_done_progress; | ||
331 | |||
332 | if !loop_state.workspace_loaded && loop_state.roots_scanned == loop_state.roots_total { | ||
333 | state_changed = true; | ||
334 | loop_state.workspace_loaded = true; | ||
335 | if let Some(flycheck) = &global_state.flycheck { | 349 | if let Some(flycheck) = &global_state.flycheck { |
336 | flycheck.update(); | 350 | flycheck.update(); |
337 | } | 351 | } |
338 | } | 352 | } |
339 | 353 | ||
340 | if show_progress { | 354 | if loop_state.status == Status::Ready && (state_changed || became_ready) { |
341 | send_startup_progress(&connection.sender, loop_state); | 355 | let subscriptions = loop_state |
342 | } | 356 | .mem_docs |
357 | .iter() | ||
358 | .map(|path| global_state.vfs.read().0.file_id(&path).unwrap()) | ||
359 | .collect::<Vec<_>>(); | ||
343 | 360 | ||
344 | if state_changed && loop_state.workspace_loaded { | ||
345 | update_file_notifications_on_threadpool( | 361 | update_file_notifications_on_threadpool( |
346 | pool, | 362 | pool, |
347 | global_state.snapshot(), | 363 | global_state.snapshot(), |
348 | task_sender.clone(), | 364 | task_sender.clone(), |
349 | loop_state.subscriptions.subscriptions(), | 365 | subscriptions.clone(), |
350 | ); | 366 | ); |
351 | pool.execute({ | 367 | pool.execute({ |
352 | let subs = loop_state.subscriptions.subscriptions(); | 368 | let subs = subscriptions; |
353 | let snap = global_state.snapshot(); | 369 | let snap = global_state.snapshot(); |
354 | move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ()) | 370 | move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ()) |
355 | }); | 371 | }); |
@@ -465,7 +481,7 @@ fn on_request( | |||
465 | 481 | ||
466 | fn on_notification( | 482 | fn on_notification( |
467 | msg_sender: &Sender<Message>, | 483 | msg_sender: &Sender<Message>, |
468 | state: &mut GlobalState, | 484 | global_state: &mut GlobalState, |
469 | loop_state: &mut LoopState, | 485 | loop_state: &mut LoopState, |
470 | not: Notification, | 486 | not: Notification, |
471 | ) -> Result<()> { | 487 | ) -> Result<()> { |
@@ -484,12 +500,15 @@ fn on_notification( | |||
484 | }; | 500 | }; |
485 | let not = match notification_cast::<lsp_types::notification::DidOpenTextDocument>(not) { | 501 | let not = match notification_cast::<lsp_types::notification::DidOpenTextDocument>(not) { |
486 | Ok(params) => { | 502 | Ok(params) => { |
487 | let uri = params.text_document.uri; | 503 | if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { |
488 | let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; | 504 | if !loop_state.mem_docs.insert(path.clone()) { |
489 | if let Some(file_id) = | 505 | log::error!("duplicate DidOpenTextDocument: {}", path) |
490 | state.vfs.write().add_file_overlay(&path, params.text_document.text) | 506 | } |
491 | { | 507 | global_state |
492 | loop_state.subscriptions.add_sub(FileId(file_id.0)); | 508 | .vfs |
509 | .write() | ||
510 | .0 | ||
511 | .set_file_contents(path, Some(params.text_document.text.into_bytes())); | ||
493 | } | 512 | } |
494 | return Ok(()); | 513 | return Ok(()); |
495 | } | 514 | } |
@@ -497,23 +516,13 @@ fn on_notification( | |||
497 | }; | 516 | }; |
498 | let not = match notification_cast::<lsp_types::notification::DidChangeTextDocument>(not) { | 517 | let not = match notification_cast::<lsp_types::notification::DidChangeTextDocument>(not) { |
499 | Ok(params) => { | 518 | Ok(params) => { |
500 | let DidChangeTextDocumentParams { text_document, content_changes } = params; | 519 | if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { |
501 | let world = state.snapshot(); | 520 | assert!(loop_state.mem_docs.contains(&path)); |
502 | let file_id = from_proto::file_id(&world, &text_document.uri)?; | 521 | let vfs = &mut global_state.vfs.write().0; |
503 | let line_index = world.analysis().file_line_index(file_id)?; | 522 | let file_id = vfs.file_id(&path).unwrap(); |
504 | let uri = text_document.uri; | 523 | let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap(); |
505 | let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; | 524 | apply_document_changes(&mut text, params.content_changes); |
506 | state.vfs.write().change_file_overlay(&path, |old_text| { | 525 | vfs.set_file_contents(path, Some(text.into_bytes())) |
507 | apply_document_changes(old_text, Cow::Borrowed(&line_index), content_changes); | ||
508 | }); | ||
509 | return Ok(()); | ||
510 | } | ||
511 | Err(not) => not, | ||
512 | }; | ||
513 | let not = match notification_cast::<lsp_types::notification::DidSaveTextDocument>(not) { | ||
514 | Ok(_params) => { | ||
515 | if let Some(flycheck) = &state.flycheck { | ||
516 | flycheck.update(); | ||
517 | } | 526 | } |
518 | return Ok(()); | 527 | return Ok(()); |
519 | } | 528 | } |
@@ -521,19 +530,34 @@ fn on_notification( | |||
521 | }; | 530 | }; |
522 | let not = match notification_cast::<lsp_types::notification::DidCloseTextDocument>(not) { | 531 | let not = match notification_cast::<lsp_types::notification::DidCloseTextDocument>(not) { |
523 | Ok(params) => { | 532 | Ok(params) => { |
524 | let uri = params.text_document.uri; | 533 | if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { |
525 | let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; | 534 | if !loop_state.mem_docs.remove(&path) { |
526 | if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) { | 535 | log::error!("orphan DidCloseTextDocument: {}", path) |
527 | loop_state.subscriptions.remove_sub(FileId(file_id.0)); | 536 | } |
537 | if let Some(path) = path.as_path() { | ||
538 | global_state.loader.invalidate(path.to_path_buf()); | ||
539 | } | ||
528 | } | 540 | } |
529 | let params = | 541 | let params = lsp_types::PublishDiagnosticsParams { |
530 | lsp_types::PublishDiagnosticsParams { uri, diagnostics: Vec::new(), version: None }; | 542 | uri: params.text_document.uri, |
543 | diagnostics: Vec::new(), | ||
544 | version: None, | ||
545 | }; | ||
531 | let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params); | 546 | let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params); |
532 | msg_sender.send(not.into()).unwrap(); | 547 | msg_sender.send(not.into()).unwrap(); |
533 | return Ok(()); | 548 | return Ok(()); |
534 | } | 549 | } |
535 | Err(not) => not, | 550 | Err(not) => not, |
536 | }; | 551 | }; |
552 | let not = match notification_cast::<lsp_types::notification::DidSaveTextDocument>(not) { | ||
553 | Ok(_params) => { | ||
554 | if let Some(flycheck) = &global_state.flycheck { | ||
555 | flycheck.update(); | ||
556 | } | ||
557 | return Ok(()); | ||
558 | } | ||
559 | Err(not) => not, | ||
560 | }; | ||
537 | let not = match notification_cast::<lsp_types::notification::DidChangeConfiguration>(not) { | 561 | let not = match notification_cast::<lsp_types::notification::DidChangeConfiguration>(not) { |
538 | Ok(_) => { | 562 | Ok(_) => { |
539 | // As stated in https://github.com/microsoft/language-server-protocol/issues/676, | 563 | // As stated in https://github.com/microsoft/language-server-protocol/issues/676, |
@@ -575,11 +599,10 @@ fn on_notification( | |||
575 | }; | 599 | }; |
576 | let not = match notification_cast::<lsp_types::notification::DidChangeWatchedFiles>(not) { | 600 | let not = match notification_cast::<lsp_types::notification::DidChangeWatchedFiles>(not) { |
577 | Ok(params) => { | 601 | Ok(params) => { |
578 | let mut vfs = state.vfs.write(); | ||
579 | for change in params.changes { | 602 | for change in params.changes { |
580 | let uri = change.uri; | 603 | if let Ok(path) = from_proto::abs_path(&change.uri) { |
581 | let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; | 604 | global_state.loader.invalidate(path) |
582 | vfs.notify_changed(path) | 605 | } |
583 | } | 606 | } |
584 | return Ok(()); | 607 | return Ok(()); |
585 | } | 608 | } |
@@ -594,9 +617,9 @@ fn on_notification( | |||
594 | 617 | ||
595 | fn apply_document_changes( | 618 | fn apply_document_changes( |
596 | old_text: &mut String, | 619 | old_text: &mut String, |
597 | mut line_index: Cow<'_, LineIndex>, | ||
598 | content_changes: Vec<TextDocumentContentChangeEvent>, | 620 | content_changes: Vec<TextDocumentContentChangeEvent>, |
599 | ) { | 621 | ) { |
622 | let mut line_index = LineIndex::new(old_text); | ||
600 | // The changes we got must be applied sequentially, but can cross lines so we | 623 | // The changes we got must be applied sequentially, but can cross lines so we |
601 | // have to keep our line index updated. | 624 | // have to keep our line index updated. |
602 | // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we | 625 | // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we |
@@ -621,7 +644,7 @@ fn apply_document_changes( | |||
621 | match change.range { | 644 | match change.range { |
622 | Some(range) => { | 645 | Some(range) => { |
623 | if !index_valid.covers(range.end.line) { | 646 | if !index_valid.covers(range.end.line) { |
624 | line_index = Cow::Owned(LineIndex::new(&old_text)); | 647 | line_index = LineIndex::new(&old_text); |
625 | } | 648 | } |
626 | index_valid = IndexValid::UpToLineExclusive(range.start.line); | 649 | index_valid = IndexValid::UpToLineExclusive(range.start.line); |
627 | let range = from_proto::text_range(&line_index, range); | 650 | let range = from_proto::text_range(&line_index, range); |
@@ -652,18 +675,11 @@ fn on_check_task( | |||
652 | &workspace_root, | 675 | &workspace_root, |
653 | ); | 676 | ); |
654 | for diag in diagnostics { | 677 | for diag in diagnostics { |
655 | let path = diag | 678 | let path = from_proto::vfs_path(&diag.location.uri)?; |
656 | .location | 679 | let file_id = match global_state.vfs.read().0.file_id(&path) { |
657 | .uri | ||
658 | .to_file_path() | ||
659 | .map_err(|()| format!("invalid uri: {}", diag.location.uri))?; | ||
660 | let file_id = match global_state.vfs.read().path2file(&path) { | ||
661 | Some(file) => FileId(file.0), | 680 | Some(file) => FileId(file.0), |
662 | None => { | 681 | None => { |
663 | log::error!( | 682 | log::error!("File with cargo diagnostic not found in VFS: {}", path); |
664 | "File with cargo diagnostic not found in VFS: {}", | ||
665 | path.display() | ||
666 | ); | ||
667 | return Ok(()); | 683 | return Ok(()); |
668 | } | 684 | } |
669 | }; | 685 | }; |
@@ -679,7 +695,7 @@ fn on_check_task( | |||
679 | CheckTask::Status(status) => { | 695 | CheckTask::Status(status) => { |
680 | if global_state.config.client_caps.work_done_progress { | 696 | if global_state.config.client_caps.work_done_progress { |
681 | let progress = match status { | 697 | let progress = match status { |
682 | Status::Being => { | 698 | ra_flycheck::Status::Being => { |
683 | lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { | 699 | lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { |
684 | title: "Running `cargo check`".to_string(), | 700 | title: "Running `cargo check`".to_string(), |
685 | cancellable: Some(false), | 701 | cancellable: Some(false), |
@@ -687,14 +703,14 @@ fn on_check_task( | |||
687 | percentage: None, | 703 | percentage: None, |
688 | }) | 704 | }) |
689 | } | 705 | } |
690 | Status::Progress(target) => { | 706 | ra_flycheck::Status::Progress(target) => { |
691 | lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport { | 707 | lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport { |
692 | cancellable: Some(false), | 708 | cancellable: Some(false), |
693 | message: Some(target), | 709 | message: Some(target), |
694 | percentage: None, | 710 | percentage: None, |
695 | }) | 711 | }) |
696 | } | 712 | } |
697 | Status::End => { | 713 | ra_flycheck::Status::End => { |
698 | lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { | 714 | lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { |
699 | message: None, | 715 | message: None, |
700 | }) | 716 | }) |
@@ -720,7 +736,7 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state: | |||
720 | let subscriptions = state.diagnostics.handle_task(task); | 736 | let subscriptions = state.diagnostics.handle_task(task); |
721 | 737 | ||
722 | for file_id in subscriptions { | 738 | for file_id in subscriptions { |
723 | let url = file_id_to_url(&state.vfs.read(), file_id); | 739 | let url = file_id_to_url(&state.vfs.read().0, file_id); |
724 | let diagnostics = state.diagnostics.diagnostics_for(file_id).cloned().collect(); | 740 | let diagnostics = state.diagnostics.diagnostics_for(file_id).cloned().collect(); |
725 | let params = lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version: None }; | 741 | let params = lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version: None }; |
726 | let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params); | 742 | let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params); |
@@ -728,57 +744,46 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state: | |||
728 | } | 744 | } |
729 | } | 745 | } |
730 | 746 | ||
731 | fn send_startup_progress(sender: &Sender<Message>, loop_state: &mut LoopState) { | 747 | fn report_progress( |
732 | let total: usize = loop_state.roots_total; | 748 | loop_state: &mut LoopState, |
733 | let prev = loop_state.roots_progress_reported; | 749 | sender: &Sender<Message>, |
734 | let progress = loop_state.roots_scanned; | 750 | done: usize, |
735 | loop_state.roots_progress_reported = Some(progress); | 751 | total: usize, |
736 | 752 | message: &str, | |
737 | match (prev, loop_state.workspace_loaded) { | 753 | ) { |
738 | (None, false) => { | 754 | let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", message)); |
739 | let request = loop_state.req_queue.outgoing.register( | 755 | let message = Some(format!("{}/{} {}", done, total, message)); |
740 | lsp_types::request::WorkDoneProgressCreate::METHOD.to_string(), | 756 | let percentage = Some(100.0 * done as f64 / total.max(1) as f64); |
741 | WorkDoneProgressCreateParams { | 757 | let work_done_progress = if done == 0 { |
742 | token: lsp_types::ProgressToken::String("rustAnalyzer/startup".into()), | 758 | let work_done_progress_create = loop_state.req_queue.outgoing.register( |
743 | }, | 759 | lsp_types::request::WorkDoneProgressCreate::METHOD.to_string(), |
744 | DO_NOTHING, | 760 | lsp_types::WorkDoneProgressCreateParams { token: token.clone() }, |
745 | ); | 761 | DO_NOTHING, |
746 | sender.send(request.into()).unwrap(); | 762 | ); |
747 | send_startup_progress_notif( | 763 | sender.send(work_done_progress_create.into()).unwrap(); |
748 | sender, | ||
749 | WorkDoneProgress::Begin(WorkDoneProgressBegin { | ||
750 | title: "rust-analyzer".into(), | ||
751 | cancellable: None, | ||
752 | message: Some(format!("{}/{} packages", progress, total)), | ||
753 | percentage: Some(100.0 * progress as f64 / total as f64), | ||
754 | }), | ||
755 | ); | ||
756 | } | ||
757 | (Some(prev), false) if progress != prev => send_startup_progress_notif( | ||
758 | sender, | ||
759 | WorkDoneProgress::Report(WorkDoneProgressReport { | ||
760 | cancellable: None, | ||
761 | message: Some(format!("{}/{} packages", progress, total)), | ||
762 | percentage: Some(100.0 * progress as f64 / total as f64), | ||
763 | }), | ||
764 | ), | ||
765 | (_, true) => send_startup_progress_notif( | ||
766 | sender, | ||
767 | WorkDoneProgress::End(WorkDoneProgressEnd { | ||
768 | message: Some(format!("rust-analyzer loaded, {} packages", progress)), | ||
769 | }), | ||
770 | ), | ||
771 | _ => {} | ||
772 | } | ||
773 | 764 | ||
774 | fn send_startup_progress_notif(sender: &Sender<Message>, work_done_progress: WorkDoneProgress) { | 765 | lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { |
775 | let notif = | 766 | title: "rust-analyzer".into(), |
776 | notification_new::<lsp_types::notification::Progress>(lsp_types::ProgressParams { | 767 | cancellable: None, |
777 | token: lsp_types::ProgressToken::String("rustAnalyzer/startup".into()), | 768 | message, |
778 | value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress), | 769 | percentage, |
779 | }); | 770 | }) |
780 | sender.send(notif.into()).unwrap(); | 771 | } else if done < total { |
781 | } | 772 | lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport { |
773 | cancellable: None, | ||
774 | message, | ||
775 | percentage, | ||
776 | }) | ||
777 | } else { | ||
778 | assert!(done == total); | ||
779 | lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message }) | ||
780 | }; | ||
781 | let notification = | ||
782 | notification_new::<lsp_types::notification::Progress>(lsp_types::ProgressParams { | ||
783 | token, | ||
784 | value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress), | ||
785 | }); | ||
786 | sender.send(notification.into()).unwrap(); | ||
782 | } | 787 | } |
783 | 788 | ||
784 | struct PoolDispatcher<'a> { | 789 | struct PoolDispatcher<'a> { |
@@ -976,18 +981,12 @@ where | |||
976 | 981 | ||
977 | #[cfg(test)] | 982 | #[cfg(test)] |
978 | mod tests { | 983 | mod tests { |
979 | use std::borrow::Cow; | ||
980 | |||
981 | use lsp_types::{Position, Range, TextDocumentContentChangeEvent}; | 984 | use lsp_types::{Position, Range, TextDocumentContentChangeEvent}; |
982 | use ra_ide::LineIndex; | ||
983 | 985 | ||
984 | #[test] | 986 | use super::*; |
985 | fn apply_document_changes() { | ||
986 | fn run(text: &mut String, changes: Vec<TextDocumentContentChangeEvent>) { | ||
987 | let line_index = Cow::Owned(LineIndex::new(&text)); | ||
988 | super::apply_document_changes(text, line_index, changes); | ||
989 | } | ||
990 | 987 | ||
988 | #[test] | ||
989 | fn test_apply_document_changes() { | ||
991 | macro_rules! c { | 990 | macro_rules! c { |
992 | [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => { | 991 | [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => { |
993 | vec![$(TextDocumentContentChangeEvent { | 992 | vec![$(TextDocumentContentChangeEvent { |
@@ -1002,9 +1001,9 @@ mod tests { | |||
1002 | } | 1001 | } |
1003 | 1002 | ||
1004 | let mut text = String::new(); | 1003 | let mut text = String::new(); |
1005 | run(&mut text, vec![]); | 1004 | apply_document_changes(&mut text, vec![]); |
1006 | assert_eq!(text, ""); | 1005 | assert_eq!(text, ""); |
1007 | run( | 1006 | apply_document_changes( |
1008 | &mut text, | 1007 | &mut text, |
1009 | vec![TextDocumentContentChangeEvent { | 1008 | vec![TextDocumentContentChangeEvent { |
1010 | range: None, | 1009 | range: None, |
@@ -1013,36 +1012,39 @@ mod tests { | |||
1013 | }], | 1012 | }], |
1014 | ); | 1013 | ); |
1015 | assert_eq!(text, "the"); | 1014 | assert_eq!(text, "the"); |
1016 | run(&mut text, c![0, 3; 0, 3 => " quick"]); | 1015 | apply_document_changes(&mut text, c![0, 3; 0, 3 => " quick"]); |
1017 | assert_eq!(text, "the quick"); | 1016 | assert_eq!(text, "the quick"); |
1018 | run(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); | 1017 | apply_document_changes(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); |
1019 | assert_eq!(text, "quick foxes"); | 1018 | assert_eq!(text, "quick foxes"); |
1020 | run(&mut text, c![0, 11; 0, 11 => "\ndream"]); | 1019 | apply_document_changes(&mut text, c![0, 11; 0, 11 => "\ndream"]); |
1021 | assert_eq!(text, "quick foxes\ndream"); | 1020 | assert_eq!(text, "quick foxes\ndream"); |
1022 | run(&mut text, c![1, 0; 1, 0 => "have "]); | 1021 | apply_document_changes(&mut text, c![1, 0; 1, 0 => "have "]); |
1023 | assert_eq!(text, "quick foxes\nhave dream"); | 1022 | assert_eq!(text, "quick foxes\nhave dream"); |
1024 | run(&mut text, c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"]); | 1023 | apply_document_changes( |
1024 | &mut text, | ||
1025 | c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"], | ||
1026 | ); | ||
1025 | assert_eq!(text, "the quick foxes\nhave quiet dreams\n"); | 1027 | assert_eq!(text, "the quick foxes\nhave quiet dreams\n"); |
1026 | run(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]); | 1028 | apply_document_changes(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]); |
1027 | assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n"); | 1029 | assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n"); |
1028 | run( | 1030 | apply_document_changes( |
1029 | &mut text, | 1031 | &mut text, |
1030 | c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"], | 1032 | c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"], |
1031 | ); | 1033 | ); |
1032 | assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n"); | 1034 | assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n"); |
1033 | run(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); | 1035 | apply_document_changes(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); |
1034 | assert_eq!(text, "the quick \nthey have quiet dreams\n"); | 1036 | assert_eq!(text, "the quick \nthey have quiet dreams\n"); |
1035 | 1037 | ||
1036 | text = String::from("❤️"); | 1038 | text = String::from("❤️"); |
1037 | run(&mut text, c![0, 0; 0, 0 => "a"]); | 1039 | apply_document_changes(&mut text, c![0, 0; 0, 0 => "a"]); |
1038 | assert_eq!(text, "a❤️"); | 1040 | assert_eq!(text, "a❤️"); |
1039 | 1041 | ||
1040 | text = String::from("a\nb"); | 1042 | text = String::from("a\nb"); |
1041 | run(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]); | 1043 | apply_document_changes(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]); |
1042 | assert_eq!(text, "adcb"); | 1044 | assert_eq!(text, "adcb"); |
1043 | 1045 | ||
1044 | text = String::from("a\nb"); | 1046 | text = String::from("a\nb"); |
1045 | run(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); | 1047 | apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); |
1046 | assert_eq!(text, "ațc\ncb"); | 1048 | assert_eq!(text, "ațc\ncb"); |
1047 | } | 1049 | } |
1048 | } | 1050 | } |
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index 2d7e649d2..a44959abe 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs | |||
@@ -396,7 +396,6 @@ pub fn handle_runnables( | |||
396 | let line_index = snap.analysis().file_line_index(file_id)?; | 396 | let line_index = snap.analysis().file_line_index(file_id)?; |
397 | let offset = params.position.map(|it| from_proto::offset(&line_index, it)); | 397 | let offset = params.position.map(|it| from_proto::offset(&line_index, it)); |
398 | let mut res = Vec::new(); | 398 | let mut res = Vec::new(); |
399 | let workspace_root = snap.workspace_root_for(file_id); | ||
400 | let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?; | 399 | let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?; |
401 | for runnable in snap.analysis().runnables(file_id)? { | 400 | for runnable in snap.analysis().runnables(file_id)? { |
402 | if let Some(offset) = offset { | 401 | if let Some(offset) = offset { |
@@ -420,7 +419,7 @@ pub fn handle_runnables( | |||
420 | location: None, | 419 | location: None, |
421 | kind: lsp_ext::RunnableKind::Cargo, | 420 | kind: lsp_ext::RunnableKind::Cargo, |
422 | args: lsp_ext::CargoRunnable { | 421 | args: lsp_ext::CargoRunnable { |
423 | workspace_root: workspace_root.map(|root| root.to_owned()), | 422 | workspace_root: Some(spec.workspace_root.clone()), |
424 | cargo_args: vec![ | 423 | cargo_args: vec![ |
425 | cmd.to_string(), | 424 | cmd.to_string(), |
426 | "--package".to_string(), | 425 | "--package".to_string(), |
@@ -437,7 +436,7 @@ pub fn handle_runnables( | |||
437 | location: None, | 436 | location: None, |
438 | kind: lsp_ext::RunnableKind::Cargo, | 437 | kind: lsp_ext::RunnableKind::Cargo, |
439 | args: lsp_ext::CargoRunnable { | 438 | args: lsp_ext::CargoRunnable { |
440 | workspace_root: workspace_root.map(|root| root.to_owned()), | 439 | workspace_root: None, |
441 | cargo_args: vec!["check".to_string(), "--workspace".to_string()], | 440 | cargo_args: vec!["check".to_string(), "--workspace".to_string()], |
442 | executable_args: Vec::new(), | 441 | executable_args: Vec::new(), |
443 | }, | 442 | }, |
diff --git a/crates/rust-analyzer/src/main_loop/subscriptions.rs b/crates/rust-analyzer/src/main_loop/subscriptions.rs deleted file mode 100644 index 2c76418be..000000000 --- a/crates/rust-analyzer/src/main_loop/subscriptions.rs +++ /dev/null | |||
@@ -1,22 +0,0 @@ | |||
1 | //! Keeps track of file subscriptions -- the set of currently opened files for | ||
2 | //! which we want to publish diagnostics, syntax highlighting, etc. | ||
3 | |||
4 | use ra_ide::FileId; | ||
5 | use rustc_hash::FxHashSet; | ||
6 | |||
7 | #[derive(Default, Debug)] | ||
8 | pub(crate) struct Subscriptions { | ||
9 | subs: FxHashSet<FileId>, | ||
10 | } | ||
11 | |||
12 | impl Subscriptions { | ||
13 | pub(crate) fn add_sub(&mut self, file_id: FileId) { | ||
14 | self.subs.insert(file_id); | ||
15 | } | ||
16 | pub(crate) fn remove_sub(&mut self, file_id: FileId) { | ||
17 | self.subs.remove(&file_id); | ||
18 | } | ||
19 | pub(crate) fn subscriptions(&self) -> Vec<FileId> { | ||
20 | self.subs.iter().copied().collect() | ||
21 | } | ||
22 | } | ||
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index ec153097e..7b45b169d 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -10,11 +10,10 @@ use ra_ide::{ | |||
10 | ResolvedAssist, Runnable, Severity, SourceChange, SourceFileEdit, TextEdit, | 10 | ResolvedAssist, Runnable, Severity, SourceChange, SourceFileEdit, TextEdit, |
11 | }; | 11 | }; |
12 | use ra_syntax::{SyntaxKind, TextRange, TextSize}; | 12 | use ra_syntax::{SyntaxKind, TextRange, TextSize}; |
13 | use ra_vfs::LineEndings; | ||
14 | 13 | ||
15 | use crate::{ | 14 | use crate::{ |
16 | cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot, lsp_ext, | 15 | cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot, |
17 | semantic_tokens, Result, | 16 | line_endings::LineEndings, lsp_ext, semantic_tokens, Result, |
18 | }; | 17 | }; |
19 | 18 | ||
20 | pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position { | 19 | pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position { |
@@ -650,6 +649,7 @@ pub(crate) fn runnable( | |||
650 | runnable: Runnable, | 649 | runnable: Runnable, |
651 | ) -> Result<lsp_ext::Runnable> { | 650 | ) -> Result<lsp_ext::Runnable> { |
652 | let spec = CargoTargetSpec::for_file(snap, file_id)?; | 651 | let spec = CargoTargetSpec::for_file(snap, file_id)?; |
652 | let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone()); | ||
653 | let target = spec.as_ref().map(|s| s.target.clone()); | 653 | let target = spec.as_ref().map(|s| s.target.clone()); |
654 | let (cargo_args, executable_args) = | 654 | let (cargo_args, executable_args) = |
655 | CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?; | 655 | CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?; |
@@ -661,7 +661,7 @@ pub(crate) fn runnable( | |||
661 | location: Some(location), | 661 | location: Some(location), |
662 | kind: lsp_ext::RunnableKind::Cargo, | 662 | kind: lsp_ext::RunnableKind::Cargo, |
663 | args: lsp_ext::CargoRunnable { | 663 | args: lsp_ext::CargoRunnable { |
664 | workspace_root: snap.workspace_root_for(file_id).map(|root| root.to_owned()), | 664 | workspace_root: workspace_root, |
665 | cargo_args, | 665 | cargo_args, |
666 | executable_args, | 666 | executable_args, |
667 | }, | 667 | }, |
diff --git a/crates/rust-analyzer/src/vfs_glob.rs b/crates/rust-analyzer/src/vfs_glob.rs deleted file mode 100644 index ff37a7008..000000000 --- a/crates/rust-analyzer/src/vfs_glob.rs +++ /dev/null | |||
@@ -1,98 +0,0 @@ | |||
1 | //! Exclusion rules for vfs. | ||
2 | //! | ||
3 | //! By default, we include only `.rs` files, and skip some know offenders like | ||
4 | //! `/target` or `/node_modules` altogether. | ||
5 | //! | ||
6 | //! It's also possible to add custom exclusion globs. | ||
7 | |||
8 | use globset::{GlobSet, GlobSetBuilder}; | ||
9 | use ra_vfs::{Filter, RelativePath}; | ||
10 | |||
11 | pub use globset::{Glob, GlobBuilder}; | ||
12 | |||
13 | const ALWAYS_IGNORED: &[&str] = &["target/**", "**/node_modules/**", "**/.git/**"]; | ||
14 | const IGNORED_FOR_NON_MEMBERS: &[&str] = &["examples/**", "tests/**", "benches/**"]; | ||
15 | |||
16 | pub struct RustPackageFilterBuilder { | ||
17 | is_member: bool, | ||
18 | exclude: GlobSetBuilder, | ||
19 | } | ||
20 | |||
21 | impl Default for RustPackageFilterBuilder { | ||
22 | fn default() -> RustPackageFilterBuilder { | ||
23 | RustPackageFilterBuilder { is_member: false, exclude: GlobSetBuilder::new() } | ||
24 | } | ||
25 | } | ||
26 | |||
27 | impl RustPackageFilterBuilder { | ||
28 | pub fn set_member(mut self, is_member: bool) -> RustPackageFilterBuilder { | ||
29 | self.is_member = is_member; | ||
30 | self | ||
31 | } | ||
32 | |||
33 | pub fn exclude(mut self, globs: impl IntoIterator<Item = Glob>) -> RustPackageFilterBuilder { | ||
34 | for glob in globs.into_iter() { | ||
35 | self.exclude.add(glob); | ||
36 | } | ||
37 | self | ||
38 | } | ||
39 | |||
40 | pub fn into_vfs_filter(self) -> Box<dyn Filter> { | ||
41 | let RustPackageFilterBuilder { is_member, mut exclude } = self; | ||
42 | for &glob in ALWAYS_IGNORED { | ||
43 | exclude.add(Glob::new(glob).unwrap()); | ||
44 | } | ||
45 | if !is_member { | ||
46 | for &glob in IGNORED_FOR_NON_MEMBERS { | ||
47 | exclude.add(Glob::new(glob).unwrap()); | ||
48 | } | ||
49 | } | ||
50 | Box::new(RustPackageFilter { exclude: exclude.build().unwrap() }) | ||
51 | } | ||
52 | } | ||
53 | |||
54 | struct RustPackageFilter { | ||
55 | exclude: GlobSet, | ||
56 | } | ||
57 | |||
58 | impl Filter for RustPackageFilter { | ||
59 | fn include_dir(&self, dir_path: &RelativePath) -> bool { | ||
60 | !self.exclude.is_match(dir_path.as_str()) | ||
61 | } | ||
62 | |||
63 | fn include_file(&self, file_path: &RelativePath) -> bool { | ||
64 | file_path.extension() == Some("rs") | ||
65 | } | ||
66 | } | ||
67 | |||
68 | #[test] | ||
69 | fn test_globs() { | ||
70 | let filter = RustPackageFilterBuilder::default().set_member(true).into_vfs_filter(); | ||
71 | |||
72 | assert!(filter.include_dir(RelativePath::new("src/tests"))); | ||
73 | assert!(filter.include_dir(RelativePath::new("src/target"))); | ||
74 | assert!(filter.include_dir(RelativePath::new("tests"))); | ||
75 | assert!(filter.include_dir(RelativePath::new("benches"))); | ||
76 | |||
77 | assert!(!filter.include_dir(RelativePath::new("target"))); | ||
78 | assert!(!filter.include_dir(RelativePath::new("src/foo/.git"))); | ||
79 | assert!(!filter.include_dir(RelativePath::new("foo/node_modules"))); | ||
80 | |||
81 | let filter = RustPackageFilterBuilder::default().set_member(false).into_vfs_filter(); | ||
82 | |||
83 | assert!(filter.include_dir(RelativePath::new("src/tests"))); | ||
84 | assert!(filter.include_dir(RelativePath::new("src/target"))); | ||
85 | |||
86 | assert!(!filter.include_dir(RelativePath::new("target"))); | ||
87 | assert!(!filter.include_dir(RelativePath::new("src/foo/.git"))); | ||
88 | assert!(!filter.include_dir(RelativePath::new("foo/node_modules"))); | ||
89 | assert!(!filter.include_dir(RelativePath::new("tests"))); | ||
90 | assert!(!filter.include_dir(RelativePath::new("benches"))); | ||
91 | |||
92 | let filter = RustPackageFilterBuilder::default() | ||
93 | .set_member(true) | ||
94 | .exclude(std::iter::once(Glob::new("src/llvm-project/**").unwrap())) | ||
95 | .into_vfs_filter(); | ||
96 | |||
97 | assert!(!filter.include_dir(RelativePath::new("src/llvm-project/clang"))); | ||
98 | } | ||
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs index 48ce831af..e0de377b4 100644 --- a/crates/rust-analyzer/tests/heavy_tests/main.rs +++ b/crates/rust-analyzer/tests/heavy_tests/main.rs | |||
@@ -52,7 +52,7 @@ use std::collections::Spam; | |||
52 | partial_result_params: PartialResultParams::default(), | 52 | partial_result_params: PartialResultParams::default(), |
53 | work_done_progress_params: WorkDoneProgressParams::default(), | 53 | work_done_progress_params: WorkDoneProgressParams::default(), |
54 | }); | 54 | }); |
55 | assert!(format!("{}", res).contains("HashMap")); | 55 | assert!(res.to_string().contains("HashMap")); |
56 | eprintln!("completion took {:?}", completion_start.elapsed()); | 56 | eprintln!("completion took {:?}", completion_start.elapsed()); |
57 | } | 57 | } |
58 | 58 | ||
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs index f58790ded..bb8585355 100644 --- a/crates/rust-analyzer/tests/heavy_tests/support.rs +++ b/crates/rust-analyzer/tests/heavy_tests/support.rs | |||
@@ -212,7 +212,7 @@ impl Server { | |||
212 | ProgressParams { | 212 | ProgressParams { |
213 | token: lsp_types::ProgressToken::String(ref token), | 213 | token: lsp_types::ProgressToken::String(ref token), |
214 | value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(_)), | 214 | value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(_)), |
215 | } if token == "rustAnalyzer/startup" => true, | 215 | } if token == "rustAnalyzer/roots scanned" => true, |
216 | _ => false, | 216 | _ => false, |
217 | } | 217 | } |
218 | } | 218 | } |
diff --git a/crates/vfs-notify/Cargo.toml b/crates/vfs-notify/Cargo.toml new file mode 100644 index 000000000..4737a52a7 --- /dev/null +++ b/crates/vfs-notify/Cargo.toml | |||
@@ -0,0 +1,17 @@ | |||
1 | [package] | ||
2 | name = "vfs-notify" | ||
3 | version = "0.1.0" | ||
4 | authors = ["rust-analyzer developers"] | ||
5 | edition = "2018" | ||
6 | |||
7 | [dependencies] | ||
8 | log = "0.4.8" | ||
9 | rustc-hash = "1.0" | ||
10 | jod-thread = "0.1.0" | ||
11 | walkdir = "2.3.1" | ||
12 | globset = "0.4.5" | ||
13 | crossbeam-channel = "0.4.0" | ||
14 | notify = "5.0.0-pre.3" | ||
15 | |||
16 | vfs = { path = "../vfs" } | ||
17 | paths = { path = "../paths" } | ||
diff --git a/crates/vfs-notify/src/include.rs b/crates/vfs-notify/src/include.rs new file mode 100644 index 000000000..7378766f5 --- /dev/null +++ b/crates/vfs-notify/src/include.rs | |||
@@ -0,0 +1,43 @@ | |||
1 | //! See `Include`. | ||
2 | |||
3 | use std::convert::TryFrom; | ||
4 | |||
5 | use globset::{Glob, GlobSet, GlobSetBuilder}; | ||
6 | use paths::{RelPath, RelPathBuf}; | ||
7 | |||
8 | /// `Include` is the opposite of .gitignore. | ||
9 | /// | ||
10 | /// It describes the set of files inside some directory. | ||
11 | /// | ||
12 | /// The current implementation is very limited, it allows white-listing file | ||
13 | /// globs and black-listing directories. | ||
14 | #[derive(Debug, Clone)] | ||
15 | pub(crate) struct Include { | ||
16 | include_files: GlobSet, | ||
17 | exclude_dirs: Vec<RelPathBuf>, | ||
18 | } | ||
19 | |||
20 | impl Include { | ||
21 | pub(crate) fn new(include: Vec<String>) -> Include { | ||
22 | let mut include_files = GlobSetBuilder::new(); | ||
23 | let mut exclude_dirs = Vec::new(); | ||
24 | |||
25 | for glob in include { | ||
26 | if glob.starts_with("!/") { | ||
27 | if let Ok(path) = RelPathBuf::try_from(&glob["!/".len()..]) { | ||
28 | exclude_dirs.push(path) | ||
29 | } | ||
30 | } else { | ||
31 | include_files.add(Glob::new(&glob).unwrap()); | ||
32 | } | ||
33 | } | ||
34 | let include_files = include_files.build().unwrap(); | ||
35 | Include { include_files, exclude_dirs } | ||
36 | } | ||
37 | pub(crate) fn include_file(&self, path: &RelPath) -> bool { | ||
38 | self.include_files.is_match(path) | ||
39 | } | ||
40 | pub(crate) fn exclude_dir(&self, path: &RelPath) -> bool { | ||
41 | self.exclude_dirs.iter().any(|excluded| path.starts_with(excluded)) | ||
42 | } | ||
43 | } | ||
diff --git a/crates/vfs-notify/src/lib.rs b/crates/vfs-notify/src/lib.rs new file mode 100644 index 000000000..baee6ddc8 --- /dev/null +++ b/crates/vfs-notify/src/lib.rs | |||
@@ -0,0 +1,247 @@ | |||
1 | //! An implementation of `loader::Handle`, based on `walkdir` and `notify`. | ||
2 | //! | ||
3 | //! The file watching bits here are untested and quite probably buggy. For this | ||
4 | //! reason, by default we don't watch files and rely on editor's file watching | ||
5 | //! capabilities. | ||
6 | //! | ||
7 | //! Hopefully, one day a reliable file watching/walking crate appears on | ||
8 | //! crates.io, and we can reduce this to trivial glue code. | ||
9 | mod include; | ||
10 | |||
11 | use std::convert::{TryFrom, TryInto}; | ||
12 | |||
13 | use crossbeam_channel::{select, unbounded, Receiver}; | ||
14 | use notify::{RecommendedWatcher, RecursiveMode, Watcher}; | ||
15 | use paths::{AbsPath, AbsPathBuf}; | ||
16 | use rustc_hash::FxHashSet; | ||
17 | use vfs::loader; | ||
18 | use walkdir::WalkDir; | ||
19 | |||
20 | use crate::include::Include; | ||
21 | |||
22 | #[derive(Debug)] | ||
23 | pub struct LoaderHandle { | ||
24 | // Relative order of fields below is significant. | ||
25 | sender: crossbeam_channel::Sender<Message>, | ||
26 | _thread: jod_thread::JoinHandle, | ||
27 | } | ||
28 | |||
29 | #[derive(Debug)] | ||
30 | enum Message { | ||
31 | Config(loader::Config), | ||
32 | Invalidate(AbsPathBuf), | ||
33 | } | ||
34 | |||
35 | impl loader::Handle for LoaderHandle { | ||
36 | fn spawn(sender: loader::Sender) -> LoaderHandle { | ||
37 | let actor = LoaderActor::new(sender); | ||
38 | let (sender, receiver) = unbounded::<Message>(); | ||
39 | let thread = jod_thread::spawn(move || actor.run(receiver)); | ||
40 | LoaderHandle { sender, _thread: thread } | ||
41 | } | ||
42 | fn set_config(&mut self, config: loader::Config) { | ||
43 | self.sender.send(Message::Config(config)).unwrap() | ||
44 | } | ||
45 | fn invalidate(&mut self, path: AbsPathBuf) { | ||
46 | self.sender.send(Message::Invalidate(path)).unwrap(); | ||
47 | } | ||
48 | fn load_sync(&mut self, path: &AbsPathBuf) -> Option<Vec<u8>> { | ||
49 | read(path) | ||
50 | } | ||
51 | } | ||
52 | |||
53 | type NotifyEvent = notify::Result<notify::Event>; | ||
54 | |||
55 | struct LoaderActor { | ||
56 | config: Vec<(AbsPathBuf, Include, bool)>, | ||
57 | watched_paths: FxHashSet<AbsPathBuf>, | ||
58 | sender: loader::Sender, | ||
59 | // Drop order of fields bellow is significant, | ||
60 | watcher: Option<RecommendedWatcher>, | ||
61 | watcher_receiver: Receiver<NotifyEvent>, | ||
62 | } | ||
63 | |||
64 | #[derive(Debug)] | ||
65 | enum Event { | ||
66 | Message(Message), | ||
67 | NotifyEvent(NotifyEvent), | ||
68 | } | ||
69 | |||
70 | impl LoaderActor { | ||
71 | fn new(sender: loader::Sender) -> LoaderActor { | ||
72 | let (watcher_sender, watcher_receiver) = unbounded(); | ||
73 | let watcher = log_notify_error(Watcher::new_immediate(move |event| { | ||
74 | watcher_sender.send(event).unwrap() | ||
75 | })); | ||
76 | |||
77 | LoaderActor { | ||
78 | watcher, | ||
79 | watcher_receiver, | ||
80 | watched_paths: FxHashSet::default(), | ||
81 | sender, | ||
82 | config: Vec::new(), | ||
83 | } | ||
84 | } | ||
85 | |||
86 | fn run(mut self, receiver: Receiver<Message>) { | ||
87 | while let Some(event) = self.next_event(&receiver) { | ||
88 | log::debug!("vfs-notify event: {:?}", event); | ||
89 | match event { | ||
90 | Event::Message(msg) => match msg { | ||
91 | Message::Config(config) => { | ||
92 | let n_entries_total = config.load.len(); | ||
93 | self.send(loader::Message::Progress { n_entries_total, n_entries_done: 0 }); | ||
94 | |||
95 | self.unwatch_all(); | ||
96 | self.config.clear(); | ||
97 | |||
98 | for (i, entry) in config.load.into_iter().enumerate() { | ||
99 | let watch = config.watch.contains(&i); | ||
100 | let files = self.load_entry(entry, watch); | ||
101 | self.send(loader::Message::Loaded { files }); | ||
102 | self.send(loader::Message::Progress { | ||
103 | n_entries_total, | ||
104 | n_entries_done: i + 1, | ||
105 | }); | ||
106 | } | ||
107 | self.config.sort_by(|x, y| x.0.cmp(&y.0)); | ||
108 | } | ||
109 | Message::Invalidate(path) => { | ||
110 | let contents = read(path.as_path()); | ||
111 | let files = vec![(path, contents)]; | ||
112 | self.send(loader::Message::Loaded { files }); | ||
113 | } | ||
114 | }, | ||
115 | Event::NotifyEvent(event) => { | ||
116 | if let Some(event) = log_notify_error(event) { | ||
117 | let files = event | ||
118 | .paths | ||
119 | .into_iter() | ||
120 | .map(|path| AbsPathBuf::try_from(path).unwrap()) | ||
121 | .filter_map(|path| { | ||
122 | let is_dir = path.is_dir(); | ||
123 | let is_file = path.is_file(); | ||
124 | |||
125 | let config_idx = | ||
126 | match self.config.binary_search_by(|it| it.0.cmp(&path)) { | ||
127 | Ok(it) => it, | ||
128 | Err(it) => it.saturating_sub(1), | ||
129 | }; | ||
130 | let include = self.config.get(config_idx).and_then(|it| { | ||
131 | let rel_path = path.strip_prefix(&it.0)?; | ||
132 | Some((rel_path, &it.1)) | ||
133 | }); | ||
134 | |||
135 | if let Some((rel_path, include)) = include { | ||
136 | if is_dir && include.exclude_dir(&rel_path) | ||
137 | || is_file && !include.include_file(&rel_path) | ||
138 | { | ||
139 | return None; | ||
140 | } | ||
141 | } | ||
142 | |||
143 | if is_dir { | ||
144 | self.watch(path); | ||
145 | return None; | ||
146 | } | ||
147 | if !is_file { | ||
148 | return None; | ||
149 | } | ||
150 | let contents = read(&path); | ||
151 | Some((path, contents)) | ||
152 | }) | ||
153 | .collect(); | ||
154 | self.send(loader::Message::Loaded { files }) | ||
155 | } | ||
156 | } | ||
157 | } | ||
158 | } | ||
159 | } | ||
160 | fn next_event(&self, receiver: &Receiver<Message>) -> Option<Event> { | ||
161 | select! { | ||
162 | recv(receiver) -> it => it.ok().map(Event::Message), | ||
163 | recv(&self.watcher_receiver) -> it => Some(Event::NotifyEvent(it.unwrap())), | ||
164 | } | ||
165 | } | ||
166 | fn load_entry( | ||
167 | &mut self, | ||
168 | entry: loader::Entry, | ||
169 | watch: bool, | ||
170 | ) -> Vec<(AbsPathBuf, Option<Vec<u8>>)> { | ||
171 | match entry { | ||
172 | loader::Entry::Files(files) => files | ||
173 | .into_iter() | ||
174 | .map(|file| { | ||
175 | if watch { | ||
176 | self.watch(file.clone()) | ||
177 | } | ||
178 | let contents = read(file.as_path()); | ||
179 | (file, contents) | ||
180 | }) | ||
181 | .collect::<Vec<_>>(), | ||
182 | loader::Entry::Directory { path, include } => { | ||
183 | let include = Include::new(include); | ||
184 | self.config.push((path.clone(), include.clone(), watch)); | ||
185 | |||
186 | let files = WalkDir::new(&path) | ||
187 | .into_iter() | ||
188 | .filter_entry(|entry| { | ||
189 | let abs_path: &AbsPath = entry.path().try_into().unwrap(); | ||
190 | match abs_path.strip_prefix(&path) { | ||
191 | Some(rel_path) => { | ||
192 | !(entry.file_type().is_dir() && include.exclude_dir(rel_path)) | ||
193 | } | ||
194 | None => false, | ||
195 | } | ||
196 | }) | ||
197 | .filter_map(|entry| entry.ok()) | ||
198 | .filter_map(|entry| { | ||
199 | let is_dir = entry.file_type().is_dir(); | ||
200 | let is_file = entry.file_type().is_file(); | ||
201 | let abs_path = AbsPathBuf::try_from(entry.into_path()).unwrap(); | ||
202 | if is_dir { | ||
203 | self.watch(abs_path.clone()); | ||
204 | } | ||
205 | let rel_path = abs_path.strip_prefix(&path)?; | ||
206 | if is_file && include.include_file(&rel_path) { | ||
207 | Some(abs_path) | ||
208 | } else { | ||
209 | None | ||
210 | } | ||
211 | }); | ||
212 | |||
213 | files | ||
214 | .map(|file| { | ||
215 | let contents = read(file.as_path()); | ||
216 | (file, contents) | ||
217 | }) | ||
218 | .collect() | ||
219 | } | ||
220 | } | ||
221 | } | ||
222 | |||
223 | fn watch(&mut self, path: AbsPathBuf) { | ||
224 | if let Some(watcher) = &mut self.watcher { | ||
225 | log_notify_error(watcher.watch(&path, RecursiveMode::NonRecursive)); | ||
226 | self.watched_paths.insert(path); | ||
227 | } | ||
228 | } | ||
229 | fn unwatch_all(&mut self) { | ||
230 | if let Some(watcher) = &mut self.watcher { | ||
231 | for path in self.watched_paths.drain() { | ||
232 | log_notify_error(watcher.unwatch(path)); | ||
233 | } | ||
234 | } | ||
235 | } | ||
236 | fn send(&mut self, msg: loader::Message) { | ||
237 | (self.sender)(msg) | ||
238 | } | ||
239 | } | ||
240 | |||
241 | fn read(path: &AbsPath) -> Option<Vec<u8>> { | ||
242 | std::fs::read(path).ok() | ||
243 | } | ||
244 | |||
245 | fn log_notify_error<T>(res: notify::Result<T>) -> Option<T> { | ||
246 | res.map_err(|err| log::warn!("notify error: {}", err)).ok() | ||
247 | } | ||
diff --git a/crates/vfs/Cargo.toml b/crates/vfs/Cargo.toml index c03e6363b..263069002 100644 --- a/crates/vfs/Cargo.toml +++ b/crates/vfs/Cargo.toml | |||
@@ -6,9 +6,5 @@ edition = "2018" | |||
6 | 6 | ||
7 | [dependencies] | 7 | [dependencies] |
8 | rustc-hash = "1.0" | 8 | rustc-hash = "1.0" |
9 | jod-thread = "0.1.0" | ||
10 | walkdir = "2.3.1" | ||
11 | globset = "0.4.5" | ||
12 | crossbeam-channel = "0.4.0" | ||
13 | 9 | ||
14 | paths = { path = "../paths" } | 10 | paths = { path = "../paths" } |
diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs index 724606a3d..0173f7464 100644 --- a/crates/vfs/src/file_set.rs +++ b/crates/vfs/src/file_set.rs | |||
@@ -4,7 +4,6 @@ | |||
4 | //! the default `FileSet`. | 4 | //! the default `FileSet`. |
5 | use std::{fmt, iter}; | 5 | use std::{fmt, iter}; |
6 | 6 | ||
7 | use paths::AbsPathBuf; | ||
8 | use rustc_hash::FxHashMap; | 7 | use rustc_hash::FxHashMap; |
9 | 8 | ||
10 | use crate::{FileId, Vfs, VfsPath}; | 9 | use crate::{FileId, Vfs, VfsPath}; |
@@ -41,7 +40,7 @@ impl fmt::Debug for FileSet { | |||
41 | #[derive(Debug)] | 40 | #[derive(Debug)] |
42 | pub struct FileSetConfig { | 41 | pub struct FileSetConfig { |
43 | n_file_sets: usize, | 42 | n_file_sets: usize, |
44 | roots: Vec<(AbsPathBuf, usize)>, | 43 | roots: Vec<(VfsPath, usize)>, |
45 | } | 44 | } |
46 | 45 | ||
47 | impl Default for FileSetConfig { | 46 | impl Default for FileSetConfig { |
@@ -66,11 +65,7 @@ impl FileSetConfig { | |||
66 | self.n_file_sets | 65 | self.n_file_sets |
67 | } | 66 | } |
68 | fn classify(&self, path: &VfsPath) -> usize { | 67 | fn classify(&self, path: &VfsPath) -> usize { |
69 | let path = match path.as_path() { | 68 | let idx = match self.roots.binary_search_by(|(p, _)| p.cmp(path)) { |
70 | Some(it) => it, | ||
71 | None => return self.len() - 1, | ||
72 | }; | ||
73 | let idx = match self.roots.binary_search_by(|(p, _)| p.as_path().cmp(path)) { | ||
74 | Ok(it) => it, | 69 | Ok(it) => it, |
75 | Err(it) => it.saturating_sub(1), | 70 | Err(it) => it.saturating_sub(1), |
76 | }; | 71 | }; |
@@ -83,7 +78,7 @@ impl FileSetConfig { | |||
83 | } | 78 | } |
84 | 79 | ||
85 | pub struct FileSetConfigBuilder { | 80 | pub struct FileSetConfigBuilder { |
86 | roots: Vec<Vec<AbsPathBuf>>, | 81 | roots: Vec<Vec<VfsPath>>, |
87 | } | 82 | } |
88 | 83 | ||
89 | impl Default for FileSetConfigBuilder { | 84 | impl Default for FileSetConfigBuilder { |
@@ -96,12 +91,12 @@ impl FileSetConfigBuilder { | |||
96 | pub fn len(&self) -> usize { | 91 | pub fn len(&self) -> usize { |
97 | self.roots.len() | 92 | self.roots.len() |
98 | } | 93 | } |
99 | pub fn add_file_set(&mut self, roots: Vec<AbsPathBuf>) { | 94 | pub fn add_file_set(&mut self, roots: Vec<VfsPath>) { |
100 | self.roots.push(roots) | 95 | self.roots.push(roots) |
101 | } | 96 | } |
102 | pub fn build(self) -> FileSetConfig { | 97 | pub fn build(self) -> FileSetConfig { |
103 | let n_file_sets = self.roots.len() + 1; | 98 | let n_file_sets = self.roots.len() + 1; |
104 | let mut roots: Vec<(AbsPathBuf, usize)> = self | 99 | let mut roots: Vec<(VfsPath, usize)> = self |
105 | .roots | 100 | .roots |
106 | .into_iter() | 101 | .into_iter() |
107 | .enumerate() | 102 | .enumerate() |
diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs index 055219b0c..024e58018 100644 --- a/crates/vfs/src/lib.rs +++ b/crates/vfs/src/lib.rs | |||
@@ -38,7 +38,6 @@ mod vfs_path; | |||
38 | mod path_interner; | 38 | mod path_interner; |
39 | pub mod file_set; | 39 | pub mod file_set; |
40 | pub mod loader; | 40 | pub mod loader; |
41 | pub mod walkdir_loader; | ||
42 | 41 | ||
43 | use std::{fmt, mem}; | 42 | use std::{fmt, mem}; |
44 | 43 | ||
diff --git a/crates/vfs/src/loader.rs b/crates/vfs/src/loader.rs index 5a0ca68f3..a216b5f13 100644 --- a/crates/vfs/src/loader.rs +++ b/crates/vfs/src/loader.rs | |||
@@ -3,19 +3,20 @@ use std::fmt; | |||
3 | 3 | ||
4 | use paths::AbsPathBuf; | 4 | use paths::AbsPathBuf; |
5 | 5 | ||
6 | #[derive(Debug)] | ||
6 | pub enum Entry { | 7 | pub enum Entry { |
7 | Files(Vec<AbsPathBuf>), | 8 | Files(Vec<AbsPathBuf>), |
8 | Directory { path: AbsPathBuf, globs: Vec<String> }, | 9 | Directory { path: AbsPathBuf, include: Vec<String> }, |
9 | } | 10 | } |
10 | 11 | ||
12 | #[derive(Debug)] | ||
11 | pub struct Config { | 13 | pub struct Config { |
12 | pub load: Vec<Entry>, | 14 | pub load: Vec<Entry>, |
13 | pub watch: Vec<usize>, | 15 | pub watch: Vec<usize>, |
14 | } | 16 | } |
15 | 17 | ||
16 | pub enum Message { | 18 | pub enum Message { |
17 | DidSwitchConfig { n_entries: usize }, | 19 | Progress { n_entries_total: usize, n_entries_done: usize }, |
18 | DidLoadAllEntries, | ||
19 | Loaded { files: Vec<(AbsPathBuf, Option<Vec<u8>>)> }, | 20 | Loaded { files: Vec<(AbsPathBuf, Option<Vec<u8>>)> }, |
20 | } | 21 | } |
21 | 22 | ||
@@ -32,15 +33,15 @@ pub trait Handle: fmt::Debug { | |||
32 | 33 | ||
33 | impl Entry { | 34 | impl Entry { |
34 | pub fn rs_files_recursively(base: AbsPathBuf) -> Entry { | 35 | pub fn rs_files_recursively(base: AbsPathBuf) -> Entry { |
35 | Entry::Directory { path: base, globs: globs(&["*.rs"]) } | 36 | Entry::Directory { path: base, include: globs(&["*.rs", "!/.git/"]) } |
36 | } | 37 | } |
37 | pub fn local_cargo_package(base: AbsPathBuf) -> Entry { | 38 | pub fn local_cargo_package(base: AbsPathBuf) -> Entry { |
38 | Entry::Directory { path: base, globs: globs(&["*.rs", "!/target/"]) } | 39 | Entry::Directory { path: base, include: globs(&["*.rs", "!/target/", "!/.git/"]) } |
39 | } | 40 | } |
40 | pub fn cargo_package_dependency(base: AbsPathBuf) -> Entry { | 41 | pub fn cargo_package_dependency(base: AbsPathBuf) -> Entry { |
41 | Entry::Directory { | 42 | Entry::Directory { |
42 | path: base, | 43 | path: base, |
43 | globs: globs(&["*.rs", "!/tests/", "!/examples/", "!/benches/"]), | 44 | include: globs(&["*.rs", "!/tests/", "!/examples/", "!/benches/", "!/.git/"]), |
44 | } | 45 | } |
45 | } | 46 | } |
46 | } | 47 | } |
@@ -55,10 +56,11 @@ impl fmt::Debug for Message { | |||
55 | Message::Loaded { files } => { | 56 | Message::Loaded { files } => { |
56 | f.debug_struct("Loaded").field("n_files", &files.len()).finish() | 57 | f.debug_struct("Loaded").field("n_files", &files.len()).finish() |
57 | } | 58 | } |
58 | Message::DidSwitchConfig { n_entries } => { | 59 | Message::Progress { n_entries_total, n_entries_done } => f |
59 | f.debug_struct("DidSwitchConfig").field("n_entries", n_entries).finish() | 60 | .debug_struct("Progress") |
60 | } | 61 | .field("n_entries_total", n_entries_total) |
61 | Message::DidLoadAllEntries => f.debug_struct("DidLoadAllEntries").finish(), | 62 | .field("n_entries_done", n_entries_done) |
63 | .finish(), | ||
62 | } | 64 | } |
63 | } | 65 | } |
64 | } | 66 | } |
diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs index de5dc0bf3..0a8a86c62 100644 --- a/crates/vfs/src/vfs_path.rs +++ b/crates/vfs/src/vfs_path.rs | |||
@@ -9,9 +9,17 @@ use paths::{AbsPath, AbsPathBuf}; | |||
9 | pub struct VfsPath(VfsPathRepr); | 9 | pub struct VfsPath(VfsPathRepr); |
10 | 10 | ||
11 | impl VfsPath { | 11 | impl VfsPath { |
12 | /// Creates an "in-memory" path from `/`-separates string. | ||
13 | /// This is most useful for testing, to avoid windows/linux differences | ||
14 | pub fn new_virtual_path(path: String) -> VfsPath { | ||
15 | assert!(path.starts_with('/')); | ||
16 | VfsPath(VfsPathRepr::VirtualPath(VirtualPath(path))) | ||
17 | } | ||
18 | |||
12 | pub fn as_path(&self) -> Option<&AbsPath> { | 19 | pub fn as_path(&self) -> Option<&AbsPath> { |
13 | match &self.0 { | 20 | match &self.0 { |
14 | VfsPathRepr::PathBuf(it) => Some(it.as_path()), | 21 | VfsPathRepr::PathBuf(it) => Some(it.as_path()), |
22 | VfsPathRepr::VirtualPath(_) => None, | ||
15 | } | 23 | } |
16 | } | 24 | } |
17 | pub fn join(&self, path: &str) -> VfsPath { | 25 | pub fn join(&self, path: &str) -> VfsPath { |
@@ -20,11 +28,24 @@ impl VfsPath { | |||
20 | let res = it.join(path).normalize(); | 28 | let res = it.join(path).normalize(); |
21 | VfsPath(VfsPathRepr::PathBuf(res)) | 29 | VfsPath(VfsPathRepr::PathBuf(res)) |
22 | } | 30 | } |
31 | VfsPathRepr::VirtualPath(it) => { | ||
32 | let res = it.join(path); | ||
33 | VfsPath(VfsPathRepr::VirtualPath(res)) | ||
34 | } | ||
23 | } | 35 | } |
24 | } | 36 | } |
25 | pub fn pop(&mut self) -> bool { | 37 | pub fn pop(&mut self) -> bool { |
26 | match &mut self.0 { | 38 | match &mut self.0 { |
27 | VfsPathRepr::PathBuf(it) => it.pop(), | 39 | VfsPathRepr::PathBuf(it) => it.pop(), |
40 | VfsPathRepr::VirtualPath(it) => it.pop(), | ||
41 | } | ||
42 | } | ||
43 | pub fn starts_with(&self, other: &VfsPath) -> bool { | ||
44 | match (&self.0, &other.0) { | ||
45 | (VfsPathRepr::PathBuf(lhs), VfsPathRepr::PathBuf(rhs)) => lhs.starts_with(rhs), | ||
46 | (VfsPathRepr::PathBuf(_), _) => false, | ||
47 | (VfsPathRepr::VirtualPath(lhs), VfsPathRepr::VirtualPath(rhs)) => lhs.starts_with(rhs), | ||
48 | (VfsPathRepr::VirtualPath(_), _) => false, | ||
28 | } | 49 | } |
29 | } | 50 | } |
30 | } | 51 | } |
@@ -32,11 +53,12 @@ impl VfsPath { | |||
32 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] | 53 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] |
33 | enum VfsPathRepr { | 54 | enum VfsPathRepr { |
34 | PathBuf(AbsPathBuf), | 55 | PathBuf(AbsPathBuf), |
56 | VirtualPath(VirtualPath), | ||
35 | } | 57 | } |
36 | 58 | ||
37 | impl From<AbsPathBuf> for VfsPath { | 59 | impl From<AbsPathBuf> for VfsPath { |
38 | fn from(v: AbsPathBuf) -> Self { | 60 | fn from(v: AbsPathBuf) -> Self { |
39 | VfsPath(VfsPathRepr::PathBuf(v)) | 61 | VfsPath(VfsPathRepr::PathBuf(v.normalize())) |
40 | } | 62 | } |
41 | } | 63 | } |
42 | 64 | ||
@@ -44,6 +66,33 @@ impl fmt::Display for VfsPath { | |||
44 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 66 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
45 | match &self.0 { | 67 | match &self.0 { |
46 | VfsPathRepr::PathBuf(it) => fmt::Display::fmt(&it.display(), f), | 68 | VfsPathRepr::PathBuf(it) => fmt::Display::fmt(&it.display(), f), |
69 | VfsPathRepr::VirtualPath(VirtualPath(it)) => fmt::Display::fmt(it, f), | ||
70 | } | ||
71 | } | ||
72 | } | ||
73 | |||
74 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] | ||
75 | struct VirtualPath(String); | ||
76 | |||
77 | impl VirtualPath { | ||
78 | fn starts_with(&self, other: &VirtualPath) -> bool { | ||
79 | self.0.starts_with(&other.0) | ||
80 | } | ||
81 | fn pop(&mut self) -> bool { | ||
82 | let pos = match self.0.rfind('/') { | ||
83 | Some(pos) => pos, | ||
84 | None => return false, | ||
85 | }; | ||
86 | self.0 = self.0[..pos].to_string(); | ||
87 | true | ||
88 | } | ||
89 | fn join(&self, mut path: &str) -> VirtualPath { | ||
90 | let mut res = self.clone(); | ||
91 | while path.starts_with("../") { | ||
92 | assert!(res.pop()); | ||
93 | path = &path["../".len()..] | ||
47 | } | 94 | } |
95 | res.0 = format!("{}/{}", res.0, path); | ||
96 | res | ||
48 | } | 97 | } |
49 | } | 98 | } |
diff --git a/crates/vfs/src/walkdir_loader.rs b/crates/vfs/src/walkdir_loader.rs deleted file mode 100644 index 13e59e3f3..000000000 --- a/crates/vfs/src/walkdir_loader.rs +++ /dev/null | |||
@@ -1,108 +0,0 @@ | |||
1 | //! A walkdir-based implementation of `loader::Handle`, which doesn't try to | ||
2 | //! watch files. | ||
3 | use std::convert::TryFrom; | ||
4 | |||
5 | use globset::{Glob, GlobSetBuilder}; | ||
6 | use paths::{AbsPath, AbsPathBuf}; | ||
7 | use walkdir::WalkDir; | ||
8 | |||
9 | use crate::loader; | ||
10 | |||
11 | #[derive(Debug)] | ||
12 | pub struct WalkdirLoaderHandle { | ||
13 | // Relative order of fields below is significant. | ||
14 | sender: crossbeam_channel::Sender<Message>, | ||
15 | _thread: jod_thread::JoinHandle, | ||
16 | } | ||
17 | |||
18 | enum Message { | ||
19 | Config(loader::Config), | ||
20 | Invalidate(AbsPathBuf), | ||
21 | } | ||
22 | |||
23 | impl loader::Handle for WalkdirLoaderHandle { | ||
24 | fn spawn(sender: loader::Sender) -> WalkdirLoaderHandle { | ||
25 | let actor = WalkdirLoaderActor { sender }; | ||
26 | let (sender, receiver) = crossbeam_channel::unbounded::<Message>(); | ||
27 | let thread = jod_thread::spawn(move || actor.run(receiver)); | ||
28 | WalkdirLoaderHandle { sender, _thread: thread } | ||
29 | } | ||
30 | fn set_config(&mut self, config: loader::Config) { | ||
31 | self.sender.send(Message::Config(config)).unwrap() | ||
32 | } | ||
33 | fn invalidate(&mut self, path: AbsPathBuf) { | ||
34 | self.sender.send(Message::Invalidate(path)).unwrap(); | ||
35 | } | ||
36 | fn load_sync(&mut self, path: &AbsPathBuf) -> Option<Vec<u8>> { | ||
37 | read(path) | ||
38 | } | ||
39 | } | ||
40 | |||
41 | struct WalkdirLoaderActor { | ||
42 | sender: loader::Sender, | ||
43 | } | ||
44 | |||
45 | impl WalkdirLoaderActor { | ||
46 | fn run(mut self, receiver: crossbeam_channel::Receiver<Message>) { | ||
47 | for msg in receiver { | ||
48 | match msg { | ||
49 | Message::Config(config) => { | ||
50 | self.send(loader::Message::DidSwitchConfig { n_entries: config.load.len() }); | ||
51 | for entry in config.load.into_iter() { | ||
52 | let files = self.load_entry(entry); | ||
53 | self.send(loader::Message::Loaded { files }); | ||
54 | } | ||
55 | drop(config.watch); | ||
56 | self.send(loader::Message::DidLoadAllEntries); | ||
57 | } | ||
58 | Message::Invalidate(path) => { | ||
59 | let contents = read(path.as_path()); | ||
60 | let files = vec![(path, contents)]; | ||
61 | self.send(loader::Message::Loaded { files }); | ||
62 | } | ||
63 | } | ||
64 | } | ||
65 | } | ||
66 | fn load_entry(&mut self, entry: loader::Entry) -> Vec<(AbsPathBuf, Option<Vec<u8>>)> { | ||
67 | match entry { | ||
68 | loader::Entry::Files(files) => files | ||
69 | .into_iter() | ||
70 | .map(|file| { | ||
71 | let contents = read(file.as_path()); | ||
72 | (file, contents) | ||
73 | }) | ||
74 | .collect::<Vec<_>>(), | ||
75 | loader::Entry::Directory { path, globs } => { | ||
76 | let globset = { | ||
77 | let mut builder = GlobSetBuilder::new(); | ||
78 | for glob in &globs { | ||
79 | builder.add(Glob::new(glob).unwrap()); | ||
80 | } | ||
81 | builder.build().unwrap() | ||
82 | }; | ||
83 | |||
84 | let files = WalkDir::new(path) | ||
85 | .into_iter() | ||
86 | .filter_map(|it| it.ok()) | ||
87 | .filter(|it| it.file_type().is_file()) | ||
88 | .map(|it| it.into_path()) | ||
89 | .map(|it| AbsPathBuf::try_from(it).unwrap()) | ||
90 | .filter(|it| globset.is_match(&it)); | ||
91 | |||
92 | files | ||
93 | .map(|file| { | ||
94 | let contents = read(file.as_path()); | ||
95 | (file, contents) | ||
96 | }) | ||
97 | .collect() | ||
98 | } | ||
99 | } | ||
100 | } | ||
101 | fn send(&mut self, msg: loader::Message) { | ||
102 | (self.sender)(msg) | ||
103 | } | ||
104 | } | ||
105 | |||
106 | fn read(path: &AbsPath) -> Option<Vec<u8>> { | ||
107 | std::fs::read(path).ok() | ||
108 | } | ||