mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-01 23:12:02 +00:00
New VFS
This commit is contained in:
parent
7aa66371ee
commit
dad1333b48
50
Cargo.lock
generated
50
Cargo.lock
generated
@ -354,9 +354,9 @@ checksum = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674"
|
||||
|
||||
[[package]]
|
||||
name = "fsevent"
|
||||
version = "0.4.0"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6"
|
||||
checksum = "97f347202c95c98805c216f9e1df210e8ebaec9fdb2365700a43c10797a35e63"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fsevent-sys",
|
||||
@ -364,9 +364,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "2.0.1"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0"
|
||||
checksum = "77a29c77f1ca394c3e73a9a5d24cfcabb734682d9634fc398f2204a63c994120"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@ -483,9 +483,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.7.1"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f"
|
||||
checksum = "46dd0a94b393c730779ccfd2a872b67b1eb67be3fc33082e733bdb38b5fde4d4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"inotify-sys",
|
||||
@ -766,11 +766,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "4.0.15"
|
||||
version = "5.0.0-pre.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80ae4a7688d1fab81c5bf19c64fc8db920be8d519ce6336ed4e7efe024724dbd"
|
||||
checksum = "77d03607cf88b4b160ba0e9ed425fff3cee3b55ac813f0c685b3a3772da37d0e"
|
||||
dependencies = [
|
||||
"anymap",
|
||||
"bitflags",
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"fsevent",
|
||||
"fsevent-sys",
|
||||
@ -952,7 +954,9 @@ dependencies = [
|
||||
"relative-path",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"stdx",
|
||||
"test_utils",
|
||||
"vfs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1232,22 +1236,6 @@ dependencies = [
|
||||
"smol_str",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ra_vfs"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbf31a173fc77ec59c27cf39af6baa137b40f4dbd45a8b3eccb1b2e4cfc922c1"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"jod-thread",
|
||||
"log",
|
||||
"notify",
|
||||
"parking_lot",
|
||||
"relative-path",
|
||||
"rustc-hash",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.7.3"
|
||||
@ -1405,7 +1393,6 @@ dependencies = [
|
||||
"ra_syntax",
|
||||
"ra_text_edit",
|
||||
"ra_tt",
|
||||
"ra_vfs",
|
||||
"rand",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
@ -1414,6 +1401,8 @@ dependencies = [
|
||||
"tempfile",
|
||||
"test_utils",
|
||||
"threadpool",
|
||||
"vfs",
|
||||
"vfs-notify",
|
||||
"winapi 0.3.8",
|
||||
]
|
||||
|
||||
@ -1763,12 +1752,23 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "vfs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"paths",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vfs-notify"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"globset",
|
||||
"jod-thread",
|
||||
"log",
|
||||
"notify",
|
||||
"paths",
|
||||
"rustc-hash",
|
||||
"vfs",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
//! relative paths.
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
io, ops,
|
||||
ops,
|
||||
path::{Component, Path, PathBuf},
|
||||
};
|
||||
|
||||
@ -46,9 +46,6 @@ impl TryFrom<&str> for AbsPathBuf {
|
||||
}
|
||||
|
||||
impl AbsPathBuf {
|
||||
pub fn canonicalized(path: &Path) -> io::Result<AbsPathBuf> {
|
||||
path.canonicalize().map(|it| AbsPathBuf::try_from(it).unwrap())
|
||||
}
|
||||
pub fn as_path(&self) -> &AbsPath {
|
||||
AbsPath::new_unchecked(self.0.as_path())
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
mod generated;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use hir::Semantics;
|
||||
use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
|
||||
use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
|
||||
use ra_ide_db::RootDatabase;
|
||||
use ra_syntax::TextRange;
|
||||
use test_utils::{
|
||||
assert_eq_text, extract_offset, extract_range, extract_range_or_offset, RangeOrOffset,
|
||||
@ -13,11 +11,7 @@ use test_utils::{
|
||||
use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists};
|
||||
|
||||
pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
|
||||
let (mut db, file_id) = RootDatabase::with_single_file(text);
|
||||
// FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`,
|
||||
// but it looks like this might need specialization? :(
|
||||
db.set_local_roots(Arc::new(vec![db.file_source_root(file_id)]));
|
||||
(db, file_id)
|
||||
RootDatabase::with_single_file(text)
|
||||
}
|
||||
|
||||
pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
|
||||
@ -72,8 +66,7 @@ enum ExpectedResult<'a> {
|
||||
|
||||
fn check(handler: Handler, before: &str, expected: ExpectedResult) {
|
||||
let (text_without_caret, file_with_caret_id, range_or_offset, db) = if before.contains("//-") {
|
||||
let (mut db, position) = RootDatabase::with_position(before);
|
||||
db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)]));
|
||||
let (db, position) = RootDatabase::with_position(before);
|
||||
(
|
||||
db.file_text(position.file_id).as_ref().to_owned(),
|
||||
position.file_id,
|
||||
|
@ -17,3 +17,5 @@ ra_cfg = { path = "../ra_cfg" }
|
||||
ra_prof = { path = "../ra_prof" }
|
||||
ra_tt = { path = "../ra_tt" }
|
||||
test_utils = { path = "../test_utils" }
|
||||
vfs = { path = "../vfs" }
|
||||
stdx = { path = "../stdx" }
|
||||
|
@ -57,17 +57,16 @@
|
||||
//! fn insert_source_code_here() {}
|
||||
//! "
|
||||
//! ```
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
|
||||
use ra_cfg::CfgOptions;
|
||||
use rustc_hash::FxHashMap;
|
||||
use test_utils::{extract_offset, parse_fixture, parse_single_fixture, FixtureMeta, CURSOR_MARKER};
|
||||
use vfs::{file_set::FileSet, VfsPath};
|
||||
|
||||
use crate::{
|
||||
input::CrateName, CrateGraph, CrateId, Edition, Env, FileId, FilePosition, RelativePathBuf,
|
||||
SourceDatabaseExt, SourceRoot, SourceRootId,
|
||||
input::CrateName, CrateGraph, CrateId, Edition, Env, FileId, FilePosition, SourceDatabaseExt,
|
||||
SourceRoot, SourceRootId,
|
||||
};
|
||||
|
||||
pub const WORKSPACE: SourceRootId = SourceRootId(0);
|
||||
@ -105,10 +104,10 @@ impl<DB: SourceDatabaseExt + Default + 'static> WithFixture for DB {}
|
||||
|
||||
fn with_single_file(db: &mut dyn SourceDatabaseExt, ra_fixture: &str) -> FileId {
|
||||
let file_id = FileId(0);
|
||||
let rel_path: RelativePathBuf = "/main.rs".into();
|
||||
let mut file_set = vfs::file_set::FileSet::default();
|
||||
file_set.insert(file_id, vfs::VfsPath::new_virtual_path("/main.rs".to_string()));
|
||||
|
||||
let mut source_root = SourceRoot::new_local();
|
||||
source_root.insert_file(rel_path.clone(), file_id);
|
||||
let source_root = SourceRoot::new_local(file_set);
|
||||
|
||||
let fixture = parse_single_fixture(ra_fixture);
|
||||
|
||||
@ -128,7 +127,6 @@ fn with_single_file(db: &mut dyn SourceDatabaseExt, ra_fixture: &str) -> FileId
|
||||
meta.cfg,
|
||||
meta.env,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
crate_graph
|
||||
} else {
|
||||
@ -140,13 +138,11 @@ fn with_single_file(db: &mut dyn SourceDatabaseExt, ra_fixture: &str) -> FileId
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
crate_graph
|
||||
};
|
||||
|
||||
db.set_file_text(file_id, Arc::new(ra_fixture.to_string()));
|
||||
db.set_file_relative_path(file_id, rel_path);
|
||||
db.set_file_source_root(file_id, WORKSPACE);
|
||||
db.set_source_root(WORKSPACE, Arc::new(source_root));
|
||||
db.set_crate_graph(Arc::new(crate_graph));
|
||||
@ -162,7 +158,7 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
|
||||
let mut crate_deps = Vec::new();
|
||||
let mut default_crate_root: Option<FileId> = None;
|
||||
|
||||
let mut source_root = SourceRoot::new_local();
|
||||
let mut file_set = FileSet::default();
|
||||
let mut source_root_id = WORKSPACE;
|
||||
let mut source_root_prefix = "/".to_string();
|
||||
let mut file_id = FileId(0);
|
||||
@ -172,8 +168,8 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
|
||||
for entry in fixture.iter() {
|
||||
let meta = match ParsedMeta::from(&entry.meta) {
|
||||
ParsedMeta::Root { path } => {
|
||||
let source_root = std::mem::replace(&mut source_root, SourceRoot::new_local());
|
||||
db.set_source_root(source_root_id, Arc::new(source_root));
|
||||
let file_set = std::mem::replace(&mut file_set, FileSet::default());
|
||||
db.set_source_root(source_root_id, Arc::new(SourceRoot::new_local(file_set)));
|
||||
source_root_id.0 += 1;
|
||||
source_root_prefix = path;
|
||||
continue;
|
||||
@ -190,7 +186,6 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
|
||||
meta.cfg,
|
||||
meta.env,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let prev = crates.insert(krate.clone(), crate_id);
|
||||
assert!(prev.is_none());
|
||||
@ -212,9 +207,9 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
|
||||
};
|
||||
|
||||
db.set_file_text(file_id, Arc::new(text));
|
||||
db.set_file_relative_path(file_id, meta.path.clone().into());
|
||||
db.set_file_source_root(file_id, source_root_id);
|
||||
source_root.insert_file(meta.path.into(), file_id);
|
||||
let path = VfsPath::new_virtual_path(meta.path);
|
||||
file_set.insert(file_id, path.into());
|
||||
|
||||
file_id.0 += 1;
|
||||
}
|
||||
@ -228,7 +223,6 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
} else {
|
||||
for (from, to) in crate_deps {
|
||||
@ -238,7 +232,7 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
|
||||
}
|
||||
}
|
||||
|
||||
db.set_source_root(source_root_id, Arc::new(source_root));
|
||||
db.set_source_root(source_root_id, Arc::new(SourceRoot::new_local(file_set)));
|
||||
db.set_crate_graph(Arc::new(crate_graph));
|
||||
|
||||
file_position
|
||||
|
@ -6,27 +6,15 @@
|
||||
//! actual IO. See `vfs` and `project_model` in the `rust-analyzer` crate for how
|
||||
//! actual IO is done and lowered to input.
|
||||
|
||||
use std::{
|
||||
fmt, ops,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
};
|
||||
use std::{fmt, ops, str::FromStr, sync::Arc};
|
||||
|
||||
use ra_cfg::CfgOptions;
|
||||
use ra_syntax::SmolStr;
|
||||
use ra_tt::TokenExpander;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use vfs::file_set::FileSet;
|
||||
|
||||
use crate::{RelativePath, RelativePathBuf};
|
||||
|
||||
/// `FileId` is an integer which uniquely identifies a file. File paths are
|
||||
/// messy and system-dependent, so most of the code should work directly with
|
||||
/// `FileId`, without inspecting the path. The mapping between `FileId` and path
|
||||
/// and `SourceRoot` is constant. A file rename is represented as a pair of
|
||||
/// deletion/creation.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct FileId(pub u32);
|
||||
pub use vfs::FileId;
|
||||
|
||||
/// Files are grouped into source roots. A source root is a directory on the
|
||||
/// file systems which is watched for changes. Typically it corresponds to a
|
||||
@ -45,27 +33,18 @@ pub struct SourceRoot {
|
||||
/// Libraries are considered mostly immutable, this assumption is used to
|
||||
/// optimize salsa's query structure
|
||||
pub is_library: bool,
|
||||
files: FxHashMap<RelativePathBuf, FileId>,
|
||||
pub(crate) file_set: FileSet,
|
||||
}
|
||||
|
||||
impl SourceRoot {
|
||||
pub fn new_local() -> SourceRoot {
|
||||
SourceRoot { is_library: false, files: Default::default() }
|
||||
pub fn new_local(file_set: FileSet) -> SourceRoot {
|
||||
SourceRoot { is_library: false, file_set }
|
||||
}
|
||||
pub fn new_library() -> SourceRoot {
|
||||
SourceRoot { is_library: true, files: Default::default() }
|
||||
pub fn new_library(file_set: FileSet) -> SourceRoot {
|
||||
SourceRoot { is_library: true, file_set }
|
||||
}
|
||||
pub fn insert_file(&mut self, path: RelativePathBuf, file_id: FileId) {
|
||||
self.files.insert(path, file_id);
|
||||
}
|
||||
pub fn remove_file(&mut self, path: &RelativePath) {
|
||||
self.files.remove(path);
|
||||
}
|
||||
pub fn walk(&self) -> impl Iterator<Item = FileId> + '_ {
|
||||
self.files.values().copied()
|
||||
}
|
||||
pub fn file_by_relative_path(&self, path: &RelativePath) -> Option<FileId> {
|
||||
self.files.get(path).copied()
|
||||
pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ {
|
||||
self.file_set.iter()
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,7 +120,6 @@ pub struct CrateData {
|
||||
pub display_name: Option<CrateName>,
|
||||
pub cfg_options: CfgOptions,
|
||||
pub env: Env,
|
||||
pub extern_source: ExternSource,
|
||||
pub dependencies: Vec<Dependency>,
|
||||
pub proc_macro: Vec<ProcMacro>,
|
||||
}
|
||||
@ -152,22 +130,11 @@ pub enum Edition {
|
||||
Edition2015,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ExternSourceId(pub u32);
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Env {
|
||||
entries: FxHashMap<String, String>,
|
||||
}
|
||||
|
||||
// FIXME: Redesign vfs for solve the following limitation ?
|
||||
// Note: Some env variables (e.g. OUT_DIR) are located outside of the
|
||||
// crate. We store a map to allow remap it to ExternSourceId
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ExternSource {
|
||||
extern_paths: FxHashMap<PathBuf, ExternSourceId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Dependency {
|
||||
pub crate_id: CrateId,
|
||||
@ -182,7 +149,6 @@ impl CrateGraph {
|
||||
display_name: Option<CrateName>,
|
||||
cfg_options: CfgOptions,
|
||||
env: Env,
|
||||
extern_source: ExternSource,
|
||||
proc_macro: Vec<(SmolStr, Arc<dyn ra_tt::TokenExpander>)>,
|
||||
) -> CrateId {
|
||||
let proc_macro =
|
||||
@ -194,7 +160,6 @@ impl CrateGraph {
|
||||
display_name,
|
||||
cfg_options,
|
||||
env,
|
||||
extern_source,
|
||||
proc_macro,
|
||||
dependencies: Vec::new(),
|
||||
};
|
||||
@ -334,20 +299,6 @@ impl Env {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternSource {
|
||||
pub fn extern_path(&self, path: &Path) -> Option<(ExternSourceId, RelativePathBuf)> {
|
||||
self.extern_paths.iter().find_map(|(root_path, id)| {
|
||||
let rel_path = path.strip_prefix(root_path).ok()?;
|
||||
let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
|
||||
Some((*id, rel_path))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_extern_path(&mut self, root_path: &Path, root: ExternSourceId) {
|
||||
self.extern_paths.insert(root_path.to_path_buf(), root);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseEditionError {
|
||||
invalid_input: String,
|
||||
@ -378,7 +329,6 @@ mod tests {
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let crate2 = graph.add_crate_root(
|
||||
FileId(2u32),
|
||||
@ -387,7 +337,6 @@ mod tests {
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let crate3 = graph.add_crate_root(
|
||||
FileId(3u32),
|
||||
@ -396,7 +345,6 @@ mod tests {
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok());
|
||||
assert!(graph.add_dep(crate2, CrateName::new("crate3").unwrap(), crate3).is_ok());
|
||||
@ -413,7 +361,6 @@ mod tests {
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let crate2 = graph.add_crate_root(
|
||||
FileId(2u32),
|
||||
@ -422,7 +369,6 @@ mod tests {
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok());
|
||||
assert!(graph.add_dep(crate2, CrateName::new("crate2").unwrap(), crate2).is_err());
|
||||
@ -438,7 +384,6 @@ mod tests {
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let crate2 = graph.add_crate_root(
|
||||
FileId(2u32),
|
||||
@ -447,7 +392,6 @@ mod tests {
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let crate3 = graph.add_crate_root(
|
||||
FileId(3u32),
|
||||
@ -456,7 +400,6 @@ mod tests {
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
assert!(graph.add_dep(crate1, CrateName::new("crate2").unwrap(), crate2).is_ok());
|
||||
assert!(graph.add_dep(crate2, CrateName::new("crate3").unwrap(), crate3).is_ok());
|
||||
@ -472,7 +415,6 @@ mod tests {
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let crate2 = graph.add_crate_root(
|
||||
FileId(2u32),
|
||||
@ -481,7 +423,6 @@ mod tests {
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
assert!(graph
|
||||
.add_dep(crate1, CrateName::normalize_dashes("crate-name-with-dashes"), crate2)
|
||||
|
@ -12,12 +12,13 @@ use rustc_hash::FxHashSet;
|
||||
pub use crate::{
|
||||
cancellation::Canceled,
|
||||
input::{
|
||||
CrateData, CrateGraph, CrateId, CrateName, Dependency, Edition, Env, ExternSource,
|
||||
ExternSourceId, FileId, ProcMacroId, SourceRoot, SourceRootId,
|
||||
CrateData, CrateGraph, CrateId, CrateName, Dependency, Edition, Env, FileId, ProcMacroId,
|
||||
SourceRoot, SourceRootId,
|
||||
},
|
||||
};
|
||||
pub use relative_path::{RelativePath, RelativePathBuf};
|
||||
pub use salsa;
|
||||
pub use vfs::{file_set::FileSet, AbsPathBuf, VfsPath};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! impl_intern_key {
|
||||
@ -125,8 +126,6 @@ pub trait SourceDatabaseExt: SourceDatabase {
|
||||
#[salsa::input]
|
||||
fn file_text(&self, file_id: FileId) -> Arc<String>;
|
||||
/// Path to a file, relative to the root of its source root.
|
||||
#[salsa::input]
|
||||
fn file_relative_path(&self, file_id: FileId) -> RelativePathBuf;
|
||||
/// Source root of the file.
|
||||
#[salsa::input]
|
||||
fn file_source_root(&self, file_id: FileId) -> SourceRootId;
|
||||
@ -161,24 +160,9 @@ impl<T: SourceDatabaseExt> FileLoader for FileLoaderDelegate<&'_ T> {
|
||||
}
|
||||
fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> {
|
||||
// FIXME: this *somehow* should be platform agnostic...
|
||||
if std::path::Path::new(path).is_absolute() {
|
||||
let krate = *self.relevant_crates(anchor).iter().next()?;
|
||||
let (extern_source_id, relative_file) =
|
||||
self.0.crate_graph()[krate].extern_source.extern_path(path.as_ref())?;
|
||||
|
||||
let source_root = self.0.source_root(SourceRootId(extern_source_id.0));
|
||||
source_root.file_by_relative_path(&relative_file)
|
||||
} else {
|
||||
let rel_path = {
|
||||
let mut rel_path = self.0.file_relative_path(anchor);
|
||||
assert!(rel_path.pop());
|
||||
rel_path.push(path);
|
||||
rel_path.normalize()
|
||||
};
|
||||
let source_root = self.0.file_source_root(anchor);
|
||||
let source_root = self.0.source_root(source_root);
|
||||
source_root.file_by_relative_path(&rel_path)
|
||||
}
|
||||
let source_root = self.0.file_source_root(anchor);
|
||||
let source_root = self.0.source_root(source_root);
|
||||
source_root.file_set.resolve_path(anchor, path)
|
||||
}
|
||||
|
||||
fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> {
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
use either::Either;
|
||||
use hir_def::{
|
||||
nameres::ModuleSource,
|
||||
nameres::{ModuleOrigin, ModuleSource},
|
||||
src::{HasChildSource, HasSource as _},
|
||||
Lookup, VariantId,
|
||||
};
|
||||
@ -29,6 +29,14 @@ impl Module {
|
||||
def_map[self.id.local_id].definition_source(db.upcast())
|
||||
}
|
||||
|
||||
pub fn is_mod_rs(self, db: &dyn HirDatabase) -> bool {
|
||||
let def_map = db.crate_def_map(self.id.krate);
|
||||
match def_map[self.id.local_id].origin {
|
||||
ModuleOrigin::File { is_mod_rs, .. } => is_mod_rs,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a node which declares this module, either a `mod foo;` or a `mod foo {}`.
|
||||
/// `None` for the crate root.
|
||||
pub fn declaration_source(self, db: &dyn HirDatabase) -> Option<InFile<ast::Module>> {
|
||||
|
@ -104,6 +104,7 @@ pub enum ModuleOrigin {
|
||||
},
|
||||
/// Note that non-inline modules, by definition, live inside non-macro file.
|
||||
File {
|
||||
is_mod_rs: bool,
|
||||
declaration: AstId<ast::Module>,
|
||||
definition: FileId,
|
||||
},
|
||||
|
@ -777,11 +777,11 @@ impl ModCollector<'_, '_> {
|
||||
name,
|
||||
path_attr,
|
||||
) {
|
||||
Ok((file_id, mod_dir)) => {
|
||||
Ok((file_id, is_mod_rs, mod_dir)) => {
|
||||
let module_id = self.push_child_module(
|
||||
name.clone(),
|
||||
ast_id,
|
||||
Some(file_id),
|
||||
Some((file_id, is_mod_rs)),
|
||||
&visibility,
|
||||
);
|
||||
let raw_items = self.def_collector.db.raw_items(file_id.into());
|
||||
@ -814,7 +814,7 @@ impl ModCollector<'_, '_> {
|
||||
&mut self,
|
||||
name: Name,
|
||||
declaration: AstId<ast::Module>,
|
||||
definition: Option<FileId>,
|
||||
definition: Option<(FileId, bool)>,
|
||||
visibility: &crate::visibility::RawVisibility,
|
||||
) -> LocalModuleId {
|
||||
let vis = self
|
||||
@ -827,7 +827,9 @@ impl ModCollector<'_, '_> {
|
||||
modules[res].parent = Some(self.module_id);
|
||||
modules[res].origin = match definition {
|
||||
None => ModuleOrigin::Inline { definition: declaration },
|
||||
Some(definition) => ModuleOrigin::File { declaration, definition },
|
||||
Some((definition, is_mod_rs)) => {
|
||||
ModuleOrigin::File { declaration, definition, is_mod_rs }
|
||||
}
|
||||
};
|
||||
for (name, mac) in modules[self.module_id].scope.collect_legacy_macros() {
|
||||
modules[res].scope.define_legacy_macro(name, mac)
|
||||
|
@ -44,7 +44,7 @@ impl ModDir {
|
||||
file_id: HirFileId,
|
||||
name: &Name,
|
||||
attr_path: Option<&SmolStr>,
|
||||
) -> Result<(FileId, ModDir), String> {
|
||||
) -> Result<(FileId, bool, ModDir), String> {
|
||||
let file_id = file_id.original_file(db.upcast());
|
||||
|
||||
let mut candidate_files = Vec::new();
|
||||
@ -64,11 +64,12 @@ impl ModDir {
|
||||
if let Some(file_id) = db.resolve_path(file_id, candidate.as_str()) {
|
||||
let mut root_non_dir_owner = false;
|
||||
let mut mod_path = RelativePathBuf::new();
|
||||
if !(candidate.ends_with("mod.rs") || attr_path.is_some()) {
|
||||
let is_mod_rs = candidate.ends_with("mod.rs");
|
||||
if !(is_mod_rs || attr_path.is_some()) {
|
||||
root_non_dir_owner = true;
|
||||
mod_path.push(&name.to_string());
|
||||
}
|
||||
return Ok((file_id, ModDir { path: mod_path, root_non_dir_owner }));
|
||||
return Ok((file_id, is_mod_rs, ModDir { path: mod_path, root_non_dir_owner }));
|
||||
}
|
||||
}
|
||||
Err(candidate_files.remove(0))
|
||||
|
@ -47,7 +47,7 @@ use std::sync::Arc;
|
||||
use ra_cfg::CfgOptions;
|
||||
use ra_db::{
|
||||
salsa::{self, ParallelDatabase},
|
||||
CheckCanceled, Env, FileLoader, SourceDatabase,
|
||||
CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath,
|
||||
};
|
||||
use ra_ide_db::{
|
||||
symbol_index::{self, FileSymbol},
|
||||
@ -78,7 +78,8 @@ pub use crate::{
|
||||
pub use hir::Documentation;
|
||||
pub use ra_assists::{Assist, AssistConfig, AssistId, ResolvedAssist};
|
||||
pub use ra_db::{
|
||||
Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId,
|
||||
Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRoot,
|
||||
SourceRootId,
|
||||
};
|
||||
pub use ra_ide_db::{
|
||||
change::AnalysisChange,
|
||||
@ -212,11 +213,14 @@ impl Analysis {
|
||||
// `AnalysisHost` for creating a fully-featured analysis.
|
||||
pub fn from_single_file(text: String) -> (Analysis, FileId) {
|
||||
let mut host = AnalysisHost::default();
|
||||
let source_root = SourceRootId(0);
|
||||
let mut change = AnalysisChange::new();
|
||||
change.add_root(source_root, true);
|
||||
let mut crate_graph = CrateGraph::default();
|
||||
let file_id = FileId(0);
|
||||
let mut file_set = FileSet::default();
|
||||
file_set.insert(file_id, VfsPath::new_virtual_path("/main.rs".to_string()));
|
||||
let source_root = SourceRoot::new_local(file_set);
|
||||
|
||||
let mut change = AnalysisChange::new();
|
||||
change.set_roots(vec![source_root]);
|
||||
let mut crate_graph = CrateGraph::default();
|
||||
// FIXME: cfg options
|
||||
// Default to enable test for single file.
|
||||
let mut cfg_options = CfgOptions::default();
|
||||
@ -228,9 +232,8 @@ impl Analysis {
|
||||
cfg_options,
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
change.add_file(source_root, file_id, "main.rs".into(), Arc::new(text));
|
||||
change.change_file(file_id, Some(Arc::new(text)));
|
||||
change.set_crate_graph(crate_graph);
|
||||
host.apply_change(change);
|
||||
(host.analysis(), file_id)
|
||||
|
@ -1,15 +1,12 @@
|
||||
//! FIXME: write short doc here
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
|
||||
use ra_cfg::CfgOptions;
|
||||
use ra_db::{CrateName, Env};
|
||||
use ra_db::{CrateName, Env, FileSet, SourceRoot, VfsPath};
|
||||
use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER};
|
||||
|
||||
use crate::{
|
||||
Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition, FileId, FilePosition, FileRange,
|
||||
SourceRootId,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -159,9 +156,8 @@ impl MockAnalysis {
|
||||
}
|
||||
pub fn analysis_host(self) -> AnalysisHost {
|
||||
let mut host = AnalysisHost::default();
|
||||
let source_root = SourceRootId(0);
|
||||
let mut change = AnalysisChange::new();
|
||||
change.add_root(source_root, true);
|
||||
let mut file_set = FileSet::default();
|
||||
let mut crate_graph = CrateGraph::default();
|
||||
let mut root_crate = None;
|
||||
for (i, data) in self.files.into_iter().enumerate() {
|
||||
@ -179,7 +175,6 @@ impl MockAnalysis {
|
||||
cfg_options,
|
||||
env,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
));
|
||||
} else if path.ends_with("/lib.rs") {
|
||||
let base = &path[..path.len() - "/lib.rs".len()];
|
||||
@ -191,7 +186,6 @@ impl MockAnalysis {
|
||||
cfg_options,
|
||||
env,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
if let Some(root_crate) = root_crate {
|
||||
crate_graph
|
||||
@ -199,9 +193,12 @@ impl MockAnalysis {
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
change.add_file(source_root, file_id, path.into(), Arc::new(data.content().to_owned()));
|
||||
let path = VfsPath::new_virtual_path(path.to_string());
|
||||
file_set.insert(file_id, path);
|
||||
change.change_file(file_id, Some(Arc::new(data.content().to_owned())));
|
||||
}
|
||||
change.set_crate_graph(crate_graph);
|
||||
change.set_roots(vec![SourceRoot::new_local(file_set)]);
|
||||
host.apply_change(change);
|
||||
host
|
||||
}
|
||||
|
@ -145,7 +145,6 @@ mod tests {
|
||||
CfgOptions::default(),
|
||||
Env::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let mut change = AnalysisChange::new();
|
||||
change.set_crate_graph(crate_graph);
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! FIXME: write short doc here
|
||||
|
||||
use hir::{Module, ModuleDef, ModuleSource, Semantics};
|
||||
use ra_db::{RelativePathBuf, SourceDatabaseExt};
|
||||
use ra_db::SourceDatabaseExt;
|
||||
use ra_ide_db::{
|
||||
defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
|
||||
RootDatabase,
|
||||
@ -109,9 +109,8 @@ fn rename_mod(
|
||||
let file_id = src.file_id.original_file(db);
|
||||
match src.value {
|
||||
ModuleSource::SourceFile(..) => {
|
||||
let mod_path: RelativePathBuf = db.file_relative_path(file_id);
|
||||
// mod is defined in path/to/dir/mod.rs
|
||||
let dst = if mod_path.file_stem() == Some("mod") {
|
||||
let dst = if module.is_mod_rs(db) {
|
||||
format!("../{}/mod.rs", new_name)
|
||||
} else {
|
||||
format!("{}.rs", new_name)
|
||||
|
@ -41,7 +41,7 @@ pub fn parse_search_replace(
|
||||
match_finder.add_rule(rule);
|
||||
for &root in db.local_roots().iter() {
|
||||
let sr = db.source_root(root);
|
||||
for file_id in sr.walk() {
|
||||
for file_id in sr.iter() {
|
||||
if let Some(edit) = match_finder.edits_for_file(file_id) {
|
||||
edits.push(SourceFileEdit { file_id, edit });
|
||||
}
|
||||
|
@ -9,26 +9,22 @@ use ra_db::{
|
||||
SourceRootId,
|
||||
};
|
||||
use ra_prof::{memory_usage, profile, Bytes};
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::{symbol_index::SymbolsDatabase, RootDatabase};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AnalysisChange {
|
||||
new_roots: Vec<(SourceRootId, bool)>,
|
||||
roots_changed: FxHashMap<SourceRootId, RootChange>,
|
||||
files_changed: Vec<(FileId, Arc<String>)>,
|
||||
roots: Option<Vec<SourceRoot>>,
|
||||
files_changed: Vec<(FileId, Option<Arc<String>>)>,
|
||||
crate_graph: Option<CrateGraph>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for AnalysisChange {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut d = fmt.debug_struct("AnalysisChange");
|
||||
if !self.new_roots.is_empty() {
|
||||
d.field("new_roots", &self.new_roots);
|
||||
}
|
||||
if !self.roots_changed.is_empty() {
|
||||
d.field("roots_changed", &self.roots_changed);
|
||||
if let Some(roots) = &self.roots {
|
||||
d.field("roots", roots);
|
||||
}
|
||||
if !self.files_changed.is_empty() {
|
||||
d.field("files_changed", &self.files_changed.len());
|
||||
@ -45,30 +41,14 @@ impl AnalysisChange {
|
||||
AnalysisChange::default()
|
||||
}
|
||||
|
||||
pub fn add_root(&mut self, root_id: SourceRootId, is_local: bool) {
|
||||
self.new_roots.push((root_id, is_local));
|
||||
pub fn set_roots(&mut self, roots: Vec<SourceRoot>) {
|
||||
self.roots = Some(roots);
|
||||
}
|
||||
|
||||
pub fn add_file(
|
||||
&mut self,
|
||||
root_id: SourceRootId,
|
||||
file_id: FileId,
|
||||
path: RelativePathBuf,
|
||||
text: Arc<String>,
|
||||
) {
|
||||
let file = AddFile { file_id, path, text };
|
||||
self.roots_changed.entry(root_id).or_default().added.push(file);
|
||||
}
|
||||
|
||||
pub fn change_file(&mut self, file_id: FileId, new_text: Arc<String>) {
|
||||
pub fn change_file(&mut self, file_id: FileId, new_text: Option<Arc<String>>) {
|
||||
self.files_changed.push((file_id, new_text))
|
||||
}
|
||||
|
||||
pub fn remove_file(&mut self, root_id: SourceRootId, file_id: FileId, path: RelativePathBuf) {
|
||||
let file = RemoveFile { file_id, path };
|
||||
self.roots_changed.entry(root_id).or_default().removed.push(file);
|
||||
}
|
||||
|
||||
pub fn set_crate_graph(&mut self, graph: CrateGraph) {
|
||||
self.crate_graph = Some(graph);
|
||||
}
|
||||
@ -114,31 +94,32 @@ impl RootDatabase {
|
||||
let _p = profile("RootDatabase::apply_change");
|
||||
self.request_cancellation();
|
||||
log::info!("apply_change {:?}", change);
|
||||
if !change.new_roots.is_empty() {
|
||||
let mut local_roots = Vec::clone(&self.local_roots());
|
||||
let mut libraries = Vec::clone(&self.library_roots());
|
||||
for (root_id, is_local) in change.new_roots {
|
||||
let root =
|
||||
if is_local { SourceRoot::new_local() } else { SourceRoot::new_library() };
|
||||
if let Some(roots) = change.roots {
|
||||
let mut local_roots = FxHashSet::default();
|
||||
let mut library_roots = FxHashSet::default();
|
||||
for (idx, root) in roots.into_iter().enumerate() {
|
||||
let root_id = SourceRootId(idx as u32);
|
||||
let durability = durability(&root);
|
||||
self.set_source_root_with_durability(root_id, Arc::new(root), durability);
|
||||
if is_local {
|
||||
local_roots.push(root_id);
|
||||
if root.is_library {
|
||||
library_roots.insert(root_id);
|
||||
} else {
|
||||
libraries.push(root_id)
|
||||
local_roots.insert(root_id);
|
||||
}
|
||||
for file_id in root.iter() {
|
||||
self.set_file_source_root_with_durability(file_id, root_id, durability);
|
||||
}
|
||||
self.set_source_root_with_durability(root_id, Arc::new(root), durability);
|
||||
}
|
||||
self.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
|
||||
self.set_library_roots_with_durability(Arc::new(libraries), Durability::HIGH);
|
||||
self.set_library_roots_with_durability(Arc::new(library_roots), Durability::HIGH);
|
||||
}
|
||||
|
||||
for (root_id, root_change) in change.roots_changed {
|
||||
self.apply_root_change(root_id, root_change);
|
||||
}
|
||||
for (file_id, text) in change.files_changed {
|
||||
let source_root_id = self.file_source_root(file_id);
|
||||
let source_root = self.source_root(source_root_id);
|
||||
let durability = durability(&source_root);
|
||||
// XXX: can't actually remove the file, just reset the text
|
||||
let text = text.unwrap_or_default();
|
||||
self.set_file_text_with_durability(file_id, text, durability)
|
||||
}
|
||||
if let Some(crate_graph) = change.crate_graph {
|
||||
@ -146,26 +127,6 @@ impl RootDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_root_change(&mut self, root_id: SourceRootId, root_change: RootChange) {
|
||||
let mut source_root = SourceRoot::clone(&self.source_root(root_id));
|
||||
let durability = durability(&source_root);
|
||||
for add_file in root_change.added {
|
||||
self.set_file_text_with_durability(add_file.file_id, add_file.text, durability);
|
||||
self.set_file_relative_path_with_durability(
|
||||
add_file.file_id,
|
||||
add_file.path.clone(),
|
||||
durability,
|
||||
);
|
||||
self.set_file_source_root_with_durability(add_file.file_id, root_id, durability);
|
||||
source_root.insert_file(add_file.path, add_file.file_id);
|
||||
}
|
||||
for remove_file in root_change.removed {
|
||||
self.set_file_text_with_durability(remove_file.file_id, Default::default(), durability);
|
||||
source_root.remove_file(&remove_file.path);
|
||||
}
|
||||
self.set_source_root_with_durability(root_id, Arc::new(source_root), durability);
|
||||
}
|
||||
|
||||
pub fn maybe_collect_garbage(&mut self) {
|
||||
if cfg!(feature = "wasm") {
|
||||
return;
|
||||
|
@ -157,14 +157,14 @@ impl Definition {
|
||||
if let Some(Visibility::Public) = vis {
|
||||
let source_root_id = db.file_source_root(file_id);
|
||||
let source_root = db.source_root(source_root_id);
|
||||
let mut res = source_root.walk().map(|id| (id, None)).collect::<FxHashMap<_, _>>();
|
||||
let mut res = source_root.iter().map(|id| (id, None)).collect::<FxHashMap<_, _>>();
|
||||
|
||||
let krate = module.krate();
|
||||
for rev_dep in krate.reverse_dependencies(db) {
|
||||
let root_file = rev_dep.root_file(db);
|
||||
let source_root_id = db.file_source_root(root_file);
|
||||
let source_root = db.source_root(source_root_id);
|
||||
res.extend(source_root.walk().map(|id| (id, None)));
|
||||
res.extend(source_root.iter().map(|id| (id, None)));
|
||||
}
|
||||
return SearchScope::new(res);
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ use ra_syntax::{
|
||||
SyntaxNode, SyntaxNodePtr, TextRange, WalkEvent,
|
||||
};
|
||||
use rayon::prelude::*;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::RootDatabase;
|
||||
|
||||
@ -93,11 +93,11 @@ pub trait SymbolsDatabase: hir::db::HirDatabase + SourceDatabaseExt + ParallelDa
|
||||
/// The set of "local" (that is, from the current workspace) roots.
|
||||
/// Files in local roots are assumed to change frequently.
|
||||
#[salsa::input]
|
||||
fn local_roots(&self) -> Arc<Vec<SourceRootId>>;
|
||||
fn local_roots(&self) -> Arc<FxHashSet<SourceRootId>>;
|
||||
/// The set of roots for crates.io libraries.
|
||||
/// Files in libraries are assumed to never change.
|
||||
#[salsa::input]
|
||||
fn library_roots(&self) -> Arc<Vec<SourceRootId>>;
|
||||
fn library_roots(&self) -> Arc<FxHashSet<SourceRootId>>;
|
||||
}
|
||||
|
||||
fn library_symbols(
|
||||
@ -111,7 +111,7 @@ fn library_symbols(
|
||||
.map(|&root_id| {
|
||||
let root = db.source_root(root_id);
|
||||
let files = root
|
||||
.walk()
|
||||
.iter()
|
||||
.map(|it| (it, SourceDatabaseExt::file_text(db, it)))
|
||||
.collect::<Vec<_>>();
|
||||
let symbol_index = SymbolIndex::for_files(
|
||||
@ -175,7 +175,7 @@ pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> {
|
||||
let mut files = Vec::new();
|
||||
for &root in db.local_roots().iter() {
|
||||
let sr = db.source_root(root);
|
||||
files.extend(sr.walk())
|
||||
files.extend(sr.iter())
|
||||
}
|
||||
|
||||
let snap = Snap(db.snapshot());
|
||||
|
@ -13,7 +13,7 @@ use std::{
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use ra_cfg::CfgOptions;
|
||||
use ra_db::{CrateGraph, CrateName, Edition, Env, ExternSource, ExternSourceId, FileId};
|
||||
use ra_db::{CrateGraph, CrateName, Edition, Env, FileId};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use serde_json::from_reader;
|
||||
|
||||
@ -246,7 +246,6 @@ impl ProjectWorkspace {
|
||||
pub fn to_crate_graph(
|
||||
&self,
|
||||
target: Option<&str>,
|
||||
extern_source_roots: &FxHashMap<PathBuf, ExternSourceId>,
|
||||
proc_macro_client: &ProcMacroClient,
|
||||
load: &mut dyn FnMut(&Path) -> Option<FileId>,
|
||||
) -> CrateGraph {
|
||||
@ -280,15 +279,11 @@ impl ProjectWorkspace {
|
||||
};
|
||||
|
||||
let mut env = Env::default();
|
||||
let mut extern_source = ExternSource::default();
|
||||
if let Some(out_dir) = &krate.out_dir {
|
||||
// NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
|
||||
if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
|
||||
env.set("OUT_DIR", out_dir);
|
||||
}
|
||||
if let Some(&extern_source_id) = extern_source_roots.get(out_dir) {
|
||||
extern_source.set_extern_path(&out_dir, extern_source_id);
|
||||
}
|
||||
}
|
||||
let proc_macro = krate
|
||||
.proc_macro_dylib_path
|
||||
@ -304,7 +299,6 @@ impl ProjectWorkspace {
|
||||
None,
|
||||
cfg_options,
|
||||
env,
|
||||
extern_source,
|
||||
proc_macro.unwrap_or_default(),
|
||||
),
|
||||
))
|
||||
@ -341,7 +335,6 @@ impl ProjectWorkspace {
|
||||
let file_id = load(&sysroot[krate].root)?;
|
||||
|
||||
let env = Env::default();
|
||||
let extern_source = ExternSource::default();
|
||||
let proc_macro = vec![];
|
||||
let crate_name = CrateName::new(&sysroot[krate].name)
|
||||
.expect("Sysroot crate names should not contain dashes");
|
||||
@ -352,7 +345,6 @@ impl ProjectWorkspace {
|
||||
Some(crate_name),
|
||||
cfg_options.clone(),
|
||||
env,
|
||||
extern_source,
|
||||
proc_macro,
|
||||
);
|
||||
Some((krate, crate_id))
|
||||
@ -409,15 +401,11 @@ impl ProjectWorkspace {
|
||||
opts
|
||||
};
|
||||
let mut env = Env::default();
|
||||
let mut extern_source = ExternSource::default();
|
||||
if let Some(out_dir) = &cargo[pkg].out_dir {
|
||||
// NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
|
||||
if let Some(out_dir) = out_dir.to_str().map(|s| s.to_owned()) {
|
||||
env.set("OUT_DIR", out_dir);
|
||||
}
|
||||
if let Some(&extern_source_id) = extern_source_roots.get(out_dir) {
|
||||
extern_source.set_extern_path(&out_dir, extern_source_id);
|
||||
}
|
||||
}
|
||||
let proc_macro = cargo[pkg]
|
||||
.proc_macro_dylib_path
|
||||
@ -431,7 +419,6 @@ impl ProjectWorkspace {
|
||||
Some(CrateName::normalize_dashes(&cargo[pkg].name)),
|
||||
cfg_options,
|
||||
env,
|
||||
extern_source,
|
||||
proc_macro.clone(),
|
||||
);
|
||||
if cargo[tgt].kind == TargetKind::Lib {
|
||||
|
@ -38,7 +38,8 @@ ra_prof = { path = "../ra_prof" }
|
||||
ra_project_model = { path = "../ra_project_model" }
|
||||
ra_syntax = { path = "../ra_syntax" }
|
||||
ra_text_edit = { path = "../ra_text_edit" }
|
||||
ra_vfs = "0.6.0"
|
||||
vfs = { path = "../vfs" }
|
||||
vfs-notify = { path = "../vfs-notify" }
|
||||
ra_cfg = { path = "../ra_cfg"}
|
||||
|
||||
# This should only be used in CLI
|
||||
|
@ -1,5 +1,7 @@
|
||||
//! See `CargoTargetSpec`
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use ra_cfg::CfgExpr;
|
||||
use ra_ide::{FileId, RunnableKind, TestId};
|
||||
use ra_project_model::{self, TargetKind};
|
||||
@ -12,6 +14,7 @@ use crate::{global_state::GlobalStateSnapshot, Result};
|
||||
/// build/test/run the target.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct CargoTargetSpec {
|
||||
pub(crate) workspace_root: PathBuf,
|
||||
pub(crate) package: String,
|
||||
pub(crate) target: String,
|
||||
pub(crate) target_kind: TargetKind,
|
||||
@ -101,6 +104,7 @@ impl CargoTargetSpec {
|
||||
None => return Ok(None),
|
||||
};
|
||||
let res = CargoTargetSpec {
|
||||
workspace_root: cargo_ws.workspace_root().to_path_buf(),
|
||||
package: cargo_ws.package_flag(&cargo_ws[cargo_ws[target].package]),
|
||||
target: cargo_ws[target].name.clone(),
|
||||
target_kind: cargo_ws[target].kind,
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! Benchmark operations like highlighting or goto definition.
|
||||
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
@ -10,7 +11,7 @@ use std::{
|
||||
use anyhow::{format_err, Result};
|
||||
use ra_db::{
|
||||
salsa::{Database, Durability},
|
||||
FileId, SourceDatabaseExt,
|
||||
AbsPathBuf, FileId,
|
||||
};
|
||||
use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CompletionConfig, FilePosition, LineCol};
|
||||
|
||||
@ -53,8 +54,7 @@ pub fn analysis_bench(
|
||||
|
||||
let start = Instant::now();
|
||||
eprint!("loading: ");
|
||||
let (mut host, roots) = load_cargo(path, load_output_dirs, with_proc_macro)?;
|
||||
let db = host.raw_database();
|
||||
let (mut host, vfs) = load_cargo(path, load_output_dirs, with_proc_macro)?;
|
||||
eprintln!("{:?}\n", start.elapsed());
|
||||
|
||||
let file_id = {
|
||||
@ -62,22 +62,9 @@ pub fn analysis_bench(
|
||||
BenchWhat::Highlight { path } => path,
|
||||
BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => &pos.path,
|
||||
};
|
||||
let path = std::env::current_dir()?.join(path).canonicalize()?;
|
||||
roots
|
||||
.iter()
|
||||
.find_map(|(source_root_id, project_root)| {
|
||||
if project_root.is_member() {
|
||||
for file_id in db.source_root(*source_root_id).walk() {
|
||||
let rel_path = db.file_relative_path(file_id);
|
||||
let abs_path = rel_path.to_path(project_root.path());
|
||||
if abs_path == path {
|
||||
return Some(file_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.ok_or_else(|| format_err!("Can't find {}", path.display()))?
|
||||
let path = AbsPathBuf::try_from(path.clone()).unwrap();
|
||||
let path = path.into();
|
||||
vfs.file_id(&path).ok_or_else(|| format_err!("Can't find {}", path))?
|
||||
};
|
||||
|
||||
match &what {
|
||||
@ -149,7 +136,7 @@ fn do_work<F: Fn(&Analysis) -> T, T>(host: &mut AnalysisHost, file_id: FileId, w
|
||||
let mut text = host.analysis().file_text(file_id).unwrap().to_string();
|
||||
text.push_str("\n/* Hello world */\n");
|
||||
let mut change = AnalysisChange::new();
|
||||
change.change_file(file_id, Arc::new(text));
|
||||
change.change_file(file_id, Some(Arc::new(text)));
|
||||
host.apply_change(change);
|
||||
}
|
||||
work(&host.analysis());
|
||||
|
@ -28,26 +28,14 @@ pub fn analysis_stats(
|
||||
with_proc_macro: bool,
|
||||
) -> Result<()> {
|
||||
let db_load_time = Instant::now();
|
||||
let (mut host, roots) = load_cargo(path, load_output_dirs, with_proc_macro)?;
|
||||
let (mut host, vfs) = load_cargo(path, load_output_dirs, with_proc_macro)?;
|
||||
let db = host.raw_database();
|
||||
println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed());
|
||||
println!("Database loaded {:?}", db_load_time.elapsed());
|
||||
let analysis_time = Instant::now();
|
||||
let mut num_crates = 0;
|
||||
let mut visited_modules = HashSet::new();
|
||||
let mut visit_queue = Vec::new();
|
||||
|
||||
let members =
|
||||
roots
|
||||
.into_iter()
|
||||
.filter_map(|(source_root_id, project_root)| {
|
||||
if with_deps || project_root.is_member() {
|
||||
Some(source_root_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let mut krates = Crate::all(db);
|
||||
if randomize {
|
||||
krates.shuffle(&mut thread_rng());
|
||||
@ -55,7 +43,10 @@ pub fn analysis_stats(
|
||||
for krate in krates {
|
||||
let module = krate.root_module(db).expect("crate without root module");
|
||||
let file_id = module.definition_source(db).file_id;
|
||||
if members.contains(&db.file_source_root(file_id.original_file(db))) {
|
||||
let file_id = file_id.original_file(db);
|
||||
let source_root = db.file_source_root(file_id);
|
||||
let source_root = db.source_root(source_root);
|
||||
if !source_root.is_library || with_deps {
|
||||
num_crates += 1;
|
||||
visit_queue.push(module);
|
||||
}
|
||||
@ -128,7 +119,7 @@ pub fn analysis_stats(
|
||||
if verbosity.is_verbose() {
|
||||
let src = f.source(db);
|
||||
let original_file = src.file_id.original_file(db);
|
||||
let path = db.file_relative_path(original_file);
|
||||
let path = vfs.file_path(original_file);
|
||||
let syntax_range = src.value.syntax().text_range();
|
||||
format_to!(msg, " ({:?} {:?})", path, syntax_range);
|
||||
}
|
||||
@ -196,7 +187,7 @@ pub fn analysis_stats(
|
||||
let root = db.parse_or_expand(src.file_id).unwrap();
|
||||
let node = src.map(|e| e.to_node(&root).syntax().clone());
|
||||
let original_range = original_range(db, node.as_ref());
|
||||
let path = db.file_relative_path(original_range.file_id);
|
||||
let path = vfs.file_path(original_range.file_id);
|
||||
let line_index =
|
||||
host.analysis().file_line_index(original_range.file_id).unwrap();
|
||||
let text_range = original_range.range;
|
||||
|
@ -2,68 +2,57 @@
|
||||
//! code if any errors are found.
|
||||
|
||||
use anyhow::anyhow;
|
||||
use hir::Crate;
|
||||
use ra_db::SourceDatabaseExt;
|
||||
use ra_ide::Severity;
|
||||
use std::{collections::HashSet, path::Path};
|
||||
|
||||
use crate::cli::{load_cargo::load_cargo, Result};
|
||||
use hir::Semantics;
|
||||
|
||||
pub fn diagnostics(
|
||||
path: &Path,
|
||||
load_output_dirs: bool,
|
||||
with_proc_macro: bool,
|
||||
all: bool,
|
||||
_all: bool,
|
||||
) -> Result<()> {
|
||||
let (host, roots) = load_cargo(path, load_output_dirs, with_proc_macro)?;
|
||||
let (host, _vfs) = load_cargo(path, load_output_dirs, with_proc_macro)?;
|
||||
let db = host.raw_database();
|
||||
let analysis = host.analysis();
|
||||
let semantics = Semantics::new(db);
|
||||
let members = roots
|
||||
.into_iter()
|
||||
.filter_map(|(source_root_id, project_root)| {
|
||||
// filter out dependencies
|
||||
if project_root.is_member() {
|
||||
Some(source_root_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let mut found_error = false;
|
||||
let mut visited_files = HashSet::new();
|
||||
for source_root_id in members {
|
||||
for file_id in db.source_root(source_root_id).walk() {
|
||||
// Filter out files which are not actually modules (unless `--all` flag is
|
||||
// passed). In the rust-analyzer repository this filters out the parser test files.
|
||||
if semantics.to_module_def(file_id).is_some() || all {
|
||||
if !visited_files.contains(&file_id) {
|
||||
let crate_name = if let Some(module) = semantics.to_module_def(file_id) {
|
||||
if let Some(name) = module.krate().display_name(db) {
|
||||
format!("{}", name)
|
||||
} else {
|
||||
String::from("unknown")
|
||||
}
|
||||
} else {
|
||||
String::from("unknown")
|
||||
};
|
||||
println!(
|
||||
"processing crate: {}, module: {}",
|
||||
crate_name,
|
||||
db.file_relative_path(file_id)
|
||||
);
|
||||
for diagnostic in analysis.diagnostics(file_id).unwrap() {
|
||||
if matches!(diagnostic.severity, Severity::Error) {
|
||||
found_error = true;
|
||||
}
|
||||
|
||||
println!("{:?}", diagnostic);
|
||||
}
|
||||
let mut work = Vec::new();
|
||||
let krates = Crate::all(db);
|
||||
for krate in krates {
|
||||
let module = krate.root_module(db).expect("crate without root module");
|
||||
let file_id = module.definition_source(db).file_id;
|
||||
let file_id = file_id.original_file(db);
|
||||
let source_root = db.file_source_root(file_id);
|
||||
let source_root = db.source_root(source_root);
|
||||
if !source_root.is_library {
|
||||
work.push(module);
|
||||
}
|
||||
}
|
||||
|
||||
visited_files.insert(file_id);
|
||||
for module in work {
|
||||
let file_id = module.definition_source(db).file_id.original_file(db);
|
||||
if !visited_files.contains(&file_id) {
|
||||
let crate_name = if let Some(name) = module.krate().display_name(db) {
|
||||
format!("{}", name)
|
||||
} else {
|
||||
String::from("unknown")
|
||||
};
|
||||
println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id));
|
||||
for diagnostic in analysis.diagnostics(file_id).unwrap() {
|
||||
if matches!(diagnostic.severity, Severity::Error) {
|
||||
found_error = true;
|
||||
}
|
||||
|
||||
println!("{:?}", diagnostic);
|
||||
}
|
||||
|
||||
visited_files.insert(file_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,32 +1,21 @@
|
||||
//! Loads a Cargo project into a static instance of analysis, without support
|
||||
//! for incorporating changes.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{convert::TryFrom, path::Path, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use crossbeam_channel::{unbounded, Receiver};
|
||||
use ra_db::{ExternSourceId, FileId, SourceRootId};
|
||||
use ra_db::{AbsPathBuf, CrateGraph};
|
||||
use ra_ide::{AnalysisChange, AnalysisHost};
|
||||
use ra_project_model::{
|
||||
CargoConfig, PackageRoot, ProcMacroClient, ProjectManifest, ProjectWorkspace,
|
||||
};
|
||||
use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use ra_project_model::{CargoConfig, ProcMacroClient, ProjectManifest, ProjectWorkspace};
|
||||
use vfs::loader::Handle;
|
||||
|
||||
use crate::vfs_glob::RustPackageFilterBuilder;
|
||||
|
||||
fn vfs_file_to_id(f: ra_vfs::VfsFile) -> FileId {
|
||||
FileId(f.0)
|
||||
}
|
||||
fn vfs_root_to_id(r: ra_vfs::VfsRoot) -> SourceRootId {
|
||||
SourceRootId(r.0)
|
||||
}
|
||||
use crate::global_state::{ProjectFolders, SourceRootConfig};
|
||||
|
||||
pub fn load_cargo(
|
||||
root: &Path,
|
||||
load_out_dirs_from_check: bool,
|
||||
with_proc_macro: bool,
|
||||
) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> {
|
||||
) -> Result<(AnalysisHost, vfs::Vfs)> {
|
||||
let root = std::env::current_dir()?.join(root);
|
||||
let root = ProjectManifest::discover_single(&root)?;
|
||||
let ws = ProjectWorkspace::load(
|
||||
@ -35,123 +24,74 @@ pub fn load_cargo(
|
||||
true,
|
||||
)?;
|
||||
|
||||
let mut extern_dirs = FxHashSet::default();
|
||||
|
||||
let (sender, receiver) = unbounded();
|
||||
let sender = Box::new(move |t| sender.send(t).unwrap());
|
||||
let mut vfs = vfs::Vfs::default();
|
||||
let mut loader = {
|
||||
let loader =
|
||||
vfs_notify::LoaderHandle::spawn(Box::new(move |msg| sender.send(msg).unwrap()));
|
||||
Box::new(loader)
|
||||
};
|
||||
|
||||
let mut roots = Vec::new();
|
||||
let project_roots = ws.to_roots();
|
||||
for root in &project_roots {
|
||||
roots.push(RootEntry::new(
|
||||
root.path().to_owned(),
|
||||
RustPackageFilterBuilder::default().set_member(root.is_member()).into_vfs_filter(),
|
||||
));
|
||||
|
||||
if let Some(out_dir) = root.out_dir() {
|
||||
extern_dirs.insert(out_dir.to_path_buf());
|
||||
roots.push(RootEntry::new(
|
||||
out_dir.to_owned(),
|
||||
RustPackageFilterBuilder::default().set_member(root.is_member()).into_vfs_filter(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
let (mut vfs, roots) = Vfs::new(roots, sender, Watch(false));
|
||||
|
||||
let source_roots = roots
|
||||
.into_iter()
|
||||
.map(|vfs_root| {
|
||||
let source_root_id = vfs_root_to_id(vfs_root);
|
||||
let project_root = project_roots
|
||||
.iter()
|
||||
.find(|it| it.path() == vfs.root2path(vfs_root))
|
||||
.unwrap()
|
||||
.clone();
|
||||
(source_root_id, project_root)
|
||||
})
|
||||
.collect::<FxHashMap<_, _>>();
|
||||
|
||||
let proc_macro_client = if !with_proc_macro {
|
||||
ProcMacroClient::dummy()
|
||||
} else {
|
||||
let proc_macro_client = if with_proc_macro {
|
||||
let path = std::env::current_exe()?;
|
||||
ProcMacroClient::extern_process(path, &["proc-macro"]).unwrap()
|
||||
} else {
|
||||
ProcMacroClient::dummy()
|
||||
};
|
||||
let host = load(&source_roots, ws, &mut vfs, receiver, extern_dirs, &proc_macro_client);
|
||||
Ok((host, source_roots))
|
||||
|
||||
let crate_graph = ws.to_crate_graph(None, &proc_macro_client, &mut |path: &Path| {
|
||||
let path = AbsPathBuf::try_from(path.to_path_buf()).unwrap();
|
||||
let contents = loader.load_sync(&path);
|
||||
let path = vfs::VfsPath::from(path);
|
||||
vfs.set_file_contents(path.clone(), contents);
|
||||
vfs.file_id(&path)
|
||||
});
|
||||
|
||||
let project_folders = ProjectFolders::new(&[ws]);
|
||||
loader.set_config(vfs::loader::Config { load: project_folders.load, watch: vec![] });
|
||||
|
||||
log::debug!("crate graph: {:?}", crate_graph);
|
||||
let host = load(crate_graph, project_folders.source_root_config, &mut vfs, &receiver);
|
||||
Ok((host, vfs))
|
||||
}
|
||||
|
||||
pub(crate) fn load(
|
||||
source_roots: &FxHashMap<SourceRootId, PackageRoot>,
|
||||
ws: ProjectWorkspace,
|
||||
vfs: &mut Vfs,
|
||||
receiver: Receiver<VfsTask>,
|
||||
extern_dirs: FxHashSet<PathBuf>,
|
||||
proc_macro_client: &ProcMacroClient,
|
||||
crate_graph: CrateGraph,
|
||||
source_root_config: SourceRootConfig,
|
||||
vfs: &mut vfs::Vfs,
|
||||
receiver: &Receiver<vfs::loader::Message>,
|
||||
) -> AnalysisHost {
|
||||
let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
|
||||
let mut host = AnalysisHost::new(lru_cap);
|
||||
let mut analysis_change = AnalysisChange::new();
|
||||
|
||||
// wait until Vfs has loaded all roots
|
||||
let mut roots_loaded = FxHashSet::default();
|
||||
let mut extern_source_roots = FxHashMap::default();
|
||||
for task in receiver {
|
||||
vfs.handle_task(task);
|
||||
let mut done = false;
|
||||
for change in vfs.commit_changes() {
|
||||
match change {
|
||||
VfsChange::AddRoot { root, files } => {
|
||||
let source_root_id = vfs_root_to_id(root);
|
||||
let is_local = source_roots[&source_root_id].is_member();
|
||||
log::debug!(
|
||||
"loaded source root {:?} with path {:?}",
|
||||
source_root_id,
|
||||
vfs.root2path(root)
|
||||
);
|
||||
analysis_change.add_root(source_root_id, is_local);
|
||||
|
||||
let vfs_root_path = vfs.root2path(root);
|
||||
if extern_dirs.contains(&vfs_root_path) {
|
||||
extern_source_roots.insert(vfs_root_path, ExternSourceId(root.0));
|
||||
}
|
||||
|
||||
let mut file_map = FxHashMap::default();
|
||||
for (vfs_file, path, text) in files {
|
||||
let file_id = vfs_file_to_id(vfs_file);
|
||||
analysis_change.add_file(source_root_id, file_id, path.clone(), text);
|
||||
file_map.insert(path, file_id);
|
||||
}
|
||||
roots_loaded.insert(source_root_id);
|
||||
if roots_loaded.len() == vfs.n_roots() {
|
||||
done = true;
|
||||
}
|
||||
match task {
|
||||
vfs::loader::Message::Progress { n_entries_done, n_entries_total } => {
|
||||
if n_entries_done == n_entries_total {
|
||||
break;
|
||||
}
|
||||
VfsChange::AddFile { root, file, path, text } => {
|
||||
let source_root_id = vfs_root_to_id(root);
|
||||
let file_id = vfs_file_to_id(file);
|
||||
analysis_change.add_file(source_root_id, file_id, path, text);
|
||||
}
|
||||
VfsChange::RemoveFile { .. } | VfsChange::ChangeFile { .. } => {
|
||||
// We just need the first scan, so just ignore these
|
||||
}
|
||||
vfs::loader::Message::Loaded { files } => {
|
||||
for (path, contents) in files {
|
||||
vfs.set_file_contents(path.into(), contents)
|
||||
}
|
||||
}
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
}
|
||||
let changes = vfs.take_changes();
|
||||
for file in changes {
|
||||
if file.exists() {
|
||||
let contents = vfs.file_contents(file.file_id).to_vec();
|
||||
if let Ok(text) = String::from_utf8(contents) {
|
||||
analysis_change.change_file(file.file_id, Some(Arc::new(text)))
|
||||
}
|
||||
}
|
||||
}
|
||||
let source_roots = source_root_config.partition(&vfs);
|
||||
analysis_change.set_roots(source_roots);
|
||||
|
||||
let crate_graph =
|
||||
ws.to_crate_graph(None, &extern_source_roots, proc_macro_client, &mut |path: &Path| {
|
||||
// Some path from metadata will be non canonicalized, e.g. /foo/../bar/lib.rs
|
||||
let path = path.canonicalize().ok()?;
|
||||
let vfs_file = vfs.load(&path);
|
||||
log::debug!("vfs file {:?} -> {:?}", path, vfs_file);
|
||||
vfs_file.map(vfs_file_to_id)
|
||||
});
|
||||
log::debug!("crate graph: {:?}", crate_graph);
|
||||
analysis_change.set_crate_graph(crate_graph);
|
||||
|
||||
host.apply_change(analysis_change);
|
||||
@ -167,7 +107,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_loading_rust_analyzer() {
|
||||
let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
|
||||
let (host, _roots) = load_cargo(path, false, false).unwrap();
|
||||
let (host, _vfs) = load_cargo(path, false, false).unwrap();
|
||||
let n_crates = Crate::all(host.raw_database()).len();
|
||||
// RA has quite a few crates, but the exact count doesn't matter
|
||||
assert!(n_crates > 20);
|
||||
|
@ -1,10 +1,22 @@
|
||||
//! Conversion lsp_types types to rust-analyzer specific ones.
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use ra_db::{FileId, FilePosition, FileRange};
|
||||
use ra_ide::{LineCol, LineIndex};
|
||||
use ra_syntax::{TextRange, TextSize};
|
||||
use vfs::AbsPathBuf;
|
||||
|
||||
use crate::{global_state::GlobalStateSnapshot, Result};
|
||||
|
||||
pub(crate) fn abs_path(url: &lsp_types::Url) -> Result<AbsPathBuf> {
|
||||
let path = url.to_file_path().map_err(|()| "url is not a file")?;
|
||||
Ok(AbsPathBuf::try_from(path).unwrap())
|
||||
}
|
||||
|
||||
pub(crate) fn vfs_path(url: &lsp_types::Url) -> Result<vfs::VfsPath> {
|
||||
abs_path(url).map(vfs::VfsPath::from)
|
||||
}
|
||||
|
||||
pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> TextSize {
|
||||
let line_col = LineCol { line: position.line as u32, col_utf16: position.character as u32 };
|
||||
line_index.offset(line_col)
|
||||
|
@ -3,30 +3,28 @@
|
||||
//!
|
||||
//! Each tick provides an immutable snapshot of the state as `WorldSnapshot`.
|
||||
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use std::{convert::TryFrom, path::Path, sync::Arc};
|
||||
|
||||
use crossbeam_channel::{unbounded, Receiver};
|
||||
use lsp_types::Url;
|
||||
use parking_lot::RwLock;
|
||||
use ra_db::{CrateId, SourceRoot, VfsPath};
|
||||
use ra_flycheck::{Flycheck, FlycheckConfig};
|
||||
use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, SourceRootId};
|
||||
use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId};
|
||||
use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target};
|
||||
use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsTask, Watch};
|
||||
use stdx::format_to;
|
||||
use vfs::{file_set::FileSetConfig, loader::Handle, AbsPathBuf};
|
||||
|
||||
use crate::{
|
||||
config::{Config, FilesWatcher},
|
||||
diagnostics::{CheckFixes, DiagnosticCollection},
|
||||
from_proto,
|
||||
line_endings::LineEndings,
|
||||
main_loop::request_metrics::{LatestRequests, RequestMetrics},
|
||||
to_proto::url_from_abs_path,
|
||||
vfs_glob::{Glob, RustPackageFilterBuilder},
|
||||
LspError, Result,
|
||||
Result,
|
||||
};
|
||||
use ra_db::{CrateId, ExternSourceId};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> {
|
||||
// FIXME: Figure out the multi-workspace situation
|
||||
@ -50,15 +48,16 @@ fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) ->
|
||||
#[derive(Debug)]
|
||||
pub struct GlobalState {
|
||||
pub config: Config,
|
||||
pub local_roots: Vec<PathBuf>,
|
||||
pub workspaces: Arc<Vec<ProjectWorkspace>>,
|
||||
pub analysis_host: AnalysisHost,
|
||||
pub vfs: Arc<RwLock<Vfs>>,
|
||||
pub task_receiver: Receiver<VfsTask>,
|
||||
pub loader: Box<dyn vfs::loader::Handle>,
|
||||
pub task_receiver: Receiver<vfs::loader::Message>,
|
||||
pub flycheck: Option<Flycheck>,
|
||||
pub diagnostics: DiagnosticCollection,
|
||||
pub proc_macro_client: ProcMacroClient,
|
||||
pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
|
||||
pub(crate) latest_requests: Arc<RwLock<LatestRequests>>,
|
||||
source_root_config: SourceRootConfig,
|
||||
}
|
||||
|
||||
/// An immutable snapshot of the world's state at a point in time.
|
||||
@ -68,62 +67,21 @@ pub struct GlobalStateSnapshot {
|
||||
pub analysis: Analysis,
|
||||
pub check_fixes: CheckFixes,
|
||||
pub(crate) latest_requests: Arc<RwLock<LatestRequests>>,
|
||||
vfs: Arc<RwLock<Vfs>>,
|
||||
vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
|
||||
}
|
||||
|
||||
impl GlobalState {
|
||||
pub fn new(
|
||||
workspaces: Vec<ProjectWorkspace>,
|
||||
lru_capacity: Option<usize>,
|
||||
exclude_globs: &[Glob],
|
||||
config: Config,
|
||||
) -> GlobalState {
|
||||
let mut change = AnalysisChange::new();
|
||||
|
||||
let mut extern_dirs: FxHashSet<PathBuf> = FxHashSet::default();
|
||||
let project_folders = ProjectFolders::new(&workspaces);
|
||||
|
||||
let mut local_roots = Vec::new();
|
||||
let roots: Vec<_> = {
|
||||
let create_filter = |is_member| {
|
||||
RustPackageFilterBuilder::default()
|
||||
.set_member(is_member)
|
||||
.exclude(exclude_globs.iter().cloned())
|
||||
.into_vfs_filter()
|
||||
};
|
||||
let mut roots = Vec::new();
|
||||
for root in workspaces.iter().flat_map(ProjectWorkspace::to_roots) {
|
||||
let path = root.path().to_owned();
|
||||
if root.is_member() {
|
||||
local_roots.push(path.clone());
|
||||
}
|
||||
roots.push(RootEntry::new(path, create_filter(root.is_member())));
|
||||
if let Some(out_dir) = root.out_dir() {
|
||||
extern_dirs.insert(out_dir.to_path_buf());
|
||||
roots.push(RootEntry::new(
|
||||
out_dir.to_path_buf(),
|
||||
create_filter(root.is_member()),
|
||||
))
|
||||
}
|
||||
}
|
||||
roots
|
||||
};
|
||||
|
||||
let (task_sender, task_receiver) = unbounded();
|
||||
let task_sender = Box::new(move |t| task_sender.send(t).unwrap());
|
||||
let watch = Watch(matches!(config.files.watcher, FilesWatcher::Notify));
|
||||
let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch);
|
||||
|
||||
let mut extern_source_roots = FxHashMap::default();
|
||||
for r in vfs_roots {
|
||||
let vfs_root_path = vfs.root2path(r);
|
||||
let is_local = local_roots.iter().any(|it| vfs_root_path.starts_with(it));
|
||||
change.add_root(SourceRootId(r.0), is_local);
|
||||
|
||||
// FIXME: add path2root in vfs to simpily this logic
|
||||
if extern_dirs.contains(&vfs_root_path) {
|
||||
extern_source_roots.insert(vfs_root_path, ExternSourceId(r.0));
|
||||
}
|
||||
}
|
||||
let (task_sender, task_receiver) = unbounded::<vfs::loader::Message>();
|
||||
let mut vfs = vfs::Vfs::default();
|
||||
|
||||
let proc_macro_client = match &config.proc_macro_srv {
|
||||
None => ProcMacroClient::dummy(),
|
||||
@ -140,18 +98,30 @@ impl GlobalState {
|
||||
},
|
||||
};
|
||||
|
||||
let mut loader = {
|
||||
let loader = vfs_notify::LoaderHandle::spawn(Box::new(move |msg| {
|
||||
task_sender.send(msg).unwrap()
|
||||
}));
|
||||
Box::new(loader)
|
||||
};
|
||||
let watch = match config.files.watcher {
|
||||
FilesWatcher::Client => vec![],
|
||||
FilesWatcher::Notify => project_folders.watch,
|
||||
};
|
||||
loader.set_config(vfs::loader::Config { load: project_folders.load, watch });
|
||||
|
||||
// Create crate graph from all the workspaces
|
||||
let mut crate_graph = CrateGraph::default();
|
||||
let mut load = |path: &Path| {
|
||||
// Some path from metadata will be non canonicalized, e.g. /foo/../bar/lib.rs
|
||||
let path = path.canonicalize().ok()?;
|
||||
let vfs_file = vfs.load(&path);
|
||||
vfs_file.map(|f| FileId(f.0))
|
||||
let path = AbsPathBuf::try_from(path.to_path_buf()).ok()?;
|
||||
let contents = loader.load_sync(&path);
|
||||
let path = vfs::VfsPath::from(path);
|
||||
vfs.set_file_contents(path.clone(), contents);
|
||||
vfs.file_id(&path)
|
||||
};
|
||||
for ws in workspaces.iter() {
|
||||
crate_graph.extend(ws.to_crate_graph(
|
||||
config.cargo.target.as_deref(),
|
||||
&extern_source_roots,
|
||||
&proc_macro_client,
|
||||
&mut load,
|
||||
));
|
||||
@ -162,18 +132,21 @@ impl GlobalState {
|
||||
|
||||
let mut analysis_host = AnalysisHost::new(lru_capacity);
|
||||
analysis_host.apply_change(change);
|
||||
GlobalState {
|
||||
let mut res = GlobalState {
|
||||
config,
|
||||
local_roots,
|
||||
workspaces: Arc::new(workspaces),
|
||||
analysis_host,
|
||||
vfs: Arc::new(RwLock::new(vfs)),
|
||||
loader,
|
||||
vfs: Arc::new(RwLock::new((vfs, FxHashMap::default()))),
|
||||
task_receiver,
|
||||
latest_requests: Default::default(),
|
||||
flycheck,
|
||||
diagnostics: Default::default(),
|
||||
proc_macro_client,
|
||||
}
|
||||
source_root_config: project_folders.source_root_config,
|
||||
};
|
||||
res.process_changes();
|
||||
res
|
||||
}
|
||||
|
||||
pub fn update_configuration(&mut self, config: Config) {
|
||||
@ -186,33 +159,40 @@ impl GlobalState {
|
||||
self.config = config;
|
||||
}
|
||||
|
||||
/// Returns a vec of libraries
|
||||
/// FIXME: better API here
|
||||
pub fn process_changes(&mut self, roots_scanned: &mut usize) -> bool {
|
||||
let changes = self.vfs.write().commit_changes();
|
||||
if changes.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let mut change = AnalysisChange::new();
|
||||
for c in changes {
|
||||
match c {
|
||||
VfsChange::AddRoot { root, files } => {
|
||||
*roots_scanned += 1;
|
||||
for (file, path, text) in files {
|
||||
change.add_file(SourceRootId(root.0), FileId(file.0), path, text);
|
||||
}
|
||||
}
|
||||
VfsChange::AddFile { root, file, path, text } => {
|
||||
change.add_file(SourceRootId(root.0), FileId(file.0), path, text);
|
||||
}
|
||||
VfsChange::RemoveFile { root, file, path } => {
|
||||
change.remove_file(SourceRootId(root.0), FileId(file.0), path)
|
||||
}
|
||||
VfsChange::ChangeFile { file, text } => {
|
||||
change.change_file(FileId(file.0), text);
|
||||
}
|
||||
pub fn process_changes(&mut self) -> bool {
|
||||
let change = {
|
||||
let mut change = AnalysisChange::new();
|
||||
let (vfs, line_endings_map) = &mut *self.vfs.write();
|
||||
let changed_files = vfs.take_changes();
|
||||
if changed_files.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let fs_op = changed_files.iter().any(|it| it.is_created_or_deleted());
|
||||
if fs_op {
|
||||
let roots = self.source_root_config.partition(&vfs);
|
||||
change.set_roots(roots)
|
||||
}
|
||||
|
||||
for file in changed_files {
|
||||
let text = if file.exists() {
|
||||
let bytes = vfs.file_contents(file.file_id).to_vec();
|
||||
match String::from_utf8(bytes).ok() {
|
||||
Some(text) => {
|
||||
let (text, line_endings) = LineEndings::normalize(text);
|
||||
line_endings_map.insert(file.file_id, line_endings);
|
||||
Some(Arc::new(text))
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
change.change_file(file.file_id, text);
|
||||
}
|
||||
change
|
||||
};
|
||||
|
||||
self.analysis_host.apply_change(change);
|
||||
true
|
||||
}
|
||||
@ -242,35 +222,31 @@ impl GlobalState {
|
||||
}
|
||||
|
||||
impl GlobalStateSnapshot {
|
||||
pub fn analysis(&self) -> &Analysis {
|
||||
pub(crate) fn analysis(&self) -> &Analysis {
|
||||
&self.analysis
|
||||
}
|
||||
|
||||
pub fn url_to_file_id(&self, url: &Url) -> Result<FileId> {
|
||||
let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?;
|
||||
let file = self.vfs.read().path2file(&path).ok_or_else(|| {
|
||||
// Show warning as this file is outside current workspace
|
||||
// FIXME: just handle such files, and remove `LspError::UNKNOWN_FILE`.
|
||||
LspError {
|
||||
code: LspError::UNKNOWN_FILE,
|
||||
message: "Rust file outside current workspace is not supported yet.".to_string(),
|
||||
}
|
||||
})?;
|
||||
Ok(FileId(file.0))
|
||||
pub(crate) fn url_to_file_id(&self, url: &Url) -> Result<FileId> {
|
||||
let path = from_proto::abs_path(url)?;
|
||||
let path = path.into();
|
||||
let res =
|
||||
self.vfs.read().0.file_id(&path).ok_or_else(|| format!("file not found: {}", path))?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn file_id_to_url(&self, id: FileId) -> Url {
|
||||
file_id_to_url(&self.vfs.read(), id)
|
||||
pub(crate) fn file_id_to_url(&self, id: FileId) -> Url {
|
||||
file_id_to_url(&self.vfs.read().0, id)
|
||||
}
|
||||
|
||||
pub fn file_line_endings(&self, id: FileId) -> LineEndings {
|
||||
self.vfs.read().file_line_endings(VfsFile(id.0))
|
||||
pub(crate) fn file_line_endings(&self, id: FileId) -> LineEndings {
|
||||
self.vfs.read().1[&id]
|
||||
}
|
||||
|
||||
pub fn anchored_path(&self, file_id: FileId, path: &str) -> Url {
|
||||
let mut base = self.vfs.read().file2path(VfsFile(file_id.0));
|
||||
let mut base = self.vfs.read().0.file_path(file_id);
|
||||
base.pop();
|
||||
let path = base.join(path);
|
||||
let path = path.as_path().unwrap();
|
||||
url_from_abs_path(&path)
|
||||
}
|
||||
|
||||
@ -279,7 +255,8 @@ impl GlobalStateSnapshot {
|
||||
crate_id: CrateId,
|
||||
) -> Option<(&CargoWorkspace, Target)> {
|
||||
let file_id = self.analysis().crate_root(crate_id).ok()?;
|
||||
let path = self.vfs.read().file2path(VfsFile(file_id.0));
|
||||
let path = self.vfs.read().0.file_path(file_id);
|
||||
let path = path.as_path()?;
|
||||
self.workspaces.iter().find_map(|ws| match ws {
|
||||
ProjectWorkspace::Cargo { cargo, .. } => {
|
||||
cargo.target_by_root(&path).map(|it| (cargo, it))
|
||||
@ -307,14 +284,86 @@ impl GlobalStateSnapshot {
|
||||
);
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
pub fn workspace_root_for(&self, file_id: FileId) -> Option<&Path> {
|
||||
let path = self.vfs.read().file2path(VfsFile(file_id.0));
|
||||
self.workspaces.iter().find_map(|ws| ws.workspace_root_for(&path))
|
||||
pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url {
|
||||
let path = vfs.file_path(id);
|
||||
let path = path.as_path().unwrap();
|
||||
url_from_abs_path(&path)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ProjectFolders {
|
||||
pub(crate) load: Vec<vfs::loader::Entry>,
|
||||
pub(crate) watch: Vec<usize>,
|
||||
pub(crate) source_root_config: SourceRootConfig,
|
||||
}
|
||||
|
||||
impl ProjectFolders {
|
||||
pub(crate) fn new(workspaces: &[ProjectWorkspace]) -> ProjectFolders {
|
||||
let mut res = ProjectFolders::default();
|
||||
let mut fsc = FileSetConfig::builder();
|
||||
let mut local_filesets = vec![];
|
||||
|
||||
for root in workspaces.iter().flat_map(|it| it.to_roots()) {
|
||||
let path = root.path().to_owned();
|
||||
|
||||
let mut file_set_roots: Vec<VfsPath> = vec![];
|
||||
|
||||
let path = AbsPathBuf::try_from(path).unwrap();
|
||||
let entry = if root.is_member() {
|
||||
vfs::loader::Entry::local_cargo_package(path.clone())
|
||||
} else {
|
||||
vfs::loader::Entry::cargo_package_dependency(path.clone())
|
||||
};
|
||||
res.load.push(entry);
|
||||
if root.is_member() {
|
||||
res.watch.push(res.load.len() - 1);
|
||||
}
|
||||
|
||||
if let Some(out_dir) = root.out_dir() {
|
||||
let out_dir = AbsPathBuf::try_from(out_dir.to_path_buf()).unwrap();
|
||||
res.load.push(vfs::loader::Entry::rs_files_recursively(out_dir.clone()));
|
||||
if root.is_member() {
|
||||
res.watch.push(res.load.len() - 1);
|
||||
}
|
||||
file_set_roots.push(out_dir.into());
|
||||
}
|
||||
file_set_roots.push(path.into());
|
||||
|
||||
if root.is_member() {
|
||||
local_filesets.push(fsc.len());
|
||||
}
|
||||
fsc.add_file_set(file_set_roots)
|
||||
}
|
||||
|
||||
let fsc = fsc.build();
|
||||
res.source_root_config = SourceRootConfig { fsc, local_filesets };
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn file_id_to_url(vfs: &Vfs, id: FileId) -> Url {
|
||||
let path = vfs.file2path(VfsFile(id.0));
|
||||
url_from_abs_path(&path)
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct SourceRootConfig {
|
||||
pub(crate) fsc: FileSetConfig,
|
||||
pub(crate) local_filesets: Vec<usize>,
|
||||
}
|
||||
|
||||
impl SourceRootConfig {
|
||||
pub fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
|
||||
self.fsc
|
||||
.partition(vfs)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(idx, file_set)| {
|
||||
let is_local = self.local_filesets.contains(&idx);
|
||||
if is_local {
|
||||
SourceRoot::new_local(file_set)
|
||||
} else {
|
||||
SourceRoot::new_library(file_set)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ macro_rules! eprintln {
|
||||
($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
|
||||
}
|
||||
|
||||
mod vfs_glob;
|
||||
mod caps;
|
||||
mod cargo_target_spec;
|
||||
mod to_proto;
|
||||
@ -29,6 +28,7 @@ pub mod config;
|
||||
mod global_state;
|
||||
mod diagnostics;
|
||||
mod semantic_tokens;
|
||||
mod line_endings;
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
|
64
crates/rust-analyzer/src/line_endings.rs
Normal file
64
crates/rust-analyzer/src/line_endings.rs
Normal file
@ -0,0 +1,64 @@
|
||||
//! We maintain invariant that all internal strings use `\n` as line separator.
|
||||
//! This module does line ending conversion and detection (so that we can
|
||||
//! convert back to `\r\n` on the way out).
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(crate) enum LineEndings {
|
||||
Unix,
|
||||
Dos,
|
||||
}
|
||||
|
||||
impl LineEndings {
|
||||
/// Replaces `\r\n` with `\n` in-place in `src`.
|
||||
pub(crate) fn normalize(src: String) -> (String, LineEndings) {
|
||||
if !src.as_bytes().contains(&b'\r') {
|
||||
return (src, LineEndings::Unix);
|
||||
}
|
||||
|
||||
// We replace `\r\n` with `\n` in-place, which doesn't break utf-8 encoding.
|
||||
// While we *can* call `as_mut_vec` and do surgery on the live string
|
||||
// directly, let's rather steal the contents of `src`. This makes the code
|
||||
// safe even if a panic occurs.
|
||||
|
||||
let mut buf = src.into_bytes();
|
||||
let mut gap_len = 0;
|
||||
let mut tail = buf.as_mut_slice();
|
||||
loop {
|
||||
let idx = match find_crlf(&tail[gap_len..]) {
|
||||
None => tail.len(),
|
||||
Some(idx) => idx + gap_len,
|
||||
};
|
||||
tail.copy_within(gap_len..idx, 0);
|
||||
tail = &mut tail[idx - gap_len..];
|
||||
if tail.len() == gap_len {
|
||||
break;
|
||||
}
|
||||
gap_len += 1;
|
||||
}
|
||||
|
||||
// Account for removed `\r`.
|
||||
// After `set_len`, `buf` is guaranteed to contain utf-8 again.
|
||||
let new_len = buf.len() - gap_len;
|
||||
let src = unsafe {
|
||||
buf.set_len(new_len);
|
||||
String::from_utf8_unchecked(buf)
|
||||
};
|
||||
return (src, LineEndings::Dos);
|
||||
|
||||
fn find_crlf(src: &[u8]) -> Option<usize> {
|
||||
let mut search_idx = 0;
|
||||
while let Some(idx) = find_cr(&src[search_idx..]) {
|
||||
if src[search_idx..].get(idx + 1) != Some(&b'\n') {
|
||||
search_idx += idx + 1;
|
||||
continue;
|
||||
}
|
||||
return Some(search_idx + idx);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn find_cr(src: &[u8]) -> Option<usize> {
|
||||
src.iter().enumerate().find_map(|(idx, &b)| if b == b'\r' { Some(idx) } else { None })
|
||||
}
|
||||
}
|
||||
}
|
@ -2,11 +2,9 @@
|
||||
//! requests/replies and notifications back to the client.
|
||||
|
||||
mod handlers;
|
||||
mod subscriptions;
|
||||
pub(crate) mod request_metrics;
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
env,
|
||||
error::Error,
|
||||
fmt,
|
||||
@ -20,16 +18,12 @@ use crossbeam_channel::{never, select, unbounded, RecvError, Sender};
|
||||
use lsp_server::{
|
||||
Connection, ErrorCode, Message, Notification, ReqQueue, Request, RequestId, Response,
|
||||
};
|
||||
use lsp_types::{
|
||||
request::Request as _, DidChangeTextDocumentParams, NumberOrString,
|
||||
TextDocumentContentChangeEvent, WorkDoneProgress, WorkDoneProgressBegin,
|
||||
WorkDoneProgressCreateParams, WorkDoneProgressEnd, WorkDoneProgressReport,
|
||||
};
|
||||
use ra_flycheck::{CheckTask, Status};
|
||||
use lsp_types::{request::Request as _, NumberOrString, TextDocumentContentChangeEvent};
|
||||
use ra_flycheck::CheckTask;
|
||||
use ra_ide::{Canceled, FileId, LineIndex};
|
||||
use ra_prof::profile;
|
||||
use ra_project_model::{PackageRoot, ProjectWorkspace};
|
||||
use ra_vfs::VfsTask;
|
||||
use rustc_hash::FxHashSet;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use threadpool::ThreadPool;
|
||||
|
||||
@ -39,9 +33,10 @@ use crate::{
|
||||
from_proto,
|
||||
global_state::{file_id_to_url, GlobalState, GlobalStateSnapshot},
|
||||
lsp_ext,
|
||||
main_loop::{request_metrics::RequestMetrics, subscriptions::Subscriptions},
|
||||
main_loop::request_metrics::RequestMetrics,
|
||||
Result,
|
||||
};
|
||||
use ra_db::VfsPath;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LspError {
|
||||
@ -128,13 +123,6 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let globs = config
|
||||
.files
|
||||
.exclude
|
||||
.iter()
|
||||
.map(|glob| crate::vfs_glob::Glob::new(glob))
|
||||
.collect::<std::result::Result<Vec<_>, _>>()?;
|
||||
|
||||
if let FilesWatcher::Client = config.files.watcher {
|
||||
let registration_options = lsp_types::DidChangeWatchedFilesRegistrationOptions {
|
||||
watchers: workspaces
|
||||
@ -159,11 +147,9 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
|
||||
connection.sender.send(request.into()).unwrap();
|
||||
}
|
||||
|
||||
GlobalState::new(workspaces, config.lru_capacity, &globs, config)
|
||||
GlobalState::new(workspaces, config.lru_capacity, config)
|
||||
};
|
||||
|
||||
loop_state.roots_total = global_state.vfs.read().n_roots();
|
||||
|
||||
let pool = ThreadPool::default();
|
||||
let (task_sender, task_receiver) = unbounded::<Task>();
|
||||
|
||||
@ -192,7 +178,9 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
|
||||
break;
|
||||
};
|
||||
}
|
||||
assert!(!global_state.vfs.read().0.has_changes());
|
||||
loop_turn(&pool, &task_sender, &connection, &mut global_state, &mut loop_state, event)?;
|
||||
assert!(!global_state.vfs.read().0.has_changes());
|
||||
}
|
||||
}
|
||||
global_state.analysis_host.request_cancellation();
|
||||
@ -222,7 +210,7 @@ enum Task {
|
||||
enum Event {
|
||||
Msg(Message),
|
||||
Task(Task),
|
||||
Vfs(VfsTask),
|
||||
Vfs(vfs::loader::Message),
|
||||
CheckWatcher(CheckTask),
|
||||
}
|
||||
|
||||
@ -270,11 +258,20 @@ type Incoming = lsp_server::Incoming<(&'static str, Instant)>;
|
||||
#[derive(Default)]
|
||||
struct LoopState {
|
||||
req_queue: ReqQueue<(&'static str, Instant), ReqHandler>,
|
||||
subscriptions: Subscriptions,
|
||||
workspace_loaded: bool,
|
||||
roots_progress_reported: Option<usize>,
|
||||
roots_scanned: usize,
|
||||
roots_total: usize,
|
||||
mem_docs: FxHashSet<VfsPath>,
|
||||
status: Status,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
enum Status {
|
||||
Loading,
|
||||
Ready,
|
||||
}
|
||||
|
||||
impl Default for Status {
|
||||
fn default() -> Self {
|
||||
Status::Loading
|
||||
}
|
||||
}
|
||||
|
||||
fn loop_turn(
|
||||
@ -295,14 +292,36 @@ fn loop_turn(
|
||||
log::info!("queued count = {}", queue_count);
|
||||
}
|
||||
|
||||
let mut became_ready = false;
|
||||
match event {
|
||||
Event::Task(task) => {
|
||||
on_task(task, &connection.sender, &mut loop_state.req_queue.incoming, global_state);
|
||||
global_state.maybe_collect_garbage();
|
||||
}
|
||||
Event::Vfs(task) => {
|
||||
global_state.vfs.write().handle_task(task);
|
||||
}
|
||||
Event::Vfs(task) => match task {
|
||||
vfs::loader::Message::Loaded { files } => {
|
||||
let vfs = &mut global_state.vfs.write().0;
|
||||
for (path, contents) in files {
|
||||
let path = VfsPath::from(path);
|
||||
if !loop_state.mem_docs.contains(&path) {
|
||||
vfs.set_file_contents(path, contents)
|
||||
}
|
||||
}
|
||||
}
|
||||
vfs::loader::Message::Progress { n_entries_total, n_entries_done } => {
|
||||
if n_entries_done == n_entries_done {
|
||||
loop_state.status = Status::Ready;
|
||||
became_ready = true;
|
||||
}
|
||||
report_progress(
|
||||
loop_state,
|
||||
&connection.sender,
|
||||
n_entries_done,
|
||||
n_entries_total,
|
||||
"roots scanned",
|
||||
)
|
||||
}
|
||||
},
|
||||
Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?,
|
||||
Event::Msg(msg) => match msg {
|
||||
Message::Request(req) => on_request(
|
||||
@ -324,32 +343,29 @@ fn loop_turn(
|
||||
},
|
||||
};
|
||||
|
||||
let mut state_changed = global_state.process_changes(&mut loop_state.roots_scanned);
|
||||
let state_changed = global_state.process_changes();
|
||||
|
||||
let show_progress =
|
||||
!loop_state.workspace_loaded && global_state.config.client_caps.work_done_progress;
|
||||
|
||||
if !loop_state.workspace_loaded && loop_state.roots_scanned == loop_state.roots_total {
|
||||
state_changed = true;
|
||||
loop_state.workspace_loaded = true;
|
||||
if became_ready {
|
||||
if let Some(flycheck) = &global_state.flycheck {
|
||||
flycheck.update();
|
||||
}
|
||||
}
|
||||
|
||||
if show_progress {
|
||||
send_startup_progress(&connection.sender, loop_state);
|
||||
}
|
||||
if loop_state.status == Status::Ready && (state_changed || became_ready) {
|
||||
let subscriptions = loop_state
|
||||
.mem_docs
|
||||
.iter()
|
||||
.map(|path| global_state.vfs.read().0.file_id(&path).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if state_changed && loop_state.workspace_loaded {
|
||||
update_file_notifications_on_threadpool(
|
||||
pool,
|
||||
global_state.snapshot(),
|
||||
task_sender.clone(),
|
||||
loop_state.subscriptions.subscriptions(),
|
||||
subscriptions.clone(),
|
||||
);
|
||||
pool.execute({
|
||||
let subs = loop_state.subscriptions.subscriptions();
|
||||
let subs = subscriptions;
|
||||
let snap = global_state.snapshot();
|
||||
move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ())
|
||||
});
|
||||
@ -465,7 +481,7 @@ fn on_request(
|
||||
|
||||
fn on_notification(
|
||||
msg_sender: &Sender<Message>,
|
||||
state: &mut GlobalState,
|
||||
global_state: &mut GlobalState,
|
||||
loop_state: &mut LoopState,
|
||||
not: Notification,
|
||||
) -> Result<()> {
|
||||
@ -484,12 +500,15 @@ fn on_notification(
|
||||
};
|
||||
let not = match notification_cast::<lsp_types::notification::DidOpenTextDocument>(not) {
|
||||
Ok(params) => {
|
||||
let uri = params.text_document.uri;
|
||||
let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
|
||||
if let Some(file_id) =
|
||||
state.vfs.write().add_file_overlay(&path, params.text_document.text)
|
||||
{
|
||||
loop_state.subscriptions.add_sub(FileId(file_id.0));
|
||||
if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) {
|
||||
if !loop_state.mem_docs.insert(path.clone()) {
|
||||
log::error!("duplicate DidOpenTextDocument: {}", path)
|
||||
}
|
||||
global_state
|
||||
.vfs
|
||||
.write()
|
||||
.0
|
||||
.set_file_contents(path, Some(params.text_document.text.into_bytes()));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@ -497,23 +516,13 @@ fn on_notification(
|
||||
};
|
||||
let not = match notification_cast::<lsp_types::notification::DidChangeTextDocument>(not) {
|
||||
Ok(params) => {
|
||||
let DidChangeTextDocumentParams { text_document, content_changes } = params;
|
||||
let world = state.snapshot();
|
||||
let file_id = from_proto::file_id(&world, &text_document.uri)?;
|
||||
let line_index = world.analysis().file_line_index(file_id)?;
|
||||
let uri = text_document.uri;
|
||||
let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
|
||||
state.vfs.write().change_file_overlay(&path, |old_text| {
|
||||
apply_document_changes(old_text, Cow::Borrowed(&line_index), content_changes);
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
Err(not) => not,
|
||||
};
|
||||
let not = match notification_cast::<lsp_types::notification::DidSaveTextDocument>(not) {
|
||||
Ok(_params) => {
|
||||
if let Some(flycheck) = &state.flycheck {
|
||||
flycheck.update();
|
||||
if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) {
|
||||
assert!(loop_state.mem_docs.contains(&path));
|
||||
let vfs = &mut global_state.vfs.write().0;
|
||||
let file_id = vfs.file_id(&path).unwrap();
|
||||
let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap();
|
||||
apply_document_changes(&mut text, params.content_changes);
|
||||
vfs.set_file_contents(path, Some(text.into_bytes()))
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@ -521,19 +530,34 @@ fn on_notification(
|
||||
};
|
||||
let not = match notification_cast::<lsp_types::notification::DidCloseTextDocument>(not) {
|
||||
Ok(params) => {
|
||||
let uri = params.text_document.uri;
|
||||
let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
|
||||
if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) {
|
||||
loop_state.subscriptions.remove_sub(FileId(file_id.0));
|
||||
if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) {
|
||||
if !loop_state.mem_docs.remove(&path) {
|
||||
log::error!("orphan DidCloseTextDocument: {}", path)
|
||||
}
|
||||
if let Some(path) = path.as_path() {
|
||||
global_state.loader.invalidate(path.to_path_buf());
|
||||
}
|
||||
}
|
||||
let params =
|
||||
lsp_types::PublishDiagnosticsParams { uri, diagnostics: Vec::new(), version: None };
|
||||
let params = lsp_types::PublishDiagnosticsParams {
|
||||
uri: params.text_document.uri,
|
||||
diagnostics: Vec::new(),
|
||||
version: None,
|
||||
};
|
||||
let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params);
|
||||
msg_sender.send(not.into()).unwrap();
|
||||
return Ok(());
|
||||
}
|
||||
Err(not) => not,
|
||||
};
|
||||
let not = match notification_cast::<lsp_types::notification::DidSaveTextDocument>(not) {
|
||||
Ok(_params) => {
|
||||
if let Some(flycheck) = &global_state.flycheck {
|
||||
flycheck.update();
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
Err(not) => not,
|
||||
};
|
||||
let not = match notification_cast::<lsp_types::notification::DidChangeConfiguration>(not) {
|
||||
Ok(_) => {
|
||||
// As stated in https://github.com/microsoft/language-server-protocol/issues/676,
|
||||
@ -575,11 +599,10 @@ fn on_notification(
|
||||
};
|
||||
let not = match notification_cast::<lsp_types::notification::DidChangeWatchedFiles>(not) {
|
||||
Ok(params) => {
|
||||
let mut vfs = state.vfs.write();
|
||||
for change in params.changes {
|
||||
let uri = change.uri;
|
||||
let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
|
||||
vfs.notify_changed(path)
|
||||
if let Ok(path) = from_proto::abs_path(&change.uri) {
|
||||
global_state.loader.invalidate(path)
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@ -594,9 +617,9 @@ fn on_notification(
|
||||
|
||||
fn apply_document_changes(
|
||||
old_text: &mut String,
|
||||
mut line_index: Cow<'_, LineIndex>,
|
||||
content_changes: Vec<TextDocumentContentChangeEvent>,
|
||||
) {
|
||||
let mut line_index = LineIndex::new(old_text);
|
||||
// The changes we got must be applied sequentially, but can cross lines so we
|
||||
// have to keep our line index updated.
|
||||
// Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we
|
||||
@ -621,7 +644,7 @@ fn apply_document_changes(
|
||||
match change.range {
|
||||
Some(range) => {
|
||||
if !index_valid.covers(range.end.line) {
|
||||
line_index = Cow::Owned(LineIndex::new(&old_text));
|
||||
line_index = LineIndex::new(&old_text);
|
||||
}
|
||||
index_valid = IndexValid::UpToLineExclusive(range.start.line);
|
||||
let range = from_proto::text_range(&line_index, range);
|
||||
@ -652,18 +675,11 @@ fn on_check_task(
|
||||
&workspace_root,
|
||||
);
|
||||
for diag in diagnostics {
|
||||
let path = diag
|
||||
.location
|
||||
.uri
|
||||
.to_file_path()
|
||||
.map_err(|()| format!("invalid uri: {}", diag.location.uri))?;
|
||||
let file_id = match global_state.vfs.read().path2file(&path) {
|
||||
let path = from_proto::vfs_path(&diag.location.uri)?;
|
||||
let file_id = match global_state.vfs.read().0.file_id(&path) {
|
||||
Some(file) => FileId(file.0),
|
||||
None => {
|
||||
log::error!(
|
||||
"File with cargo diagnostic not found in VFS: {}",
|
||||
path.display()
|
||||
);
|
||||
log::error!("File with cargo diagnostic not found in VFS: {}", path);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
@ -679,7 +695,7 @@ fn on_check_task(
|
||||
CheckTask::Status(status) => {
|
||||
if global_state.config.client_caps.work_done_progress {
|
||||
let progress = match status {
|
||||
Status::Being => {
|
||||
ra_flycheck::Status::Being => {
|
||||
lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
|
||||
title: "Running `cargo check`".to_string(),
|
||||
cancellable: Some(false),
|
||||
@ -687,14 +703,14 @@ fn on_check_task(
|
||||
percentage: None,
|
||||
})
|
||||
}
|
||||
Status::Progress(target) => {
|
||||
ra_flycheck::Status::Progress(target) => {
|
||||
lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
|
||||
cancellable: Some(false),
|
||||
message: Some(target),
|
||||
percentage: None,
|
||||
})
|
||||
}
|
||||
Status::End => {
|
||||
ra_flycheck::Status::End => {
|
||||
lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd {
|
||||
message: None,
|
||||
})
|
||||
@ -720,7 +736,7 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state:
|
||||
let subscriptions = state.diagnostics.handle_task(task);
|
||||
|
||||
for file_id in subscriptions {
|
||||
let url = file_id_to_url(&state.vfs.read(), file_id);
|
||||
let url = file_id_to_url(&state.vfs.read().0, file_id);
|
||||
let diagnostics = state.diagnostics.diagnostics_for(file_id).cloned().collect();
|
||||
let params = lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version: None };
|
||||
let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params);
|
||||
@ -728,57 +744,46 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state:
|
||||
}
|
||||
}
|
||||
|
||||
fn send_startup_progress(sender: &Sender<Message>, loop_state: &mut LoopState) {
|
||||
let total: usize = loop_state.roots_total;
|
||||
let prev = loop_state.roots_progress_reported;
|
||||
let progress = loop_state.roots_scanned;
|
||||
loop_state.roots_progress_reported = Some(progress);
|
||||
fn report_progress(
|
||||
loop_state: &mut LoopState,
|
||||
sender: &Sender<Message>,
|
||||
done: usize,
|
||||
total: usize,
|
||||
message: &str,
|
||||
) {
|
||||
let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", message));
|
||||
let message = Some(format!("{}/{} {}", done, total, message));
|
||||
let percentage = Some(100.0 * done as f64 / total.max(1) as f64);
|
||||
let work_done_progress = if done == 0 {
|
||||
let work_done_progress_create = loop_state.req_queue.outgoing.register(
|
||||
lsp_types::request::WorkDoneProgressCreate::METHOD.to_string(),
|
||||
lsp_types::WorkDoneProgressCreateParams { token: token.clone() },
|
||||
DO_NOTHING,
|
||||
);
|
||||
sender.send(work_done_progress_create.into()).unwrap();
|
||||
|
||||
match (prev, loop_state.workspace_loaded) {
|
||||
(None, false) => {
|
||||
let request = loop_state.req_queue.outgoing.register(
|
||||
lsp_types::request::WorkDoneProgressCreate::METHOD.to_string(),
|
||||
WorkDoneProgressCreateParams {
|
||||
token: lsp_types::ProgressToken::String("rustAnalyzer/startup".into()),
|
||||
},
|
||||
DO_NOTHING,
|
||||
);
|
||||
sender.send(request.into()).unwrap();
|
||||
send_startup_progress_notif(
|
||||
sender,
|
||||
WorkDoneProgress::Begin(WorkDoneProgressBegin {
|
||||
title: "rust-analyzer".into(),
|
||||
cancellable: None,
|
||||
message: Some(format!("{}/{} packages", progress, total)),
|
||||
percentage: Some(100.0 * progress as f64 / total as f64),
|
||||
}),
|
||||
);
|
||||
}
|
||||
(Some(prev), false) if progress != prev => send_startup_progress_notif(
|
||||
sender,
|
||||
WorkDoneProgress::Report(WorkDoneProgressReport {
|
||||
cancellable: None,
|
||||
message: Some(format!("{}/{} packages", progress, total)),
|
||||
percentage: Some(100.0 * progress as f64 / total as f64),
|
||||
}),
|
||||
),
|
||||
(_, true) => send_startup_progress_notif(
|
||||
sender,
|
||||
WorkDoneProgress::End(WorkDoneProgressEnd {
|
||||
message: Some(format!("rust-analyzer loaded, {} packages", progress)),
|
||||
}),
|
||||
),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
fn send_startup_progress_notif(sender: &Sender<Message>, work_done_progress: WorkDoneProgress) {
|
||||
let notif =
|
||||
notification_new::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
|
||||
token: lsp_types::ProgressToken::String("rustAnalyzer/startup".into()),
|
||||
value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress),
|
||||
});
|
||||
sender.send(notif.into()).unwrap();
|
||||
}
|
||||
lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
|
||||
title: "rust-analyzer".into(),
|
||||
cancellable: None,
|
||||
message,
|
||||
percentage,
|
||||
})
|
||||
} else if done < total {
|
||||
lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
|
||||
cancellable: None,
|
||||
message,
|
||||
percentage,
|
||||
})
|
||||
} else {
|
||||
assert!(done == total);
|
||||
lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message })
|
||||
};
|
||||
let notification =
|
||||
notification_new::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
|
||||
token,
|
||||
value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress),
|
||||
});
|
||||
sender.send(notification.into()).unwrap();
|
||||
}
|
||||
|
||||
struct PoolDispatcher<'a> {
|
||||
@ -976,18 +981,12 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::borrow::Cow;
|
||||
|
||||
use lsp_types::{Position, Range, TextDocumentContentChangeEvent};
|
||||
use ra_ide::LineIndex;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn apply_document_changes() {
|
||||
fn run(text: &mut String, changes: Vec<TextDocumentContentChangeEvent>) {
|
||||
let line_index = Cow::Owned(LineIndex::new(&text));
|
||||
super::apply_document_changes(text, line_index, changes);
|
||||
}
|
||||
|
||||
fn test_apply_document_changes() {
|
||||
macro_rules! c {
|
||||
[$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => {
|
||||
vec![$(TextDocumentContentChangeEvent {
|
||||
@ -1002,9 +1001,9 @@ mod tests {
|
||||
}
|
||||
|
||||
let mut text = String::new();
|
||||
run(&mut text, vec![]);
|
||||
apply_document_changes(&mut text, vec![]);
|
||||
assert_eq!(text, "");
|
||||
run(
|
||||
apply_document_changes(
|
||||
&mut text,
|
||||
vec![TextDocumentContentChangeEvent {
|
||||
range: None,
|
||||
@ -1013,36 +1012,39 @@ mod tests {
|
||||
}],
|
||||
);
|
||||
assert_eq!(text, "the");
|
||||
run(&mut text, c![0, 3; 0, 3 => " quick"]);
|
||||
apply_document_changes(&mut text, c![0, 3; 0, 3 => " quick"]);
|
||||
assert_eq!(text, "the quick");
|
||||
run(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
|
||||
apply_document_changes(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
|
||||
assert_eq!(text, "quick foxes");
|
||||
run(&mut text, c![0, 11; 0, 11 => "\ndream"]);
|
||||
apply_document_changes(&mut text, c![0, 11; 0, 11 => "\ndream"]);
|
||||
assert_eq!(text, "quick foxes\ndream");
|
||||
run(&mut text, c![1, 0; 1, 0 => "have "]);
|
||||
apply_document_changes(&mut text, c![1, 0; 1, 0 => "have "]);
|
||||
assert_eq!(text, "quick foxes\nhave dream");
|
||||
run(&mut text, c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"]);
|
||||
apply_document_changes(
|
||||
&mut text,
|
||||
c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"],
|
||||
);
|
||||
assert_eq!(text, "the quick foxes\nhave quiet dreams\n");
|
||||
run(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
|
||||
apply_document_changes(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
|
||||
assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n");
|
||||
run(
|
||||
apply_document_changes(
|
||||
&mut text,
|
||||
c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
|
||||
);
|
||||
assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
|
||||
run(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
|
||||
apply_document_changes(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
|
||||
assert_eq!(text, "the quick \nthey have quiet dreams\n");
|
||||
|
||||
text = String::from("❤️");
|
||||
run(&mut text, c![0, 0; 0, 0 => "a"]);
|
||||
apply_document_changes(&mut text, c![0, 0; 0, 0 => "a"]);
|
||||
assert_eq!(text, "a❤️");
|
||||
|
||||
text = String::from("a\nb");
|
||||
run(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
|
||||
apply_document_changes(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
|
||||
assert_eq!(text, "adcb");
|
||||
|
||||
text = String::from("a\nb");
|
||||
run(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
|
||||
apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
|
||||
assert_eq!(text, "ațc\ncb");
|
||||
}
|
||||
}
|
||||
|
@ -396,7 +396,6 @@ pub fn handle_runnables(
|
||||
let line_index = snap.analysis().file_line_index(file_id)?;
|
||||
let offset = params.position.map(|it| from_proto::offset(&line_index, it));
|
||||
let mut res = Vec::new();
|
||||
let workspace_root = snap.workspace_root_for(file_id);
|
||||
let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
|
||||
for runnable in snap.analysis().runnables(file_id)? {
|
||||
if let Some(offset) = offset {
|
||||
@ -420,7 +419,7 @@ pub fn handle_runnables(
|
||||
location: None,
|
||||
kind: lsp_ext::RunnableKind::Cargo,
|
||||
args: lsp_ext::CargoRunnable {
|
||||
workspace_root: workspace_root.map(|root| root.to_owned()),
|
||||
workspace_root: Some(spec.workspace_root.clone()),
|
||||
cargo_args: vec![
|
||||
cmd.to_string(),
|
||||
"--package".to_string(),
|
||||
@ -437,7 +436,7 @@ pub fn handle_runnables(
|
||||
location: None,
|
||||
kind: lsp_ext::RunnableKind::Cargo,
|
||||
args: lsp_ext::CargoRunnable {
|
||||
workspace_root: workspace_root.map(|root| root.to_owned()),
|
||||
workspace_root: None,
|
||||
cargo_args: vec!["check".to_string(), "--workspace".to_string()],
|
||||
executable_args: Vec::new(),
|
||||
},
|
||||
|
@ -1,22 +0,0 @@
|
||||
//! Keeps track of file subscriptions -- the set of currently opened files for
|
||||
//! which we want to publish diagnostics, syntax highlighting, etc.
|
||||
|
||||
use ra_ide::FileId;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct Subscriptions {
|
||||
subs: FxHashSet<FileId>,
|
||||
}
|
||||
|
||||
impl Subscriptions {
|
||||
pub(crate) fn add_sub(&mut self, file_id: FileId) {
|
||||
self.subs.insert(file_id);
|
||||
}
|
||||
pub(crate) fn remove_sub(&mut self, file_id: FileId) {
|
||||
self.subs.remove(&file_id);
|
||||
}
|
||||
pub(crate) fn subscriptions(&self) -> Vec<FileId> {
|
||||
self.subs.iter().copied().collect()
|
||||
}
|
||||
}
|
@ -10,11 +10,10 @@ use ra_ide::{
|
||||
ResolvedAssist, Runnable, Severity, SourceChange, SourceFileEdit, TextEdit,
|
||||
};
|
||||
use ra_syntax::{SyntaxKind, TextRange, TextSize};
|
||||
use ra_vfs::LineEndings;
|
||||
|
||||
use crate::{
|
||||
cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot, lsp_ext,
|
||||
semantic_tokens, Result,
|
||||
cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot,
|
||||
line_endings::LineEndings, lsp_ext, semantic_tokens, Result,
|
||||
};
|
||||
|
||||
pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position {
|
||||
@ -650,6 +649,7 @@ pub(crate) fn runnable(
|
||||
runnable: Runnable,
|
||||
) -> Result<lsp_ext::Runnable> {
|
||||
let spec = CargoTargetSpec::for_file(snap, file_id)?;
|
||||
let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone());
|
||||
let target = spec.as_ref().map(|s| s.target.clone());
|
||||
let (cargo_args, executable_args) =
|
||||
CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?;
|
||||
@ -661,7 +661,7 @@ pub(crate) fn runnable(
|
||||
location: Some(location),
|
||||
kind: lsp_ext::RunnableKind::Cargo,
|
||||
args: lsp_ext::CargoRunnable {
|
||||
workspace_root: snap.workspace_root_for(file_id).map(|root| root.to_owned()),
|
||||
workspace_root: workspace_root,
|
||||
cargo_args,
|
||||
executable_args,
|
||||
},
|
||||
|
@ -1,98 +0,0 @@
|
||||
//! Exclusion rules for vfs.
|
||||
//!
|
||||
//! By default, we include only `.rs` files, and skip some know offenders like
|
||||
//! `/target` or `/node_modules` altogether.
|
||||
//!
|
||||
//! It's also possible to add custom exclusion globs.
|
||||
|
||||
use globset::{GlobSet, GlobSetBuilder};
|
||||
use ra_vfs::{Filter, RelativePath};
|
||||
|
||||
pub use globset::{Glob, GlobBuilder};
|
||||
|
||||
const ALWAYS_IGNORED: &[&str] = &["target/**", "**/node_modules/**", "**/.git/**"];
|
||||
const IGNORED_FOR_NON_MEMBERS: &[&str] = &["examples/**", "tests/**", "benches/**"];
|
||||
|
||||
pub struct RustPackageFilterBuilder {
|
||||
is_member: bool,
|
||||
exclude: GlobSetBuilder,
|
||||
}
|
||||
|
||||
impl Default for RustPackageFilterBuilder {
|
||||
fn default() -> RustPackageFilterBuilder {
|
||||
RustPackageFilterBuilder { is_member: false, exclude: GlobSetBuilder::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl RustPackageFilterBuilder {
|
||||
pub fn set_member(mut self, is_member: bool) -> RustPackageFilterBuilder {
|
||||
self.is_member = is_member;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn exclude(mut self, globs: impl IntoIterator<Item = Glob>) -> RustPackageFilterBuilder {
|
||||
for glob in globs.into_iter() {
|
||||
self.exclude.add(glob);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn into_vfs_filter(self) -> Box<dyn Filter> {
|
||||
let RustPackageFilterBuilder { is_member, mut exclude } = self;
|
||||
for &glob in ALWAYS_IGNORED {
|
||||
exclude.add(Glob::new(glob).unwrap());
|
||||
}
|
||||
if !is_member {
|
||||
for &glob in IGNORED_FOR_NON_MEMBERS {
|
||||
exclude.add(Glob::new(glob).unwrap());
|
||||
}
|
||||
}
|
||||
Box::new(RustPackageFilter { exclude: exclude.build().unwrap() })
|
||||
}
|
||||
}
|
||||
|
||||
struct RustPackageFilter {
|
||||
exclude: GlobSet,
|
||||
}
|
||||
|
||||
impl Filter for RustPackageFilter {
|
||||
fn include_dir(&self, dir_path: &RelativePath) -> bool {
|
||||
!self.exclude.is_match(dir_path.as_str())
|
||||
}
|
||||
|
||||
fn include_file(&self, file_path: &RelativePath) -> bool {
|
||||
file_path.extension() == Some("rs")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_globs() {
|
||||
let filter = RustPackageFilterBuilder::default().set_member(true).into_vfs_filter();
|
||||
|
||||
assert!(filter.include_dir(RelativePath::new("src/tests")));
|
||||
assert!(filter.include_dir(RelativePath::new("src/target")));
|
||||
assert!(filter.include_dir(RelativePath::new("tests")));
|
||||
assert!(filter.include_dir(RelativePath::new("benches")));
|
||||
|
||||
assert!(!filter.include_dir(RelativePath::new("target")));
|
||||
assert!(!filter.include_dir(RelativePath::new("src/foo/.git")));
|
||||
assert!(!filter.include_dir(RelativePath::new("foo/node_modules")));
|
||||
|
||||
let filter = RustPackageFilterBuilder::default().set_member(false).into_vfs_filter();
|
||||
|
||||
assert!(filter.include_dir(RelativePath::new("src/tests")));
|
||||
assert!(filter.include_dir(RelativePath::new("src/target")));
|
||||
|
||||
assert!(!filter.include_dir(RelativePath::new("target")));
|
||||
assert!(!filter.include_dir(RelativePath::new("src/foo/.git")));
|
||||
assert!(!filter.include_dir(RelativePath::new("foo/node_modules")));
|
||||
assert!(!filter.include_dir(RelativePath::new("tests")));
|
||||
assert!(!filter.include_dir(RelativePath::new("benches")));
|
||||
|
||||
let filter = RustPackageFilterBuilder::default()
|
||||
.set_member(true)
|
||||
.exclude(std::iter::once(Glob::new("src/llvm-project/**").unwrap()))
|
||||
.into_vfs_filter();
|
||||
|
||||
assert!(!filter.include_dir(RelativePath::new("src/llvm-project/clang")));
|
||||
}
|
@ -52,7 +52,7 @@ use std::collections::Spam;
|
||||
partial_result_params: PartialResultParams::default(),
|
||||
work_done_progress_params: WorkDoneProgressParams::default(),
|
||||
});
|
||||
assert!(format!("{}", res).contains("HashMap"));
|
||||
assert!(res.to_string().contains("HashMap"));
|
||||
eprintln!("completion took {:?}", completion_start.elapsed());
|
||||
}
|
||||
|
||||
|
@ -212,7 +212,7 @@ impl Server {
|
||||
ProgressParams {
|
||||
token: lsp_types::ProgressToken::String(ref token),
|
||||
value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(_)),
|
||||
} if token == "rustAnalyzer/startup" => true,
|
||||
} if token == "rustAnalyzer/roots scanned" => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
17
crates/vfs-notify/Cargo.toml
Normal file
17
crates/vfs-notify/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "vfs-notify"
|
||||
version = "0.1.0"
|
||||
authors = ["rust-analyzer developers"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
rustc-hash = "1.0"
|
||||
jod-thread = "0.1.0"
|
||||
walkdir = "2.3.1"
|
||||
globset = "0.4.5"
|
||||
crossbeam-channel = "0.4.0"
|
||||
notify = "5.0.0-pre.3"
|
||||
|
||||
vfs = { path = "../vfs" }
|
||||
paths = { path = "../paths" }
|
43
crates/vfs-notify/src/include.rs
Normal file
43
crates/vfs-notify/src/include.rs
Normal file
@ -0,0 +1,43 @@
|
||||
//! See `Include`.
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||
use paths::{RelPath, RelPathBuf};
|
||||
|
||||
/// `Include` is the opposite of .gitignore.
|
||||
///
|
||||
/// It describes the set of files inside some directory.
|
||||
///
|
||||
/// The current implementation is very limited, it allows white-listing file
|
||||
/// globs and black-listing directories.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Include {
|
||||
include_files: GlobSet,
|
||||
exclude_dirs: Vec<RelPathBuf>,
|
||||
}
|
||||
|
||||
impl Include {
|
||||
pub(crate) fn new(include: Vec<String>) -> Include {
|
||||
let mut include_files = GlobSetBuilder::new();
|
||||
let mut exclude_dirs = Vec::new();
|
||||
|
||||
for glob in include {
|
||||
if glob.starts_with("!/") {
|
||||
if let Ok(path) = RelPathBuf::try_from(&glob["!/".len()..]) {
|
||||
exclude_dirs.push(path)
|
||||
}
|
||||
} else {
|
||||
include_files.add(Glob::new(&glob).unwrap());
|
||||
}
|
||||
}
|
||||
let include_files = include_files.build().unwrap();
|
||||
Include { include_files, exclude_dirs }
|
||||
}
|
||||
pub(crate) fn include_file(&self, path: &RelPath) -> bool {
|
||||
self.include_files.is_match(path)
|
||||
}
|
||||
pub(crate) fn exclude_dir(&self, path: &RelPath) -> bool {
|
||||
self.exclude_dirs.iter().any(|excluded| path.starts_with(excluded))
|
||||
}
|
||||
}
|
247
crates/vfs-notify/src/lib.rs
Normal file
247
crates/vfs-notify/src/lib.rs
Normal file
@ -0,0 +1,247 @@
|
||||
//! An implementation of `loader::Handle`, based on `walkdir` and `notify`.
|
||||
//!
|
||||
//! The file watching bits here are untested and quite probably buggy. For this
|
||||
//! reason, by default we don't watch files and rely on editor's file watching
|
||||
//! capabilities.
|
||||
//!
|
||||
//! Hopefully, one day a reliable file watching/walking crate appears on
|
||||
//! crates.io, and we can reduce this to trivial glue code.
|
||||
mod include;
|
||||
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
use crossbeam_channel::{select, unbounded, Receiver};
|
||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use paths::{AbsPath, AbsPathBuf};
|
||||
use rustc_hash::FxHashSet;
|
||||
use vfs::loader;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::include::Include;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoaderHandle {
|
||||
// Relative order of fields below is significant.
|
||||
sender: crossbeam_channel::Sender<Message>,
|
||||
_thread: jod_thread::JoinHandle,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Message {
|
||||
Config(loader::Config),
|
||||
Invalidate(AbsPathBuf),
|
||||
}
|
||||
|
||||
impl loader::Handle for LoaderHandle {
|
||||
fn spawn(sender: loader::Sender) -> LoaderHandle {
|
||||
let actor = LoaderActor::new(sender);
|
||||
let (sender, receiver) = unbounded::<Message>();
|
||||
let thread = jod_thread::spawn(move || actor.run(receiver));
|
||||
LoaderHandle { sender, _thread: thread }
|
||||
}
|
||||
fn set_config(&mut self, config: loader::Config) {
|
||||
self.sender.send(Message::Config(config)).unwrap()
|
||||
}
|
||||
fn invalidate(&mut self, path: AbsPathBuf) {
|
||||
self.sender.send(Message::Invalidate(path)).unwrap();
|
||||
}
|
||||
fn load_sync(&mut self, path: &AbsPathBuf) -> Option<Vec<u8>> {
|
||||
read(path)
|
||||
}
|
||||
}
|
||||
|
||||
type NotifyEvent = notify::Result<notify::Event>;
|
||||
|
||||
struct LoaderActor {
|
||||
config: Vec<(AbsPathBuf, Include, bool)>,
|
||||
watched_paths: FxHashSet<AbsPathBuf>,
|
||||
sender: loader::Sender,
|
||||
// Drop order of fields bellow is significant,
|
||||
watcher: Option<RecommendedWatcher>,
|
||||
watcher_receiver: Receiver<NotifyEvent>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Event {
|
||||
Message(Message),
|
||||
NotifyEvent(NotifyEvent),
|
||||
}
|
||||
|
||||
impl LoaderActor {
|
||||
fn new(sender: loader::Sender) -> LoaderActor {
|
||||
let (watcher_sender, watcher_receiver) = unbounded();
|
||||
let watcher = log_notify_error(Watcher::new_immediate(move |event| {
|
||||
watcher_sender.send(event).unwrap()
|
||||
}));
|
||||
|
||||
LoaderActor {
|
||||
watcher,
|
||||
watcher_receiver,
|
||||
watched_paths: FxHashSet::default(),
|
||||
sender,
|
||||
config: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn run(mut self, receiver: Receiver<Message>) {
|
||||
while let Some(event) = self.next_event(&receiver) {
|
||||
log::debug!("vfs-notify event: {:?}", event);
|
||||
match event {
|
||||
Event::Message(msg) => match msg {
|
||||
Message::Config(config) => {
|
||||
let n_entries_total = config.load.len();
|
||||
self.send(loader::Message::Progress { n_entries_total, n_entries_done: 0 });
|
||||
|
||||
self.unwatch_all();
|
||||
self.config.clear();
|
||||
|
||||
for (i, entry) in config.load.into_iter().enumerate() {
|
||||
let watch = config.watch.contains(&i);
|
||||
let files = self.load_entry(entry, watch);
|
||||
self.send(loader::Message::Loaded { files });
|
||||
self.send(loader::Message::Progress {
|
||||
n_entries_total,
|
||||
n_entries_done: i + 1,
|
||||
});
|
||||
}
|
||||
self.config.sort_by(|x, y| x.0.cmp(&y.0));
|
||||
}
|
||||
Message::Invalidate(path) => {
|
||||
let contents = read(path.as_path());
|
||||
let files = vec![(path, contents)];
|
||||
self.send(loader::Message::Loaded { files });
|
||||
}
|
||||
},
|
||||
Event::NotifyEvent(event) => {
|
||||
if let Some(event) = log_notify_error(event) {
|
||||
let files = event
|
||||
.paths
|
||||
.into_iter()
|
||||
.map(|path| AbsPathBuf::try_from(path).unwrap())
|
||||
.filter_map(|path| {
|
||||
let is_dir = path.is_dir();
|
||||
let is_file = path.is_file();
|
||||
|
||||
let config_idx =
|
||||
match self.config.binary_search_by(|it| it.0.cmp(&path)) {
|
||||
Ok(it) => it,
|
||||
Err(it) => it.saturating_sub(1),
|
||||
};
|
||||
let include = self.config.get(config_idx).and_then(|it| {
|
||||
let rel_path = path.strip_prefix(&it.0)?;
|
||||
Some((rel_path, &it.1))
|
||||
});
|
||||
|
||||
if let Some((rel_path, include)) = include {
|
||||
if is_dir && include.exclude_dir(&rel_path)
|
||||
|| is_file && !include.include_file(&rel_path)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if is_dir {
|
||||
self.watch(path);
|
||||
return None;
|
||||
}
|
||||
if !is_file {
|
||||
return None;
|
||||
}
|
||||
let contents = read(&path);
|
||||
Some((path, contents))
|
||||
})
|
||||
.collect();
|
||||
self.send(loader::Message::Loaded { files })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn next_event(&self, receiver: &Receiver<Message>) -> Option<Event> {
|
||||
select! {
|
||||
recv(receiver) -> it => it.ok().map(Event::Message),
|
||||
recv(&self.watcher_receiver) -> it => Some(Event::NotifyEvent(it.unwrap())),
|
||||
}
|
||||
}
|
||||
fn load_entry(
|
||||
&mut self,
|
||||
entry: loader::Entry,
|
||||
watch: bool,
|
||||
) -> Vec<(AbsPathBuf, Option<Vec<u8>>)> {
|
||||
match entry {
|
||||
loader::Entry::Files(files) => files
|
||||
.into_iter()
|
||||
.map(|file| {
|
||||
if watch {
|
||||
self.watch(file.clone())
|
||||
}
|
||||
let contents = read(file.as_path());
|
||||
(file, contents)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
loader::Entry::Directory { path, include } => {
|
||||
let include = Include::new(include);
|
||||
self.config.push((path.clone(), include.clone(), watch));
|
||||
|
||||
let files = WalkDir::new(&path)
|
||||
.into_iter()
|
||||
.filter_entry(|entry| {
|
||||
let abs_path: &AbsPath = entry.path().try_into().unwrap();
|
||||
match abs_path.strip_prefix(&path) {
|
||||
Some(rel_path) => {
|
||||
!(entry.file_type().is_dir() && include.exclude_dir(rel_path))
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
})
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter_map(|entry| {
|
||||
let is_dir = entry.file_type().is_dir();
|
||||
let is_file = entry.file_type().is_file();
|
||||
let abs_path = AbsPathBuf::try_from(entry.into_path()).unwrap();
|
||||
if is_dir {
|
||||
self.watch(abs_path.clone());
|
||||
}
|
||||
let rel_path = abs_path.strip_prefix(&path)?;
|
||||
if is_file && include.include_file(&rel_path) {
|
||||
Some(abs_path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
files
|
||||
.map(|file| {
|
||||
let contents = read(file.as_path());
|
||||
(file, contents)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn watch(&mut self, path: AbsPathBuf) {
|
||||
if let Some(watcher) = &mut self.watcher {
|
||||
log_notify_error(watcher.watch(&path, RecursiveMode::NonRecursive));
|
||||
self.watched_paths.insert(path);
|
||||
}
|
||||
}
|
||||
fn unwatch_all(&mut self) {
|
||||
if let Some(watcher) = &mut self.watcher {
|
||||
for path in self.watched_paths.drain() {
|
||||
log_notify_error(watcher.unwatch(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
fn send(&mut self, msg: loader::Message) {
|
||||
(self.sender)(msg)
|
||||
}
|
||||
}
|
||||
|
||||
fn read(path: &AbsPath) -> Option<Vec<u8>> {
|
||||
std::fs::read(path).ok()
|
||||
}
|
||||
|
||||
fn log_notify_error<T>(res: notify::Result<T>) -> Option<T> {
|
||||
res.map_err(|err| log::warn!("notify error: {}", err)).ok()
|
||||
}
|
@ -6,9 +6,5 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
rustc-hash = "1.0"
|
||||
jod-thread = "0.1.0"
|
||||
walkdir = "2.3.1"
|
||||
globset = "0.4.5"
|
||||
crossbeam-channel = "0.4.0"
|
||||
|
||||
paths = { path = "../paths" }
|
||||
|
@ -4,7 +4,6 @@
|
||||
//! the default `FileSet`.
|
||||
use std::{fmt, iter};
|
||||
|
||||
use paths::AbsPathBuf;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::{FileId, Vfs, VfsPath};
|
||||
@ -41,7 +40,7 @@ impl fmt::Debug for FileSet {
|
||||
#[derive(Debug)]
|
||||
pub struct FileSetConfig {
|
||||
n_file_sets: usize,
|
||||
roots: Vec<(AbsPathBuf, usize)>,
|
||||
roots: Vec<(VfsPath, usize)>,
|
||||
}
|
||||
|
||||
impl Default for FileSetConfig {
|
||||
@ -66,11 +65,7 @@ impl FileSetConfig {
|
||||
self.n_file_sets
|
||||
}
|
||||
fn classify(&self, path: &VfsPath) -> usize {
|
||||
let path = match path.as_path() {
|
||||
Some(it) => it,
|
||||
None => return self.len() - 1,
|
||||
};
|
||||
let idx = match self.roots.binary_search_by(|(p, _)| p.as_path().cmp(path)) {
|
||||
let idx = match self.roots.binary_search_by(|(p, _)| p.cmp(path)) {
|
||||
Ok(it) => it,
|
||||
Err(it) => it.saturating_sub(1),
|
||||
};
|
||||
@ -83,7 +78,7 @@ impl FileSetConfig {
|
||||
}
|
||||
|
||||
pub struct FileSetConfigBuilder {
|
||||
roots: Vec<Vec<AbsPathBuf>>,
|
||||
roots: Vec<Vec<VfsPath>>,
|
||||
}
|
||||
|
||||
impl Default for FileSetConfigBuilder {
|
||||
@ -96,12 +91,12 @@ impl FileSetConfigBuilder {
|
||||
pub fn len(&self) -> usize {
|
||||
self.roots.len()
|
||||
}
|
||||
pub fn add_file_set(&mut self, roots: Vec<AbsPathBuf>) {
|
||||
pub fn add_file_set(&mut self, roots: Vec<VfsPath>) {
|
||||
self.roots.push(roots)
|
||||
}
|
||||
pub fn build(self) -> FileSetConfig {
|
||||
let n_file_sets = self.roots.len() + 1;
|
||||
let mut roots: Vec<(AbsPathBuf, usize)> = self
|
||||
let mut roots: Vec<(VfsPath, usize)> = self
|
||||
.roots
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
|
@ -38,7 +38,6 @@ mod vfs_path;
|
||||
mod path_interner;
|
||||
pub mod file_set;
|
||||
pub mod loader;
|
||||
pub mod walkdir_loader;
|
||||
|
||||
use std::{fmt, mem};
|
||||
|
||||
|
@ -3,19 +3,20 @@ use std::fmt;
|
||||
|
||||
use paths::AbsPathBuf;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Entry {
|
||||
Files(Vec<AbsPathBuf>),
|
||||
Directory { path: AbsPathBuf, globs: Vec<String> },
|
||||
Directory { path: AbsPathBuf, include: Vec<String> },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
pub load: Vec<Entry>,
|
||||
pub watch: Vec<usize>,
|
||||
}
|
||||
|
||||
pub enum Message {
|
||||
DidSwitchConfig { n_entries: usize },
|
||||
DidLoadAllEntries,
|
||||
Progress { n_entries_total: usize, n_entries_done: usize },
|
||||
Loaded { files: Vec<(AbsPathBuf, Option<Vec<u8>>)> },
|
||||
}
|
||||
|
||||
@ -32,15 +33,15 @@ pub trait Handle: fmt::Debug {
|
||||
|
||||
impl Entry {
|
||||
pub fn rs_files_recursively(base: AbsPathBuf) -> Entry {
|
||||
Entry::Directory { path: base, globs: globs(&["*.rs"]) }
|
||||
Entry::Directory { path: base, include: globs(&["*.rs", "!/.git/"]) }
|
||||
}
|
||||
pub fn local_cargo_package(base: AbsPathBuf) -> Entry {
|
||||
Entry::Directory { path: base, globs: globs(&["*.rs", "!/target/"]) }
|
||||
Entry::Directory { path: base, include: globs(&["*.rs", "!/target/", "!/.git/"]) }
|
||||
}
|
||||
pub fn cargo_package_dependency(base: AbsPathBuf) -> Entry {
|
||||
Entry::Directory {
|
||||
path: base,
|
||||
globs: globs(&["*.rs", "!/tests/", "!/examples/", "!/benches/"]),
|
||||
include: globs(&["*.rs", "!/tests/", "!/examples/", "!/benches/", "!/.git/"]),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,10 +56,11 @@ impl fmt::Debug for Message {
|
||||
Message::Loaded { files } => {
|
||||
f.debug_struct("Loaded").field("n_files", &files.len()).finish()
|
||||
}
|
||||
Message::DidSwitchConfig { n_entries } => {
|
||||
f.debug_struct("DidSwitchConfig").field("n_entries", n_entries).finish()
|
||||
}
|
||||
Message::DidLoadAllEntries => f.debug_struct("DidLoadAllEntries").finish(),
|
||||
Message::Progress { n_entries_total, n_entries_done } => f
|
||||
.debug_struct("Progress")
|
||||
.field("n_entries_total", n_entries_total)
|
||||
.field("n_entries_done", n_entries_done)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,17 @@ use paths::{AbsPath, AbsPathBuf};
|
||||
pub struct VfsPath(VfsPathRepr);
|
||||
|
||||
impl VfsPath {
|
||||
/// Creates an "in-memory" path from `/`-separates string.
|
||||
/// This is most useful for testing, to avoid windows/linux differences
|
||||
pub fn new_virtual_path(path: String) -> VfsPath {
|
||||
assert!(path.starts_with('/'));
|
||||
VfsPath(VfsPathRepr::VirtualPath(VirtualPath(path)))
|
||||
}
|
||||
|
||||
pub fn as_path(&self) -> Option<&AbsPath> {
|
||||
match &self.0 {
|
||||
VfsPathRepr::PathBuf(it) => Some(it.as_path()),
|
||||
VfsPathRepr::VirtualPath(_) => None,
|
||||
}
|
||||
}
|
||||
pub fn join(&self, path: &str) -> VfsPath {
|
||||
@ -20,11 +28,24 @@ impl VfsPath {
|
||||
let res = it.join(path).normalize();
|
||||
VfsPath(VfsPathRepr::PathBuf(res))
|
||||
}
|
||||
VfsPathRepr::VirtualPath(it) => {
|
||||
let res = it.join(path);
|
||||
VfsPath(VfsPathRepr::VirtualPath(res))
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn pop(&mut self) -> bool {
|
||||
match &mut self.0 {
|
||||
VfsPathRepr::PathBuf(it) => it.pop(),
|
||||
VfsPathRepr::VirtualPath(it) => it.pop(),
|
||||
}
|
||||
}
|
||||
pub fn starts_with(&self, other: &VfsPath) -> bool {
|
||||
match (&self.0, &other.0) {
|
||||
(VfsPathRepr::PathBuf(lhs), VfsPathRepr::PathBuf(rhs)) => lhs.starts_with(rhs),
|
||||
(VfsPathRepr::PathBuf(_), _) => false,
|
||||
(VfsPathRepr::VirtualPath(lhs), VfsPathRepr::VirtualPath(rhs)) => lhs.starts_with(rhs),
|
||||
(VfsPathRepr::VirtualPath(_), _) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -32,11 +53,12 @@ impl VfsPath {
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
||||
enum VfsPathRepr {
|
||||
PathBuf(AbsPathBuf),
|
||||
VirtualPath(VirtualPath),
|
||||
}
|
||||
|
||||
impl From<AbsPathBuf> for VfsPath {
|
||||
fn from(v: AbsPathBuf) -> Self {
|
||||
VfsPath(VfsPathRepr::PathBuf(v))
|
||||
VfsPath(VfsPathRepr::PathBuf(v.normalize()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +66,33 @@ impl fmt::Display for VfsPath {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match &self.0 {
|
||||
VfsPathRepr::PathBuf(it) => fmt::Display::fmt(&it.display(), f),
|
||||
VfsPathRepr::VirtualPath(VirtualPath(it)) => fmt::Display::fmt(it, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
||||
struct VirtualPath(String);
|
||||
|
||||
impl VirtualPath {
|
||||
fn starts_with(&self, other: &VirtualPath) -> bool {
|
||||
self.0.starts_with(&other.0)
|
||||
}
|
||||
fn pop(&mut self) -> bool {
|
||||
let pos = match self.0.rfind('/') {
|
||||
Some(pos) => pos,
|
||||
None => return false,
|
||||
};
|
||||
self.0 = self.0[..pos].to_string();
|
||||
true
|
||||
}
|
||||
fn join(&self, mut path: &str) -> VirtualPath {
|
||||
let mut res = self.clone();
|
||||
while path.starts_with("../") {
|
||||
assert!(res.pop());
|
||||
path = &path["../".len()..]
|
||||
}
|
||||
res.0 = format!("{}/{}", res.0, path);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
@ -1,108 +0,0 @@
|
||||
//! A walkdir-based implementation of `loader::Handle`, which doesn't try to
|
||||
//! watch files.
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use globset::{Glob, GlobSetBuilder};
|
||||
use paths::{AbsPath, AbsPathBuf};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::loader;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WalkdirLoaderHandle {
|
||||
// Relative order of fields below is significant.
|
||||
sender: crossbeam_channel::Sender<Message>,
|
||||
_thread: jod_thread::JoinHandle,
|
||||
}
|
||||
|
||||
enum Message {
|
||||
Config(loader::Config),
|
||||
Invalidate(AbsPathBuf),
|
||||
}
|
||||
|
||||
impl loader::Handle for WalkdirLoaderHandle {
|
||||
fn spawn(sender: loader::Sender) -> WalkdirLoaderHandle {
|
||||
let actor = WalkdirLoaderActor { sender };
|
||||
let (sender, receiver) = crossbeam_channel::unbounded::<Message>();
|
||||
let thread = jod_thread::spawn(move || actor.run(receiver));
|
||||
WalkdirLoaderHandle { sender, _thread: thread }
|
||||
}
|
||||
fn set_config(&mut self, config: loader::Config) {
|
||||
self.sender.send(Message::Config(config)).unwrap()
|
||||
}
|
||||
fn invalidate(&mut self, path: AbsPathBuf) {
|
||||
self.sender.send(Message::Invalidate(path)).unwrap();
|
||||
}
|
||||
fn load_sync(&mut self, path: &AbsPathBuf) -> Option<Vec<u8>> {
|
||||
read(path)
|
||||
}
|
||||
}
|
||||
|
||||
struct WalkdirLoaderActor {
|
||||
sender: loader::Sender,
|
||||
}
|
||||
|
||||
impl WalkdirLoaderActor {
|
||||
fn run(mut self, receiver: crossbeam_channel::Receiver<Message>) {
|
||||
for msg in receiver {
|
||||
match msg {
|
||||
Message::Config(config) => {
|
||||
self.send(loader::Message::DidSwitchConfig { n_entries: config.load.len() });
|
||||
for entry in config.load.into_iter() {
|
||||
let files = self.load_entry(entry);
|
||||
self.send(loader::Message::Loaded { files });
|
||||
}
|
||||
drop(config.watch);
|
||||
self.send(loader::Message::DidLoadAllEntries);
|
||||
}
|
||||
Message::Invalidate(path) => {
|
||||
let contents = read(path.as_path());
|
||||
let files = vec![(path, contents)];
|
||||
self.send(loader::Message::Loaded { files });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn load_entry(&mut self, entry: loader::Entry) -> Vec<(AbsPathBuf, Option<Vec<u8>>)> {
|
||||
match entry {
|
||||
loader::Entry::Files(files) => files
|
||||
.into_iter()
|
||||
.map(|file| {
|
||||
let contents = read(file.as_path());
|
||||
(file, contents)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
loader::Entry::Directory { path, globs } => {
|
||||
let globset = {
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
for glob in &globs {
|
||||
builder.add(Glob::new(glob).unwrap());
|
||||
}
|
||||
builder.build().unwrap()
|
||||
};
|
||||
|
||||
let files = WalkDir::new(path)
|
||||
.into_iter()
|
||||
.filter_map(|it| it.ok())
|
||||
.filter(|it| it.file_type().is_file())
|
||||
.map(|it| it.into_path())
|
||||
.map(|it| AbsPathBuf::try_from(it).unwrap())
|
||||
.filter(|it| globset.is_match(&it));
|
||||
|
||||
files
|
||||
.map(|file| {
|
||||
let contents = read(file.as_path());
|
||||
(file, contents)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
fn send(&mut self, msg: loader::Message) {
|
||||
(self.sender)(msg)
|
||||
}
|
||||
}
|
||||
|
||||
fn read(path: &AbsPath) -> Option<Vec<u8>> {
|
||||
std::fs::read(path).ok()
|
||||
}
|
Loading…
Reference in New Issue
Block a user