1
0
mirror of https://github.com/bobwen-dev/hunter synced 2025-04-12 00:55:41 +02:00
hunter/src/window.rs
2019-02-22 00:46:41 +01:00

333 lines
9.6 KiB
Rust

use std::io::{stdin, stdout, Stdout, Write};
use std::sync::{Arc, Mutex};
use std::sync::mpsc::{Sender, Receiver, channel};
use termion::event::{Event, Key};
use termion::input::TermRead;
use termion::screen::AlternateScreen;
use crate::term;
use crate::term::ScreenExt;
use crate::coordinates::{Coordinates, Position, Size};
use crate::widget::Widget;
use crate::fail::HResult;
lazy_static! {
static ref TX_EVENT: Arc<Mutex<Option<Sender<Events>>>> = { Arc::new(Mutex::new(None)) };
}
#[derive(Debug)]
pub enum Events {
InputEvent(Event),
WidgetReady
}
pub struct Window<T>
where
T: Widget,
{
pub selection: usize,
pub widget: T,
pub status: Arc<Mutex<Option<String>>>,
pub screen: AlternateScreen<Box<Stdout>>,
pub coordinates: Coordinates,
}
impl<T> Window<T>
where
T: Widget,
{
pub fn new(widget: T) -> Window<T> {
let mut screen = AlternateScreen::from(Box::new(stdout()));
screen.cursor_hide();
let (xsize, ysize) = termion::terminal_size().unwrap();
let mut win = Window::<T> {
selection: 0,
widget: widget,
status: STATUS_BAR_CONTENT.clone(),
screen: screen,
coordinates: Coordinates {
size: Size((xsize, ysize)),
position: Position((1, 1)),
},
};
win.widget.set_coordinates(&Coordinates {
size: Size((xsize, ysize - 2)),
position: Position((1, 2)),
});
win.widget.refresh();
win
}
pub fn draw(&mut self) {
let output = self.widget.get_drawlist() + &self.widget.get_header_drawlist()
+ &self.widget.get_footer_drawlist();
self.screen.write(output.as_ref()).unwrap();
self.screen.flush().unwrap();
}
// pub fn show_status(status: &str) {
// show_status(status);
// }
// pub fn draw_status() {
// draw_status();
// }
// pub fn clear_status() {
// Self::show_status("");
// }
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);
event_thread(rx_event, tx_event_internal.clone());
input_thread(tx_event_internal);
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();
}
_ => {
self.widget.refresh();
self.draw();
},
}
}
}
}
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>) {
std::thread::spawn(move || {
for input in stdin().events() {
let input = input.unwrap();
tx.send(Events::InputEvent(input)).unwrap();
}
});
}
pub fn send_event(event: Events) -> HResult<()> {
let tx = TX_EVENT.lock()?.clone()?.clone();
tx.send(event)?;
Ok(())
}
impl<T> Drop for Window<T>
where
T: Widget,
{
fn drop(&mut self) {
// When done, restore the defaults to avoid messing with the terminal.
self.screen
.write(
format!(
"{}{}{}{}{}",
termion::screen::ToMainScreen,
termion::clear::All,
termion::style::Reset,
termion::cursor::Show,
termion::cursor::Goto(1, 1)
)
.as_ref(),
)
.unwrap();
}
}
lazy_static! {
static ref STATUS_BAR_CONTENT: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
}
pub fn draw_status() {
let xsize = term::xsize() as u16;
let status = STATUS_BAR_CONTENT.try_lock().unwrap().clone();
status.or(Some("".to_string())).and_then(|status| {
write!(
stdout(),
"{}{}{:xsize$}{}{}",
term::move_bottom(),
term::status_bg(),
" ",
term::move_bottom(),
status,
xsize = xsize as usize
)
.ok()
});
stdout().flush().unwrap();
}
pub fn show_status(status: &str) {
{
let mut status_content = STATUS_BAR_CONTENT.try_lock().unwrap();
*status_content = Some(status.to_string());
}
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 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>>()
}