mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-29 21:35:20 +00:00
New VFS
This commit is contained in:
parent
7aa66371ee
commit
dad1333b48
46 changed files with 1028 additions and 1001 deletions
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue