mirror of https://github.com/bobwen-dev/hunter
minibuffer Widgetified
This commit is contained in:
parent
a6c147442c
commit
7fc77f8605
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -342,8 +342,10 @@ impl ListView<Files>
|
|||
let file_names
|
||||
= selected_files.iter().map(|f| f.name.clone()).collect::<Vec<String>>();
|
||||
|
||||
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<Files>
|
|||
cmd, err)),
|
||||
}
|
||||
}
|
||||
None => self.show_status(""),
|
||||
Err(_) => self.show_status(""),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@ mod hbox;
|
|||
mod tabview;
|
||||
mod async_widget;
|
||||
mod fail;
|
||||
mod minibuffer;
|
||||
|
||||
|
||||
use window::Window;
|
||||
|
||||
|
|
|
@ -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<String>
|
||||
}
|
||||
|
||||
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<String> {
|
||||
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<String> {
|
||||
let paths = std::env::var_os("PATH").unwrap()
|
||||
.to_string_lossy()
|
||||
.split(":")
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
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::<Vec<String>>()
|
||||
}).flatten().collect::<Vec<String>>()
|
||||
}
|
||||
|
||||
pub fn find_files(mut comp_name: String) -> Vec<String> {
|
||||
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::<Vec<String>>()
|
||||
}
|
||||
|
||||
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::<String>();
|
||||
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;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String> {
|
||||
fn minibuffer(&self, query: &str) -> HResult<String> {
|
||||
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();
|
||||
|
|
128
src/window.rs
128
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<Mutex<Option<Sender<Events>>>> = { Arc::new(Mutex::new(None)) };
|
||||
static ref MINIBUFFER: Arc<Mutex<MiniBuffer>>
|
||||
= Arc::new(Mutex::new(MiniBuffer::new()));
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Events {
|
||||
InputEvent(Event),
|
||||
WidgetReady
|
||||
WidgetReady,
|
||||
ExclusiveInput(bool),
|
||||
}
|
||||
|
||||
pub struct Window<T>
|
||||
|
@ -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<Events>, tx: Sender<Events>) {
|
||||
fn event_thread(rx: Receiver<Events>,
|
||||
tx: Sender<Events>) {
|
||||
std::thread::spawn(move || {
|
||||
for event in rx.iter() {
|
||||
dbg!(&event);
|
||||
tx.send(event).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn input_thread(tx: Sender<Events>) {
|
||||
fn input_thread(tx: Sender<Events>, 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<String> {
|
||||
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::<String>();
|
||||
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<String> {
|
||||
MINIBUFFER.lock()?.query(query)
|
||||
}
|
||||
|
||||
pub fn find_bins(comp_name: &str) -> Vec<String> {
|
||||
|
|
Loading…
Reference in New Issue