diff options
-rw-r--r-- | src/fail.rs | 4 | ||||
-rw-r--r-- | src/file_browser.rs | 49 | ||||
-rw-r--r-- | src/hbox.rs | 6 | ||||
-rw-r--r-- | src/listview.rs | 11 | ||||
-rw-r--r-- | src/main.rs | 3 | ||||
-rw-r--r-- | src/miller_columns.rs | 3 | ||||
-rw-r--r-- | src/minibuffer.rs | 5 | ||||
-rw-r--r-- | src/preview.rs | 6 | ||||
-rw-r--r-- | src/proclist.rs | 250 | ||||
-rw-r--r-- | src/tabview.rs | 4 | ||||
-rw-r--r-- | src/textview.rs | 6 | ||||
-rw-r--r-- | src/widget.rs | 62 | ||||
-rw-r--r-- | src/window.rs | 61 |
13 files changed, 399 insertions, 71 deletions
diff --git a/src/fail.rs b/src/fail.rs index 3e85e11..02287f9 100644 --- a/src/fail.rs +++ b/src/fail.rs @@ -32,7 +32,9 @@ pub enum HError { #[fail(display = "No widget found")] NoWidgetError, #[fail(display = "Path: {:?} not in this directory: {:?}", path, dir)] - WrongDirectoryError{ path: PathBuf, dir: PathBuf } + WrongDirectoryError{ path: PathBuf, dir: PathBuf }, + #[fail(display = "Widget finnished")] + PopupFinnished, } impl From<std::io::Error> for HError { diff --git a/src/file_browser.rs b/src/file_browser.rs index 31e15da..a950f0a 100644 --- a/src/file_browser.rs +++ b/src/file_browser.rs @@ -17,6 +17,7 @@ use crate::tabview::{TabView, Tabbable}; use crate::preview::WillBeWidget; use crate::fail::{HResult, HError}; use crate::window::{Events, send_event}; +use crate::proclist::ProcView; @@ -25,12 +26,17 @@ pub struct FileBrowser { pub cwd: File, watcher: INotifyWatcher, watches: Vec<PathBuf>, - dir_events: Arc<Mutex<Vec<DebouncedEvent>>> + dir_events: Arc<Mutex<Vec<DebouncedEvent>>>, + proc_view: Arc<Mutex<ProcView>>, } impl Tabbable for TabView<FileBrowser> { fn new_tab(&mut self) { - let tab = FileBrowser::new().unwrap(); + let mut tab = FileBrowser::new().unwrap(); + + let proc_view = self.active_tab_().proc_view.clone(); + tab.proc_view = proc_view; + self.push_widget(tab); self.active += 1; } @@ -71,7 +77,7 @@ impl Tabbable for TabView<FileBrowser> { .collect::<Vec<_>>(); self.widgets[self.active].exec_cmd(tab_dirs).ok(); } - _ => self.active_tab_mut().on_key(key) + _ => { self.active_tab_mut().on_key(key).ok(); } } } } @@ -125,11 +131,15 @@ impl FileBrowser { let watcher = INotifyWatcher::new(tx_watch, Duration::from_secs(2)).unwrap(); watch_dir(rx_watch, dir_events.clone()); + let mut proc_view = ProcView::new(); + proc_view.set_coordinates(&coords); + Ok(FileBrowser { columns: miller, cwd: cwd, watcher: watcher, watches: vec![], - dir_events: dir_events }) + dir_events: dir_events, + proc_view: Arc::new(Mutex::new(proc_view)) }) } pub fn enter_dir(&mut self) -> HResult<()> { @@ -250,7 +260,7 @@ impl FileBrowser { self.watches.push(left_dir.path); } if let Some(preview_dir) = preview_dir { - if !watched_dirs.contains(&preview_dir) { + if !watched_dirs.contains(&preview_dir) && preview_dir.is_dir() { self.watcher.watch(&preview_dir, RecursiveMode::NonRecursive).unwrap(); self.watches.push(preview_dir); } @@ -380,21 +390,8 @@ impl FileBrowser { cmd = cmd.replace(&tab_identifier, &tab_path); } - let status = std::process::Command::new("sh") - .arg("-c") - .arg(&cmd) - .status(); - let mut bufout = std::io::BufWriter::new(std::io::stdout()); - write!(bufout, "{}{}", - termion::style::Reset, - termion::clear::All).unwrap(); - - match status { - Ok(status) => self.show_status(&format!("\"{}\" exited with {}", - cmd, status)), - Err(err) => self.show_status(&format!("Can't run this \"{}\": {}", - cmd, err)), - } + self.proc_view.lock()?.run_proc(&cmd)?; + Ok(()) } } @@ -404,7 +401,8 @@ impl Widget for FileBrowser { &self.columns.coordinates } fn set_coordinates(&mut self, coordinates: &Coordinates) { - self.columns.coordinates = coordinates.clone(); + self.columns.set_coordinates(coordinates); + self.proc_view.lock().unwrap().set_coordinates(coordinates); self.refresh(); } fn render_header(&self) -> String { @@ -467,15 +465,20 @@ impl Widget for FileBrowser { } } - fn on_key(&mut self, key: Key) { + fn on_key(&mut self, key: Key) -> HResult<()> { match key { Key::Char('/') => { self.turbo_cd().ok(); }, Key::Char('Q') => { self.quit_with_dir().ok(); }, Key::Right | Key::Char('f') => { self.enter_dir().ok(); }, Key::Left | Key::Char('b') => { self.go_back().ok(); }, - _ => self.columns.get_main_widget_mut().unwrap().on_key(key), + Key::Char('w') => { + self.proc_view.lock()?.popup().ok(); + } + , + _ => { self.columns.get_main_widget_mut()?.on_key(key).ok(); }, } self.update_preview().ok(); + Ok(()) } } diff --git a/src/hbox.rs b/src/hbox.rs index 23c580b..7e27656 100644 --- a/src/hbox.rs +++ b/src/hbox.rs @@ -2,6 +2,7 @@ use termion::event::{Event}; use crate::widget::Widget; use crate::coordinates::{Coordinates, Size, Position}; +use crate::fail::HResult; #[derive(PartialEq)] pub struct HBox<T: Widget> { @@ -106,7 +107,8 @@ impl<T> Widget for HBox<T> where T: Widget + PartialEq { self.coordinates = coordinates.clone(); self.refresh(); } - fn on_event(&mut self, event: Event) { - self.widgets.last_mut().unwrap().on_event(event); + fn on_event(&mut self, event: Event) -> HResult<()> { + self.widgets.last_mut()?.on_event(event).ok(); + Ok(()) } } diff --git a/src/listview.rs b/src/listview.rs index 19e806a..0a25dca 100644 --- a/src/listview.rs +++ b/src/listview.rs @@ -90,7 +90,7 @@ where view } - fn move_up(&mut self) { + pub fn move_up(&mut self) { if self.selection == 0 { return; } @@ -102,11 +102,11 @@ where self.selection -= 1; self.seeking = false; } - fn move_down(&mut self) { + pub fn move_down(&mut self) { let lines = self.lines; let y_size = self.coordinates.ysize() as usize; - if self.selection == lines - 1 { + if self.lines == 0 || self.selection == lines - 1 { return; } @@ -379,7 +379,7 @@ impl<T> Widget for ListView<T> where ListView<T>: Listable { fn refresh(&mut self) { self.on_refresh(); self.lines = self.len(); - if self.selection >= self.lines { + if self.selection >= self.lines && self.selection != 0 { self.selection -= 1; } self.buffer = self.render(); @@ -418,7 +418,8 @@ impl<T> Widget for ListView<T> where ListView<T>: Listable { format!("{} files", self.len()) } - fn on_key(&mut self, key: Key) { + fn on_key(&mut self, key: Key) -> HResult<()> { Listable::on_key(self, key); + Ok(()) } } diff --git a/src/main.rs b/src/main.rs index ea5f92c..09d05d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,6 +40,9 @@ mod tabview; mod async_widget; mod fail; mod minibuffer; +mod proclist; + + use window::Window; diff --git a/src/miller_columns.rs b/src/miller_columns.rs index c56bd0e..e6c5c54 100644 --- a/src/miller_columns.rs +++ b/src/miller_columns.rs @@ -155,7 +155,8 @@ where } } - fn on_key(&mut self, key: Key) { + fn on_key(&mut self, key: Key) -> HResult<()> { self.get_main_widget_mut().unwrap().on_key(key); + Ok(()) } } diff --git a/src/minibuffer.rs b/src/minibuffer.rs index 8c45bd0..f48d84c 100644 --- a/src/minibuffer.rs +++ b/src/minibuffer.rs @@ -145,7 +145,7 @@ impl Widget for MiniBuffer { self.input) } - fn on_key(&mut self, key: Key) { + fn on_key(&mut self, key: Key) -> HResult<()> { match key { Key::Esc | Key::Ctrl('c') => { self.input.clear(); self.done = true; }, Key::Char('\n') => { @@ -205,7 +205,8 @@ impl Widget for MiniBuffer { self.input.insert(self.position, key); self.position += 1; } - _ => {} + _ => { } } + Ok(()) } } diff --git a/src/preview.rs b/src/preview.rs index 273c13a..fade5b2 100644 --- a/src/preview.rs +++ b/src/preview.rs @@ -185,12 +185,12 @@ impl<T: Widget + Send + 'static> Widget for WillBeWidget<T> { let widget = widget.as_ref().unwrap(); widget.get_drawlist() } - fn on_key(&mut self, key: termion::event::Key) { - if self.willbe.check().is_err() { return } + fn on_key(&mut self, key: termion::event::Key) -> HResult<()> { + if self.willbe.check().is_err() { return Ok(()) } let widget = self.widget().unwrap(); let mut widget = widget.try_lock().unwrap(); let widget = widget.as_mut().unwrap(); - widget.on_key(key); + widget.on_key(key) } } diff --git a/src/proclist.rs b/src/proclist.rs new file mode 100644 index 0000000..c2d58ee --- /dev/null +++ b/src/proclist.rs @@ -0,0 +1,250 @@ +use std::sync::{Arc, Mutex}; +use std::process::Child; +use std::process::Stdio; +use std::os::unix::io::FromRawFd; +use std::io::{BufRead, BufReader}; + +use termion::event::Key; +use unicode_width::UnicodeWidthStr; + +use crate::coordinates::{Coordinates, Size, Position}; +use crate::listview::{Listable, ListView}; +use crate::textview::TextView; +use crate::widget::Widget; +use crate::window::{send_event, Events}; +use crate::fail::{HResult, HError}; +use crate::term; + +#[derive(Debug)] +struct Process { + cmd: String, + handle: Arc<Mutex<Child>>, + output: Arc<Mutex<String>>, + status: Arc<Mutex<Option<i32>>>, + success: Arc<Mutex<Option<bool>>> +} + +impl Process { + fn read_proc(&mut self) -> HResult<()> { + let handle = self.handle.clone(); + let output = self.output.clone(); + let status = self.status.clone(); + let success = self.success.clone(); + + std::thread::spawn(move || { + let stdout = handle.lock().unwrap().stdout.take().unwrap(); + let mut stdout = BufReader::new(stdout); + loop { + let mut line = String::new(); + match stdout.read_line(&mut line) { + Ok(0) => break, + Ok(_) => { + output.lock().unwrap().push_str(&line); + send_event(Events::WidgetReady).unwrap(); + } + Err(err) => { + dbg!(err); + break; + } + } + } + if let Ok(proc_status) = handle.lock().unwrap().wait() { + *success.lock().unwrap() = Some(proc_status.success()); + *status.lock().unwrap() = proc_status.code(); + } + }); + + Ok(()) + } +} + +impl Listable for ListView<Vec<Process>> { + fn len(&self) -> usize { self.content.len() } + fn render(&self) -> Vec<String> { + self.content.iter().map(|proc| { + self.render_proc(proc) + }).collect() + } +} + +impl ListView<Vec<Process>> { + fn run_proc(&mut self, cmd: &str) -> HResult<()> { + let handle = std::process::Command::new("sh") + .arg("-c") + .arg(cmd) + .stdin(std::process::Stdio::null()) + .stdout(std::process::Stdio::piped()) + .stderr(unsafe { Stdio::from_raw_fd(2) }) + .spawn()?; + let mut proc = Process { + cmd: cmd.to_string(), + handle: Arc::new(Mutex::new(handle)), + output: Arc::new(Mutex::new(String::new())), + status: Arc::new(Mutex::new(None)), + success: Arc::new(Mutex::new(None)) + }; + proc.read_proc()?; + self.content.push(proc); + Ok(()) + } + + fn kill_proc(&mut self) -> HResult<()> { + let proc = self.selected_proc()?; + proc.handle.lock()?.kill()?; + Ok(()) + } + + fn remove_proc(&mut self) -> HResult<()> { + self.kill_proc().ok(); + let selection = self.get_selection(); + self.content.remove(selection); + Ok(()) + } + + fn selected_proc(&mut self) -> Option<&mut Process> { + let selection = self.get_selection(); + self.content.get_mut(selection) + } + + pub fn render_proc(&self, proc: &Process) -> String { + let status = match *proc.status.lock().unwrap() { + Some(status) => format!("{}", status), + None => "<R>".to_string() + }; + + let xsize = self.get_coordinates().xsize(); + let sized_string = term::sized_string(&proc.cmd, xsize); + let status_pos = xsize - status.len() as u16; + let padding = sized_string.len() - sized_string.width_cjk(); + let padding = xsize - padding as u16; + + let color_status = match *proc.success.lock().unwrap() { + Some(false) => { format!("{}{}", term::color_red(), status) } + _ => { status } + }; + + format!( + "{}{}{}{}{}{}", + termion::cursor::Save, + format!("{}{}{:padding$}{}", + term::normal_color(), + &sized_string, + " ", + term::normal_color(), + padding = padding as usize), + termion::cursor::Restore, + termion::cursor::Right(status_pos), + term::highlight_color(), + color_status + ) + } +} + +pub struct ProcView { + coordinates: Coordinates, + proc_list: ListView<Vec<Process>>, + textview: TextView, +} + +impl ProcView { + pub fn new() -> ProcView { + ProcView { + coordinates: Coordinates::new(), + proc_list: ListView::new(vec![]), + textview: TextView::new_blank(), + } + } + + pub fn run_proc(&mut self, cmd: &str) -> HResult<()> { + self.proc_list.run_proc(cmd)?; + Ok(()) + } + + pub fn remove_proc(&mut self) -> HResult<()> { + self.proc_list.remove_proc()?; + self.textview.set_text(""); + Ok(()) + } + + fn show_output(&mut self) -> HResult<()> { + let output = self.proc_list.selected_proc()?.output.lock()?; + self.textview.set_text(&*output); + Ok(()) + } + + pub fn calculate_coordinates(&self) -> (Coordinates, Coordinates) { + let xsize = self.coordinates.xsize(); + let ysize = self.coordinates.ysize(); + let top = self.coordinates.top().y(); + let ratio = (33, 66); + + let left_xsize = xsize * ratio.0 / 100; + let left_size = Size((left_xsize, ysize)); + let left_pos = self.coordinates.top(); + + let main_xsize = xsize * ratio.1 / 100; + let main_size = Size((main_xsize, ysize)); + let main_pos = Position((left_xsize + 2, top)); + + + + let left_coords = Coordinates { + size: left_size, + position: left_pos, + }; + + let main_coords = Coordinates { + size: main_size, + position: main_pos, + }; + + (left_coords, main_coords) + } + + +} + +impl Widget for ProcView { + fn get_coordinates(&self) -> &Coordinates { + &self.coordinates + } + fn set_coordinates(&mut self, coordinates: &Coordinates) { + self.coordinates = coordinates.clone(); + + let (lcoord, rcoord) = self.calculate_coordinates(); + self.proc_list.set_coordinates(&lcoord); + self.textview.set_coordinates(&rcoord); + + self.refresh(); + } + fn render_header(&self) -> String { + "".to_string() + } + fn refresh(&mut self) { + self.show_output().ok(); + self.proc_list.refresh(); + self.textview.refresh(); + } + fn get_drawlist(&self) -> String { + self.proc_list.get_drawlist() + &self.textview.get_drawlist() + } + fn on_key(&mut self, key: Key) -> HResult<()> { + match key { + Key::Char('w') => { return Err(HError::PopupFinnished) } + Key::Char('d') => { self.remove_proc()? } + Key::Char('k') => { self.proc_list.kill_proc()? } + Key::Up | Key::Char('p') => { + self.proc_list.move_up(); + self.proc_list.refresh(); + } + Key::Down | Key::Char('n') => { + self.proc_list.move_down(); + self.proc_list.refresh(); + } + _ => {} + } + self.refresh(); + self.draw()?; + Ok(()) + } +} diff --git a/src/tabview.rs b/src/tabview.rs index 22ef78f..e4904a5 100644 --- a/src/tabview.rs +++ b/src/tabview.rs @@ -2,6 +2,7 @@ use termion::event::Key; use crate::coordinates::{Coordinates}; use crate::widget::Widget; +use crate::fail::HResult; pub trait Tabbable { fn new_tab(&mut self); @@ -129,7 +130,8 @@ impl<T> Widget for TabView<T> where T: Widget, TabView<T>: Tabbable { self.refresh(); } - fn on_key(&mut self, key: Key) { + fn on_key(&mut self, key: Key) -> HResult<()> { Tabbable::on_key(self, key); + Ok(()) } } diff --git a/src/textview.rs b/src/textview.rs index a7a28c6..455e961 100644 --- a/src/textview.rs +++ b/src/textview.rs @@ -48,6 +48,12 @@ impl TextView { coordinates: Coordinates::new(), } } + + pub fn set_text(&mut self, text: &str) { + let lines = text.lines().map(|l| l.to_string()).collect(); + self.lines = lines; + self.refresh(); + } } impl Widget for TextView { diff --git a/src/widget.rs b/src/widget.rs index d41900f..353d198 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -1,9 +1,13 @@ +use std::sync::mpsc::channel; + use termion::event::{Event, Key, MouseEvent}; +use termion::input::TermRead; use crate::coordinates::{Coordinates, Position, Size}; -use crate::fail::HResult; +use crate::fail::{HResult, HError}; +use crate::window::{send_event, Events}; -use std::io::{BufWriter, Write}; +use std::io::{BufWriter, Write, stdin}; pub trait Widget { @@ -18,7 +22,7 @@ pub trait Widget { fn get_drawlist(&self) -> String; - fn on_event(&mut self, event: Event) { + fn on_event(&mut self, event: Event) -> HResult<()> { match event { Event::Key(Key::Char('q')) => panic!("It's your fault!"), Event::Key(key) => self.on_key(key), @@ -27,22 +31,25 @@ pub trait Widget { } } - fn on_key(&mut self, key: Key) { + fn on_key(&mut self, key: Key) -> HResult<()> { match key { _ => self.bad(Event::Key(key)), } + Ok(()) } - fn on_mouse(&mut self, event: MouseEvent) { + fn on_mouse(&mut self, event: MouseEvent) -> HResult<()> { match event { _ => self.bad(Event::Mouse(event)), } + Ok(()) } - fn on_wtf(&mut self, event: Vec<u8>) { + fn on_wtf(&mut self, event: Vec<u8>) -> HResult<()> { match event { _ => self.bad(Event::Unsupported(event)), } + Ok(()) } fn show_status(&self, status: &str) { @@ -125,6 +132,49 @@ pub trait Widget { Ok(()) } + fn popup(&mut self) -> HResult<()> { + self.run_widget(); + send_event(Events::ExclusiveEvent(None)); + Ok(()) + } + + fn run_widget(&mut self) -> HResult<()> { + let (tx_event, rx_event) = channel(); + send_event(Events::ExclusiveEvent(Some(tx_event)))?; + dbg!("sent exclusive request"); + + self.clear()?; + self.refresh(); + self.draw()?; + + dbg!("entering loop"); + + for event in rx_event.iter() { + dbg!(&event); + match event { + Events::InputEvent(input) => { + if let Err(HError::PopupFinnished) = self.on_event(input) { + return Err(HError::PopupFinnished) + } + } + Events::WidgetReady => { + self.refresh(); + } + _ => {} + } + + self.draw()?; + } + Ok(()) + } + + fn clear(&self) -> HResult<()> { + let clearlist = self.get_clearlist(); + write!(std::io::stdout(), "{}", clearlist)?; + std::io::stdout().flush()?; + Ok(()) + } + fn animate_slide_up(&mut self) { let coords = self.get_coordinates().clone(); let xpos = coords.position().x(); diff --git a/src/window.rs b/src/window.rs index f974208..90c7812 100644 --- a/src/window.rs +++ b/src/window.rs @@ -20,11 +20,12 @@ lazy_static! { = Arc::new(Mutex::new(MiniBuffer::new())); } - +#[derive(Debug)] pub enum Events { InputEvent(Event), WidgetReady, ExclusiveInput(bool), + ExclusiveEvent(Option<Sender<Events>>), } pub struct Window<T> @@ -87,32 +88,22 @@ where pub fn handle_input(&mut self) { - let (tx_event_internal, rx_event_internal) = channel(); let (tx_event, rx_event) = channel(); - *TX_EVENT.try_lock().unwrap() = Some(tx_event); - let (tx_request_input, rx_request_input) = channel(); - - let mut exclusive_mode = false; + let (tx_global_event, rx_global_event) = channel(); + *TX_EVENT.try_lock().unwrap() = Some(tx_global_event); + let (tx_internal_event, rx_internal_event) = channel(); - event_thread(rx_event, tx_event_internal.clone()); - input_thread(tx_event_internal.clone(), rx_request_input); - tx_request_input.send(()).unwrap(); + input_thread(tx_event.clone()); + global_event_thread(rx_global_event, tx_event.clone()); + dispatch_events(rx_event, tx_internal_event); - for event in rx_event_internal.iter() { - //Self::clear_status(); - //let event = event.unwrap(); + for event in rx_internal_event.iter() { match event { Events::InputEvent(event) => { self.widget.on_event(event); self.screen.cursor_hide(); self.draw(); - if !exclusive_mode { - tx_request_input.send(()).unwrap(); - } }, - Events::ExclusiveInput(setting) => { - exclusive_mode = setting - } _ => { self.widget.refresh(); self.draw(); @@ -122,23 +113,39 @@ where } } -fn event_thread(rx: Receiver<Events>, - tx: Sender<Events>) { +fn dispatch_events(rx: Receiver<Events>, tx: Sender<Events>) { std::thread::spawn(move || { + let mut tx_exclusive_event: Option<Sender<Events>> = None; for event in rx.iter() { + match &event { + Events::ExclusiveEvent(tx_event) => { + tx_exclusive_event = tx_event.clone(); + } + _ => {} + } + if let Some(tx_event) = &tx_exclusive_event { + tx_event.send(event).unwrap(); + } else { + tx.send(event).unwrap(); + } + } + }); +} + +fn global_event_thread(rx_global: Receiver<Events>, + tx: Sender<Events>) { + std::thread::spawn(move || { + for event in rx_global.iter() { tx.send(event).unwrap(); } }); } -fn input_thread(tx: Sender<Events>, request_input: Receiver<()>) { +fn input_thread(tx: Sender<Events>) { std::thread::spawn(move || { - for _ in request_input.iter() { - for input in stdin().events() { - let input = input.unwrap(); - tx.send(Events::InputEvent(input)).unwrap(); - break; - } + for input in stdin().events() { + let input = input.unwrap(); + tx.send(Events::InputEvent(input)).unwrap(); } }); } |