mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-30 05:45:12 +00:00
refactor-fvs
This commit is contained in:
parent
3feaf2a008
commit
20d7a431fd
3 changed files with 254 additions and 345 deletions
|
@ -1,19 +1,23 @@
|
|||
use std::{fs, sync::Arc, thread};
|
||||
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
sync::{mpsc, Arc},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
use crossbeam_channel::{Receiver, Sender, SendError};
|
||||
use relative_path::RelativePathBuf;
|
||||
use thread_worker::WorkerHandle;
|
||||
use walkdir::WalkDir;
|
||||
use parking_lot::Mutex;
|
||||
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
|
||||
|
||||
mod watcher;
|
||||
use watcher::Watcher;
|
||||
|
||||
use crate::{RootFilter, Roots, VfsRoot};
|
||||
use crate::{RootConfig, Roots, VfsRoot};
|
||||
|
||||
pub(crate) enum Task {
|
||||
AddRoot {
|
||||
root: VfsRoot,
|
||||
filter: Arc<RootFilter>,
|
||||
config: Arc<RootConfig>,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -39,6 +43,15 @@ pub enum TaskResult {
|
|||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ChangeKind {
|
||||
Create,
|
||||
Write,
|
||||
Remove,
|
||||
}
|
||||
|
||||
const WATCHER_DELAY: Duration = Duration::from_millis(250);
|
||||
|
||||
pub(crate) struct Worker {
|
||||
worker: thread_worker::Worker<Task, TaskResult>,
|
||||
worker_handle: WorkerHandle,
|
||||
|
@ -48,21 +61,36 @@ impl Worker {
|
|||
pub(crate) fn start(roots: Arc<Roots>) -> Worker {
|
||||
let (worker, worker_handle) =
|
||||
thread_worker::spawn("vfs", 128, move |input_receiver, output_sender| {
|
||||
let mut watcher = match Watcher::start(roots, output_sender.clone()) {
|
||||
Ok(w) => Some(w),
|
||||
Err(e) => {
|
||||
log::error!("could not start watcher: {}", e);
|
||||
None
|
||||
}
|
||||
let (notify_sender, notify_receiver) = mpsc::channel();
|
||||
let watcher = notify::watcher(notify_sender, WATCHER_DELAY)
|
||||
.map_err(|e| log::error!("failed to spawn notify {}", e))
|
||||
.ok();
|
||||
let ctx = WatcherCtx {
|
||||
roots,
|
||||
watcher: Arc::new(Mutex::new(watcher)),
|
||||
sender: output_sender,
|
||||
};
|
||||
let res = input_receiver
|
||||
.into_iter()
|
||||
.filter_map(|t| handle_task(t, &mut watcher))
|
||||
.try_for_each(|it| output_sender.send(it));
|
||||
if let Some(watcher) = watcher {
|
||||
let _ = watcher.shutdown();
|
||||
let thread = thread::spawn({
|
||||
let ctx = ctx.clone();
|
||||
move || {
|
||||
let _ = notify_receiver
|
||||
.into_iter()
|
||||
// forward relevant events only
|
||||
.try_for_each(|change| ctx.handle_debounced_event(change));
|
||||
}
|
||||
});
|
||||
let res1 = input_receiver.into_iter().try_for_each(|t| match t {
|
||||
Task::AddRoot { root, config } => watch_root(&ctx, root, Arc::clone(&config)),
|
||||
});
|
||||
drop(ctx.watcher.lock().take());
|
||||
drop(ctx);
|
||||
let res2 = thread.join();
|
||||
match &res2 {
|
||||
Ok(()) => log::info!("... Watcher terminated with ok"),
|
||||
Err(_) => log::error!("... Watcher terminated with err"),
|
||||
}
|
||||
res.unwrap()
|
||||
res1.unwrap();
|
||||
res2.unwrap();
|
||||
});
|
||||
Worker {
|
||||
worker,
|
||||
|
@ -84,46 +112,141 @@ impl Worker {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_task(task: Task, watcher: &mut Option<Watcher>) -> Option<TaskResult> {
|
||||
match task {
|
||||
Task::AddRoot { root, filter } => {
|
||||
if let Some(watcher) = watcher {
|
||||
watcher.watch_root(&filter)
|
||||
fn watch_root(
|
||||
woker: &WatcherCtx,
|
||||
root: VfsRoot,
|
||||
config: Arc<RootConfig>,
|
||||
) -> Result<(), SendError<TaskResult>> {
|
||||
let mut guard = woker.watcher.lock();
|
||||
log::debug!("loading {} ...", config.root.as_path().display());
|
||||
let files = watch_recursive(guard.as_mut(), config.root.as_path(), &*config)
|
||||
.into_iter()
|
||||
.filter_map(|path| {
|
||||
let abs_path = path.to_path(&config.root);
|
||||
let text = fs::read_to_string(abs_path)
|
||||
.map_err(|e| log::warn!("watcher error: {}", e))
|
||||
.ok()?;
|
||||
Some((path, text))
|
||||
})
|
||||
.collect();
|
||||
woker
|
||||
.sender
|
||||
.send(TaskResult::BulkLoadRoot { root, files })?;
|
||||
log::debug!("... loaded {}", config.root.as_path().display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct WatcherCtx {
|
||||
roots: Arc<Roots>,
|
||||
watcher: Arc<Mutex<Option<RecommendedWatcher>>>,
|
||||
sender: Sender<TaskResult>,
|
||||
}
|
||||
|
||||
impl WatcherCtx {
|
||||
fn handle_debounced_event(&self, ev: DebouncedEvent) -> Result<(), SendError<TaskResult>> {
|
||||
match ev {
|
||||
DebouncedEvent::NoticeWrite(_)
|
||||
| DebouncedEvent::NoticeRemove(_)
|
||||
| DebouncedEvent::Chmod(_) => {
|
||||
// ignore
|
||||
}
|
||||
DebouncedEvent::Rescan => {
|
||||
// TODO rescan all roots
|
||||
}
|
||||
DebouncedEvent::Create(path) => {
|
||||
self.handle_change(path, ChangeKind::Create)?;
|
||||
}
|
||||
DebouncedEvent::Write(path) => {
|
||||
self.handle_change(path, ChangeKind::Write)?;
|
||||
}
|
||||
DebouncedEvent::Remove(path) => {
|
||||
self.handle_change(path, ChangeKind::Remove)?;
|
||||
}
|
||||
DebouncedEvent::Rename(src, dst) => {
|
||||
self.handle_change(src, ChangeKind::Remove)?;
|
||||
self.handle_change(dst, ChangeKind::Create)?;
|
||||
}
|
||||
DebouncedEvent::Error(err, path) => {
|
||||
// TODO should we reload the file contents?
|
||||
log::warn!("watcher error \"{}\", {:?}", err, path);
|
||||
}
|
||||
log::debug!("loading {} ...", filter.root.as_path().display());
|
||||
let files = load_root(filter.as_ref());
|
||||
log::debug!("... loaded {}", filter.root.as_path().display());
|
||||
Some(TaskResult::BulkLoadRoot { root, files })
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_change(&self, path: PathBuf, kind: ChangeKind) -> Result<(), SendError<TaskResult>> {
|
||||
let (root, rel_path) = match self.roots.find(&path) {
|
||||
None => return Ok(()),
|
||||
Some(it) => it,
|
||||
};
|
||||
let config = &self.roots[root];
|
||||
match kind {
|
||||
ChangeKind::Create => {
|
||||
let mut paths = Vec::new();
|
||||
if path.is_dir() {
|
||||
let mut guard = self.watcher.lock();
|
||||
paths.extend(watch_recursive(guard.as_mut(), &path, &config));
|
||||
} else {
|
||||
paths.push(rel_path);
|
||||
}
|
||||
paths
|
||||
.into_iter()
|
||||
.filter_map(|rel_path| {
|
||||
let abs_path = rel_path.to_path(&config.root);
|
||||
let text = fs::read_to_string(&abs_path)
|
||||
.map_err(|e| log::warn!("watcher failed {}", e))
|
||||
.ok()?;
|
||||
Some((rel_path, text))
|
||||
})
|
||||
.try_for_each(|(path, text)| {
|
||||
self.sender
|
||||
.send(TaskResult::AddSingleFile { root, path, text })
|
||||
})?
|
||||
}
|
||||
ChangeKind::Write => match fs::read_to_string(&path) {
|
||||
Err(e) => log::warn!("watcher failed {}", e),
|
||||
Ok(text) => self.sender.send(TaskResult::ChangeSingleFile {
|
||||
root,
|
||||
path: rel_path,
|
||||
text,
|
||||
})?,
|
||||
},
|
||||
ChangeKind::Remove => self.sender.send(TaskResult::RemoveSingleFile {
|
||||
root,
|
||||
path: rel_path,
|
||||
})?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn load_root(filter: &RootFilter) -> Vec<(RelativePathBuf, String)> {
|
||||
let mut res = Vec::new();
|
||||
for entry in WalkDir::new(&filter.root)
|
||||
fn watch_recursive(
|
||||
mut watcher: Option<&mut RecommendedWatcher>,
|
||||
dir: &Path,
|
||||
config: &RootConfig,
|
||||
) -> Vec<RelativePathBuf> {
|
||||
let mut files = Vec::new();
|
||||
for entry in WalkDir::new(dir)
|
||||
.into_iter()
|
||||
.filter_entry(filter.entry_filter())
|
||||
.filter_entry(|it| config.contains(it.path()).is_some())
|
||||
.filter_map(|it| it.map_err(|e| log::warn!("watcher error: {}", e)).ok())
|
||||
{
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(e) => {
|
||||
log::warn!("watcher error: {}", e);
|
||||
continue;
|
||||
if entry.file_type().is_dir() {
|
||||
if let Some(watcher) = &mut watcher {
|
||||
watch_one(watcher, entry.path());
|
||||
}
|
||||
};
|
||||
if !entry.file_type().is_file() {
|
||||
continue;
|
||||
} else {
|
||||
let path = config.contains(entry.path()).unwrap();
|
||||
files.push(path.to_owned());
|
||||
}
|
||||
let path = entry.path();
|
||||
let text = match fs::read_to_string(path) {
|
||||
Ok(text) => text,
|
||||
Err(e) => {
|
||||
log::warn!("watcher error: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let path = RelativePathBuf::from_path(path.strip_prefix(&filter.root).unwrap()).unwrap();
|
||||
res.push((path.to_owned(), text))
|
||||
}
|
||||
res
|
||||
files
|
||||
}
|
||||
|
||||
fn watch_one(watcher: &mut RecommendedWatcher, dir: &Path) {
|
||||
match watcher.watch(dir, RecursiveMode::NonRecursive) {
|
||||
Ok(()) => log::debug!("watching \"{}\"", dir.display()),
|
||||
Err(e) => log::warn!("could not watch \"{}\": {}", dir.display(), e),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue