diff --git a/Cargo.lock b/Cargo.lock index e5edcaac..3a77c6b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -140,6 +140,16 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.11" @@ -260,6 +270,18 @@ dependencies = [ "log", ] +[[package]] +name = "filetime" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys", +] + [[package]] name = "fnv" version = "1.0.7" @@ -275,6 +297,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futures-core" version = "0.3.24" @@ -483,6 +514,7 @@ dependencies = [ "ignore", "indoc", "log", + "notify", "once_cell", "pulldown-cmark", "serde", @@ -594,6 +626,26 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3" +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "instant" version = "0.1.12" @@ -618,6 +670,26 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kqueue" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6112e8f37b59803ac47a42d14f1f3a59bbf72fc6857ffc5be455e28a691f8e" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -699,6 +771,24 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "notify" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2c66da08abae1c024c01d635253e402341b4060a12e99b31c7594063bf490a" +dependencies = [ + "bitflags", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "mio", + "walkdir", + "winapi", +] + [[package]] name = "num-integer" version = "0.1.45" diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index ac50b610..d8049888 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -66,6 +66,7 @@ serde = { version = "1.0", features = ["derive"] } # ripgrep for global search grep-regex = "0.1.10" grep-searcher = "0.1.10" +notify = "5.0.0" [target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index b94e01ad..1acec031 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1370,6 +1370,7 @@ impl Component for EditorView { if let Some(explore) = self.explorer.as_mut() { if !explore.content.is_focus() && config.explorer.is_embed() { + explore.content.handle_changes(cx).unwrap(); let current_doc = view!(cx.editor).doc; let current_doc = cx.editor.document(current_doc).unwrap(); if let Some(path) = current_doc.path() { diff --git a/helix-term/src/ui/explore.rs b/helix-term/src/ui/explore.rs index 929cccf4..5bbe7bba 100644 --- a/helix-term/src/ui/explore.rs +++ b/helix-term/src/ui/explore.rs @@ -11,9 +11,10 @@ use helix_view::{ input::{Event, KeyEvent}, Editor, }; -use std::borrow::Cow; -use std::cmp::Ordering; +use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher}; use std::path::{Path, PathBuf}; +use std::{borrow::Cow, sync::mpsc::channel}; +use std::{cmp::Ordering, sync::mpsc::Receiver}; use tui::{ buffer::Buffer as Surface, text::{Span, Spans}, @@ -238,39 +239,6 @@ impl TreeItem for FileInfo { } } -// #[derive(Default, Debug, Clone)] -// struct PathState { -// root: PathBuf, -// sub_items: Vec, -// selected: usize, -// save_view: (usize, usize), // (selected, row) -// row: usize, -// col: usize, -// max_len: usize, -// } - -// impl PathState { - -// fn mkdir(&mut self, dir: &str) -> Result<()> { -// self.new_path(dir, FileType::Dir) -// } - -// fn create_file(&mut self, f: &str) -> Result<()> { -// self.new_path(f, FileType::File) -// } - -// fn remove_current_file(&mut self) -> Result<()> { -// let item = &self.sub_items[self.selected]; -// std::fs::remove_file(item.path_with_root(&self.root))?; -// self.sub_items.remove(self.selected); -// if self.selected >= self.sub_items.len() { -// self.selected = self.sub_items.len() - 1; -// } -// Ok(()) -// } - -// } - #[derive(Clone, Copy, Debug)] enum PromptAction { Search(bool), // search next/search pre @@ -304,12 +272,17 @@ pub struct Explorer { on_next_key: Option EventResult>>, #[allow(clippy::type_complexity)] repeat_motion: Option>, + watcher: RecommendedWatcher, + io_events: Receiver>, } impl Explorer { pub fn new(cx: &mut Context) -> Result { let current_root = std::env::current_dir().unwrap_or_else(|_| "./".into()); let items = Self::get_items(current_root.clone(), cx)?; + let (mut watcher, receiver) = Self::create_watcher()?; + watcher.watch(¤t_root, RecursiveMode::Recursive)?; + Ok(Self { tree: Tree::build_tree(items) .with_enter_fn(Self::toggle_current) @@ -318,6 +291,8 @@ impl Explorer { repeat_motion: None, prompt: None, on_next_key: None, + watcher, + io_events: receiver, }) } @@ -339,25 +314,20 @@ impl Explorer { .with_enter_fn(Self::toggle_current) .with_folded_fn(Self::fold_current); tree.insert_current_level(parent); + let (mut watcher, receiver) = Self::create_watcher()?; + watcher.watch(¤t_root, RecursiveMode::Recursive)?; + Ok(Self { tree, state: State::new(true, current_root), repeat_motion: None, prompt: None, on_next_key: None, + watcher, + io_events: receiver, }) - // let mut root = vec![, FileInfo::root(p)]; } - // pub fn new_with_uri(uri: String) -> Result { - // // support remote file? - - // let p = Path::new(&uri); - // ensure!(p.exists(), "path: {uri} is not exist"); - // ensure!(p.is_dir(), "path: {uri} is not dir"); - // Ok(Self::default().with_list(get_sub(p, None)?)) - // } - pub fn focus(&mut self) { self.state.focus = true } @@ -381,6 +351,24 @@ impl Explorer { Ok(items) } + pub fn handle_changes(&mut self, cx: &mut Context) -> Result<()> { + if let Ok(o) = self.io_events.try_recv() { + match o { + Ok(_) => self.refresh(cx), + Err(e) => Err(e.into()), + } + } else { + Ok(()) + } + } + + fn refresh(&mut self, cx: &mut Context) -> Result<()> { + let items = Self::get_items(self.state.current_root.clone(), cx)?; + self.tree.replace_with_new_items(items); + + Ok(()) + } + fn render_preview(&mut self, area: Rect, surface: &mut Surface, editor: &Editor) { if area.height <= 2 || area.width < 60 { return; @@ -422,6 +410,28 @@ impl Explorer { } } + fn watch(&mut self, path: &Path) -> Result<()> { + self.watcher.watch(path, RecursiveMode::Recursive)?; + + Ok(()) + } + + fn unwatch(&mut self, path: &Path) -> Result<()> { + self.watcher.unwatch(path)?; + + Ok(()) + } + + fn create_watcher() -> Result<( + RecommendedWatcher, + Receiver>, + )> { + let (tx, rx) = channel(); + let watcher = RecommendedWatcher::new(tx, Config::default())?; + + Ok((watcher, rx)) + } + fn new_search_prompt(&mut self, search_next: bool) { self.tree.save_view(); self.prompt = Some((