1
0
mirror of https://github.com/bobwen-dev/hunter synced 2025-04-12 00:55:41 +02:00
hunter/src/proclist.rs
2019-04-06 13:08:20 +02:00

625 lines
20 KiB
Rust

use std::sync::{Arc, Mutex};
use std::sync::mpsc::Sender;
use std::process::Child;
use std::os::unix::process::{CommandExt, ExitStatusExt};
use std::io::{BufRead, BufReader};
use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
use termion::event::Key;
use unicode_width::UnicodeWidthStr;
use crate::listview::{Listable, ListView};
use crate::textview::TextView;
use crate::widget::{Widget, Events, WidgetCore};
use crate::coordinates::Coordinates;
use crate::preview::{AsyncWidget, Stale};
use crate::dirty::Dirtyable;
use crate::hbox::HBox;
use crate::fail::{HResult, HError, ErrorLog};
use crate::term;
use crate::files::{File, OsStrTools};
#[derive(Debug)]
struct Process {
cmd: String,
handle: Arc<Mutex<Child>>,
output: Arc<Mutex<String>>,
status: Arc<Mutex<Option<i32>>>,
success: Arc<Mutex<Option<bool>>>,
sender: Sender<Events>
}
pub struct Cmd {
pub cmd: OsString,
pub args: Option<Vec<OsString>>,
pub short_cmd: Option<String>,
pub cwd: File,
pub cwd_files: Option<Vec<File>>,
pub tab_files: Option<Vec<Vec<File>>>,
pub tab_paths: Option<Vec<File>>,
}
impl Cmd {
fn process(&mut self) -> Vec<OsString> {
let cmd = self.cmd.clone().split(&OsString::from(" "));
let cmd = self.substitute_cwd_files(cmd);
let cmd = self.substitute_tab_files(cmd);
let cmd = self.substitute_tab_paths(cmd);
cmd
}
fn substitute_cwd_files(&mut self, cmd: Vec<OsString>) -> Vec<OsString> {
let cwd_pat = OsString::from("$s");
let cwd_files = self.cwd_files
.take()
.unwrap()
.iter()
.map(|file| file.strip_prefix(&self.cwd).into_os_string())
.collect::<Vec<OsString>>();
cmd.iter()
.map(|part| part.splice_quoted(&cwd_pat,
cwd_files.clone()))
.flatten().collect()
}
fn substitute_tab_files(&mut self, cmd: Vec<OsString>) -> Vec<OsString> {
let tab_files = self.tab_files.take().unwrap();
tab_files.into_iter()
.enumerate()
.fold(cmd, |cmd, (i, tab_files)| {
let tab_files_pat = OsString::from(format!("${}s", i));
let tab_file_paths = tab_files.iter()
.map(|file| file.strip_prefix(&self.cwd).into_os_string())
.collect::<Vec<OsString>>();
cmd.iter().map(|part| {
part.splice_quoted(&tab_files_pat,
tab_file_paths.clone())
}).flatten().collect()
})
}
fn substitute_tab_paths(&mut self, cmd: Vec<OsString>) -> Vec<OsString> {
let tab_paths = self.tab_paths.take().unwrap();
tab_paths.into_iter()
.enumerate()
.fold(cmd, |cmd, (i, tab_path)| {
let tab_path_pat = OsString::from(format!("${}", i));
let tab_path = tab_path.strip_prefix(&self.cwd).into_os_string();
cmd.iter().map(|part| {
part.splice_quoted(&tab_path_pat,
vec![tab_path.clone()])
}).flatten().collect()
})
}
}
impl PartialEq for Process {
fn eq(&self, other: &Process) -> bool {
self.cmd == other.cmd
}
}
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();
let sender = self.sender.clone();
let cmd = self.cmd.clone();
let pid = self.handle.lock()?.id();
std::thread::spawn(move || -> HResult<()> {
let stdout = handle.lock()?.stdout.take()?;
let mut stdout = BufReader::new(stdout);
let mut processor = move |cmd, sender: &Sender<Events>| -> HResult<()> {
loop {
let buffer = stdout.fill_buf()?;
let len = buffer.len();
let buffer = String::from_utf8_lossy(buffer);
if len == 0 { return Ok(()) }
output.lock()?.push_str(&buffer);
let status = format!("{}: read {} chars!", cmd, len);
sender.send(Events::Status(status))?;
stdout.consume(len);
}
};
processor(&cmd, &sender).log();
if let Ok(proc_status) = handle.lock()?.wait() {
let proc_success = proc_status.success();
let proc_status = match proc_status.code() {
Some(status) => status,
None => proc_status.signal().unwrap_or(-1)
};
*success.lock()? = Some(proc_success);
*status.lock()? = Some(proc_status);
let color_success =
if proc_success {
format!("{}successfully", term::color_green())
} else {
format!("{}unsuccessfully", term::color_red())
};
let color_status =
if proc_success {
format!("{}{}", term::color_green(), proc_status)
} else {
format!("{}{}", term::color_red(), proc_status)
};
let status = format!("Process: {}:{} exited {}{} with status: {}",
cmd,
pid,
color_success,
term::normal_color(),
color_status);
sender.send(Events::Status(status))?;
}
Ok(())
});
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).unwrap()
}).collect()
}
fn on_refresh(&mut self) -> HResult<()> {
self.core.set_dirty();
Ok(())
}
}
impl ListView<Vec<Process>> {
fn run_proc_subshell(&mut self, mut cmd: Cmd) -> HResult<()> {
let shell = std::env::var("SHELL").unwrap_or("sh".into());
let home = crate::paths::home_path()?.into_os_string();
let cmd_args = cmd.process();
let short = OsString::from("~");
let short_cmd = cmd_args
.concat()
.replace(&home, &short)
.replace(&OsString::from("\""), &OsString::from(""))
.to_string_lossy()
.to_string();
self.show_status(&format!("Running: {}", &short_cmd)).log();
let shell_args = cmd_args.concat();
let shell_args = vec![OsString::from("-c"), shell_args.clone()];
cmd.cmd = OsString::from(shell.clone());
cmd.args = Some(shell_args.clone());
cmd.short_cmd = Some(short_cmd);
self.run_proc_raw(cmd)
}
fn run_proc_raw(&mut self, cmd: Cmd) -> HResult<()> {
let real_cmd = cmd.cmd;
let short_cmd = cmd.short_cmd
.unwrap_or(real_cmd
.to_string_lossy()
.to_string());
let args = cmd.args.unwrap_or(vec![]);
self.show_status(&format!("Running: {}", &short_cmd)).log();
let handle = std::process::Command::new(real_cmd)
.args(args)
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::piped())
.before_exec(|| unsafe { libc::dup2(1, 2); Ok(()) })
.spawn()?;
let mut proc = Process {
cmd: short_cmd,
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)),
sender: self.get_core()?.get_sender()
};
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) -> HResult<String> {
let pid = proc.handle.lock()?.id();
let status = match *proc.status.lock()? {
Some(status) => format!("{}", status),
None => format!("<{}>", pid),
};
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 }
};
Ok(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))
}
}
#[derive(PartialEq)]
enum ProcViewWidgets {
List(ListView<Vec<Process>>),
TextView(AsyncWidget<TextView>),
}
impl Widget for ProcViewWidgets {
fn get_core(&self) -> HResult<&WidgetCore> {
match self {
ProcViewWidgets::List(widget) => widget.get_core(),
ProcViewWidgets::TextView(widget) => widget.get_core()
}
}
fn get_core_mut(&mut self) -> HResult<&mut WidgetCore> {
match self {
ProcViewWidgets::List(widget) => widget.get_core_mut(),
ProcViewWidgets::TextView(widget) => widget.get_core_mut()
}
}
fn refresh(&mut self) -> HResult<()> {
match self {
ProcViewWidgets::List(widget) => widget.refresh(),
ProcViewWidgets::TextView(widget) => widget.refresh()
}
}
fn get_drawlist(&self) -> HResult<String> {
match self {
ProcViewWidgets::List(widget) => widget.get_drawlist(),
ProcViewWidgets::TextView(widget) => widget.get_drawlist()
}
}
}
pub struct ProcView {
core: WidgetCore,
hbox: HBox<ProcViewWidgets>,
viewing: Option<usize>,
animator: Stale
}
impl HBox<ProcViewWidgets> {
fn get_listview(&self) -> &ListView<Vec<Process>> {
match &self.widgets[0] {
ProcViewWidgets::List(listview) => listview,
_ => unreachable!()
}
}
fn get_listview_mut(&mut self) -> &mut ListView<Vec<Process>> {
match &mut self.widgets[0] {
ProcViewWidgets::List(listview) => listview,
_ => unreachable!()
}
}
fn get_textview(&mut self) -> &mut AsyncWidget<TextView> {
match &mut self.widgets[1] {
ProcViewWidgets::TextView(textview) => textview,
_ => unreachable!()
}
}
}
impl ProcView {
pub fn new(core: &WidgetCore) -> ProcView {
let tcore = core.clone();
let listview = ListView::new(&core, vec![]);
let textview = AsyncWidget::new(&core, Box::new(move |_| {
let textview = TextView::new_blank(&tcore);
Ok(textview)
}));
let mut hbox = HBox::new(&core);
hbox.push_widget(ProcViewWidgets::List(listview));
hbox.push_widget(ProcViewWidgets::TextView(textview));
hbox.set_ratios(vec![33, 66]);
hbox.refresh().log();
ProcView {
core: core.clone(),
hbox: hbox,
viewing: None,
animator: Stale::new()
}
}
fn get_listview(& self) -> & ListView<Vec<Process>> {
self.hbox.get_listview()
}
fn get_listview_mut(&mut self) -> &mut ListView<Vec<Process>> {
self.hbox.get_listview_mut()
}
fn get_textview(&mut self) -> &mut AsyncWidget<TextView> {
self.hbox.get_textview()
}
pub fn run_proc_subshell(&mut self, cmd: Cmd) -> HResult<()> {
self.get_listview_mut().run_proc_subshell(cmd)?;
Ok(())
}
pub fn run_proc_raw(&mut self, cmd: Cmd) -> HResult<()> {
self.get_listview_mut().run_proc_raw(cmd)?;
Ok(())
}
pub fn remove_proc(&mut self) -> HResult<()> {
if self.get_listview_mut().content.len() == 0 { return Ok(()) }
self.get_listview_mut().remove_proc()?;
self.get_textview().clear().log();
self.get_textview().widget_mut()?.set_text("")
}
fn show_output(&mut self) -> HResult<()> {
if Some(self.get_listview_mut().get_selection()) == self.viewing {
return Ok(());
}
let output = self.get_listview_mut().selected_proc()?.output.lock()?.clone();
let animator = self.animator.clone();
animator.set_fresh().log();
self.get_textview().change_to(Box::new(move |_, core| {
let mut textview = TextView::new_blank(&core);
textview.set_text(&output).log();
textview.animate_slide_up(Some(animator)).log();
Ok(textview)
})).log();
self.viewing = Some(self.get_listview_mut().get_selection());
Ok(())
}
pub fn toggle_follow(&mut self) -> HResult<()> {
self.get_textview().widget_mut()?.toggle_follow();
Ok(())
}
pub fn scroll_up(&mut self) -> HResult<()> {
self.get_textview().widget_mut()?.scroll_up();
Ok(())
}
pub fn scroll_down(&mut self) -> HResult<()> {
self.get_textview().widget_mut()?.scroll_down();
Ok(())
}
pub fn page_up(&mut self) -> HResult<()> {
self.get_textview().widget_mut()?.page_up();
Ok(())
}
pub fn page_down(&mut self) -> HResult<()> {
self.get_textview().widget_mut()?.page_down();
Ok(())
}
pub fn scroll_top(&mut self) -> HResult<()> {
self.get_textview().widget_mut()?.scroll_top();
Ok(())
}
pub fn scroll_bottom(&mut self) -> HResult<()> {
self.get_textview().widget_mut()?.scroll_bottom();
Ok(())
}
}
impl Widget for ProcView {
fn get_core(&self) -> HResult<&WidgetCore> {
Ok(&self.core)
}
fn get_core_mut(&mut self) -> HResult<&mut WidgetCore> {
Ok(&mut self.core)
}
fn set_coordinates(&mut self, coordinates: &Coordinates) -> HResult<()> {
self.core.coordinates = coordinates.clone();
self.hbox.core.coordinates = coordinates.clone();
self.hbox.set_coordinates(&coordinates)
}
fn render_header(&self) -> HResult<String> {
let listview = self.get_listview();
let procs_num = listview.len();
let procs_running = listview
.content
.iter()
.filter(|proc| proc.status.lock().unwrap().is_none())
.count();
let header = format!("Running processes: {} / {}",
procs_running,
procs_num);
Ok(header)
}
fn render_footer(&self) -> HResult<String> {
let listview = self.get_listview();
let selection = listview.get_selection();
let xsize = self.core.coordinates.xsize_u();
if let Some(proc) = listview.content.get(selection) {
let cmd = &proc.cmd;
let pid = proc.handle.lock()?.id();
let proc_status = proc.status.lock()?;
let proc_success = proc.success.lock()?;
let procinfo = if proc_status.is_some() {
let color_success =
if let Some(_) = *proc_success {
format!("{}successfully", term::color_green())
} else {
format!("{}unsuccessfully", term::color_red())
};
let color_status =
if let Some(success) = *proc_success {
if success {
format!("{}{}", term::color_green(), proc_status.unwrap())
} else {
format!("{}{}", term::color_red(), proc_status.unwrap())
}
} else { "wtf".to_string() };
let procinfo = format!("{}:{} exited {}{}{} with status: {}",
cmd,
pid,
color_success,
term::reset(),
term::status_bg(),
color_status);
procinfo
} else { "still running".to_string() };
let footer = term::sized_string_u(&procinfo, xsize);
Ok(footer)
} else { Ok("No proccesses".to_string()) }
}
fn refresh(&mut self) -> HResult<()> {
self.hbox.refresh().log();
self.show_output().log();
self.get_listview_mut().refresh().log();
self.get_textview().refresh().log();
Ok(())
}
fn get_drawlist(&self) -> HResult<String> {
self.hbox.get_drawlist()
}
fn on_key(&mut self, key: Key) -> HResult<()> {
match key {
Key::Char('w') => {
self.animator.set_stale().log();
self.clear().log();
return Err(HError::PopupFinnished) }
Key::Char('d') => { self.remove_proc()? }
Key::Char('k') => { self.get_listview_mut().kill_proc()? }
Key::Up | Key::Char('p') => {
self.get_listview_mut().move_up();
}
Key::Down | Key::Char('n') => {
self.get_listview_mut().move_down();
}
Key::Char('f') => { self.toggle_follow().log(); }
Key::Ctrl('n') => { self.scroll_down().log(); },
Key::Ctrl('p') => { self.scroll_up().log(); },
Key::Ctrl('v') => { self.page_down().log(); },
Key::Alt('v') => { self.page_up().log(); },
Key::Char('>') => { self.scroll_bottom().log(); },
Key::Char('<') => { self.scroll_top().log(); }
_ => {}
}
self.refresh().log();
self.draw().log();
Ok(())
}
}
trait ConcatOsString {
fn concat(&self) -> OsString;
fn concat_quoted(&self) -> OsString;
}
impl ConcatOsString for Vec<OsString> {
fn concat(&self) -> OsString {
let len = self.len();
self.iter().enumerate().fold(OsString::new(), |string, (i, part)| {
let mut string = string.into_vec();
let mut space = " ".as_bytes().to_vec();
let mut part = part.clone().into_vec();
string.append(&mut part);
if i != len {
string.append(&mut space);
}
OsString::from_vec(string)
})
}
fn concat_quoted(&self) -> OsString {
let len = self.len();
self.iter().enumerate().fold(OsString::new(), |string, (i, part)| {
let mut string = string.into_vec();
let mut space = " ".as_bytes().to_vec();
let mut quote = "\"".as_bytes().to_vec();
let mut part = part.clone().into_vec();
string.append(&mut quote.clone());
string.append(&mut part);
string.append(&mut quote);
if i+1 != len {
string.append(&mut space);
}
OsString::from_vec(string)
})
}
}