aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-06-11 10:04:09 +0100
committerAleksey Kladov <[email protected]>2020-06-23 16:51:06 +0100
commitdad1333b48c38bc7a5628fc0ff5304d003776a85 (patch)
tree29be52a980b4cae72f46a48c48135a15e31641e0 /crates
parent7aa66371ee3e8b31217513204c8b4f683584419d (diff)
New VFS
Diffstat (limited to 'crates')
-rw-r--r--crates/paths/src/lib.rs5
-rw-r--r--crates/ra_assists/src/tests.rs13
-rw-r--r--crates/ra_db/Cargo.toml2
-rw-r--r--crates/ra_db/src/fixture.rs32
-rw-r--r--crates/ra_db/src/input.rs79
-rw-r--r--crates/ra_db/src/lib.rs28
-rw-r--r--crates/ra_hir/src/has_source.rs10
-rw-r--r--crates/ra_hir_def/src/nameres.rs1
-rw-r--r--crates/ra_hir_def/src/nameres/collector.rs10
-rw-r--r--crates/ra_hir_def/src/nameres/mod_resolution.rs7
-rw-r--r--crates/ra_ide/src/lib.rs17
-rw-r--r--crates/ra_ide/src/mock_analysis.rs17
-rw-r--r--crates/ra_ide/src/parent_module.rs1
-rw-r--r--crates/ra_ide/src/references/rename.rs5
-rw-r--r--crates/ra_ide/src/ssr.rs2
-rw-r--r--crates/ra_ide_db/src/change.rs85
-rw-r--r--crates/ra_ide_db/src/search.rs4
-rw-r--r--crates/ra_ide_db/src/symbol_index.rs10
-rw-r--r--crates/ra_project_model/src/lib.rs15
-rw-r--r--crates/rust-analyzer/Cargo.toml3
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs4
-rw-r--r--crates/rust-analyzer/src/cli/analysis_bench.rs27
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs25
-rw-r--r--crates/rust-analyzer/src/cli/diagnostics.rs73
-rw-r--r--crates/rust-analyzer/src/cli/load_cargo.rs166
-rw-r--r--crates/rust-analyzer/src/from_proto.rs12
-rw-r--r--crates/rust-analyzer/src/global_state.rs281
-rw-r--r--crates/rust-analyzer/src/lib.rs2
-rw-r--r--crates/rust-analyzer/src/line_endings.rs64
-rw-r--r--crates/rust-analyzer/src/main_loop.rs330
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs5
-rw-r--r--crates/rust-analyzer/src/main_loop/subscriptions.rs22
-rw-r--r--crates/rust-analyzer/src/to_proto.rs8
-rw-r--r--crates/rust-analyzer/src/vfs_glob.rs98
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs2
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/support.rs2
-rw-r--r--crates/vfs-notify/Cargo.toml17
-rw-r--r--crates/vfs-notify/src/include.rs43
-rw-r--r--crates/vfs-notify/src/lib.rs247
-rw-r--r--crates/vfs/Cargo.toml4
-rw-r--r--crates/vfs/src/file_set.rs15
-rw-r--r--crates/vfs/src/lib.rs1
-rw-r--r--crates/vfs/src/loader.rs22
-rw-r--r--crates/vfs/src/vfs_path.rs51
-rw-r--r--crates/vfs/src/walkdir_loader.rs108
45 files changed, 1001 insertions, 974 deletions
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.
3use std::{ 3use 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
48impl AbsPathBuf { 48impl 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 @@
1mod generated; 1mod generated;
2 2
3use std::sync::Arc;
4
5use hir::Semantics; 3use hir::Semantics;
6use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; 4use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
7use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; 5use ra_ide_db::RootDatabase;
8use ra_syntax::TextRange; 6use ra_syntax::TextRange;
9use test_utils::{ 7use 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::{
13use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; 11use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists};
14 12
15pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { 13pub(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
23pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) { 17pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
@@ -72,8 +66,7 @@ enum ExpectedResult<'a> {
72 66
73fn check(handler: Handler, before: &str, expected: ExpectedResult) { 67fn 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" }
17ra_prof = { path = "../ra_prof" } 17ra_prof = { path = "../ra_prof" }
18ra_tt = { path = "../ra_tt" } 18ra_tt = { path = "../ra_tt" }
19test_utils = { path = "../test_utils" } 19test_utils = { path = "../test_utils" }
20vfs = { path = "../vfs" }
21stdx = { 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 60use std::{str::FromStr, sync::Arc};
61use std::str::FromStr;
62use std::sync::Arc;
63 61
64use ra_cfg::CfgOptions; 62use ra_cfg::CfgOptions;
65use rustc_hash::FxHashMap; 63use rustc_hash::FxHashMap;
66use test_utils::{extract_offset, parse_fixture, parse_single_fixture, FixtureMeta, CURSOR_MARKER}; 64use test_utils::{extract_offset, parse_fixture, parse_single_fixture, FixtureMeta, CURSOR_MARKER};
65use vfs::{file_set::FileSet, VfsPath};
67 66
68use crate::{ 67use 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
73pub const WORKSPACE: SourceRootId = SourceRootId(0); 72pub const WORKSPACE: SourceRootId = SourceRootId(0);
@@ -105,10 +104,10 @@ impl<DB: SourceDatabaseExt + Default + 'static> WithFixture for DB {}
105 104
106fn with_single_file(db: &mut dyn SourceDatabaseExt, ra_fixture: &str) -> FileId { 105fn 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
9use std::{ 9use std::{fmt, ops, str::FromStr, sync::Arc};
10 fmt, ops,
11 path::{Path, PathBuf},
12 str::FromStr,
13 sync::Arc,
14};
15 10
16use ra_cfg::CfgOptions; 11use ra_cfg::CfgOptions;
17use ra_syntax::SmolStr; 12use ra_syntax::SmolStr;
18use ra_tt::TokenExpander; 13use ra_tt::TokenExpander;
19use rustc_hash::{FxHashMap, FxHashSet}; 14use rustc_hash::{FxHashMap, FxHashSet};
15use vfs::file_set::FileSet;
20 16
21use crate::{RelativePath, RelativePathBuf}; 17pub 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)]
29pub 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
51impl SourceRoot { 39impl 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)]
156pub struct ExternSourceId(pub u32);
157
158#[derive(Default, Debug, Clone, PartialEq, Eq)] 133#[derive(Default, Debug, Clone, PartialEq, Eq)]
159pub struct Env { 134pub 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)]
167pub struct ExternSource {
168 extern_paths: FxHashMap<PathBuf, ExternSourceId>,
169}
170
171#[derive(Debug, Clone, PartialEq, Eq)] 138#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct Dependency { 139pub 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
337impl 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)]
352pub struct ParseEditionError { 303pub 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;
12pub use crate::{ 12pub 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};
19pub use relative_path::{RelativePath, RelativePathBuf}; 19pub use relative_path::{RelativePath, RelativePathBuf};
20pub use salsa; 20pub use salsa;
21pub use vfs::{file_set::FileSet, AbsPathBuf, VfsPath};
21 22
22#[macro_export] 23#[macro_export]
23macro_rules! impl_intern_key { 24macro_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
3use either::Either; 3use either::Either;
4use hir_def::{ 4use 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;
47use ra_cfg::CfgOptions; 47use ra_cfg::CfgOptions;
48use ra_db::{ 48use ra_db::{
49 salsa::{self, ParallelDatabase}, 49 salsa::{self, ParallelDatabase},
50 CheckCanceled, Env, FileLoader, SourceDatabase, 50 CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath,
51}; 51};
52use ra_ide_db::{ 52use ra_ide_db::{
53 symbol_index::{self, FileSymbol}, 53 symbol_index::{self, FileSymbol},
@@ -78,7 +78,8 @@ pub use crate::{
78pub use hir::Documentation; 78pub use hir::Documentation;
79pub use ra_assists::{Assist, AssistConfig, AssistId, ResolvedAssist}; 79pub use ra_assists::{Assist, AssistConfig, AssistId, ResolvedAssist};
80pub use ra_db::{ 80pub 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};
83pub use ra_ide_db::{ 84pub 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 2use std::{str::FromStr, sync::Arc};
3use std::str::FromStr;
4use std::sync::Arc;
5 3
6use ra_cfg::CfgOptions; 4use ra_cfg::CfgOptions;
7use ra_db::{CrateName, Env}; 5use ra_db::{CrateName, Env, FileSet, SourceRoot, VfsPath};
8use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER}; 6use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER};
9 7
10use crate::{ 8use 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
3use hir::{Module, ModuleDef, ModuleSource, Semantics}; 3use hir::{Module, ModuleDef, ModuleSource, Semantics};
4use ra_db::{RelativePathBuf, SourceDatabaseExt}; 4use ra_db::SourceDatabaseExt;
5use ra_ide_db::{ 5use 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};
11use ra_prof::{memory_usage, profile, Bytes}; 11use ra_prof::{memory_usage, profile, Bytes};
12use rustc_hash::FxHashMap; 12use rustc_hash::FxHashSet;
13 13
14use crate::{symbol_index::SymbolsDatabase, RootDatabase}; 14use crate::{symbol_index::SymbolsDatabase, RootDatabase};
15 15
16#[derive(Default)] 16#[derive(Default)]
17pub struct AnalysisChange { 17pub 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
24impl fmt::Debug for AnalysisChange { 23impl 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};
44use rayon::prelude::*; 44use rayon::prelude::*;
45use rustc_hash::FxHashMap; 45use rustc_hash::{FxHashMap, FxHashSet};
46 46
47use crate::RootDatabase; 47use 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
103fn library_symbols( 103fn 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
14use anyhow::{bail, Context, Result}; 14use anyhow::{bail, Context, Result};
15use ra_cfg::CfgOptions; 15use ra_cfg::CfgOptions;
16use ra_db::{CrateGraph, CrateName, Edition, Env, ExternSource, ExternSourceId, FileId}; 16use ra_db::{CrateGraph, CrateName, Edition, Env, FileId};
17use rustc_hash::{FxHashMap, FxHashSet}; 17use rustc_hash::{FxHashMap, FxHashSet};
18use serde_json::from_reader; 18use 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" }
38ra_project_model = { path = "../ra_project_model" } 38ra_project_model = { path = "../ra_project_model" }
39ra_syntax = { path = "../ra_syntax" } 39ra_syntax = { path = "../ra_syntax" }
40ra_text_edit = { path = "../ra_text_edit" } 40ra_text_edit = { path = "../ra_text_edit" }
41ra_vfs = "0.6.0" 41vfs = { path = "../vfs" }
42vfs-notify = { path = "../vfs-notify" }
42ra_cfg = { path = "../ra_cfg"} 43ra_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
3use std::path::PathBuf;
4
3use ra_cfg::CfgExpr; 5use ra_cfg::CfgExpr;
4use ra_ide::{FileId, RunnableKind, TestId}; 6use ra_ide::{FileId, RunnableKind, TestId};
5use ra_project_model::{self, TargetKind}; 7use 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)]
14pub(crate) struct CargoTargetSpec { 16pub(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
3use std::{ 3use 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::{
10use anyhow::{format_err, Result}; 11use anyhow::{format_err, Result};
11use ra_db::{ 12use ra_db::{
12 salsa::{Database, Durability}, 13 salsa::{Database, Durability},
13 FileId, SourceDatabaseExt, 14 AbsPathBuf, FileId,
14}; 15};
15use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CompletionConfig, FilePosition, LineCol}; 16use 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
4use anyhow::anyhow; 4use anyhow::anyhow;
5use hir::Crate;
5use ra_db::SourceDatabaseExt; 6use ra_db::SourceDatabaseExt;
6use ra_ide::Severity; 7use ra_ide::Severity;
7use std::{collections::HashSet, path::Path}; 8use std::{collections::HashSet, path::Path};
8 9
9use crate::cli::{load_cargo::load_cargo, Result}; 10use crate::cli::{load_cargo::load_cargo, Result};
10use hir::Semantics;
11 11
12pub fn diagnostics( 12pub 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 3use std::{convert::TryFrom, path::Path, sync::Arc};
4use std::path::{Path, PathBuf};
5 4
6use anyhow::Result; 5use anyhow::Result;
7use crossbeam_channel::{unbounded, Receiver}; 6use crossbeam_channel::{unbounded, Receiver};
8use ra_db::{ExternSourceId, FileId, SourceRootId}; 7use ra_db::{AbsPathBuf, CrateGraph};
9use ra_ide::{AnalysisChange, AnalysisHost}; 8use ra_ide::{AnalysisChange, AnalysisHost};
10use ra_project_model::{ 9use ra_project_model::{CargoConfig, ProcMacroClient, ProjectManifest, ProjectWorkspace};
11 CargoConfig, PackageRoot, ProcMacroClient, ProjectManifest, ProjectWorkspace, 10use vfs::loader::Handle;
12};
13use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch};
14use rustc_hash::{FxHashMap, FxHashSet};
15
16use crate::vfs_glob::RustPackageFilterBuilder;
17 11
18fn vfs_file_to_id(f: ra_vfs::VfsFile) -> FileId { 12use crate::global_state::{ProjectFolders, SourceRootConfig};
19 FileId(f.0)
20}
21fn vfs_root_to_id(r: ra_vfs::VfsRoot) -> SourceRootId {
22 SourceRootId(r.0)
23}
24 13
25pub fn load_cargo( 14pub 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
85pub(crate) fn load( 58pub(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.
2use std::convert::TryFrom;
3
2use ra_db::{FileId, FilePosition, FileRange}; 4use ra_db::{FileId, FilePosition, FileRange};
3use ra_ide::{LineCol, LineIndex}; 5use ra_ide::{LineCol, LineIndex};
4use ra_syntax::{TextRange, TextSize}; 6use ra_syntax::{TextRange, TextSize};
7use vfs::AbsPathBuf;
5 8
6use crate::{global_state::GlobalStateSnapshot, Result}; 9use crate::{global_state::GlobalStateSnapshot, Result};
7 10
11pub(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
16pub(crate) fn vfs_path(url: &lsp_types::Url) -> Result<vfs::VfsPath> {
17 abs_path(url).map(vfs::VfsPath::from)
18}
19
8pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> TextSize { 20pub(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
6use std::{ 6use std::{convert::TryFrom, path::Path, sync::Arc};
7 path::{Path, PathBuf},
8 sync::Arc,
9};
10 7
11use crossbeam_channel::{unbounded, Receiver}; 8use crossbeam_channel::{unbounded, Receiver};
12use lsp_types::Url; 9use lsp_types::Url;
13use parking_lot::RwLock; 10use parking_lot::RwLock;
11use ra_db::{CrateId, SourceRoot, VfsPath};
14use ra_flycheck::{Flycheck, FlycheckConfig}; 12use ra_flycheck::{Flycheck, FlycheckConfig};
15use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, SourceRootId}; 13use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId};
16use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target}; 14use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target};
17use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsTask, Watch};
18use stdx::format_to; 15use stdx::format_to;
16use vfs::{file_set::FileSetConfig, loader::Handle, AbsPathBuf};
19 17
20use crate::{ 18use 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};
28use ra_db::{CrateId, ExternSourceId}; 27use rustc_hash::FxHashMap;
29use rustc_hash::{FxHashMap, FxHashSet};
30 28
31fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> { 29fn 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)]
51pub struct GlobalState { 49pub 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
74impl GlobalState { 73impl 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
244impl GlobalStateSnapshot { 224impl 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
289pub(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)]
296pub(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
302impl 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
317pub(crate) fn file_id_to_url(vfs: &Vfs, id: FileId) -> Url { 347#[derive(Default, Debug)]
318 let path = vfs.file2path(VfsFile(id.0)); 348pub(crate) struct SourceRootConfig {
319 url_from_abs_path(&path) 349 pub(crate) fsc: FileSetConfig,
350 pub(crate) local_filesets: Vec<usize>,
351}
352
353impl 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
20mod vfs_glob;
21mod caps; 20mod caps;
22mod cargo_target_spec; 21mod cargo_target_spec;
23mod to_proto; 22mod to_proto;
@@ -29,6 +28,7 @@ pub mod config;
29mod global_state; 28mod global_state;
30mod diagnostics; 29mod diagnostics;
31mod semantic_tokens; 30mod semantic_tokens;
31mod line_endings;
32 32
33use serde::de::DeserializeOwned; 33use 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)]
6pub(crate) enum LineEndings {
7 Unix,
8 Dos,
9}
10
11impl 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
4mod handlers; 4mod handlers;
5mod subscriptions;
6pub(crate) mod request_metrics; 5pub(crate) mod request_metrics;
7 6
8use std::{ 7use 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};
20use lsp_server::{ 18use lsp_server::{
21 Connection, ErrorCode, Message, Notification, ReqQueue, Request, RequestId, Response, 19 Connection, ErrorCode, Message, Notification, ReqQueue, Request, RequestId, Response,
22}; 20};
23use lsp_types::{ 21use lsp_types::{request::Request as _, NumberOrString, TextDocumentContentChangeEvent};
24 request::Request as _, DidChangeTextDocumentParams, NumberOrString, 22use ra_flycheck::CheckTask;
25 TextDocumentContentChangeEvent, WorkDoneProgress, WorkDoneProgressBegin,
26 WorkDoneProgressCreateParams, WorkDoneProgressEnd, WorkDoneProgressReport,
27};
28use ra_flycheck::{CheckTask, Status};
29use ra_ide::{Canceled, FileId, LineIndex}; 23use ra_ide::{Canceled, FileId, LineIndex};
30use ra_prof::profile; 24use ra_prof::profile;
31use ra_project_model::{PackageRoot, ProjectWorkspace}; 25use ra_project_model::{PackageRoot, ProjectWorkspace};
32use ra_vfs::VfsTask; 26use rustc_hash::FxHashSet;
33use serde::{de::DeserializeOwned, Serialize}; 27use serde::{de::DeserializeOwned, Serialize};
34use threadpool::ThreadPool; 28use 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};
39use ra_db::VfsPath;
45 40
46#[derive(Debug)] 41#[derive(Debug)]
47pub struct LspError { 42pub 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 {
222enum Event { 210enum 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)]
271struct LoopState { 259struct 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)]
266enum Status {
267 Loading,
268 Ready,
269}
270
271impl Default for Status {
272 fn default() -> Self {
273 Status::Loading
274 }
278} 275}
279 276
280fn loop_turn( 277fn 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
466fn on_notification( 482fn 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(&params.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(&params.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(&params.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
595fn apply_document_changes( 618fn 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
731fn send_startup_progress(sender: &Sender<Message>, loop_state: &mut LoopState) { 747fn 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
784struct PoolDispatcher<'a> { 789struct PoolDispatcher<'a> {
@@ -976,18 +981,12 @@ where
976 981
977#[cfg(test)] 982#[cfg(test)]
978mod tests { 983mod 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
4use ra_ide::FileId;
5use rustc_hash::FxHashSet;
6
7#[derive(Default, Debug)]
8pub(crate) struct Subscriptions {
9 subs: FxHashSet<FileId>,
10}
11
12impl 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};
12use ra_syntax::{SyntaxKind, TextRange, TextSize}; 12use ra_syntax::{SyntaxKind, TextRange, TextSize};
13use ra_vfs::LineEndings;
14 13
15use crate::{ 14use 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
20pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position { 19pub(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
8use globset::{GlobSet, GlobSetBuilder};
9use ra_vfs::{Filter, RelativePath};
10
11pub use globset::{Glob, GlobBuilder};
12
13const ALWAYS_IGNORED: &[&str] = &["target/**", "**/node_modules/**", "**/.git/**"];
14const IGNORED_FOR_NON_MEMBERS: &[&str] = &["examples/**", "tests/**", "benches/**"];
15
16pub struct RustPackageFilterBuilder {
17 is_member: bool,
18 exclude: GlobSetBuilder,
19}
20
21impl Default for RustPackageFilterBuilder {
22 fn default() -> RustPackageFilterBuilder {
23 RustPackageFilterBuilder { is_member: false, exclude: GlobSetBuilder::new() }
24 }
25}
26
27impl 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
54struct RustPackageFilter {
55 exclude: GlobSet,
56}
57
58impl 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]
69fn 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]
2name = "vfs-notify"
3version = "0.1.0"
4authors = ["rust-analyzer developers"]
5edition = "2018"
6
7[dependencies]
8log = "0.4.8"
9rustc-hash = "1.0"
10jod-thread = "0.1.0"
11walkdir = "2.3.1"
12globset = "0.4.5"
13crossbeam-channel = "0.4.0"
14notify = "5.0.0-pre.3"
15
16vfs = { path = "../vfs" }
17paths = { 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
3use std::convert::TryFrom;
4
5use globset::{Glob, GlobSet, GlobSetBuilder};
6use 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)]
15pub(crate) struct Include {
16 include_files: GlobSet,
17 exclude_dirs: Vec<RelPathBuf>,
18}
19
20impl 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.
9mod include;
10
11use std::convert::{TryFrom, TryInto};
12
13use crossbeam_channel::{select, unbounded, Receiver};
14use notify::{RecommendedWatcher, RecursiveMode, Watcher};
15use paths::{AbsPath, AbsPathBuf};
16use rustc_hash::FxHashSet;
17use vfs::loader;
18use walkdir::WalkDir;
19
20use crate::include::Include;
21
22#[derive(Debug)]
23pub 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)]
30enum Message {
31 Config(loader::Config),
32 Invalidate(AbsPathBuf),
33}
34
35impl 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
53type NotifyEvent = notify::Result<notify::Event>;
54
55struct 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)]
65enum Event {
66 Message(Message),
67 NotifyEvent(NotifyEvent),
68}
69
70impl 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
241fn read(path: &AbsPath) -> Option<Vec<u8>> {
242 std::fs::read(path).ok()
243}
244
245fn 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]
8rustc-hash = "1.0" 8rustc-hash = "1.0"
9jod-thread = "0.1.0"
10walkdir = "2.3.1"
11globset = "0.4.5"
12crossbeam-channel = "0.4.0"
13 9
14paths = { path = "../paths" } 10paths = { 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`.
5use std::{fmt, iter}; 5use std::{fmt, iter};
6 6
7use paths::AbsPathBuf;
8use rustc_hash::FxHashMap; 7use rustc_hash::FxHashMap;
9 8
10use crate::{FileId, Vfs, VfsPath}; 9use crate::{FileId, Vfs, VfsPath};
@@ -41,7 +40,7 @@ impl fmt::Debug for FileSet {
41#[derive(Debug)] 40#[derive(Debug)]
42pub struct FileSetConfig { 41pub 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
47impl Default for FileSetConfig { 46impl 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
85pub struct FileSetConfigBuilder { 80pub struct FileSetConfigBuilder {
86 roots: Vec<Vec<AbsPathBuf>>, 81 roots: Vec<Vec<VfsPath>>,
87} 82}
88 83
89impl Default for FileSetConfigBuilder { 84impl 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;
38mod path_interner; 38mod path_interner;
39pub mod file_set; 39pub mod file_set;
40pub mod loader; 40pub mod loader;
41pub mod walkdir_loader;
42 41
43use std::{fmt, mem}; 42use 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
4use paths::AbsPathBuf; 4use paths::AbsPathBuf;
5 5
6#[derive(Debug)]
6pub enum Entry { 7pub 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)]
11pub struct Config { 13pub 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
16pub enum Message { 18pub 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
33impl Entry { 34impl 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};
9pub struct VfsPath(VfsPathRepr); 9pub struct VfsPath(VfsPathRepr);
10 10
11impl VfsPath { 11impl 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)]
33enum VfsPathRepr { 54enum VfsPathRepr {
34 PathBuf(AbsPathBuf), 55 PathBuf(AbsPathBuf),
56 VirtualPath(VirtualPath),
35} 57}
36 58
37impl From<AbsPathBuf> for VfsPath { 59impl 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)]
75struct VirtualPath(String);
76
77impl 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.
3use std::convert::TryFrom;
4
5use globset::{Glob, GlobSetBuilder};
6use paths::{AbsPath, AbsPathBuf};
7use walkdir::WalkDir;
8
9use crate::loader;
10
11#[derive(Debug)]
12pub struct WalkdirLoaderHandle {
13 // Relative order of fields below is significant.
14 sender: crossbeam_channel::Sender<Message>,
15 _thread: jod_thread::JoinHandle,
16}
17
18enum Message {
19 Config(loader::Config),
20 Invalidate(AbsPathBuf),
21}
22
23impl 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
41struct WalkdirLoaderActor {
42 sender: loader::Sender,
43}
44
45impl 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
106fn read(path: &AbsPath) -> Option<Vec<u8>> {
107 std::fs::read(path).ok()
108}