This commit is contained in:
Aleksey Kladov 2020-06-11 11:04:09 +02:00
parent 7aa66371ee
commit dad1333b48
46 changed files with 1028 additions and 1001 deletions

View file

@ -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

View file

@ -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,

View file

@ -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());

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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)

View file

@ -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()
}
}

View file

@ -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;

View 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 })
}
}
}

View file

@ -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(&params.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(&params.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(&params.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");
}
}

View file

@ -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(),
},

View file

@ -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()
}
}

View file

@ -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,
},

View file

@ -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")));
}

View file

@ -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());
}

View file

@ -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,
}
}