diff --git a/Cargo.toml b/Cargo.toml index b3aa1cb..df7e620 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ termion = "1.5.3" libc = "0.2.62" unicode-segmentation = "1.2.1" nix = "0.15.0" +cassowary = "^0.3.0" [profile.release] opt-level = 'z' # Optimize for size. diff --git a/README b/README new file mode 100644 index 0000000..31cffda --- /dev/null +++ b/README @@ -0,0 +1,28 @@ +NAME + bb — your system's Big Brother + +SYNOPSIS + bb + +DESCRIPTION + simple process viewer made for fun + screenshot: see ./screenshot.png + +BUILD + prerequisites: rustc, cargo >= 1.36, Linux + + execute `cargo build --release`, resulting binary will be in + ./target/release/bb + +SHORTCUTS + 'k' kill process under cursor + press enter to confirm kill, or esc to cancel + 'f' (un)freeze process list updates +AUTHORS + Copyright 2019 Manos Pitsidianakis Released + under the GPL, version 3 or greater. This software carries no warranty of + any kind. (See COPYING for full copyright and warranty notices.) + + ⟨https://nessuent.xyz/⟩ + + September 04, 2019 diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..b63bb07 Binary files /dev/null and b/screenshot.png differ diff --git a/src/main.rs b/src/main.rs index 5c0be61..94df77a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,10 +34,12 @@ use crossbeam::channel::{bounded, tick}; use crossbeam::select; use libc::c_int; use std::io::Error; -use std::time::{Duration, Instant}; +use std::time::Duration; mod ui; use ui::*; +mod thread; +use thread::start_thread; fn notify(signals: &[c_int]) -> Result, Error> { let (s, r) = bounded(100); @@ -69,66 +71,62 @@ fn main() -> Result<(), Error> { let receiver = state.receiver(); - let window = Box::new(HSplit::new( + /* Start data update thread (src/thread.rs) */ + let (s, r) = start_thread(); + let window = Box::new(Window::new( Box::new(ui::components::KernelMetrics::new()), - Box::new(ui::components::ProcessList::new()), - 83, - false, + Box::new(ui::components::ProcessList::new(s, r)), )); state.register_component(window); /* Keep track of the input mode. See ui::UIMode for details */ 'main: loop { - 'inner: loop { - /* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */ - select! { - recv(ticker) -> _ => { - state.redraw(true); - }, - recv(signal_recvr) -> sig => { - eprintln!("got signal {:?}", sig); - match sig.unwrap() { - signal_hook::SIGWINCH => { - state.update_size(); - state.render(); - state.redraw(true); - }, - _ => {} - } - }, - recv(receiver) -> msg => { - match msg.unwrap() { - ThreadEvent::Input(Key::Ctrl('z')) => { - state.switch_to_main_screen(); - //_thread_handler.join().expect("Couldn't join on the associated thread"); - let self_pid = nix::unistd::Pid::this(); - nix::sys::signal::kill(self_pid, nix::sys::signal::Signal::SIGSTOP).unwrap(); - state.switch_to_alternate_screen(); - state.restore_input(); - // BUG: thread sends input event after one received key - state.update_size(); - state.render(); - state.redraw(true); - }, - ThreadEvent::Input(k) => { - match k { - Key::Char('q') | Key::Char('Q') => { - drop(state); - break 'main; - }, - key => { - state.rcv_event(UIEvent::Input(key)); - state.redraw(false); - }, - } - }, - ThreadEvent::UIEvent(_) => { - }, - } - }, - } - } // end of 'inner + /* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */ + select! { + recv(ticker) -> _ => { + state.redraw(true); + }, + recv(signal_recvr) -> sig => { + eprintln!("got signal {:?}", sig); + match sig.unwrap() { + signal_hook::SIGWINCH => { + state.update_size(); + state.render(); + state.redraw(true); + }, + _ => {} + } + }, + recv(receiver) -> msg => { + match msg.unwrap() { + ThreadEvent::Input(Key::Ctrl('z')) => { + state.switch_to_main_screen(); + //_thread_handler.join().expect("Couldn't join on the associated thread"); + let self_pid = nix::unistd::Pid::this(); + nix::sys::signal::kill(self_pid, nix::sys::signal::Signal::SIGSTOP).unwrap(); + state.switch_to_alternate_screen(); + state.restore_input(); + // BUG: thread sends input event after one received key + state.update_size(); + state.render(); + state.redraw(true); + }, + ThreadEvent::Input(k) => { + match k { + Key::Char('q') | Key::Char('Q') => { + drop(state); + break 'main; + }, + key => { + state.rcv_event(UIEvent::Input(key)); + state.redraw(false); + }, + } + }, + } + }, + } } Ok(()) } diff --git a/src/thread.rs b/src/thread.rs new file mode 100644 index 0000000..c98d7f7 --- /dev/null +++ b/src/thread.rs @@ -0,0 +1,221 @@ +/* + * bb + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of bb. + * + * bb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with bb. If not, see . + */ + +use crate::ui::components::processes::State; +use crate::ui::components::*; +use crossbeam::channel::bounded; +use crossbeam::{Receiver, Sender}; +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; +use std::path::PathBuf; +use std::str::FromStr; + +struct ProcessData { + cpu_stat: Stat, + processes_times: HashMap, +} + +pub fn start_thread() -> (Sender>, Receiver>) { + let (s, r): (Sender>, Receiver>) = bounded(1); /* ours */ + let (ts, tr): (Sender>, Receiver>) = bounded(1); /* theirs */ + ts.send(Vec::with_capacity(1024)).unwrap(); + std::thread::spawn(move || { + let mut data = ProcessData { + cpu_stat: get_stat(&mut 0).remove(0), + processes_times: Default::default(), + }; + loop { + let mut processes = match tr.recv() { + Ok(n) => n, + Err(e) => panic!(e), + }; + processes.clear(); + + let cpu_stat = get_stat(&mut 0).remove(0); + for entry in std::fs::read_dir("/proc/").unwrap() { + let dir = entry.unwrap(); + if let Some(fname) = dir.file_name().to_str() { + if !fname.chars().all(|c| c.is_numeric()) { + continue; + } + } else { + continue; + } + + let process = if let Ok(p) = get_pid_info(dir.path()) { + p + } else { + continue; + }; + + if process.cmd_line.is_empty() { + /* This is a kernel thread, skip for now */ + continue; + } + + let mut process_display = ProcessDisplay { + i: process.pid, + pid: PidString(process.pid.to_string()), + ppid: PpidString(process.ppid.to_string()), + vm_rss: VmRssString(Bytes(process.vm_rss * 1024).as_convenient_string()), + cpu_percent: (100.0 + * ((process.utime + - data + .processes_times + .get(&process.pid) + .map(|v| *v) + .unwrap_or(process.utime)) as f64 + / ((cpu_stat.total_time() - data.cpu_stat.total_time()) as f64))) + as usize, + utime: process.utime, + state: process.state, + cmd_line: CmdLineString(process.cmd_line), + username: UserString(crate::ui::username(process.uid)), + }; + if process_display.cpu_percent > 100 { + process_display.cpu_percent = 0; + } + + data.processes_times + .insert(process.pid, process_display.utime); + + processes.push(process_display); + } + data.cpu_stat = cpu_stat; + + s.send(processes).unwrap(); + } + }); + + (ts, r) +} + +/* Might return Error if process has disappeared + * during the function's run */ +fn get_pid_info(mut path: PathBuf) -> Result { + /* proc file structure can be found in man 5 proc.*/ + path.push("status"); + let mut file: File = File::open(&path)?; + let mut res = String::with_capacity(2048); + file.read_to_string(&mut res)?; + let mut lines_iter = res.lines(); + let mut ret = Process { + pid: 0, + ppid: 0, + vm_rss: 0, + uid: 0, + utime: 0, + state: State::Waiting, + cmd_line: String::new(), + }; + let mut line; + + macro_rules! err { + ($res:expr) => { + match $res { + Ok(v) => v, + Err(_) => { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "")); + } + } + }; + } + + macro_rules! none_err { + ($res:expr) => { + if let Some(v) = $res { + v + } else { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "")); + } + }; + } + + let mut b = 0; + while b < 5 { + let line_opt = lines_iter.next(); + if line_opt.is_none() { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("{} returned malformed input", path.display()), + )); + } + line = none_err!(line_opt); + let mut mut_value_iter = line.split_whitespace(); + match mut_value_iter.next() { + Some("VmRSS:") => { + ret.vm_rss = err!(usize::from_str(none_err!(mut_value_iter.next()))); + b += 1; + } + Some("State:") => { + ret.state = State::from(none_err!(none_err!(mut_value_iter.next()).chars().next())); + b += 1; + } + Some("Pid:") => { + ret.pid = err!(i32::from_str(none_err!(mut_value_iter.next()))); + b += 1; + } + Some("PPid:") => { + ret.ppid = err!(i32::from_str(none_err!(mut_value_iter.next()))); + b += 1; + } + Some("Uid:") => { + ret.uid = err!(u32::from_str(none_err!(mut_value_iter.next()))); + b += 1; + } + None => { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!( + "{} returned malformed input. Original error was while parsing file", + path.display(), + ), + )); + } + _ => {} + } + } + + path.pop(); + path.push("cmdline"); + let mut file: File = File::open(&path)?; + res.clear(); + file.read_to_string(&mut res)?; + if !res.is_empty() { + /* values are separated by null bytes */ + ret.cmd_line = format!("{}", res.split('\0').collect::>().join(" ")); + } + path.pop(); + path.push("stat"); + let mut file: File = File::open(&path)?; + res.clear(); + file.read_to_string(&mut res)?; + /* values are separated by whitespace and are in a specific order */ + if !res.is_empty() { + let mut vals = res.split_whitespace().skip(13); + ret.utime = err!(usize::from_str(none_err!(vals.next()))); + ret.utime += err!(usize::from_str(none_err!(vals.next()))); + ret.utime += err!(usize::from_str(none_err!(vals.next()))); + ret.utime += err!(usize::from_str(none_err!(vals.next()))); + } + Ok(ret) +} diff --git a/src/ui.rs b/src/ui.rs index a01e7e6..a7bd79b 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,22 +1,22 @@ /* - * meli - ui crate. + * bb * - * Copyright 2017-2018 Manos Pitsidianakis + * Copyright 2019 Manos Pitsidianakis * - * This file is part of meli. + * This file is part of bb. * - * meli is free software: you can redistribute it and/or modify + * bb is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * meli is distributed in the hope that it will be useful, + * bb is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with meli. If not, see . + * along with bb. If not, see . */ /*! diff --git a/src/ui/components.rs b/src/ui/components.rs index 086da95..9571c9b 100644 --- a/src/ui/components.rs +++ b/src/ui/components.rs @@ -1,22 +1,22 @@ /* - * meli - ui crate. + * bb * - * Copyright 2017-2018 Manos Pitsidianakis + * Copyright 2019 Manos Pitsidianakis * - * This file is part of meli. + * This file is part of bb. * - * meli is free software: you can redistribute it and/or modify + * bb is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * meli is distributed in the hope that it will be useful, + * bb is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with meli. If not, see . + * along with bb. If not, see . */ /*! @@ -26,48 +26,24 @@ See the `Component` Trait for more details. */ use super::*; -use crate::ui::terminal::*; -use crate::ui::types::*; mod utilities; pub use utilities::*; mod kernel; pub use kernel::*; -mod processes; +pub mod processes; pub use processes::*; use std::collections::{HashMap, VecDeque}; use std::fmt; use std::fmt::{Debug, Display}; -use super::{Key, StatusEvent, UIEvent}; +use super::{Key, UIEvent}; /// The upper and lower boundary char. const HORZ_BOUNDARY: char = '─'; /// The left and right boundary char. const VERT_BOUNDARY: char = '│'; -/// The top-left corner -const _TOP_LEFT_CORNER: char = '┌'; -/// The top-right corner -const _TOP_RIGHT_CORNER: char = '┐'; -/// The bottom-left corner -const _BOTTOM_LEFT_CORNER: char = '└'; -/// The bottom-right corner -const _BOTTOM_RIGHT_CORNER: char = '┘'; - -const LIGHT_VERTICAL_AND_RIGHT: char = '├'; - -const _LIGHT_VERTICAL_AND_LEFT: char = '┤'; - -const LIGHT_DOWN_AND_HORIZONTAL: char = '┬'; - -const LIGHT_UP_AND_HORIZONTAL: char = '┴'; - -const _DOUBLE_DOWN_AND_RIGHT: char = '╔'; -const _DOUBLE_DOWN_AND_LEFT: char = '╗'; -const _DOUBLE_UP_AND_LEFT: char = '╝'; -const _DOUBLE_UP_AND_RIGHT: char = '╚'; - pub type ShortcutMap = HashMap<&'static str, Key>; pub type ShortcutMaps = HashMap; @@ -92,22 +68,6 @@ pub trait Component: Display + Debug + Send { } } -/* -pub(crate) fn is_box_char(ch: char) -> bool { - match ch { - HORZ_BOUNDARY | VERT_BOUNDARY => true, - _ => false, - } -} - - * pub(crate) fn is_box_char(ch: char) -> bool { - * match ch { - * '└' | '─' | '┘' | '┴' | '┌' | '│' | '├' | '┐' | '┬' | '┤' | '┼' | '╷' | '╵' | '╴' | '╶' => true, - * _ => false, - * } - * } - */ - fn bin_to_ch(b: u32) -> char { match b { 0b0001 => '╶', @@ -376,19 +336,20 @@ pub fn create_box(grid: &mut CellBuffer, area: Area) { let upper_left = upper_left!(area); let bottom_right = bottom_right!(area); - for x in get_x(upper_left)..get_x(bottom_right) { - grid[(x, get_y(upper_left))].set_ch(HORZ_BOUNDARY); - grid[(x, get_y(bottom_right))].set_ch(HORZ_BOUNDARY); - grid[(x, get_y(bottom_right))].set_fg(Color::Byte(240)); + for x in get_x(upper_left)..=get_x(bottom_right) { + //grid[(x, get_y(upper_left))].set_ch(HORZ_BOUNDARY); + //grid[(x, get_y(bottom_right))].set_ch(HORZ_BOUNDARY); + //grid[(x, get_y(bottom_right))].set_ch('▒'); + //grid[(x, get_y(bottom_right))].set_fg(Color::Byte(240)); } - for y in get_y(upper_left)..get_y(bottom_right) { - grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY); - grid[(get_x(bottom_right), y)].set_ch(VERT_BOUNDARY); + for y in get_y(upper_left)..=get_y(bottom_right) { + //grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY); + grid[(get_x(bottom_right), y)].set_ch('▒'); grid[(get_x(bottom_right), y)].set_fg(Color::Byte(240)); } - set_and_join_box(grid, upper_left, HORZ_BOUNDARY); - set_and_join_box(grid, set_x(upper_left, get_x(bottom_right)), HORZ_BOUNDARY); - set_and_join_box(grid, set_y(upper_left, get_y(bottom_right)), VERT_BOUNDARY); - set_and_join_box(grid, bottom_right, VERT_BOUNDARY); + //set_and_join_box(grid, upper_left, HORZ_BOUNDARY); + //set_and_join_box(grid, set_x(upper_left, get_x(bottom_right)), HORZ_BOUNDARY); + //set_and_join_box(grid, set_y(upper_left, get_y(bottom_right)), VERT_BOUNDARY); + //set_and_join_box(grid, bottom_right, VERT_BOUNDARY); } diff --git a/src/ui/components/kernel.rs b/src/ui/components/kernel.rs index c2f7ae5..710454c 100644 --- a/src/ui/components/kernel.rs +++ b/src/ui/components/kernel.rs @@ -1,3 +1,24 @@ +/* + * bb + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of bb. + * + * bb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with bb. If not, see . + */ + use super::*; use std::fs::File; use std::io::prelude::*; @@ -61,6 +82,8 @@ impl Component for KernelMetrics { let bottom_right = bottom_right!(area); let total_rows = height!(area); let total_cols = width!(area); + + let show_all_cores: bool = total_rows > 1 /* hostname, kernel version, uptime row */ + 1 /* CPU total bar row */ + 0 /* padding rows */ + 1 /* RAM row */; dirty_areas.push_back(area); if self.dirty { clear_area(grid, area); @@ -82,7 +105,7 @@ impl Component for KernelMetrics { ((x + 2, y), bottom_right), false, ); - let (x, y) = write_string_to_grid( + write_string_to_grid( &self.kernel, grid, Color::Default, @@ -139,6 +162,9 @@ impl Component for KernelMetrics { let mut boot_time: usize = 0; for (i, cpu_stat) in get_stat(&mut boot_time).into_iter().enumerate() { let (mut x, y) = if i > 0 { + if !show_all_cores { + break; + } write_string_to_grid( &format!("CPU{}", i), grid, @@ -182,6 +208,9 @@ impl Component for KernelMetrics { .total_time() .saturating_sub(self.cpu_stat[i].total_time())) as f64) * bar_max as f64) as usize; + if bar_length >= width!(area) { + return; + } let mut x_offset = 0; while x_offset < bar_length { @@ -216,8 +245,6 @@ impl Component for KernelMetrics { /* Draw RAM usage bar */ - y_offset += 1; - let bar_max = bar_max + 6; let (available, total) = get_mem_info(); let available_length = ((available as f64 / total as f64) * bar_max as f64) as usize; @@ -294,6 +321,9 @@ impl Component for KernelMetrics { /* CPU Times */ let mut cpu_column_width = "CPU".len(); let upper_left = pos_inc(upper_left, (bar_max + 5, 2)); + if get_x(upper_left) >= get_x(bottom_right) { + return; + } write_string_to_grid( "CPU%", grid, @@ -333,8 +363,10 @@ impl Component for KernelMetrics { } /* Load average */ - let mut load_column_width = "LOAD_AVG".len(); let upper_left = pos_inc(upper_left, (cpu_column_width + 3, 0)); + if get_x(upper_left) >= get_x(bottom_right) { + return; + } write_string_to_grid( "LOAD_AVG", grid, @@ -435,8 +467,10 @@ fn get_cpu_times( macro_rules! val { ($tag:literal, $field:tt) => { - let percent = (cpu_stat.$field - old_cpu_stat.$field) as f64 - / (cpu_stat.total_time() - old_cpu_stat.total_time()) as f64; + let percent = (cpu_stat.$field.saturating_sub(old_cpu_stat.$field)) as f64 + / (cpu_stat + .total_time() + .saturating_sub(old_cpu_stat.total_time())) as f64; let s = format!("{:.1}%", percent * 100.0); ret.push(( $tag, diff --git a/src/ui/components/processes.rs b/src/ui/components/processes.rs index 658d65a..6d6ae5c 100644 --- a/src/ui/components/processes.rs +++ b/src/ui/components/processes.rs @@ -1,12 +1,64 @@ +/* + * bb + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of bb. + * + * bb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with bb. If not, see . + */ + use super::*; -use std::fs::File; -use std::io::prelude::*; -use std::path::PathBuf; -use std::str::FromStr; +use crossbeam::{Receiver, Sender}; + +const SIGNAL_LIST: &[(i32, &'static str)] = &[ + (1, "1 HUP"), + (2, "2 INT"), + (3, "3 QUIT"), + (4, "4 ILL"), + (5, "5 TRAP"), + (6, "6 ABRT"), + (7, "7 BUS"), + (8, "8 FPE"), + (9, "9 KILL"), + (10, "10 USR1"), + (11, "11 SEGV"), + (12, "12 USR2"), + (13, "13 PIPE"), + (14, "14 ALRM"), + (15, "15 TERM"), + (16, "16 STKFLT"), + (17, "17 CHLD"), + (18, "18 CONT"), + (19, "19 STOP"), + (20, "20 TSTP"), + (21, "21 TTIN"), + (22, "22 TTOU"), + (23, "23 URG"), + (24, "24 XCPU"), + (25, "25 XFSZ"), + (26, "26 VTALRM"), + (27, "27 PROF"), + (28, "28 WINCH"), + (29, "29 POLL"), + (30, "30 PWR"), + (31, "31 SYS"), +]; /* Hold maximum width for each column */ #[derive(Debug)] -struct ColumnWidthMaxima { +pub struct ColumnWidthMaxima { pid: usize, ppid: usize, vm_rss: usize, @@ -21,7 +73,7 @@ impl ColumnWidthMaxima { pid: "PID".len(), ppid: "PPID".len(), vm_rss: "VM_RSS".len(), - cpu_percent: "CPU%".len(), + cpu_percent: " CPU%".len(), state: 1, username: "USER".len(), } @@ -32,10 +84,11 @@ macro_rules! define_column_string { ($($typename: tt),+) => { $( #[derive(Debug)] - struct $typename(String); + pub struct $typename(pub String); impl $typename { - fn len(&self) -> usize { + #[allow(dead_code)] + pub fn len(&self) -> usize { self.0.len() } } @@ -60,28 +113,29 @@ define_column_string!( UserString ); -type Pid = usize; +pub type Pid = i32; /* Wrapper type for display strings */ #[derive(Debug)] -struct ProcessDisplay { - i: Pid, - pid: PidString, - ppid: PpidString, - vm_rss: VmRssString, - cpu_percent: usize, - state: State, - cmd_line: CmdLineString, - username: UserString, - utime: usize, +pub struct ProcessDisplay { + pub i: Pid, + pub pid: PidString, + pub ppid: PpidString, + pub vm_rss: VmRssString, + pub cpu_percent: usize, + pub state: State, + pub cmd_line: CmdLineString, + pub username: UserString, + pub utime: usize, } /* process list components */ #[derive(Debug)] pub struct ProcessList { + sender: Sender>, + rcver: Receiver>, page_movement: Option, cpu_stat: Stat, - pid_max: usize, cursor: usize, height: usize, dirty: bool, @@ -90,10 +144,19 @@ pub struct ProcessList { freeze: bool, processes_times: HashMap, processes: Vec, + mode: ProcessListMode, +} + +#[derive(Debug, PartialEq)] +enum ProcessListMode { + Normal, + Kill(u16), } +use ProcessListMode::*; + #[derive(Debug, PartialEq)] -enum State { +pub enum State { /* Z Zombie */ Zombie, /* R Running */ @@ -106,16 +169,8 @@ enum State { Stopped, /* t Tracing stop (Linux 2.6.33 onward) */ Tracing, - /*W Paging (only before Linux 2.6.0) */ - Paging, /* X Dead (from Linux 2.6.0 onward) */ Dead, - /* K Wakekill (Linux 2.6.33 to 3.13 only) */ - Wakekill, - /* W Waking (Linux 2.6.33 to 3.13 only) */ - Waking, - /* P Parked (Linux 3.9 to 3.13 only) */ - Parked, } impl From for State { @@ -128,12 +183,8 @@ impl From for State { 'Z' => State::Zombie, 'T' => State::Stopped, 't' => State::Tracing, - 'W' => State::Paging, 'X' => State::Dead, 'x' => State::Dead, - 'K' => State::Wakekill, - 'W' => State::Waking, - 'P' => State::Parked, _ => unreachable!(), } } @@ -151,26 +202,20 @@ impl std::fmt::Display for State { State::Zombie => 'Z', State::Stopped => 'T', State::Tracing => 't', - State::Paging => 'W', State::Dead => 'X', - State::Dead => 'x', - State::Wakekill => 'K', - State::Waking => 'W', - State::Parked => 'P', - _ => unreachable!(), } ) } } -struct Process { - pid: usize, - ppid: usize, - vm_rss: usize, - state: State, - uid: u32, - cmd_line: String, - utime: usize, +pub struct Process { + pub pid: i32, + pub ppid: i32, + pub vm_rss: usize, + pub state: State, + pub uid: u32, + pub cmd_line: String, + pub utime: usize, } impl fmt::Display for ProcessList { @@ -180,20 +225,19 @@ impl fmt::Display for ProcessList { } impl ProcessList { - pub fn new() -> Self { - let mut file = File::open("/proc/sys/kernel/pid_max").unwrap(); - let mut pid_max = String::new(); - file.read_to_string(&mut pid_max).unwrap(); + pub fn new(sender: Sender>, rcver: Receiver>) -> Self { ProcessList { + sender, + rcver, cursor: 0, page_movement: None, cpu_stat: get_stat(&mut 0).remove(0), - pid_max: usize::from_str(pid_max.trim()).unwrap(), processes: Vec::with_capacity(1024), processes_times: Default::default(), height: 0, maxima: ColumnWidthMaxima::new(), freeze: false, + mode: Normal, dirty: true, } } @@ -220,9 +264,8 @@ impl Component for ProcessList { let bottom_right = pos_dec(bottom_right!(area), (1, 1)); /* Reserve first row for column headers */ - let height = height!(area) - 5; - let old_pages = self.cursor / height; - let width = width!(area); + let height = height!(area) - 2; + let old_pages = (self.cursor) / height; let old_cursor = self.cursor; if let Some(mvm) = self.page_movement.take() { @@ -254,11 +297,25 @@ impl Component for ProcessList { return; } - let pages = self.cursor / height; + let pages = (self.cursor) / height; if pages != old_pages { tick = true; } + let update_maxima = if tick && !self.freeze { + match self.rcver.recv_timeout(std::time::Duration::new(0, 0)) { + Ok(new_processes) => { + self.sender + .send(std::mem::replace(&mut self.processes, new_processes)) + .unwrap(); + true + } + Err(_) => false, + } + } else { + false + }; + if tick || self.freeze { if tick || old_cursor != self.cursor { clear_area(grid, area); @@ -266,71 +323,19 @@ impl Component for ProcessList { dirty_areas.push_back(area); - if !self.freeze { - self.processes.clear(); - - let cpu_stat = get_stat(&mut 0).remove(0); - + if !self.freeze && update_maxima { /* Keep tabs on biggest element in each column */ - let mut maxima = ColumnWidthMaxima::new(); - - self.height = 0; - for entry in std::fs::read_dir("/proc/").unwrap() { - let dir = entry.unwrap(); - if let Some(fname) = dir.file_name().to_str() { - if !fname.chars().all(|c| c.is_numeric()) { - continue; - } - } else { - continue; - } + self.maxima = ColumnWidthMaxima::new(); - let process = if let Ok(p) = get_pid_info(dir.path()) { - p - } else { - continue; - }; - - if process.cmd_line.is_empty() { - /* This is a kernel thread, skip for now */ - continue; - } - - let process_display = ProcessDisplay { - i: process.pid, - pid: PidString(process.pid.to_string()), - ppid: PpidString(process.ppid.to_string()), - vm_rss: VmRssString(Bytes(process.vm_rss * 1024).as_convenient_string()), - cpu_percent: (100.0 - * ((process.utime - - self - .processes_times - .get(&process.pid) - .map(|v| *v) - .unwrap_or(process.utime)) - as f64 - / ((cpu_stat.total_time() - self.cpu_stat.total_time()) as f64))) - as usize, - utime: process.utime, - state: process.state, - cmd_line: CmdLineString(process.cmd_line), - username: UserString(crate::ui::username(process.uid)), - }; - - self.processes_times - .insert(process.pid, process_display.utime); - - maxima.pid = std::cmp::max(maxima.pid, process_display.pid.len()); - maxima.ppid = std::cmp::max(maxima.ppid, process_display.ppid.len()); - maxima.vm_rss = std::cmp::max(maxima.vm_rss, process_display.vm_rss.len()); - maxima.username = - std::cmp::max(maxima.username, process_display.username.len()); - self.processes.push(process_display); - self.height += 1; + for p in &self.processes { + self.maxima.pid = std::cmp::max(self.maxima.pid, p.pid.len()); + self.maxima.ppid = std::cmp::max(self.maxima.ppid, p.ppid.len()); + self.maxima.vm_rss = std::cmp::max(self.maxima.vm_rss, p.vm_rss.len()); + self.maxima.username = std::cmp::max(self.maxima.username, p.username.len()); } - self.cpu_stat = cpu_stat; + + self.height = self.processes.len(); self.cursor = std::cmp::min(self.height.saturating_sub(1), self.cursor); - self.maxima = maxima; } /* Write column headers */ @@ -341,7 +346,7 @@ impl Component for ProcessList { ppid ="PPID", username = "USER", vm_rss = "VM_RSS", - cpu_percent = "CPU%", + cpu_percent = " CPU%", state = " ", cmd_line = "CMD_LINE", max_pid = self.maxima.pid, @@ -355,7 +360,7 @@ impl Component for ProcessList { Color::Black, Color::White, Attr::Default, - (pos_inc(upper_left, (0, 2)), bottom_right), + (pos_inc(upper_left, (0, 1)), bottom_right), false, ); change_colors( @@ -399,7 +404,7 @@ impl Component for ProcessList { fg_color, bg_color, Attr::Default, - (pos_inc(upper_left, (0, y_offset + 3)), bottom_right), + (pos_inc(upper_left, (0, y_offset + 2)), bottom_right), false, ); if p.state == State::Running { @@ -412,7 +417,7 @@ impl Component for ProcessList { + self.maxima.username + self.maxima.vm_rss + self.maxima.cpu_percent, - y_offset + 3, + y_offset + 2, ), )] .set_fg(if self.freeze { @@ -427,10 +432,10 @@ impl Component for ProcessList { Color::Byte(243), bg_color, Attr::Default, - (pos_inc(upper_left, (x, y_offset + 3)), bottom_right), + (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), false, ); - let (x, y) = write_string_to_grid( + let (x, _) = write_string_to_grid( bin, grid, if self.freeze { @@ -440,16 +445,16 @@ impl Component for ProcessList { }, bg_color, Attr::Default, - (pos_inc(upper_left, (x - 1, y_offset + 3)), bottom_right), + (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), false, ); - let (x, y) = write_string_to_grid( + let (x, _) = write_string_to_grid( rest, grid, fg_color, bg_color, Attr::Default, - (pos_inc(upper_left, (x - 1, y_offset + 3)), bottom_right), + (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), false, ); change_colors( @@ -460,7 +465,7 @@ impl Component for ProcessList { ); } Err((bin, rest)) => { - let (x,y) = write_string_to_grid( + let (x, y) = write_string_to_grid( &format!( "{pid:>max_pid$} {ppid:>max_ppid$} {username:>max_username$} {vm_rss:>max_vm_rss$} {cpu_percent:>max_cpu_percent$}% {state:>max_state$} ", pid = p.pid, @@ -480,7 +485,7 @@ impl Component for ProcessList { fg_color, bg_color, Attr::Default, - (pos_inc(upper_left, (0, y_offset + 3)), bottom_right), + (pos_inc(upper_left, (0, y_offset + 2)), bottom_right), false, ); if p.state == State::Running { @@ -494,7 +499,7 @@ impl Component for ProcessList { + self.maxima.username + self.maxima.vm_rss + self.maxima.cpu_percent, - y_offset + 3, + y_offset + 2, ), )] .set_fg(if self.freeze { @@ -503,7 +508,7 @@ impl Component for ProcessList { Color::Byte(10) }); } - let (x, y) = write_string_to_grid( + let (x, _) = write_string_to_grid( bin, grid, if self.freeze { @@ -513,7 +518,7 @@ impl Component for ProcessList { }, bg_color, Attr::Default, - (pos_inc(upper_left, (x - 1, y_offset + 3)), bottom_right), + (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), false, ); let (x, y) = write_string_to_grid( @@ -522,7 +527,7 @@ impl Component for ProcessList { fg_color, bg_color, Attr::Default, - (pos_inc(upper_left, (x - 1, y_offset + 3)), bottom_right), + (pos_inc(upper_left, (x - 1, y_offset + 2)), bottom_right), false, ); change_colors( @@ -534,23 +539,20 @@ impl Component for ProcessList { } } y_offset += 1; - if y_offset >= height { - break; - } } } else if old_cursor != self.cursor { let new_area = ( - pos_inc(upper_left, (0, self.cursor - pages * height + 3)), + pos_inc(upper_left, (0, self.cursor + 2 - pages * height)), set_y( bottom_right, - get_y(upper_left) + self.cursor - pages * height + 3, + get_y(upper_left) + self.cursor + 2 - pages * height, ), ); let old_area = ( - pos_inc(upper_left, (0, old_cursor - old_pages * height + 3)), + pos_inc(upper_left, (0, old_cursor + 2 - old_pages * height)), set_y( bottom_right, - get_y(upper_left) + old_cursor - old_pages * height + 3, + get_y(upper_left) + old_cursor + 2 - old_pages * height, ), ); change_colors(grid, new_area, None, Some(Color::Byte(235))); @@ -558,6 +560,135 @@ impl Component for ProcessList { dirty_areas.push_back(old_area); dirty_areas.push_back(new_area); } + + if let Kill(ref n) = self.mode { + let (cols, rows) = grid.size(); + let margin_left = (cols / 2).saturating_sub(16); + let margin_top = (rows / 2).saturating_sub(12); + let box_area = ( + (margin_left, margin_top), + (margin_left + 32, margin_top + 12), + ); + clear_area(grid, box_area); + create_box(grid, box_area); + let mut x = 1; + for (i, s) in SIGNAL_LIST.chunks(11).enumerate() { + let mut y = 0; + for sig in s { + write_string_to_grid( + sig.1, + grid, + Color::Default, + Color::Default, + Attr::Default, + (pos_inc(upper_left!(box_area), (x, 1 + y)), bottom_right), + false, + ); + y += 1; + } + x += 11; + } + let box_area = ( + (margin_left, margin_top + 13), + (margin_left + 32, margin_top + 16), + ); + clear_area(grid, box_area); + create_box(grid, box_area); + let signal_fmt = if *n == 0 { + format!("__") + } else if *n < 32 { + format!("{} [{}]", SIGNAL_LIST[*n as usize - 1].1, *n) + } else { + format!("invalid [{}]", *n) + }; + write_string_to_grid( + &format!( + "{cmd_line} [{pid}]", + pid = self.processes[self.cursor].i, + cmd_line = &self.processes[self.cursor].cmd_line.0 + [0..std::cmp::min(26, self.processes[self.cursor].cmd_line.len())], + ), + grid, + Color::Default, + Color::Default, + Attr::Default, + ( + pos_inc(upper_left!(box_area), (1, 1)), + bottom_right!(box_area), + ), + false, + ); + write_string_to_grid( + &format!("send {signal}", signal = signal_fmt,), + grid, + Color::Default, + Color::Default, + Attr::Default, + ( + pos_inc(upper_left!(box_area), (1, 2)), + bottom_right!(box_area), + ), + false, + ); + /* + let rows = SIGNAL_LIST.len() / width!(area) + 6; + let box_area = (pos_inc(upper_left, (0, height - rows)), bottom_right); + clear_area(grid, box_area); + create_box(grid, box_area); + let write_area = ( + pos_inc(upper_left, (3, height - rows + 1)), + pos_dec(bottom_right, (3, 0)), + ); + let mut processes = self.processes.iter().collect::>(); + processes.sort_unstable_by(|a, b| b.cpu_percent.cmp(&a.cpu_percent)); + let (_, y) = write_string_to_grid( + &format!("pid {}", processes[self.cursor].i,), + grid, + Color::Default, + Color::Default, + Attr::Default, + write_area, + false, + ); + let (_, y) = write_string_to_grid( + &format!("cmd_line {}", processes[self.cursor].cmd_line), + grid, + Color::Default, + Color::Default, + Attr::Default, + ( + (get_x(upper_left!(write_area)), y + 1), + bottom_right!(write_area), + ), + false, + ); + let (_, y) = write_string_to_grid( + &format!("sig_no {}", n), + grid, + Color::Default, + Color::Default, + Attr::Default, + ( + (get_x(upper_left!(write_area)), y + 1), + bottom_right!(write_area), + ), + false, + ); + write_string_to_grid( + SIGNAL_LIST, + grid, + Color::Default, + Color::Default, + Attr::Default, + ( + (get_x(upper_left!(write_area)), y + 1), + bottom_right!(write_area), + ), + true, + ); + */ + } + self.dirty = false; } @@ -588,10 +719,45 @@ impl Component for ProcessList { self.page_movement = Some(PageMovement::End); self.dirty = true; } - UIEvent::Input(k) if *k == map["freeze updates"] => { + UIEvent::Input(k) if *k == map["freeze updates"] && self.mode == Normal => { self.freeze = !self.freeze; self.dirty = true; } + UIEvent::Input(k) if *k == map["kill process"] => { + self.mode = Kill(0); + self.freeze = true; + self.dirty = true; + } + UIEvent::Input(k) if *k == map["cancel"] => { + if let Kill(_) = self.mode { + self.mode = Normal; + self.freeze = false; + self.dirty = true; + } + } + UIEvent::Input(Key::Char(f)) if self.mode != Normal && f.is_numeric() => { + if let Kill(ref mut n) = self.mode { + *n = *n * 10 + (f.to_digit(10).unwrap() as u16); + } + } + UIEvent::Input(Key::Backspace) if self.mode != Normal => { + if let Kill(ref mut n) = self.mode { + *n = *n / 10; + } + } + UIEvent::Input(Key::Char('\n')) if self.mode != Normal => { + let mut processes = self.processes.iter().collect::>(); + processes.sort_unstable_by(|a, b| b.cpu_percent.cmp(&a.cpu_percent)); + if let Kill(ref mut n) = self.mode { + use nix::sys::signal::kill; + kill( + nix::unistd::Pid::from_raw(processes[self.cursor].i), + nix::sys::signal::Signal::from_c_int(*n as i32).unwrap(), + ); + } + self.mode = Normal; + self.dirty = true; + } _ => {} } } @@ -604,81 +770,14 @@ impl Component for ProcessList { fn get_shortcuts(&self) -> ShortcutMaps { let mut map: ShortcutMap = Default::default(); map.insert("freeze updates", Key::Char('f')); + map.insert("kill process", Key::Char('k')); + map.insert("cancel", Key::Esc); let mut ret: ShortcutMaps = Default::default(); ret.insert("".to_string(), map); ret } } -/* proc file structure can be found in man 5 proc */ -fn get_pid_info(mut path: PathBuf) -> Result { - path.push("status"); - let mut file: File = File::open(&path)?; - let mut res = String::with_capacity(2048); - file.read_to_string(&mut res)?; - let mut lines_iter = res.lines(); - let mut ret = Process { - pid: 0, - ppid: 0, - vm_rss: 0, - uid: 0, - utime: 0, - state: State::Waiting, - cmd_line: String::new(), - }; - let mut line; - - loop { - let line_opt = lines_iter.next(); - if line_opt.is_none() { - break; - } - line = line_opt.unwrap(); - let mut mut_value_iter = line.split_whitespace(); - match mut_value_iter.next().unwrap() { - "VmRSS:" => { - ret.vm_rss = usize::from_str(mut_value_iter.next().unwrap()).unwrap(); - } - "State:" => { - ret.state = State::from(mut_value_iter.next().unwrap().chars().next().unwrap()); - } - "Pid:" => { - ret.pid = usize::from_str(mut_value_iter.next().unwrap()).unwrap(); - } - "PPid:" => { - ret.ppid = usize::from_str(mut_value_iter.next().unwrap()).unwrap(); - } - "Uid:" => { - ret.uid = u32::from_str(mut_value_iter.next().unwrap()).unwrap(); - } - _ => {} - } - } - path.pop(); - path.push("cmdline"); - let mut file: File = File::open(&path).unwrap(); - res.clear(); - file.read_to_string(&mut res).unwrap(); - if !res.is_empty() { - /* values are separated by null bytes */ - ret.cmd_line = format!("{}", res.split('\0').collect::>().join(" ")); - } - path.pop(); - path.push("stat"); - let mut file: File = File::open(&path).unwrap(); - res.clear(); - file.read_to_string(&mut res).unwrap(); - /* values are separated by whitespace and are in a specific order */ - if !res.is_empty() { - let mut vals = res.split_whitespace().skip(13); - ret.utime = usize::from_str(vals.next().unwrap()).unwrap(); - ret.utime += usize::from_str(vals.next().unwrap()).unwrap(); - ret.utime += usize::from_str(vals.next().unwrap()).unwrap(); - ret.utime += usize::from_str(vals.next().unwrap()).unwrap(); - } - Ok(ret) -} - fn executable_path_color(p: &CmdLineString) -> Result<(&str, &str, &str), (&str, &str)> { let p = &p.0; if !p.starts_with("/") { diff --git a/src/ui/components/utilities.rs b/src/ui/components/utilities.rs index 1b173f0..f74d8bc 100644 --- a/src/ui/components/utilities.rs +++ b/src/ui/components/utilities.rs @@ -1,254 +1,35 @@ /* - * meli - ui crate. + * bb * - * Copyright 2017-2018 Manos Pitsidianakis + * Copyright 2019 Manos Pitsidianakis * - * This file is part of meli. + * This file is part of bb. * - * meli is free software: you can redistribute it and/or modify + * bb is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * meli is distributed in the hope that it will be useful, + * bb is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with meli. If not, see . + * along with bb. If not, see . */ /*! Various useful components that can be used in a generic fashion. */ +extern crate cassowary; use super::*; use std::fs::File; use std::io::prelude::*; use std::str::FromStr; -mod widgets; - -pub use self::widgets::*; - -/// A horizontally split in half container. -#[derive(Debug)] -pub struct HSplit { - top: Box, - bottom: Box, - show_divider: bool, - ratio: usize, // bottom/whole height * 100 -} - -impl fmt::Display for HSplit { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // TODO display subject/info - Display::fmt(&self.top, f) - } -} - -impl HSplit { - pub fn new( - top: Box, - bottom: Box, - ratio: usize, - show_divider: bool, - ) -> Self { - HSplit { - top, - bottom, - show_divider, - ratio, - } - } -} - -impl Component for HSplit { - fn draw( - &mut self, - grid: &mut CellBuffer, - area: Area, - dirty_areas: &mut VecDeque, - tick: bool, - ) { - if !is_valid_area!(area) { - return; - } - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - let total_rows = get_y(bottom_right) - get_y(upper_left); - let bottom_component_height = (self.ratio * total_rows) / 100; - let mid = get_y(upper_left) + total_rows - bottom_component_height; - - if self.show_divider { - for i in get_x(upper_left)..=get_x(bottom_right) { - grid[(i, mid)].set_ch('─'); - } - dirty_areas.push_back(((get_x(upper_left), mid), (get_x(bottom_right), mid))); - } - - self.top.draw( - grid, - ( - upper_left, - (get_x(bottom_right), get_y(upper_left) + mid - 1), - ), - dirty_areas, - tick, - ); - self.bottom.draw( - grid, - ((get_x(upper_left), get_y(upper_left) + mid), bottom_right), - dirty_areas, - tick, - ); - } - - fn process_event(&mut self, event: &mut UIEvent) { - self.top.process_event(event); - self.bottom.process_event(event); - } - - fn is_dirty(&self) -> bool { - self.top.is_dirty() || self.bottom.is_dirty() - } - - fn set_dirty(&mut self) { - self.top.set_dirty(); - self.bottom.set_dirty(); - } - - fn get_shortcuts(&self) -> ShortcutMaps { - let mut top_map = self.top.get_shortcuts(); - top_map.extend(self.bottom.get_shortcuts().into_iter()); - top_map - } -} - -/// A vertically split in half container. -#[derive(Debug)] -pub struct VSplit { - left: Box, - right: Box, - show_divider: bool, - /// This is the width of the right container to the entire width. - ratio: usize, // right/(container width) * 100 -} - -impl fmt::Display for VSplit { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // TODO display focused component - Display::fmt(&self.right, f) - } -} - -impl VSplit { - pub fn new( - left: Box, - right: Box, - ratio: usize, - show_divider: bool, - ) -> Self { - VSplit { - left, - right, - show_divider, - ratio, - } - } -} - -impl Component for VSplit { - fn draw( - &mut self, - grid: &mut CellBuffer, - area: Area, - dirty_areas: &mut VecDeque, - tick: bool, - ) { - if !is_valid_area!(area) { - return; - } - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - let total_cols = get_x(bottom_right) - get_x(upper_left); - let right_component_width = (self.ratio * total_cols) / 100; - - let mid = get_x(bottom_right) - right_component_width; - - if get_y(upper_left) > 1 { - let c = grid - .get(mid, get_y(upper_left) - 1) - .map(Cell::ch) - .unwrap_or_else(|| ' '); - if let HORZ_BOUNDARY = c { - grid[(mid, get_y(upper_left) - 1)].set_ch(LIGHT_DOWN_AND_HORIZONTAL); - } - } - - if self.show_divider && mid != get_x(upper_left) { - for i in get_y(upper_left)..=get_y(bottom_right) { - grid[(mid, i)].set_ch(VERT_BOUNDARY); - grid[(mid, i)].set_fg(Color::Default); - grid[(mid, i)].set_bg(Color::Default); - } - if get_y(bottom_right) > 1 { - let c = grid - .get(mid, get_y(bottom_right) - 1) - .map(Cell::ch) - .unwrap_or_else(|| ' '); - if let HORZ_BOUNDARY = c { - grid[(mid, get_y(bottom_right) + 1)].set_ch(LIGHT_UP_AND_HORIZONTAL); - } - } - dirty_areas.push_back(((mid, get_y(upper_left)), (mid, get_y(bottom_right)))); - } - - if right_component_width == total_cols { - self.right.draw(grid, area, dirty_areas, tick); - } else if right_component_width == 0 { - self.left.draw(grid, area, dirty_areas, tick); - } else { - self.left.draw( - grid, - ( - upper_left, - ( - if self.show_divider { mid - 1 } else { mid }, - get_y(bottom_right), - ), - ), - dirty_areas, - tick, - ); - self.right.draw( - grid, - (set_x(upper_left, mid + 1), bottom_right), - dirty_areas, - tick, - ); - } - } - - fn process_event(&mut self, event: &mut UIEvent) { - self.left.process_event(event); - self.right.process_event(event); - } - - fn is_dirty(&self) -> bool { - self.left.is_dirty() || self.right.is_dirty() - } - - fn set_dirty(&mut self) { - self.left.set_dirty(); - self.right.set_dirty(); - } - - fn get_shortcuts(&self) -> ShortcutMaps { - let mut right_map = self.right.get_shortcuts(); - right_map.extend(self.left.get_shortcuts().into_iter()); - right_map - } -} +use cassowary::strength::{REQUIRED, STRONG, WEAK}; +use cassowary::WeightedRelation::*; +use cassowary::{Solver, Variable}; #[derive(Debug)] pub enum PageMovement { @@ -337,3 +118,120 @@ pub fn get_stat(boot_time: &mut usize) -> Vec { ret } + +/// A horizontally split in half container. +#[derive(Debug)] +pub struct Window { + top_bars: Box, + list: Box, +} + +impl fmt::Display for Window { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt(&self.top_bars, f) + } +} + +impl Window { + pub fn new(top_bars: Box, list: Box) -> Self { + Window { top_bars, list } + } +} + +struct Element { + top: Variable, + bottom: Variable, +} +impl Component for Window { + fn draw( + &mut self, + grid: &mut CellBuffer, + area: Area, + dirty_areas: &mut VecDeque, + tick: bool, + ) { + if !is_valid_area!(area) { + return; + } + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + let total_rows = get_y(bottom_right) - get_y(upper_left); + let window_height = Variable::new(); + + let top_bars = Element { + top: Variable::new(), + bottom: Variable::new(), + }; + + let list = Element { + top: Variable::new(), + bottom: Variable::new(), + }; + + let mut solver = Solver::new(); + solver + .add_constraints(&[ + window_height | GE(REQUIRED) | 0.0, // positive window height + top_bars.top | EQ(REQUIRED) | 0.0, // top align + list.bottom | EQ(REQUIRED) | window_height, // right align + list.top | GE(REQUIRED) | top_bars.bottom, // no overlap + // positive heights + top_bars.top | LE(REQUIRED) | top_bars.bottom, + list.top | LE(REQUIRED) | list.bottom, + // preferred heights: + top_bars.bottom - top_bars.top | GE(REQUIRED) | 6.0, + top_bars.bottom - top_bars.top | EQ(WEAK) | 8.0, + top_bars.bottom - top_bars.top | LE(REQUIRED) | 8.0, + list.bottom - list.top | GE(REQUIRED) | 11.0, + ]) + .unwrap(); + + solver.add_edit_variable(window_height, STRONG).unwrap(); + solver + .suggest_value(window_height, total_rows as f64) + .unwrap(); + + let changes = solver.fetch_changes(); + let mid = get_y(upper_left) + + (*changes + .iter() + .find(|(a, _)| *a == top_bars.bottom) + .map(|(_, b)| b) + .unwrap() as usize); + self.top_bars.draw( + grid, + ( + upper_left, + (get_x(bottom_right), get_y(upper_left) + mid - 1), + ), + dirty_areas, + tick, + ); + self.list.draw( + grid, + ((get_x(upper_left), get_y(upper_left) + mid), bottom_right), + dirty_areas, + tick, + ); + } + + fn process_event(&mut self, event: &mut UIEvent) { + self.top_bars.process_event(event); + self.list.process_event(event); + } + + fn is_dirty(&self) -> bool { + self.top_bars.is_dirty() || self.list.is_dirty() + } + + fn set_dirty(&mut self) { + self.top_bars.set_dirty(); + self.list.set_dirty(); + } + + fn get_shortcuts(&self) -> ShortcutMaps { + let mut top_bars_map = self.top_bars.get_shortcuts(); + top_bars_map.extend(self.list.get_shortcuts().into_iter()); + top_bars_map + } +} diff --git a/src/ui/components/utilities/widgets.rs b/src/ui/components/utilities/widgets.rs deleted file mode 100644 index ff20fda..0000000 --- a/src/ui/components/utilities/widgets.rs +++ /dev/null @@ -1,69 +0,0 @@ -use super::*; - -#[derive(Default)] -pub struct ScrollBar { - show_arrows: bool, - block_character: Option, -} - -impl ScrollBar { - pub fn set_show_arrows(&mut self, flag: bool) { - self.show_arrows = flag; - } - pub fn set_block_character(&mut self, val: Option) { - self.block_character = val; - } - pub fn draw( - self, - grid: &mut CellBuffer, - area: Area, - pos: usize, - visible_rows: usize, - length: usize, - ) { - if length == 0 { - return; - } - let mut height = height!(area); - if height < 3 { - return; - } - if self.show_arrows { - height -= height; - } - clear_area(grid, area); - - let visible_ratio: f32 = (std::cmp::min(visible_rows, length) as f32) / (length as f32); - let scrollbar_height = std::cmp::max((visible_ratio * (height as f32)) as usize, 1); - let scrollbar_offset = { - let temp = (((pos as f32) / (length as f32)) * (height as f32)) as usize; - if temp + scrollbar_height >= height { - height - scrollbar_height - } else { - temp - } - }; - let (mut upper_left, bottom_right) = area; - - if self.show_arrows { - grid[upper_left].set_ch('▴'); - upper_left = (upper_left.0, upper_left.1 + 1); - } - - for y in get_y(upper_left)..(get_y(upper_left) + scrollbar_offset) { - grid[set_y(upper_left, y)].set_ch(' '); - } - for y in (get_y(upper_left) + scrollbar_offset) - ..=(get_y(upper_left) + scrollbar_offset + scrollbar_height) - { - grid[set_y(upper_left, y)].set_ch(self.block_character.unwrap_or('█')); - } - for y in (get_y(upper_left) + scrollbar_offset + scrollbar_height + 1)..get_y(bottom_right) - { - grid[set_y(upper_left, y)].set_ch(' '); - } - if self.show_arrows { - grid[set_x(bottom_right, get_x(upper_left))].set_ch('▾'); - } - } -} diff --git a/src/ui/state.rs b/src/ui/state.rs index d355793..b9e5f04 100644 --- a/src/ui/state.rs +++ b/src/ui/state.rs @@ -1,22 +1,22 @@ /* - * meli - ui crate. + * bb * - * Copyright 2017-2018 Manos Pitsidianakis + * Copyright 2019 Manos Pitsidianakis * - * This file is part of meli. + * This file is part of bb. * - * meli is free software: you can redistribute it and/or modify + * bb is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * meli is distributed in the hope that it will be useful, + * bb is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with meli. If not, see . + * along with bb. If not, see . */ /*! The application's state. @@ -311,6 +311,10 @@ impl State { } pub fn draw_component(&mut self, idx: usize, tick: bool) { + if self.cols < 80 || self.cols < 24 { + return; + } + let component = &mut self.components[idx]; let upper_left = (0, 0); let bottom_right = (self.cols - 1, self.rows - 1); @@ -329,8 +333,6 @@ impl State { } /// The application's main loop sends `UIEvents` to state via this method. pub fn rcv_event(&mut self, mut event: UIEvent) { - let upper_left = (0, 0); - let bottom_right = (self.cols - 1, self.rows - 1); /* inform each component */ for i in 0..self.components.len() { self.components[i].process_event(&mut event); @@ -347,9 +349,6 @@ impl State { self.stdout.as_mut().unwrap() } - pub fn input_kill(&self) { - self.input.kill(); - } pub fn restore_input(&self) { self.input.restore(self.sender.clone()); } diff --git a/src/ui/terminal.rs b/src/ui/terminal.rs index b3d1471..df95db1 100644 --- a/src/ui/terminal.rs +++ b/src/ui/terminal.rs @@ -1,22 +1,22 @@ /* - * meli - ui crate. + * bb * - * Copyright 2017-2018 Manos Pitsidianakis + * Copyright 2019 Manos Pitsidianakis * - * This file is part of meli. + * This file is part of bb. * - * meli is free software: you can redistribute it and/or modify + * bb is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * meli is distributed in the hope that it will be useful, + * bb is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with meli. If not, see . + * along with bb. If not, see . */ #[macro_use] mod position; diff --git a/src/ui/terminal/cells.rs b/src/ui/terminal/cells.rs index 0f35597..c8cc28b 100644 --- a/src/ui/terminal/cells.rs +++ b/src/ui/terminal/cells.rs @@ -1,22 +1,22 @@ /* - * meli - ui crate. + * bb * - * Copyright 2017-2018 Manos Pitsidianakis + * Copyright 2019 Manos Pitsidianakis * - * This file is part of meli. + * This file is part of bb. * - * meli is free software: you can redistribute it and/or modify + * bb is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * meli is distributed in the hope that it will be useful, + * bb is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with meli. If not, see . + * along with bb. If not, see . */ /*! @@ -120,16 +120,6 @@ impl fmt::Debug for CellBuffer { } impl CellBuffer { - pub fn area(&self) -> Area { - ( - (0, 0), - (self.cols.saturating_sub(1), self.rows.saturating_sub(1)), - ) - } - pub fn set_cols(&mut self, new_cols: usize) { - self.cols = new_cols; - } - /// Constructs a new `CellBuffer` with the given number of columns and rows, using the given /// `cell` as a blank. pub fn new(cols: usize, rows: usize, cell: Cell) -> CellBuffer { @@ -158,34 +148,6 @@ impl CellBuffer { self.cols = newcols; self.rows = newrows; } - - pub fn split_newlines(self) -> Self { - let lines: Vec<&[Cell]> = self.split(|cell| cell.ch() == '\n').collect(); - let height = lines.len(); - let width = lines.iter().map(|l| l.len()).max().unwrap_or(0) + 1; - let mut content = CellBuffer::new(width, height, Cell::with_char(' ')); - { - let mut x; - let c_slice: &mut [Cell] = &mut content; - for (y, l) in lines.iter().enumerate() { - let y_r = y * width; - x = l.len() + y_r; - c_slice[y_r..x].copy_from_slice(l); - c_slice[x].set_ch('\n'); - } - } - content - } - - pub fn is_empty(&self) -> bool { - self.buf.is_empty() - } - - pub fn empty(&mut self) { - self.buf.clear(); - self.cols = 0; - self.rows = 0; - } } impl HasSize for CellBuffer { @@ -328,23 +290,6 @@ impl Cell { Cell::new(ch, Color::Default, Color::Default, Attr::Default) } - /// Creates a new `Cell` with the given style and a blank `char`. - /// - /// # Examples - /// - /// ```norun - /// use rustty::{Cell, Color, Attr}; - /// - /// let mut cell = Cell::with_style(Color::Default, Color::Red, Attr::Bold); - /// assert_eq!(cell.fg(), Color::Default); - /// assert_eq!(cell.bg(), Color::Red); - /// assert_eq!(cell.attrs(), Attr::Bold); - /// assert_eq!(cell.ch(), ' '); - /// ``` - pub fn with_style(fg: Color, bg: Color, attr: Attr) -> Cell { - Cell::new(' ', fg, bg, attr) - } - /// Returns the `Cell`'s character. /// /// # Examples @@ -500,6 +445,7 @@ impl Default for Cell { /// // Basic colors are also 8-bit colors (but not vice-versa). /// assert_eq!(red.as_byte(), fancy.as_byte()) /// ``` +#[allow(dead_code)] #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Color { Black, @@ -567,6 +513,7 @@ impl Color { /// // Combination. /// let comb = Attr::UnderlineReverse; /// ``` +#[allow(dead_code)] #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Attr { Default = 0b000, @@ -579,103 +526,6 @@ pub enum Attr { BoldReverseUnderline = 0b111, } -// TODO: word break. -pub fn copy_area_with_break( - grid_dest: &mut CellBuffer, - grid_src: &CellBuffer, - dest: Area, - src: Area, -) -> Pos { - if !is_valid_area!(dest) || !is_valid_area!(src) { - eprintln!( - "BUG: Invalid areas in copy_area:\n src: {:?}\n dest: {:?}", - src, dest - ); - return upper_left!(dest); - } - - if grid_src.is_empty() || grid_dest.is_empty() { - return upper_left!(dest); - } - - let mut ret = bottom_right!(dest); - let mut src_x = get_x(upper_left!(src)); - let mut src_y = get_y(upper_left!(src)); - - 'y_: for y in get_y(upper_left!(dest))..=get_y(bottom_right!(dest)) { - 'x_: for x in get_x(upper_left!(dest))..=get_x(bottom_right!(dest)) { - if grid_src[(src_x, src_y)].ch() == '\n' { - src_y += 1; - src_x = 0; - if src_y >= get_y(bottom_right!(src)) { - ret.1 = y; - break 'y_; - } - continue 'y_; - } - - grid_dest[(x, y)] = grid_src[(src_x, src_y)]; - src_x += 1; - if src_x >= get_x(bottom_right!(src)) { - src_y += 1; - src_x = 0; - if src_y >= get_y(bottom_right!(src)) { - //clear_area(grid_dest, ((get_x(upper_left!(dest)), y), bottom_right!(dest))); - ret.1 = y; - break 'y_; - } - break 'x_; - } - } - } - ret -} - -/// Copy a source `Area` to a destination. -pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area, src: Area) -> Pos { - if !is_valid_area!(dest) || !is_valid_area!(src) { - eprintln!( - "BUG: Invalid areas in copy_area:\n src: {:?}\n dest: {:?}", - src, dest - ); - return upper_left!(dest); - } - - if grid_src.is_empty() || grid_dest.is_empty() { - return upper_left!(dest); - } - - let mut ret = bottom_right!(dest); - let mut src_x = get_x(upper_left!(src)); - let mut src_y = get_y(upper_left!(src)); - let (cols, rows) = grid_src.size(); - if src_x >= cols || src_y >= rows { - eprintln!("BUG: src area outside of grid_src in copy_area",); - return upper_left!(dest); - } - - for y in get_y(upper_left!(dest))..=get_y(bottom_right!(dest)) { - 'for_x: for x in get_x(upper_left!(dest))..=get_x(bottom_right!(dest)) { - grid_dest[(x, y)] = grid_src[(src_x, src_y)]; - if src_x >= get_x(bottom_right!(src)) { - break 'for_x; - } - src_x += 1; - } - src_x = get_x(upper_left!(src)); - src_y += 1; - if src_y > get_y(bottom_right!(src)) { - clear_area( - grid_dest, - ((get_x(upper_left!(dest)), y + 1), bottom_right!(dest)), - ); - ret.1 = y; - break; - } - } - ret -} - /// Change foreground and background colors in an `Area` pub fn change_colors( grid: &mut CellBuffer, @@ -692,11 +542,9 @@ pub fn change_colors( || y >= get_y(bounds) || x >= get_x(bounds) { - eprintln!("BUG: Invalid area in change_colors:\n area: {:?}", area); return; } if !is_valid_area!(area) { - eprintln!("BUG: Invalid area in change_colors:\n area: {:?}", area); return; } for y in get_y(upper_left!(area))..=get_y(bottom_right!(area)) { @@ -751,7 +599,6 @@ pub fn write_string_to_grid( || y > get_y(bounds) || x > get_x(bounds) { - eprintln!(" Invalid area with string {} and area {:?}", s, area); return (x, y); } for c in s.chars() { diff --git a/src/ui/terminal/keys.rs b/src/ui/terminal/keys.rs index 064caad..ffa5d4b 100644 --- a/src/ui/terminal/keys.rs +++ b/src/ui/terminal/keys.rs @@ -1,27 +1,26 @@ /* - * meli - ui crate. + * bb * - * Copyright 2017-2018 Manos Pitsidianakis + * Copyright 2019 Manos Pitsidianakis * - * This file is part of meli. + * This file is part of bb. * - * meli is free software: you can redistribute it and/or modify + * bb is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * meli is distributed in the hope that it will be useful, + * bb is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with meli. If not, see . + * along with bb. If not, see . */ use crate::crossbeam::*; -use super::*; use std::fmt; use std::io; use termion::event::Event as TermionEvent; @@ -160,7 +159,7 @@ pub fn get_events( for c in stdin.events() { select! { default => {}, - recv(rx) -> val => { + recv(rx) -> _ => { return; } }; @@ -235,5 +234,3 @@ derive_csi_sequence!( pub const BRACKET_PASTE_START: &[u8] = b"\x1B[200~"; pub const BRACKET_PASTE_END: &[u8] = b"\x1B[201~"; - -const FIELDS: &[&str] = &[]; diff --git a/src/ui/terminal/position.rs b/src/ui/terminal/position.rs index d592f7c..b35c6d3 100644 --- a/src/ui/terminal/position.rs +++ b/src/ui/terminal/position.rs @@ -1,22 +1,22 @@ /* - * meli - ui crate. + * bb * - * Copyright 2017-2018 Manos Pitsidianakis + * Copyright 2019 Manos Pitsidianakis * - * This file is part of meli. + * This file is part of bb. * - * meli is free software: you can redistribute it and/or modify + * bb is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * meli is distributed in the hope that it will be useful, + * bb is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with meli. If not, see . + * along with bb. If not, see . */ /*! diff --git a/src/ui/text_processing.rs b/src/ui/text_processing.rs index 59d0312..995aef9 100644 --- a/src/ui/text_processing.rs +++ b/src/ui/text_processing.rs @@ -1,3 +1,24 @@ +/* + * bb + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of bb. + * + * bb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with bb. If not, see . + */ + pub mod grapheme_clusters; pub mod line_break; mod tables; diff --git a/src/ui/text_processing/grapheme_clusters.rs b/src/ui/text_processing/grapheme_clusters.rs index 16e4bee..7c3969a 100644 --- a/src/ui/text_processing/grapheme_clusters.rs +++ b/src/ui/text_processing/grapheme_clusters.rs @@ -1,3 +1,24 @@ +/* + * bb + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of bb. + * + * bb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with bb. If not, see . + */ + /* Breaks a string into individual user-perceived "characters" Unicode UAX-29 standard, version 10.0.0 diff --git a/src/ui/text_processing/line_break.rs b/src/ui/text_processing/line_break.rs index 484df85..4365207 100644 --- a/src/ui/text_processing/line_break.rs +++ b/src/ui/text_processing/line_break.rs @@ -1,3 +1,24 @@ +/* + * bb + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of bb. + * + * bb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with bb. If not, see . + */ + extern crate unicode_segmentation; use self::unicode_segmentation::UnicodeSegmentation; use crate::ui::text_processing::tables::LINE_BREAK_RULES; diff --git a/src/ui/text_processing/tables.rs b/src/ui/text_processing/tables.rs index 55cd06e..9270291 100644 --- a/src/ui/text_processing/tables.rs +++ b/src/ui/text_processing/tables.rs @@ -1,3 +1,24 @@ +/* + * bb + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of bb. + * + * bb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with bb. If not, see . + */ + use crate::ui::text_processing::types::LineBreakClass; use crate::ui::text_processing::types::LineBreakClass::*; diff --git a/src/ui/text_processing/types.rs b/src/ui/text_processing/types.rs index c2d619b..47961ac 100644 --- a/src/ui/text_processing/types.rs +++ b/src/ui/text_processing/types.rs @@ -1,3 +1,24 @@ +/* + * bb + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of bb. + * + * bb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with bb. If not, see . + */ + #[derive(Debug, Copy, Clone, PartialEq)] pub enum LineBreakClass { BK, diff --git a/src/ui/text_processing/wcwidth.rs b/src/ui/text_processing/wcwidth.rs index 73841e3..6145710 100644 --- a/src/ui/text_processing/wcwidth.rs +++ b/src/ui/text_processing/wcwidth.rs @@ -1,3 +1,24 @@ +/* + * bb + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of bb. + * + * bb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * bb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with bb. If not, see . + */ + /* * This is an implementation of wcwidth() and wcswidth() as defined in * "The Single UNIX Specification, Version 2, The Open Group, 1997" diff --git a/src/ui/types.rs b/src/ui/types.rs index d032ae9..4c29580 100644 --- a/src/ui/types.rs +++ b/src/ui/types.rs @@ -1,61 +1,34 @@ /* - * meli - ui crate. + * bb * - * Copyright 2017-2018 Manos Pitsidianakis + * Copyright 2019 Manos Pitsidianakis * - * This file is part of meli. + * This file is part of bb. * - * meli is free software: you can redistribute it and/or modify + * bb is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * - * meli is distributed in the hope that it will be useful, + * bb is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with meli. If not, see . + * along with bb. If not, see . */ use crate::ui::Key; -use std; -use std::fmt; -use std::thread; - -#[derive(Debug)] -pub enum StatusEvent { - DisplayMessage(String), - BufClear, - BufSet(String), -} /// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads /// to the main process. #[derive(Debug)] pub enum ThreadEvent { Input(Key), - UIEvent(UIEvent), } #[derive(Debug)] pub enum UIEvent { Input(Key), Resize, } - -pub enum UIMode { - Normal, -} - -impl fmt::Display for UIMode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - match *self { - UIMode::Normal => "NORMAL", - } - ) - } -}