mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-10 14:44:18 +00:00
refactor: Refactor code, add new tests
Refactor code so we use a lib, allowing for easier testing. Adds additional tests for layouts.
This commit is contained in:
parent
f6aa8e5d1d
commit
4b03b4b0b0
7 changed files with 689 additions and 199 deletions
|
@ -38,9 +38,11 @@ If you want to help contribute by submitting a PR, by all means, I'm open! In re
|
|||
- I develop primarily using _stable_ Rust. That is, whatever is the most up-to-date stable version you can get via running
|
||||
`rustup update stable`.
|
||||
|
||||
- There are some tests, they're mostly for sanity checks. Please run `cargo test` to ensure you didn't break anything important, unless the change will break the test (in which case please amend the tests).
|
||||
|
||||
- Note that `cargo test` will fail on anything lower than 1.43.0 due to it using a then-introduced env variable.
|
||||
|
||||
- I use both [clippy](https://github.com/rust-lang/rust-clippy) and [rustfmt](https://github.com/rust-lang/rustfmt) in development (with some settings, see [clippy.toml](./clippy.toml) and [rustfmt.toml](rustfmt.toml)). Note clippy must pass to pass CI.
|
||||
- I use both [clippy](https://github.com/rust-lang/rust-clippy) and [rustfmt](https://github.com/rust-lang/rustfmt) in development (with some settings, see [clippy.toml](./clippy.toml) and [rustfmt.toml](rustfmt.toml)). Note clippy must pass to for PRs to be accepted.
|
||||
|
||||
- You can check clippy using `cargo +nightly clippy`.
|
||||
|
||||
|
|
|
@ -9,10 +9,12 @@ license = "MIT"
|
|||
categories = ["command-line-utilities", "visualization"]
|
||||
description = "A cross-platform graphical process/system monitor with a customizable interface and a multitude of features. Supports Linux, macOS, and Windows."
|
||||
readme = "README.md"
|
||||
default-run = "btm"
|
||||
|
||||
[[bin]]
|
||||
name = "btm"
|
||||
path = "src/main.rs"
|
||||
path = "src/bin/main.rs"
|
||||
doc = false
|
||||
|
||||
[profile.release]
|
||||
debug = 1
|
||||
|
|
185
src/bin/main.rs
Normal file
185
src/bin/main.rs
Normal file
|
@ -0,0 +1,185 @@
|
|||
#![warn(rust_2018_idioms)]
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use bottom::{canvas, constants::*, data_conversion::*, options::*, utils::error, *};
|
||||
|
||||
use std::{
|
||||
boxed::Box,
|
||||
io::{stdout, Write},
|
||||
panic,
|
||||
sync::mpsc,
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
event::EnableMouseCapture,
|
||||
execute,
|
||||
terminal::{enable_raw_mode, EnterAlternateScreen},
|
||||
};
|
||||
use tui::{backend::CrosstermBackend, Terminal};
|
||||
|
||||
fn main() -> error::Result<()> {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
utils::logging::init_logger()?;
|
||||
}
|
||||
let matches = get_matches();
|
||||
|
||||
let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?;
|
||||
|
||||
// Get widget layout separately
|
||||
let (widget_layout, default_widget_id, default_widget_type_option) =
|
||||
get_widget_layout(&matches, &config)?;
|
||||
|
||||
// Create "app" struct, which will control most of the program and store settings/state
|
||||
let mut app = build_app(
|
||||
&matches,
|
||||
&config,
|
||||
&widget_layout,
|
||||
default_widget_id,
|
||||
&default_widget_type_option,
|
||||
)?;
|
||||
|
||||
// Create painter and set colours.
|
||||
let mut painter = canvas::Painter::init(widget_layout, app.app_config_fields.table_gap);
|
||||
generate_config_colours(&config, &mut painter)?;
|
||||
painter.colours.generate_remaining_cpu_colours();
|
||||
painter.complete_painter_init();
|
||||
|
||||
// Set up input handling
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
create_input_thread(sender.clone());
|
||||
|
||||
// Cleaning loop
|
||||
{
|
||||
let cleaning_sender = sender.clone();
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_millis(
|
||||
constants::STALE_MAX_MILLISECONDS + 5000,
|
||||
));
|
||||
if cleaning_sender.send(BottomEvent::Clean).is_err() {
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Event loop
|
||||
let (reset_sender, reset_receiver) = mpsc::channel();
|
||||
create_event_thread(
|
||||
sender,
|
||||
reset_receiver,
|
||||
app.app_config_fields.use_current_cpu_total,
|
||||
app.app_config_fields.update_rate_in_milliseconds,
|
||||
app.app_config_fields.temperature_type.clone(),
|
||||
app.app_config_fields.show_average_cpu,
|
||||
app.used_widgets.clone(),
|
||||
);
|
||||
|
||||
// Set up up tui and crossterm
|
||||
let mut stdout_val = stdout();
|
||||
execute!(stdout_val, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
enable_raw_mode()?;
|
||||
|
||||
let mut terminal = Terminal::new(CrosstermBackend::new(stdout_val))?;
|
||||
terminal.hide_cursor()?;
|
||||
|
||||
// Set panic hook
|
||||
panic::set_hook(Box::new(|info| panic_hook(info)));
|
||||
|
||||
loop {
|
||||
if let Ok(recv) = receiver.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
|
||||
match recv {
|
||||
BottomEvent::KeyInput(event) => {
|
||||
if handle_key_event_or_break(event, &mut app, &reset_sender) {
|
||||
break;
|
||||
}
|
||||
handle_force_redraws(&mut app);
|
||||
}
|
||||
BottomEvent::MouseInput(event) => {
|
||||
handle_mouse_event(event, &mut app);
|
||||
handle_force_redraws(&mut app);
|
||||
}
|
||||
BottomEvent::Update(data) => {
|
||||
app.data_collection.eat_data(&data);
|
||||
|
||||
if !app.is_frozen {
|
||||
// Convert all data into tui-compliant components
|
||||
|
||||
// Network
|
||||
if app.used_widgets.use_net {
|
||||
let network_data = convert_network_data_points(
|
||||
&app.data_collection,
|
||||
false,
|
||||
app.app_config_fields.use_basic_mode
|
||||
|| app.app_config_fields.use_old_network_legend,
|
||||
);
|
||||
app.canvas_data.network_data_rx = network_data.rx;
|
||||
app.canvas_data.network_data_tx = network_data.tx;
|
||||
app.canvas_data.rx_display = network_data.rx_display;
|
||||
app.canvas_data.tx_display = network_data.tx_display;
|
||||
if let Some(total_rx_display) = network_data.total_rx_display {
|
||||
app.canvas_data.total_rx_display = total_rx_display;
|
||||
}
|
||||
if let Some(total_tx_display) = network_data.total_tx_display {
|
||||
app.canvas_data.total_tx_display = total_tx_display;
|
||||
}
|
||||
}
|
||||
|
||||
// Disk
|
||||
if app.used_widgets.use_disk {
|
||||
app.canvas_data.disk_data = convert_disk_row(&app.data_collection);
|
||||
}
|
||||
|
||||
// Temperatures
|
||||
if app.used_widgets.use_temp {
|
||||
app.canvas_data.temp_sensor_data = convert_temp_row(&app);
|
||||
}
|
||||
|
||||
// Memory
|
||||
if app.used_widgets.use_mem {
|
||||
app.canvas_data.mem_data =
|
||||
convert_mem_data_points(&app.data_collection, false);
|
||||
app.canvas_data.swap_data =
|
||||
convert_swap_data_points(&app.data_collection, false);
|
||||
let memory_and_swap_labels = convert_mem_labels(&app.data_collection);
|
||||
app.canvas_data.mem_label_percent = memory_and_swap_labels.0;
|
||||
app.canvas_data.mem_label_frac = memory_and_swap_labels.1;
|
||||
app.canvas_data.swap_label_percent = memory_and_swap_labels.2;
|
||||
app.canvas_data.swap_label_frac = memory_and_swap_labels.3;
|
||||
}
|
||||
|
||||
if app.used_widgets.use_cpu {
|
||||
// CPU
|
||||
app.canvas_data.cpu_data =
|
||||
convert_cpu_data_points(&app.data_collection, false);
|
||||
}
|
||||
|
||||
// Processes
|
||||
if app.used_widgets.use_proc {
|
||||
update_all_process_lists(&mut app);
|
||||
}
|
||||
|
||||
// Battery
|
||||
if app.used_widgets.use_battery {
|
||||
app.canvas_data.battery_data =
|
||||
convert_battery_harvest(&app.data_collection);
|
||||
}
|
||||
}
|
||||
}
|
||||
BottomEvent::Clean => {
|
||||
app.data_collection
|
||||
.clean_data(constants::STALE_MAX_MILLISECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: [OPT] Should not draw if no change (ie: scroll max)
|
||||
try_drawing(&mut terminal, &mut app, &mut painter)?;
|
||||
}
|
||||
|
||||
cleanup_terminal(&mut terminal)?;
|
||||
Ok(())
|
||||
}
|
|
@ -1,14 +1,7 @@
|
|||
#![warn(rust_2018_idioms)]
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::{
|
||||
boxed::Box,
|
||||
io::{stdout, Write},
|
||||
panic::{self, PanicInfo},
|
||||
sync::mpsc,
|
||||
panic::PanicInfo,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
@ -16,15 +9,11 @@ use std::{
|
|||
use clap::*;
|
||||
|
||||
use crossterm::{
|
||||
event::{
|
||||
poll, read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent,
|
||||
KeyModifiers, MouseEvent,
|
||||
},
|
||||
event::{poll, read, DisableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent},
|
||||
execute,
|
||||
style::Print,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
terminal::{disable_raw_mode, LeaveAlternateScreen},
|
||||
};
|
||||
use tui::{backend::CrosstermBackend, Terminal};
|
||||
|
||||
use app::{
|
||||
data_harvester::{self, processes::ProcessSorting},
|
||||
|
@ -38,30 +27,29 @@ use utils::error;
|
|||
|
||||
pub mod app;
|
||||
|
||||
mod utils {
|
||||
pub mod utils {
|
||||
pub mod error;
|
||||
pub mod gen_util;
|
||||
pub mod logging;
|
||||
}
|
||||
|
||||
mod canvas;
|
||||
mod constants;
|
||||
mod data_conversion;
|
||||
|
||||
pub mod canvas;
|
||||
pub mod constants;
|
||||
pub mod data_conversion;
|
||||
pub mod options;
|
||||
|
||||
enum BottomEvent<I, J> {
|
||||
pub enum BottomEvent<I, J> {
|
||||
KeyInput(I),
|
||||
MouseInput(J),
|
||||
Update(Box<data_harvester::Data>),
|
||||
Clean,
|
||||
}
|
||||
|
||||
enum ResetEvent {
|
||||
pub enum ResetEvent {
|
||||
Reset,
|
||||
}
|
||||
|
||||
fn get_matches() -> clap::ArgMatches<'static> {
|
||||
pub fn get_matches() -> clap::ArgMatches<'static> {
|
||||
clap_app!(app =>
|
||||
(name: crate_name!())
|
||||
(version: crate_version!())
|
||||
|
@ -96,163 +84,7 @@ fn get_matches() -> clap::ArgMatches<'static> {
|
|||
.get_matches()
|
||||
}
|
||||
|
||||
fn main() -> error::Result<()> {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
utils::logging::init_logger()?;
|
||||
}
|
||||
let matches = get_matches();
|
||||
|
||||
let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?;
|
||||
|
||||
// Get widget layout separately
|
||||
let (widget_layout, default_widget_id) = get_widget_layout(&matches, &config)?;
|
||||
|
||||
// Create "app" struct, which will control most of the program and store settings/state
|
||||
let mut app = build_app(&matches, &config, &widget_layout, default_widget_id)?;
|
||||
|
||||
// Create painter and set colours.
|
||||
let mut painter = canvas::Painter::init(widget_layout, app.app_config_fields.table_gap);
|
||||
generate_config_colours(&config, &mut painter)?;
|
||||
painter.colours.generate_remaining_cpu_colours();
|
||||
painter.complete_painter_init();
|
||||
|
||||
// Set up input handling
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
create_input_thread(sender.clone());
|
||||
|
||||
// Cleaning loop
|
||||
{
|
||||
let cleaning_sender = sender.clone();
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_millis(
|
||||
constants::STALE_MAX_MILLISECONDS + 5000,
|
||||
));
|
||||
if cleaning_sender.send(BottomEvent::Clean).is_err() {
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Event loop
|
||||
let (reset_sender, reset_receiver) = mpsc::channel();
|
||||
create_event_thread(
|
||||
sender,
|
||||
reset_receiver,
|
||||
app.app_config_fields.use_current_cpu_total,
|
||||
app.app_config_fields.update_rate_in_milliseconds,
|
||||
app.app_config_fields.temperature_type.clone(),
|
||||
app.app_config_fields.show_average_cpu,
|
||||
app.used_widgets.clone(),
|
||||
);
|
||||
|
||||
// Set up up tui and crossterm
|
||||
let mut stdout_val = stdout();
|
||||
execute!(stdout_val, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
enable_raw_mode()?;
|
||||
|
||||
let mut terminal = Terminal::new(CrosstermBackend::new(stdout_val))?;
|
||||
terminal.hide_cursor()?;
|
||||
|
||||
// Set panic hook
|
||||
panic::set_hook(Box::new(|info| panic_hook(info)));
|
||||
|
||||
loop {
|
||||
if let Ok(recv) = receiver.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
|
||||
match recv {
|
||||
BottomEvent::KeyInput(event) => {
|
||||
if handle_key_event_or_break(event, &mut app, &reset_sender) {
|
||||
break;
|
||||
}
|
||||
handle_force_redraws(&mut app);
|
||||
}
|
||||
BottomEvent::MouseInput(event) => {
|
||||
handle_mouse_event(event, &mut app);
|
||||
handle_force_redraws(&mut app);
|
||||
}
|
||||
BottomEvent::Update(data) => {
|
||||
app.data_collection.eat_data(&data);
|
||||
|
||||
if !app.is_frozen {
|
||||
// Convert all data into tui-compliant components
|
||||
|
||||
// Network
|
||||
if app.used_widgets.use_net {
|
||||
let network_data = convert_network_data_points(
|
||||
&app.data_collection,
|
||||
false,
|
||||
app.app_config_fields.use_basic_mode
|
||||
|| app.app_config_fields.use_old_network_legend,
|
||||
);
|
||||
app.canvas_data.network_data_rx = network_data.rx;
|
||||
app.canvas_data.network_data_tx = network_data.tx;
|
||||
app.canvas_data.rx_display = network_data.rx_display;
|
||||
app.canvas_data.tx_display = network_data.tx_display;
|
||||
if let Some(total_rx_display) = network_data.total_rx_display {
|
||||
app.canvas_data.total_rx_display = total_rx_display;
|
||||
}
|
||||
if let Some(total_tx_display) = network_data.total_tx_display {
|
||||
app.canvas_data.total_tx_display = total_tx_display;
|
||||
}
|
||||
}
|
||||
|
||||
// Disk
|
||||
if app.used_widgets.use_disk {
|
||||
app.canvas_data.disk_data = convert_disk_row(&app.data_collection);
|
||||
}
|
||||
|
||||
// Temperatures
|
||||
if app.used_widgets.use_temp {
|
||||
app.canvas_data.temp_sensor_data = convert_temp_row(&app);
|
||||
}
|
||||
|
||||
// Memory
|
||||
if app.used_widgets.use_mem {
|
||||
app.canvas_data.mem_data =
|
||||
convert_mem_data_points(&app.data_collection, false);
|
||||
app.canvas_data.swap_data =
|
||||
convert_swap_data_points(&app.data_collection, false);
|
||||
let memory_and_swap_labels = convert_mem_labels(&app.data_collection);
|
||||
app.canvas_data.mem_label_percent = memory_and_swap_labels.0;
|
||||
app.canvas_data.mem_label_frac = memory_and_swap_labels.1;
|
||||
app.canvas_data.swap_label_percent = memory_and_swap_labels.2;
|
||||
app.canvas_data.swap_label_frac = memory_and_swap_labels.3;
|
||||
}
|
||||
|
||||
if app.used_widgets.use_cpu {
|
||||
// CPU
|
||||
app.canvas_data.cpu_data =
|
||||
convert_cpu_data_points(&app.data_collection, false);
|
||||
}
|
||||
|
||||
// Processes
|
||||
if app.used_widgets.use_proc {
|
||||
update_all_process_lists(&mut app);
|
||||
}
|
||||
|
||||
// Battery
|
||||
if app.used_widgets.use_battery {
|
||||
app.canvas_data.battery_data =
|
||||
convert_battery_harvest(&app.data_collection);
|
||||
}
|
||||
}
|
||||
}
|
||||
BottomEvent::Clean => {
|
||||
app.data_collection
|
||||
.clean_data(constants::STALE_MAX_MILLISECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: [OPT] Should not draw if no change (ie: scroll max)
|
||||
try_drawing(&mut terminal, &mut app, &mut painter)?;
|
||||
}
|
||||
|
||||
cleanup_terminal(&mut terminal)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_mouse_event(event: MouseEvent, app: &mut App) {
|
||||
pub fn handle_mouse_event(event: MouseEvent, app: &mut App) {
|
||||
match event {
|
||||
MouseEvent::ScrollUp(_x, _y, _modifiers) => app.handle_scroll_up(),
|
||||
MouseEvent::ScrollDown(_x, _y, _modifiers) => app.handle_scroll_down(),
|
||||
|
@ -260,7 +92,7 @@ fn handle_mouse_event(event: MouseEvent, app: &mut App) {
|
|||
};
|
||||
}
|
||||
|
||||
fn handle_key_event_or_break(
|
||||
pub fn handle_key_event_or_break(
|
||||
event: KeyEvent, app: &mut App, reset_sender: &std::sync::mpsc::Sender<ResetEvent>,
|
||||
) -> bool {
|
||||
// debug!("KeyEvent: {:?}", event);
|
||||
|
@ -348,7 +180,7 @@ fn handle_key_event_or_break(
|
|||
false
|
||||
}
|
||||
|
||||
fn create_config(flag_config_location: Option<&str>) -> error::Result<Config> {
|
||||
pub fn create_config(flag_config_location: Option<&str>) -> error::Result<Config> {
|
||||
use std::{ffi::OsString, fs};
|
||||
let config_path = if let Some(conf_loc) = flag_config_location {
|
||||
Some(OsString::from(conf_loc))
|
||||
|
@ -399,7 +231,7 @@ fn create_config(flag_config_location: Option<&str>) -> error::Result<Config> {
|
|||
}
|
||||
}
|
||||
|
||||
fn try_drawing(
|
||||
pub fn try_drawing(
|
||||
terminal: &mut tui::terminal::Terminal<tui::backend::CrosstermBackend<std::io::Stdout>>,
|
||||
app: &mut App, painter: &mut canvas::Painter,
|
||||
) -> error::Result<()> {
|
||||
|
@ -411,7 +243,7 @@ fn try_drawing(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn cleanup_terminal(
|
||||
pub fn cleanup_terminal(
|
||||
terminal: &mut tui::terminal::Terminal<tui::backend::CrosstermBackend<std::io::Stdout>>,
|
||||
) -> error::Result<()> {
|
||||
disable_raw_mode()?;
|
||||
|
@ -425,7 +257,9 @@ fn cleanup_terminal(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_config_colours(config: &Config, painter: &mut canvas::Painter) -> error::Result<()> {
|
||||
pub fn generate_config_colours(
|
||||
config: &Config, painter: &mut canvas::Painter,
|
||||
) -> error::Result<()> {
|
||||
if let Some(colours) = &config.colors {
|
||||
if let Some(border_color) = &colours.border_color {
|
||||
painter.colours.set_border_colour(border_color)?;
|
||||
|
@ -514,7 +348,7 @@ fn generate_config_colours(config: &Config, painter: &mut canvas::Painter) -> er
|
|||
}
|
||||
|
||||
/// Based on https://github.com/Rigellute/spotify-tui/blob/master/src/main.rs
|
||||
fn panic_hook(panic_info: &PanicInfo<'_>) {
|
||||
pub fn panic_hook(panic_info: &PanicInfo<'_>) {
|
||||
let mut stdout = stdout();
|
||||
|
||||
let msg = match panic_info.payload().downcast_ref::<&'static str>() {
|
||||
|
@ -543,7 +377,7 @@ fn panic_hook(panic_info: &PanicInfo<'_>) {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
fn handle_force_redraws(app: &mut App) {
|
||||
pub fn handle_force_redraws(app: &mut App) {
|
||||
// Currently we use an Option... because we might want to future-proof this
|
||||
// if we eventually get widget-specific redrawing!
|
||||
if app.proc_state.force_update_all {
|
||||
|
@ -573,7 +407,7 @@ fn handle_force_redraws(app: &mut App) {
|
|||
}
|
||||
}
|
||||
|
||||
fn update_all_process_lists(app: &mut App) {
|
||||
pub fn update_all_process_lists(app: &mut App) {
|
||||
let widget_ids = app
|
||||
.proc_state
|
||||
.widget_states
|
||||
|
@ -586,7 +420,7 @@ fn update_all_process_lists(app: &mut App) {
|
|||
});
|
||||
}
|
||||
|
||||
fn update_final_process_list(app: &mut App, widget_id: u64) {
|
||||
pub fn update_final_process_list(app: &mut App, widget_id: u64) {
|
||||
let is_invalid_or_blank = match app.proc_state.widget_states.get(&widget_id) {
|
||||
Some(process_state) => process_state
|
||||
.process_search_state
|
||||
|
@ -659,7 +493,7 @@ fn update_final_process_list(app: &mut App, widget_id: u64) {
|
|||
}
|
||||
}
|
||||
|
||||
fn sort_process_data(
|
||||
pub fn sort_process_data(
|
||||
to_sort_vec: &mut Vec<ConvertedProcessData>, proc_widget_state: &app::ProcWidgetState,
|
||||
) {
|
||||
to_sort_vec.sort_by(|a, b| {
|
||||
|
@ -763,7 +597,7 @@ fn sort_process_data(
|
|||
}
|
||||
}
|
||||
|
||||
fn create_input_thread(
|
||||
pub fn create_input_thread(
|
||||
sender: std::sync::mpsc::Sender<
|
||||
BottomEvent<crossterm::event::KeyEvent, crossterm::event::MouseEvent>,
|
||||
>,
|
||||
|
@ -796,7 +630,7 @@ fn create_input_thread(
|
|||
});
|
||||
}
|
||||
|
||||
fn create_event_thread(
|
||||
pub fn create_event_thread(
|
||||
sender: std::sync::mpsc::Sender<
|
||||
BottomEvent<crossterm::event::KeyEvent, crossterm::event::MouseEvent>,
|
||||
>,
|
|
@ -10,7 +10,7 @@ use crate::{
|
|||
|
||||
use layout_options::*;
|
||||
|
||||
mod layout_options;
|
||||
pub mod layout_options;
|
||||
|
||||
#[derive(Default, Deserialize)]
|
||||
pub struct Config {
|
||||
|
@ -68,7 +68,7 @@ pub struct ConfigColours {
|
|||
|
||||
pub fn build_app(
|
||||
matches: &clap::ArgMatches<'static>, config: &Config, widget_layout: &BottomLayout,
|
||||
default_widget_id: u64,
|
||||
default_widget_id: u64, default_widget_type_option: &Option<BottomWidgetType>,
|
||||
) -> error::Result<App> {
|
||||
use BottomWidgetType::*;
|
||||
let autohide_time = get_autohide_time(&matches, &config);
|
||||
|
@ -96,7 +96,6 @@ pub fn build_app(
|
|||
None
|
||||
};
|
||||
|
||||
let (default_widget_type_option, _) = get_default_widget_and_count(matches, config)?;
|
||||
let mut initial_widget_id: u64 = default_widget_id;
|
||||
let mut initial_widget_type = Proc;
|
||||
let is_custom_layout = config.row.is_some();
|
||||
|
@ -252,7 +251,7 @@ pub fn build_app(
|
|||
|
||||
pub fn get_widget_layout(
|
||||
matches: &clap::ArgMatches<'static>, config: &Config,
|
||||
) -> error::Result<(BottomLayout, u64)> {
|
||||
) -> error::Result<(BottomLayout, u64, Option<BottomWidgetType>)> {
|
||||
let left_legend = get_use_left_legend(matches, config);
|
||||
let (default_widget_type, mut default_widget_count) =
|
||||
get_default_widget_and_count(matches, config)?;
|
||||
|
@ -311,7 +310,7 @@ pub fn get_widget_layout(
|
|||
}
|
||||
};
|
||||
|
||||
Ok((bottom_layout, default_widget_id))
|
||||
Ok((bottom_layout, default_widget_id, default_widget_type))
|
||||
}
|
||||
|
||||
fn get_update_rate_in_milliseconds(
|
||||
|
|
469
tests/layout_management_tests.rs
Normal file
469
tests/layout_management_tests.rs
Normal file
|
@ -0,0 +1,469 @@
|
|||
//! Mocks layout management, so we can check if we broke anything.
|
||||
|
||||
use bottom::app::layout_manager::{BottomLayout, BottomWidgetType};
|
||||
use bottom::constants::{DEFAULT_BATTERY_LAYOUT, DEFAULT_LAYOUT, DEFAULT_WIDGET_ID};
|
||||
use bottom::options::{layout_options::Row, Config};
|
||||
use bottom::utils::error;
|
||||
|
||||
const PROC_LAYOUT: &str = r##"
|
||||
[[row]]
|
||||
[[row.child]]
|
||||
type="proc"
|
||||
[[row]]
|
||||
[[row.child]]
|
||||
type="proc"
|
||||
[[row.child]]
|
||||
type="proc"
|
||||
[[row]]
|
||||
[[row.child]]
|
||||
type="proc"
|
||||
[[row.child]]
|
||||
type="proc"
|
||||
"##;
|
||||
|
||||
fn test_create_layout(
|
||||
rows: &Vec<Row>, default_widget_id: u64, default_widget_type: Option<BottomWidgetType>,
|
||||
default_widget_count: u64, left_legend: bool,
|
||||
) -> BottomLayout {
|
||||
let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
|
||||
let mut total_height_ratio = 0;
|
||||
let mut default_widget_count = default_widget_count;
|
||||
let mut default_widget_id = default_widget_id;
|
||||
|
||||
let mut ret_bottom_layout = BottomLayout {
|
||||
rows: rows
|
||||
.iter()
|
||||
.map(|row| {
|
||||
row.convert_row_to_bottom_row(
|
||||
&mut iter_id,
|
||||
&mut total_height_ratio,
|
||||
&mut default_widget_id,
|
||||
&default_widget_type,
|
||||
&mut default_widget_count,
|
||||
left_legend,
|
||||
)
|
||||
})
|
||||
.collect::<error::Result<Vec<_>>>()
|
||||
.unwrap(),
|
||||
total_row_height_ratio: total_height_ratio,
|
||||
};
|
||||
ret_bottom_layout.get_movement_mappings();
|
||||
|
||||
ret_bottom_layout
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Tests the default setup.
|
||||
fn test_default_movement() {
|
||||
let rows = toml::from_str::<Config>(DEFAULT_LAYOUT)
|
||||
.unwrap()
|
||||
.row
|
||||
.unwrap();
|
||||
let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false);
|
||||
|
||||
// Simple tests for the top CPU widget
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[0].down_neighbour,
|
||||
Some(3)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[0].right_neighbour,
|
||||
Some(2)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[0].left_neighbour,
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[0].up_neighbour,
|
||||
None
|
||||
);
|
||||
|
||||
// Test CPU legend
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[1].down_neighbour,
|
||||
Some(4)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[1].right_neighbour,
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[1].left_neighbour,
|
||||
Some(1)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[1].up_neighbour,
|
||||
None
|
||||
);
|
||||
|
||||
// Test memory->temp, temp->disk, disk->memory mappings
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[1].children[0].children[0].children[0].right_neighbour,
|
||||
Some(4)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[1].children[1].children[0].children[0].down_neighbour,
|
||||
Some(5)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[1].children[1].children[1].children[0].left_neighbour,
|
||||
Some(3)
|
||||
);
|
||||
|
||||
// Test disk -> processes, processes -> process sort, process sort -> network
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[1].children[1].children[1].children[0].down_neighbour,
|
||||
Some(7)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[2].children[1].children[0].children[1].left_neighbour,
|
||||
Some(9)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[2].children[1].children[0].children[0].left_neighbour,
|
||||
Some(6)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Tests battery movement in the default setup.
|
||||
fn test_default_battery_movement() {
|
||||
let rows = toml::from_str::<Config>(DEFAULT_BATTERY_LAYOUT)
|
||||
.unwrap()
|
||||
.row
|
||||
.unwrap();
|
||||
let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false);
|
||||
|
||||
// Simple tests for the top CPU widget
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[0].down_neighbour,
|
||||
Some(4)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[0].right_neighbour,
|
||||
Some(2)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[0].left_neighbour,
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[0].up_neighbour,
|
||||
None
|
||||
);
|
||||
|
||||
// Test CPU legend
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[1].down_neighbour,
|
||||
Some(5)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[1].right_neighbour,
|
||||
Some(3)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[1].left_neighbour,
|
||||
Some(1)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[1].up_neighbour,
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Tests using left_legend.
|
||||
fn test_left_legend() {
|
||||
let rows = toml::from_str::<Config>(DEFAULT_LAYOUT)
|
||||
.unwrap()
|
||||
.row
|
||||
.unwrap();
|
||||
let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, true);
|
||||
|
||||
// Legend
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[0].down_neighbour,
|
||||
Some(3)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[0].right_neighbour,
|
||||
Some(1)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[0].left_neighbour,
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[0].up_neighbour,
|
||||
None
|
||||
);
|
||||
|
||||
// Widget
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[1].down_neighbour,
|
||||
Some(3)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[1].right_neighbour,
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[1].left_neighbour,
|
||||
Some(2)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[1].up_neighbour,
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Tests explicit default widget.
|
||||
fn test_default_widget_in_layout() {
|
||||
let proc_layout = r##"
|
||||
[[row]]
|
||||
[[row.child]]
|
||||
type="proc"
|
||||
[[row]]
|
||||
[[row.child]]
|
||||
type="proc"
|
||||
[[row.child]]
|
||||
type="proc"
|
||||
[[row]]
|
||||
[[row.child]]
|
||||
type="proc"
|
||||
default=true
|
||||
[[row.child]]
|
||||
type="proc"
|
||||
"##;
|
||||
let rows = toml::from_str::<Config>(proc_layout).unwrap().row.unwrap();
|
||||
let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
|
||||
let mut total_height_ratio = 0;
|
||||
let mut default_widget_count = 1;
|
||||
let mut default_widget_id = DEFAULT_WIDGET_ID;
|
||||
let default_widget_type = None;
|
||||
let left_legend = false;
|
||||
|
||||
let mut ret_bottom_layout = BottomLayout {
|
||||
rows: rows
|
||||
.iter()
|
||||
.map(|row| {
|
||||
row.convert_row_to_bottom_row(
|
||||
&mut iter_id,
|
||||
&mut total_height_ratio,
|
||||
&mut default_widget_id,
|
||||
&default_widget_type,
|
||||
&mut default_widget_count,
|
||||
left_legend,
|
||||
)
|
||||
})
|
||||
.collect::<error::Result<Vec<_>>>()
|
||||
.unwrap(),
|
||||
total_row_height_ratio: total_height_ratio,
|
||||
};
|
||||
ret_bottom_layout.get_movement_mappings();
|
||||
|
||||
assert_eq!(default_widget_id, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Tests default widget by setting type and count.
|
||||
fn test_default_widget_by_option() {
|
||||
let rows = toml::from_str::<Config>(PROC_LAYOUT).unwrap().row.unwrap();
|
||||
let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
|
||||
let mut total_height_ratio = 0;
|
||||
let mut default_widget_count = 3;
|
||||
let mut default_widget_id = DEFAULT_WIDGET_ID;
|
||||
let default_widget_type = Some(BottomWidgetType::Proc);
|
||||
let left_legend = false;
|
||||
|
||||
let mut ret_bottom_layout = BottomLayout {
|
||||
rows: rows
|
||||
.iter()
|
||||
.map(|row| {
|
||||
row.convert_row_to_bottom_row(
|
||||
&mut iter_id,
|
||||
&mut total_height_ratio,
|
||||
&mut default_widget_id,
|
||||
&default_widget_type,
|
||||
&mut default_widget_count,
|
||||
left_legend,
|
||||
)
|
||||
})
|
||||
.collect::<error::Result<Vec<_>>>()
|
||||
.unwrap(),
|
||||
total_row_height_ratio: total_height_ratio,
|
||||
};
|
||||
ret_bottom_layout.get_movement_mappings();
|
||||
|
||||
assert_eq!(default_widget_id, 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_proc_custom_layout() {
|
||||
let rows = toml::from_str::<Config>(PROC_LAYOUT).unwrap().row.unwrap();
|
||||
let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false);
|
||||
|
||||
// First proc widget
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[1].down_neighbour,
|
||||
Some(2)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[1].left_neighbour,
|
||||
Some(3)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[1].right_neighbour,
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[1].up_neighbour,
|
||||
None
|
||||
);
|
||||
|
||||
// Its search
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[1].children[0].down_neighbour,
|
||||
Some(4)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[1].children[0].left_neighbour,
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[1].children[0].right_neighbour,
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[1].children[0].up_neighbour,
|
||||
Some(1)
|
||||
);
|
||||
|
||||
// Its sort
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[0].down_neighbour,
|
||||
Some(2)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[0].left_neighbour,
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[0].right_neighbour,
|
||||
Some(1)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[0].children[0].children[0].children[0].up_neighbour,
|
||||
None
|
||||
);
|
||||
|
||||
// Let us now test the second row's first widget...
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[1].children[0].children[0].children[1].down_neighbour,
|
||||
Some(5)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[1].children[0].children[0].children[1].left_neighbour,
|
||||
Some(6)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[1].children[0].children[0].children[1].right_neighbour,
|
||||
Some(9)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[1].children[0].children[0].children[1].up_neighbour,
|
||||
Some(2)
|
||||
);
|
||||
|
||||
// Sort
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[1].children[0].children[0].children[0].down_neighbour,
|
||||
Some(5)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[1].children[0].children[0].children[0].left_neighbour,
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[1].children[0].children[0].children[0].right_neighbour,
|
||||
Some(4)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[1].children[0].children[0].children[0].up_neighbour,
|
||||
Some(2)
|
||||
);
|
||||
|
||||
// Search
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[1].children[0].children[1].children[0].down_neighbour,
|
||||
Some(10)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[1].children[0].children[1].children[0].left_neighbour,
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[1].children[0].children[1].children[0].right_neighbour,
|
||||
Some(8)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[1].children[0].children[1].children[0].up_neighbour,
|
||||
Some(4)
|
||||
);
|
||||
|
||||
// Third row, second
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[2].children[1].children[0].children[1].down_neighbour,
|
||||
Some(14)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[2].children[1].children[0].children[1].left_neighbour,
|
||||
Some(15)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[2].children[1].children[0].children[1].right_neighbour,
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[2].children[1].children[0].children[1].up_neighbour,
|
||||
Some(8)
|
||||
);
|
||||
|
||||
// Sort
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[2].children[1].children[0].children[0].down_neighbour,
|
||||
Some(14)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[2].children[1].children[0].children[0].left_neighbour,
|
||||
Some(10)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[2].children[1].children[0].children[0].right_neighbour,
|
||||
Some(13)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[2].children[1].children[0].children[0].up_neighbour,
|
||||
Some(8)
|
||||
);
|
||||
|
||||
// Search
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[2].children[1].children[1].children[0].down_neighbour,
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[2].children[1].children[1].children[0].left_neighbour,
|
||||
Some(11)
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[2].children[1].children[1].children[0].right_neighbour,
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
ret_bottom_layout.rows[2].children[1].children[1].children[0].up_neighbour,
|
||||
Some(13)
|
||||
);
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
// TODO: See if we can mock widget movements and test if they work
|
Loading…
Reference in a new issue