Msg(RawMessage),
Task(Task),
Vfs(VfsTask),
- Watcher(WatcherChange),
Lib(LibraryData),
}
Event::Task(it) => fmt::Debug::fmt(it, f),
Event::Vfs(it) => fmt::Debug::fmt(it, f),
Event::Lib(it) => fmt::Debug::fmt(it, f),
- Event::Watcher(it) => fmt::Debug::fmt(it, f),
}
}
}
Ok(task) => Event::Vfs(task),
Err(RecvError) => bail!("vfs died"),
},
- recv(state.vfs.read().change_receiver()) -> change => match change {
- Ok(change) => Event::Watcher(change),
- Err(RecvError) => bail!("vfs watcher died"),
- },
recv(libdata_receiver) -> data => Event::Lib(data.unwrap())
};
log::info!("loop_turn = {:?}", event);
state.vfs.write().handle_task(task);
state_changed = true;
}
- Event::Watcher(change) => {
- state.vfs.write().handle_change(change);
- state_changed = true;
- }
Event::Lib(lib) => {
feedback(internal_mode, "library loaded", msg_sender);
state.add_lib(lib);
if let Some(file_id) = state
.vfs
.write()
- .add_file_overlay(&path, Some(params.text_document.text))
+ .add_file_overlay(&path, params.text_document.text)
{
subs.add_sub(FileId(file_id.0.into()));
}
.pop()
.ok_or_else(|| format_err!("empty changes"))?
.text;
- state
- .vfs
- .write()
- .change_file_overlay(path.as_path(), Some(text));
+ state.vfs.write().change_file_overlay(path.as_path(), text);
return Ok(());
}
Err(not) => not,
use crate::{VfsRoot, has_rs_extension};
-pub(crate) struct Task {
- pub(crate) root: VfsRoot,
- pub(crate) path: PathBuf,
- pub(crate) filter: Box<Fn(&DirEntry) -> bool + Send>,
+pub(crate) enum Task {
+ AddRoot {
+ root: VfsRoot,
+ path: PathBuf,
+ filter: Box<Fn(&DirEntry) -> bool + Send>,
+ },
+ WatcherChange(crate::watcher::WatcherChange),
}
-pub struct TaskResult {
+#[derive(Debug)]
+pub struct AddRootResult {
pub(crate) root: VfsRoot,
pub(crate) files: Vec<(RelativePathBuf, String)>,
}
+#[derive(Debug)]
+pub enum WatcherChangeResult {
+ Create {
+ path: PathBuf,
+ text: String,
+ },
+ Write {
+ path: PathBuf,
+ text: String,
+ },
+ Remove {
+ path: PathBuf,
+ },
+ // can this be replaced and use Remove and Create instead?
+ Rename {
+ src: PathBuf,
+ dst: PathBuf,
+ text: String,
+ },
+}
+
+pub enum TaskResult {
+ AddRoot(AddRootResult),
+ WatcherChange(WatcherChangeResult),
+}
+
impl fmt::Debug for TaskResult {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("TaskResult { ... }")
}
fn handle_task(task: Task) -> TaskResult {
- let Task { root, path, filter } = task;
- log::debug!("loading {} ...", path.as_path().display());
- let files = load_root(path.as_path(), &*filter);
- log::debug!("... loaded {}", path.as_path().display());
- TaskResult { root, files }
+ match task {
+ Task::AddRoot { root, path, filter } => {
+ log::debug!("loading {} ...", path.as_path().display());
+ let files = load_root(path.as_path(), &*filter);
+ log::debug!("... loaded {}", path.as_path().display());
+ TaskResult::AddRoot(AddRootResult { root, files })
+ }
+ Task::WatcherChange(change) => {
+ // TODO
+ unimplemented!()
+ }
+ }
}
fn load_root(root: &Path, filter: &dyn Fn(&DirEntry) -> bool) -> Vec<(RelativePathBuf, String)> {
}
}
-fn has_rs_extension(p: &Path) -> bool {
+pub(crate) fn has_rs_extension(p: &Path) -> bool {
p.extension() == Some(OsStr::new("rs"))
}
pub fn new(mut roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
let (worker, worker_handle) = io::start();
- let watcher = Watcher::start().unwrap(); // TODO return Result?
+ let watcher = Watcher::start(worker.inp.clone()).unwrap(); // TODO return Result?
let mut res = Vfs {
roots: Arena::default(),
nested.iter().all(|it| it != entry.path())
}
};
- let task = io::Task {
+ let task = io::Task::AddRoot {
root,
path: path.clone(),
filter: Box::new(filter),
&self.worker.out
}
- pub fn change_receiver(&self) -> &Receiver<WatcherChange> {
- &self.watcher.change_receiver()
- }
-
pub fn handle_task(&mut self, task: io::TaskResult) {
- let mut files = Vec::new();
- // While we were scanning the root in the backgound, a file might have
- // been open in the editor, so we need to account for that.
- let exising = self.root2files[&task.root]
- .iter()
- .map(|&file| (self.files[file].path.clone(), file))
- .collect::<FxHashMap<_, _>>();
- for (path, text) in task.files {
- if let Some(&file) = exising.get(&path) {
- let text = Arc::clone(&self.files[file].text);
- files.push((file, path, text));
- continue;
- }
- let text = Arc::new(text);
- let file = self.add_file(task.root, path.clone(), Arc::clone(&text));
- files.push((file, path, text));
- }
-
- let change = VfsChange::AddRoot {
- root: task.root,
- files,
- };
- self.pending_changes.push(change);
- }
+ match task {
+ io::TaskResult::AddRoot(task) => {
+ let mut files = Vec::new();
+ // While we were scanning the root in the backgound, a file might have
+ // been open in the editor, so we need to account for that.
+ let exising = self.root2files[&task.root]
+ .iter()
+ .map(|&file| (self.files[file].path.clone(), file))
+ .collect::<FxHashMap<_, _>>();
+ for (path, text) in task.files {
+ if let Some(&file) = exising.get(&path) {
+ let text = Arc::clone(&self.files[file].text);
+ files.push((file, path, text));
+ continue;
+ }
+ let text = Arc::new(text);
+ let file = self.add_file(task.root, path.clone(), Arc::clone(&text));
+ files.push((file, path, text));
+ }
- pub fn handle_change(&mut self, change: WatcherChange) {
- match change {
- WatcherChange::Create(path) => {
- self.add_file_overlay(&path, None);
- }
- WatcherChange::Remove(path) => {
- self.remove_file_overlay(&path);
- }
- WatcherChange::Rename(src, dst) => {
- self.remove_file_overlay(&src);
- self.add_file_overlay(&dst, None);
+ let change = VfsChange::AddRoot {
+ root: task.root,
+ files,
+ };
+ self.pending_changes.push(change);
}
- WatcherChange::Write(path) => {
- self.change_file_overlay(&path, None);
+ io::TaskResult::WatcherChange(change) => {
+ // TODO
+ unimplemented!()
}
}
}
- pub fn add_file_overlay(&mut self, path: &Path, text: Option<String>) -> Option<VfsFile> {
+ pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
let mut res = None;
if let Some((root, rel_path, file)) = self.find_root(path) {
- let text = text.unwrap_or_else(|| fs::read_to_string(&path).unwrap_or_default());
let text = Arc::new(text);
let change = if let Some(file) = file {
res = Some(file);
res
}
- pub fn change_file_overlay(&mut self, path: &Path, new_text: Option<String>) {
+ pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
if let Some((_root, _path, file)) = self.find_root(path) {
- let new_text =
- new_text.unwrap_or_else(|| fs::read_to_string(&path).unwrap_or_default());
let file = file.expect("can't change a file which wasn't added");
let text = Arc::new(new_text);
self.change_file(file, Arc::clone(&text));
time::Duration,
};
-use crossbeam_channel::Receiver;
+use crossbeam_channel::Sender;
use drop_bomb::DropBomb;
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as NotifyWatcher};
+use crate::{has_rs_extension, io};
pub struct Watcher {
- receiver: Receiver<WatcherChange>,
watcher: RecommendedWatcher,
thread: thread::JoinHandle<()>,
bomb: DropBomb,
Create(PathBuf),
Write(PathBuf),
Remove(PathBuf),
+ // can this be replaced and use Remove and Create instead?
Rename(PathBuf, PathBuf),
}
impl WatcherChange {
- fn from_debounced_event(ev: DebouncedEvent) -> Option<WatcherChange> {
+ fn try_from_debounced_event(ev: DebouncedEvent) -> Option<WatcherChange> {
match ev {
DebouncedEvent::NoticeWrite(_)
| DebouncedEvent::NoticeRemove(_)
- | DebouncedEvent::Chmod(_)
- | DebouncedEvent::Rescan => {
+ | DebouncedEvent::Chmod(_) => {
// ignore
None
}
- DebouncedEvent::Create(path) => Some(WatcherChange::Create(path)),
- DebouncedEvent::Write(path) => Some(WatcherChange::Write(path)),
- DebouncedEvent::Remove(path) => Some(WatcherChange::Remove(path)),
- DebouncedEvent::Rename(src, dst) => Some(WatcherChange::Rename(src, dst)),
+ DebouncedEvent::Rescan => {
+ // TODO should we rescan the root?
+ None
+ }
+ DebouncedEvent::Create(path) => {
+ if has_rs_extension(&path) {
+ Some(WatcherChange::Create(path))
+ } else {
+ None
+ }
+ }
+ DebouncedEvent::Write(path) => {
+ if has_rs_extension(&path) {
+ Some(WatcherChange::Write(path))
+ } else {
+ None
+ }
+ }
+ DebouncedEvent::Remove(path) => {
+ if has_rs_extension(&path) {
+ Some(WatcherChange::Remove(path))
+ } else {
+ None
+ }
+ }
+ DebouncedEvent::Rename(src, dst) => {
+ match (has_rs_extension(&src), has_rs_extension(&dst)) {
+ (true, true) => Some(WatcherChange::Rename(src, dst)),
+ (true, false) => Some(WatcherChange::Remove(src)),
+ (false, true) => Some(WatcherChange::Create(dst)),
+ (false, false) => None,
+ }
+ }
DebouncedEvent::Error(err, path) => {
+ // TODO should we reload the file contents?
log::warn!("watch error {}, {:?}", err, path);
None
}
}
impl Watcher {
- pub fn start() -> Result<Watcher, Box<std::error::Error>> {
+ pub(crate) fn start(
+ output_sender: Sender<io::Task>,
+ ) -> Result<Watcher, Box<std::error::Error>> {
let (input_sender, input_receiver) = mpsc::channel();
let watcher = notify::watcher(input_sender, Duration::from_millis(250))?;
- let (output_sender, output_receiver) = crossbeam_channel::unbounded();
let thread = thread::spawn(move || {
input_receiver
.into_iter()
// forward relevant events only
- .filter_map(WatcherChange::from_debounced_event)
- .try_for_each(|change| output_sender.send(change))
+ .filter_map(WatcherChange::try_from_debounced_event)
+ .try_for_each(|change| output_sender.send(io::Task::WatcherChange(change)))
.unwrap()
});
Ok(Watcher {
- receiver: output_receiver,
watcher,
thread,
bomb: DropBomb::new(format!("Watcher was not shutdown")),
Ok(())
}
- pub fn change_receiver(&self) -> &Receiver<WatcherChange> {
- &self.receiver
- }
-
pub fn shutdown(mut self) -> thread::Result<()> {
self.bomb.defuse();
drop(self.watcher);
// on disk change
fs::write(&dir.path().join("a/b/baz.rs"), "quux").unwrap();
- let change = vfs.change_receiver().recv().unwrap();
- vfs.handle_change(change);
+ let task = vfs.task_receiver().recv().unwrap();
+ vfs.handle_task(task);
match vfs.commit_changes().as_slice() {
[VfsChange::ChangeFile { text, .. }] => assert_eq!(text.as_str(), "quux"),
_ => panic!("unexpected changes"),
}
// in memory change
- vfs.change_file_overlay(&dir.path().join("a/b/baz.rs"), Some("m".to_string()));
+ vfs.change_file_overlay(&dir.path().join("a/b/baz.rs"), "m".to_string());
match vfs.commit_changes().as_slice() {
[VfsChange::ChangeFile { text, .. }] => assert_eq!(text.as_str(), "m"),
_ => panic!("unexpected changes"),
}
// in memory add
- vfs.add_file_overlay(&dir.path().join("a/b/spam.rs"), Some("spam".to_string()));
+ vfs.add_file_overlay(&dir.path().join("a/b/spam.rs"), "spam".to_string());
match vfs.commit_changes().as_slice() {
[VfsChange::AddFile { text, path, .. }] => {
assert_eq!(text.as_str(), "spam");
// on disk add
fs::write(&dir.path().join("a/new.rs"), "new hello").unwrap();
- let change = vfs.change_receiver().recv().unwrap();
- vfs.handle_change(change);
+ let task = vfs.task_receiver().recv().unwrap();
+ vfs.handle_task(task);
match vfs.commit_changes().as_slice() {
[VfsChange::AddFile { text, path, .. }] => {
assert_eq!(text.as_str(), "new hello");
// on disk rename
fs::rename(&dir.path().join("a/new.rs"), &dir.path().join("a/new1.rs")).unwrap();
- let change = vfs.change_receiver().recv().unwrap();
- vfs.handle_change(change);
+ let task = vfs.task_receiver().recv().unwrap();
+ vfs.handle_task(task);
match vfs.commit_changes().as_slice() {
[VfsChange::RemoveFile {
path: removed_path, ..
// on disk remove
fs::remove_file(&dir.path().join("a/new1.rs")).unwrap();
- let change = vfs.change_receiver().recv().unwrap();
- vfs.handle_change(change);
+ let task = vfs.task_receiver().recv().unwrap();
+ vfs.handle_task(task);
match vfs.commit_changes().as_slice() {
[VfsChange::RemoveFile { path, .. }] => assert_eq!(path, "new1.rs"),
_ => panic!("unexpected changes"),
}
- match vfs.change_receiver().try_recv() {
+ match vfs.task_receiver().try_recv() {
Err(crossbeam_channel::TryRecvError::Empty) => (),
res => panic!("unexpected {:?}", res),
}