From 7fc77f8605bf637331f2f9c283c4b4e284891af1 Mon Sep 17 00:00:00 2001 From: rabite Date: Mon, 25 Feb 2019 18:23:48 +0100 Subject: [PATCH] minibuffer Widgetified --- src/file_browser.rs | 22 ++++- src/listview.rs | 8 +- src/main.rs | 2 + src/minibuffer.rs | 211 ++++++++++++++++++++++++++++++++++++++++++++ src/widget.rs | 12 ++- src/window.rs | 128 ++++++--------------------- 6 files changed, 274 insertions(+), 109 deletions(-) create mode 100644 src/minibuffer.rs diff --git a/src/file_browser.rs b/src/file_browser.rs index 910305e..8ede380 100644 --- a/src/file_browser.rs +++ b/src/file_browser.rs @@ -172,9 +172,11 @@ impl FileBrowser { if self.left_widget().is_err() { let file = self.selected_file()?.clone(); if let Some(grand_parent) = file.grand_parent() { + let (coords, _, _) = self.columns.calculate_coordinates(); let mut left_view = WillBeWidget::new(Box::new(move |_| { let mut view = ListView::new(Files::new_from_path(&grand_parent)?); + view.set_coordinates(&coords); Ok(view) })); self.columns.prepend_widget(left_view); @@ -232,18 +234,30 @@ impl FileBrowser { let dir = self.minibuffer("cd: "); match dir { - Some(dir) => { + Ok(dir) => { self.columns.widgets.widgets.clear(); let cwd = File::new_from_path(&std::path::PathBuf::from(&dir))?; self.cwd = cwd; + let dir = std::path::PathBuf::from(&dir); + let left_dir = std::path::PathBuf::from(&dir); + let (left_coords, main_coords, _) = self.columns.calculate_coordinates(); + let middle = WillBeWidget::new(Box::new(move |_| { - let files = Files::new_from_path(&std::path::PathBuf::from(&dir))?; - let listview = ListView::new(files); + let files = Files::new_from_path(&dir.clone())?; + let mut listview = ListView::new(files); + listview.set_coordinates(&main_coords); Ok(listview) })); + let left = WillBeWidget::new(Box::new(move |_| { + let files = Files::new_from_path(&left_dir.parent()?)?; + let mut listview = ListView::new(files); + listview.set_coordinates(&left_coords); + Ok(listview) + })); + self.columns.push_widget(left); self.columns.push_widget(middle); }, - None => {} + Err(_) => {} } Ok(()) } diff --git a/src/listview.rs b/src/listview.rs index a64a340..904baa9 100644 --- a/src/listview.rs +++ b/src/listview.rs @@ -342,8 +342,10 @@ impl ListView let file_names = selected_files.iter().map(|f| f.name.clone()).collect::>(); - match self.minibuffer("exec ($s for selected file(s))") { - Some(cmd) => { + let cmd = self.minibuffer("exec:"); + + match cmd { + Ok(cmd) => { self.show_status(&format!("Running: \"{}\"", &cmd)); let filename = self.selected_file().name.clone(); @@ -375,7 +377,7 @@ impl ListView cmd, err)), } } - None => self.show_status(""), + Err(_) => self.show_status(""), } } diff --git a/src/main.rs b/src/main.rs index 3fd895c..2e1cfb1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,6 +39,8 @@ mod hbox; mod tabview; mod async_widget; mod fail; +mod minibuffer; + use window::Window; diff --git a/src/minibuffer.rs b/src/minibuffer.rs new file mode 100644 index 0000000..8c45bd0 --- /dev/null +++ b/src/minibuffer.rs @@ -0,0 +1,211 @@ +use termion::event::Key; +use termion::input::TermRead; + +use std::io::{stdin, stdout, Write}; + +use crate::coordinates::{Coordinates}; +use crate::widget::Widget; +use crate::window::{send_event, Events}; +use crate::fail::HResult; + +pub struct MiniBuffer { + coordinates: Coordinates, + query: String, + input: String, + done: bool, + position: usize, + history: Vec +} + +impl MiniBuffer { + pub fn new() -> MiniBuffer { + let xsize = crate::term::xsize(); + let ysize = crate::term::ysize(); + let coordinates = Coordinates::new_at(xsize, 1, 1, ysize); + MiniBuffer { + coordinates: coordinates, + query: String::new(), + input: String::new(), + done: false, + position: 0, + history: vec![] + } + } + + pub fn query(&mut self, query: &str) -> HResult { + self.query = query.to_string(); + self.input.clear(); + self.done = false; + self.position = 0; + + send_event(Events::ExclusiveInput(true))?; + + self.draw()?; + write!(stdout(), "{}{}", + termion::cursor::Show, + termion::cursor::Save)?; + stdout().flush()?; + + + for event in stdin().events() { + let event = event?; + self.on_event(event); + if self.done { + break + } + self.draw()?; + + write!(stdout(), "{}", termion::cursor::Restore)?; + if self.position != 0 { + write!(stdout(), + "{}", + termion::cursor::Right(self.position as u16))?; + } + stdout().flush()?; + } + + self.done = false; + + send_event(Events::ExclusiveInput(false))?; + + Ok(self.input.clone()) + } +} + +pub fn find_bins(comp_name: &str) -> Vec { + let paths = std::env::var_os("PATH").unwrap() + .to_string_lossy() + .split(":") + .map(|s| s.to_string()) + .collect::>(); + + paths.iter().map(|path| { + std::fs::read_dir(path).unwrap().flat_map(|file| { + let file = file.unwrap(); + let name = file.file_name().into_string().unwrap(); + if name.starts_with(comp_name) { + Some(name) + } else { + None + } + }).collect::>() + }).flatten().collect::>() +} + +pub fn find_files(mut comp_name: String) -> Vec { + let mut path = std::path::PathBuf::from(&comp_name); + + let dir = if comp_name.starts_with("/") { + comp_name = path.file_name().unwrap().to_string_lossy().to_string(); + path.pop(); + path.to_string_lossy().to_string() + } else { + std::env::current_dir().unwrap().to_string_lossy().to_string() + }; + + let reader = std::fs::read_dir(dir.clone()); + if reader.is_err() { return vec![] } + let reader = reader.unwrap(); + + reader.flat_map(|file| { + let file = file.unwrap(); + let name = file.file_name().into_string().unwrap(); + if name.starts_with(&comp_name) { + if file.file_type().unwrap().is_dir() { + Some(format!("{}/{}/", &dir, name)) + } else { + Some(format!("/{}/", name)) + } + } else { + None + } + }).collect::>() +} + +impl Widget for MiniBuffer { + fn get_coordinates(&self) -> &Coordinates { + &self.coordinates + } + fn set_coordinates(&mut self, coordinates: &Coordinates) { + self.coordinates = coordinates.clone(); + self.refresh(); + } + fn render_header(&self) -> String { + "".to_string() + } + fn refresh(&mut self) { + } + + fn get_drawlist(&self) -> String { + let (xpos, ypos) = self.get_coordinates().u16position(); + format!("{}{}{}: {}", + crate::term::goto_xy(xpos, ypos), + termion::clear::CurrentLine, + self.query, + self.input) + } + + fn on_key(&mut self, key: Key) { + match key { + Key::Esc | Key::Ctrl('c') => { self.input.clear(); self.done = true; }, + Key::Char('\n') => { + if self.input != "" { + self.history.push(self.input.clone()); + } + self.done = true; + } + Key::Char('\t') => { + if !self.input.ends_with(" ") { + let part = self.input.rsplitn(2, " ").take(1) + .map(|s| s.to_string()).collect::(); + let completions = find_files(part.clone()); + if !completions.is_empty() { + self.input + = self.input[..self.input.len() - part.len()].to_string(); + self.input.push_str(&completions[0]); + self.position += &completions[0].len() - part.len(); + } else { + let completions = find_bins(&part); + if !completions.is_empty() { + self.input = self.input[..self.input.len() + - part.len()].to_string(); + self.input.push_str(&completions[0]); + self.position += &completions[0].len() - part.len(); + } + } + } else { + self.input += "$s"; + self.position += 2 + } + } + Key::Backspace => { + if self.position != 0 { + self.input.remove(self.position - 1); + self.position -= 1; + } + } + Key::Delete | Key::Ctrl('d') => { + if self.position != self.input.len() { + self.input.remove(self.position); + } + } + Key::Left | Key::Ctrl('b') => { + if self.position != 0 { + self.position -= 1; + } + } + Key::Right | Key::Ctrl('f') => { + if self.position != self.input.len() { + self.position += 1; + } + } + Key::Ctrl('a') => { self.position = 0 }, + Key::Ctrl('e') => { self.position = self.input.len(); }, + Key::Char(key) => { + self.input.insert(self.position, key); + self.position += 1; + } + _ => {} + } + } +} diff --git a/src/widget.rs b/src/widget.rs index ccf8f72..d41900f 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -1,6 +1,7 @@ use termion::event::{Event, Key, MouseEvent}; use crate::coordinates::{Coordinates, Position, Size}; +use crate::fail::HResult; use std::io::{BufWriter, Write}; @@ -48,7 +49,7 @@ pub trait Widget { crate::window::show_status(status); } - fn minibuffer(&self, query: &str) -> Option { + fn minibuffer(&self, query: &str) -> HResult { crate::window::minibuffer(query) } @@ -115,6 +116,15 @@ pub trait Widget { .collect() } + fn draw(&self) -> HResult<()> { + let drawlist = self.get_drawlist(); + let mut bufout = BufWriter::new(std::io::stdout()); + + write!(bufout, "{}", drawlist)?; + bufout.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 b31058d..fc08b47 100644 --- a/src/window.rs +++ b/src/window.rs @@ -11,17 +11,20 @@ use crate::term::ScreenExt; use crate::coordinates::{Coordinates, Position, Size}; use crate::widget::Widget; +use crate::minibuffer::MiniBuffer; use crate::fail::HResult; lazy_static! { static ref TX_EVENT: Arc>>> = { Arc::new(Mutex::new(None)) }; + static ref MINIBUFFER: Arc> + = Arc::new(Mutex::new(MiniBuffer::new())); } -#[derive(Debug)] pub enum Events { InputEvent(Event), - WidgetReady + WidgetReady, + ExclusiveInput(bool), } pub struct Window @@ -87,19 +90,28 @@ where 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; event_thread(rx_event, tx_event_internal.clone()); - input_thread(tx_event_internal); + input_thread(tx_event_internal.clone(), rx_request_input); + tx_request_input.send(()).unwrap(); for event in rx_event_internal.iter() { //Self::clear_status(); //let event = event.unwrap(); - dbg!(&event); 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(); @@ -110,20 +122,23 @@ where } } -fn event_thread(rx: Receiver, tx: Sender) { +fn event_thread(rx: Receiver, + tx: Sender) { std::thread::spawn(move || { for event in rx.iter() { - dbg!(&event); tx.send(event).unwrap(); } }); } -fn input_thread(tx: Sender) { +fn input_thread(tx: Sender, request_input: Receiver<()>) { std::thread::spawn(move || { - for input in stdin().events() { - let input = input.unwrap(); - tx.send(Events::InputEvent(input)).unwrap(); + for _ in request_input.iter() { + for input in stdin().events() { + let input = input.unwrap(); + tx.send(Events::InputEvent(input)).unwrap(); + break; + } } }); } @@ -188,97 +203,8 @@ pub fn show_status(status: &str) { draw_status(); } -pub fn minibuffer(query: &str) -> Option { - show_status(&(query.to_string() + ": ")); - write!(stdout(), "{}{}", - termion::cursor::Show, - termion::cursor::Save).unwrap(); - stdout().flush().unwrap(); - - let mut buffer = "".to_string(); - let mut pos = 0; - - for key in stdin().events() { - - match key { - Ok(Event::Key(key)) => match key { - Key::Esc | Key::Ctrl('c') => break, - Key::Char('\n') => { - if buffer == "" { - write!(stdout(), "{}", termion::cursor::Hide).unwrap(); - stdout().flush().unwrap(); - return None; - } else { - write!(stdout(), "{}", termion::cursor::Hide).unwrap(); - stdout().flush().unwrap(); - return Some(buffer); - } - } - Key::Char('\t') => { - if !buffer.ends_with(" ") { - let part = buffer.rsplitn(2, " ").take(1) - .map(|s| s.to_string()).collect::(); - let completions = find_files(part.clone()); - if !completions.is_empty() { - buffer = buffer[..buffer.len() - part.len()].to_string(); - buffer.push_str(&completions[0]); - pos += &completions[0].len() - part.len(); - } else { - let completions = find_bins(&part); - if !completions.is_empty() { - buffer = buffer[..buffer.len() - part.len()].to_string(); - buffer.push_str(&completions[0]); - pos += &completions[0].len() - part.len(); - } - } - } else { - buffer += "$s"; - pos += 2 - } - } - Key::Backspace => { - if pos != 0 { - buffer.remove(pos - 1); - pos -= 1; - } - } - Key::Delete | Key::Ctrl('d') => { - if pos != buffer.len() { - buffer.remove(pos); - } - } - Key::Left | Key::Ctrl('b') => { - if pos != 0 { - pos -= 1; - } - } - Key::Right | Key::Ctrl('f') => { - if pos != buffer.len() { - pos += 1; - } - } - Key::Ctrl('a') => { pos = 0 }, - Key::Ctrl('e') => { pos = buffer.len(); }, - Key::Char(key) => { - buffer.insert(pos, key); - pos += 1; - } - _ => {} - }, - _ => {} - } - show_status(&(query.to_string() + ": " + &buffer)); - - write!(stdout(), "{}", termion::cursor::Restore).unwrap(); - stdout().flush().unwrap(); - if pos != 0 { - write!(stdout(), - "{}", - format!("{}", termion::cursor::Right(pos as u16))).unwrap(); - } - stdout().flush().unwrap(); - } - None +pub fn minibuffer(query: &str) -> HResult { + MINIBUFFER.lock()?.query(query) } pub fn find_bins(comp_name: &str) -> Vec {