mirror of
https://github.com/ClementTsang/bottom
synced 2024-12-18 00:23:15 +00:00
Merge branch 'master' into populate-config
This commit is contained in:
commit
735038f060
19 changed files with 233 additions and 131 deletions
4
.github/pull_request_template.md
vendored
4
.github/pull_request_template.md
vendored
|
@ -14,6 +14,7 @@ _Remove the irrelevant one:_
|
|||
|
||||
- [x] Bug fix (non-breaking change which fixes an issue)
|
||||
- [x] New feature (non-breaking change which adds functionality)
|
||||
- [x] Other (something else)
|
||||
|
||||
## Test methodology
|
||||
|
||||
|
@ -24,10 +25,11 @@ _Please state how this was tested:_
|
|||
_Please ensure all are ticked (and actually done):_
|
||||
|
||||
- [ ] Change has been tested to work
|
||||
- [ ] Code has been linted using rustfmt
|
||||
- [ ] Areas your change affects has been linted using rustfmt
|
||||
- [ ] Code has been self-reviewed
|
||||
- [ ] Code has been tested and no new breakage is introduced
|
||||
- [ ] Documentation has been added/updated if needed
|
||||
- [ ] No merge conflicts arise from the change
|
||||
|
||||
## Other information
|
||||
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -15,5 +15,6 @@ Cargo.lock
|
|||
rust-unmangle
|
||||
*.svg
|
||||
*.data
|
||||
.idea/
|
||||
|
||||
sample_configs/testing.toml
|
||||
|
|
|
@ -30,7 +30,6 @@ heim = "0.0.10"
|
|||
log = "0.4.8"
|
||||
regex = "1.3.4"
|
||||
sysinfo = "0.11"
|
||||
winapi = "0.3.8"
|
||||
crossterm = "0.16"
|
||||
tui = {version = "0.8", features = ["crossterm"], default-features = false }
|
||||
lazy_static = "1.4.0"
|
||||
|
@ -40,6 +39,9 @@ serde = {version = "1.0", features = ["derive"] }
|
|||
unicode-segmentation = "1.6.0"
|
||||
unicode-width = "0.1.7"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "0.3.8"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "0.12"
|
||||
predicates = "1"
|
||||
|
|
|
@ -206,7 +206,7 @@ Note that `q` is disabled while in the search widget.
|
|||
|
||||
## Contribution
|
||||
|
||||
Contribution is welcome! Just submit a PR.
|
||||
Contribution is welcome! Just submit a PR. Note that I develop and test on stable Rust.
|
||||
|
||||
If you spot any issue with nobody assigned to it, or it seems like no work has started on it, feel free to try and do it!
|
||||
|
||||
|
|
|
@ -3,12 +3,7 @@ max_width = 100
|
|||
newline_style = "Unix"
|
||||
reorder_imports = true
|
||||
fn_args_layout = "Compressed"
|
||||
empty_item_single_line = false
|
||||
hard_tabs = true
|
||||
merge_imports = true
|
||||
merge_derives = true
|
||||
reorder_modules = true
|
||||
reorder_impl_items = true
|
||||
tab_spaces = 4
|
||||
format_strings = true
|
||||
space_after_colon = true
|
||||
|
|
82
src/app.rs
82
src/app.rs
|
@ -1,15 +1,16 @@
|
|||
pub mod data_harvester;
|
||||
use data_harvester::{processes, temperature};
|
||||
use std::time::Instant;
|
||||
|
||||
pub mod data_farmer;
|
||||
use unicode_segmentation::GraphemeCursor;
|
||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||
|
||||
use data_farmer::*;
|
||||
use data_harvester::{processes, temperature};
|
||||
|
||||
use crate::{canvas, constants, utils::error::Result};
|
||||
mod process_killer;
|
||||
|
||||
use unicode_segmentation::{GraphemeCursor};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
pub mod data_farmer;
|
||||
pub mod data_harvester;
|
||||
mod process_killer;
|
||||
|
||||
const MAX_SEARCH_LENGTH: usize = 200;
|
||||
|
||||
|
@ -33,7 +34,7 @@ pub enum ScrollDirection {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SearchDirection {
|
||||
pub enum CursorDirection {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
}
|
||||
|
@ -73,6 +74,10 @@ pub struct AppSearchState {
|
|||
pub is_blank_search: bool,
|
||||
pub is_invalid_search: bool,
|
||||
pub grapheme_cursor: GraphemeCursor,
|
||||
pub cursor_direction: CursorDirection,
|
||||
pub cursor_bar: usize,
|
||||
/// This represents the position in terms of CHARACTERS, not graphemes
|
||||
pub char_cursor_position: usize,
|
||||
}
|
||||
|
||||
impl Default for AppSearchState {
|
||||
|
@ -84,11 +89,21 @@ impl Default for AppSearchState {
|
|||
is_invalid_search: false,
|
||||
is_blank_search: true,
|
||||
grapheme_cursor: GraphemeCursor::new(0, 0, true),
|
||||
cursor_direction: CursorDirection::RIGHT,
|
||||
cursor_bar: 0,
|
||||
char_cursor_position: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppSearchState {
|
||||
/// Returns a reset but still enabled app search state
|
||||
pub fn reset() -> Self {
|
||||
let mut app_search_state = AppSearchState::default();
|
||||
app_search_state.is_enabled = true;
|
||||
app_search_state
|
||||
}
|
||||
|
||||
pub fn is_invalid_or_blank_search(&self) -> bool {
|
||||
self.is_blank_search || self.is_invalid_search
|
||||
}
|
||||
|
@ -549,6 +564,10 @@ impl App {
|
|||
.cur_cursor()
|
||||
}
|
||||
|
||||
pub fn get_char_cursor_position(&self) -> usize {
|
||||
self.process_search_state.search_state.char_cursor_position
|
||||
}
|
||||
|
||||
/// One of two functions allowed to run while in a dialog...
|
||||
pub fn on_enter(&mut self) {
|
||||
if self.delete_dialog_state.is_showing_dd {
|
||||
|
@ -620,19 +639,14 @@ impl App {
|
|||
#[allow(unused_variables)]
|
||||
pub fn skip_word_backspace(&mut self) {
|
||||
if let WidgetPosition::ProcessSearch = self.current_widget_selected {
|
||||
if self.process_search_state.search_state.is_enabled {
|
||||
}
|
||||
if self.process_search_state.search_state.is_enabled {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_search(&mut self) {
|
||||
if let WidgetPosition::ProcessSearch = self.current_widget_selected {
|
||||
self.process_search_state.search_state.grapheme_cursor =
|
||||
GraphemeCursor::new(0, 0, true);
|
||||
self.process_search_state.search_state.current_search_query = String::default();
|
||||
self.process_search_state.search_state.is_blank_search = true;
|
||||
self.process_search_state.search_state.is_invalid_search = false;
|
||||
self.update_process_gui = true;
|
||||
self.process_search_state.search_state = AppSearchState::reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -663,7 +677,8 @@ impl App {
|
|||
if self.process_search_state.search_state.is_enabled && self.get_cursor_position() > 0 {
|
||||
self.search_walk_back(self.get_cursor_position());
|
||||
|
||||
self.process_search_state
|
||||
let removed_char = self
|
||||
.process_search_state
|
||||
.search_state
|
||||
.current_search_query
|
||||
.remove(self.get_cursor_position());
|
||||
|
@ -677,6 +692,10 @@ impl App {
|
|||
true,
|
||||
);
|
||||
|
||||
self.process_search_state.search_state.char_cursor_position -=
|
||||
UnicodeWidthChar::width(removed_char).unwrap_or(0);
|
||||
self.process_search_state.search_state.cursor_direction = CursorDirection::LEFT;
|
||||
|
||||
self.update_regex();
|
||||
self.update_process_gui = true;
|
||||
}
|
||||
|
@ -710,7 +729,15 @@ impl App {
|
|||
pub fn on_left_key(&mut self) {
|
||||
if !self.is_in_dialog() {
|
||||
if let WidgetPosition::ProcessSearch = self.current_widget_selected {
|
||||
let prev_cursor = self.get_cursor_position();
|
||||
self.search_walk_back(self.get_cursor_position());
|
||||
if self.get_cursor_position() < prev_cursor {
|
||||
let str_slice = &self.process_search_state.search_state.current_search_query
|
||||
[self.get_cursor_position()..prev_cursor];
|
||||
self.process_search_state.search_state.char_cursor_position -=
|
||||
UnicodeWidthStr::width(str_slice);
|
||||
self.process_search_state.search_state.cursor_direction = CursorDirection::LEFT;
|
||||
}
|
||||
}
|
||||
} else if self.delete_dialog_state.is_showing_dd && !self.delete_dialog_state.is_on_yes {
|
||||
self.delete_dialog_state.is_on_yes = true;
|
||||
|
@ -720,7 +747,16 @@ impl App {
|
|||
pub fn on_right_key(&mut self) {
|
||||
if !self.is_in_dialog() {
|
||||
if let WidgetPosition::ProcessSearch = self.current_widget_selected {
|
||||
let prev_cursor = self.get_cursor_position();
|
||||
self.search_walk_forward(self.get_cursor_position());
|
||||
if self.get_cursor_position() > prev_cursor {
|
||||
let str_slice = &self.process_search_state.search_state.current_search_query
|
||||
[prev_cursor..self.get_cursor_position()];
|
||||
self.process_search_state.search_state.char_cursor_position +=
|
||||
UnicodeWidthStr::width(str_slice);
|
||||
self.process_search_state.search_state.cursor_direction =
|
||||
CursorDirection::RIGHT;
|
||||
}
|
||||
}
|
||||
} else if self.delete_dialog_state.is_showing_dd && self.delete_dialog_state.is_on_yes {
|
||||
self.delete_dialog_state.is_on_yes = false;
|
||||
|
@ -738,6 +774,8 @@ impl App {
|
|||
.len(),
|
||||
true,
|
||||
);
|
||||
self.process_search_state.search_state.char_cursor_position = 0;
|
||||
self.process_search_state.search_state.cursor_direction = CursorDirection::LEFT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -756,6 +794,14 @@ impl App {
|
|||
.len(),
|
||||
true,
|
||||
);
|
||||
self.process_search_state.search_state.char_cursor_position =
|
||||
UnicodeWidthStr::width(
|
||||
self.process_search_state
|
||||
.search_state
|
||||
.current_search_query
|
||||
.as_str(),
|
||||
);
|
||||
self.process_search_state.search_state.cursor_direction = CursorDirection::RIGHT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -840,6 +886,10 @@ impl App {
|
|||
true,
|
||||
);
|
||||
self.search_walk_forward(self.get_cursor_position());
|
||||
|
||||
self.process_search_state.search_state.char_cursor_position +=
|
||||
UnicodeWidthChar::width(caught_char).unwrap_or(0);
|
||||
|
||||
self.update_regex();
|
||||
self.update_process_gui = true;
|
||||
}
|
||||
|
@ -982,7 +1032,7 @@ impl App {
|
|||
pub fn kill_highlighted_process(&mut self) -> Result<()> {
|
||||
// Technically unnecessary but this is a good check...
|
||||
if let WidgetPosition::Process = self.current_widget_selected {
|
||||
if let Some(current_selected_processes) = &(self.to_delete_process_list) {
|
||||
if let Some(current_selected_processes) = &self.to_delete_process_list {
|
||||
for pid in ¤t_selected_processes.1 {
|
||||
process_killer::kill_process_given_pid(*pid)?;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use crate::data_harvester::{cpu, disks, mem, network, processes, temperature, Data};
|
||||
/// In charge of cleaning, processing, and managing data. I couldn't think of
|
||||
/// a better name for the file. Since I called data collection "harvesting",
|
||||
/// then this is the farmer I guess.
|
||||
|
@ -16,6 +15,8 @@ use crate::data_harvester::{cpu, disks, mem, network, processes, temperature, Da
|
|||
use std::time::Instant;
|
||||
use std::vec::Vec;
|
||||
|
||||
use crate::data_harvester::{cpu, disks, mem, network, processes, temperature, Data};
|
||||
|
||||
pub type TimeOffset = f64;
|
||||
pub type Value = f64;
|
||||
pub type JoinedDataPoints = (Value, Vec<(TimeOffset, Value)>);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! This is the main file to house data collection functions.
|
||||
|
||||
use std::{collections::HashMap, time::Instant};
|
||||
|
||||
use sysinfo::{System, SystemExt};
|
||||
|
||||
pub mod cpu;
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use futures::StreamExt;
|
||||
use heim::net;
|
||||
use heim::units::information::byte;
|
||||
use std::time::Instant;
|
||||
use sysinfo::{NetworkExt, System, SystemExt};
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
use crate::utils::error;
|
||||
use std::{
|
||||
collections::{hash_map::RandomState, HashMap},
|
||||
process::Command,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use sysinfo::{ProcessExt, ProcessorExt, System, SystemExt};
|
||||
|
||||
use crate::utils::error;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ProcessSorting {
|
||||
CPU,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use futures::StreamExt;
|
||||
use heim::units::thermodynamic_temperature;
|
||||
use std::cmp::Ordering;
|
||||
use sysinfo::{ComponentExt, System, SystemExt};
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/// This file is meant to house (OS specific) implementations on how to kill processes.
|
||||
use crate::utils::error::BottomError;
|
||||
use std::process::Command;
|
||||
|
||||
// Copied from SO: https://stackoverflow.com/a/55231715
|
||||
|
@ -12,6 +10,9 @@ use winapi::{
|
|||
},
|
||||
};
|
||||
|
||||
/// This file is meant to house (OS specific) implementations on how to kill processes.
|
||||
use crate::utils::error::BottomError;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
struct Process(HANDLE);
|
||||
|
||||
|
|
128
src/canvas.rs
128
src/canvas.rs
|
@ -1,11 +1,6 @@
|
|||
use crate::{
|
||||
app::{self, data_harvester::processes::ProcessHarvest, WidgetPosition},
|
||||
constants::*,
|
||||
data_conversion::{ConvertedCpuData, ConvertedProcessData},
|
||||
utils::error,
|
||||
};
|
||||
use std::cmp::{max, min};
|
||||
use std::cmp::max;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use tui::{
|
||||
backend,
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
|
@ -17,12 +12,19 @@ use tui::{
|
|||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
mod canvas_colours;
|
||||
use canvas_colours::*;
|
||||
|
||||
mod drawing_utils;
|
||||
use drawing_utils::*;
|
||||
|
||||
use crate::{
|
||||
app::{self, data_harvester::processes::ProcessHarvest, WidgetPosition},
|
||||
constants::*,
|
||||
data_conversion::{ConvertedCpuData, ConvertedProcessData},
|
||||
utils::error,
|
||||
};
|
||||
|
||||
mod canvas_colours;
|
||||
mod drawing_utils;
|
||||
|
||||
// Headers
|
||||
const CPU_LEGEND_HEADER: [&str; 2] = ["CPU", "Use%"];
|
||||
const CPU_SELECT_LEGEND_HEADER: [&str; 2] = ["CPU", "Show (Space)"];
|
||||
|
@ -620,12 +622,12 @@ impl Painter {
|
|||
fn draw_cpu_legend<B: backend::Backend>(
|
||||
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
|
||||
) {
|
||||
let cpu_data: &[ConvertedCpuData] = &(app_state.canvas_data.cpu_data);
|
||||
let cpu_data: &[ConvertedCpuData] = &app_state.canvas_data.cpu_data;
|
||||
|
||||
let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64;
|
||||
let start_position = get_start_position(
|
||||
num_rows,
|
||||
&(app_state.app_scroll_positions.scroll_direction),
|
||||
&app_state.app_scroll_positions.scroll_direction,
|
||||
&mut app_state
|
||||
.app_scroll_positions
|
||||
.cpu_scroll_state
|
||||
|
@ -766,8 +768,8 @@ impl Painter {
|
|||
fn draw_memory_graph<B: backend::Backend>(
|
||||
&self, f: &mut Frame<'_, B>, app_state: &app::App, draw_loc: Rect,
|
||||
) {
|
||||
let mem_data: &[(f64, f64)] = &(app_state.canvas_data.mem_data);
|
||||
let swap_data: &[(f64, f64)] = &(app_state.canvas_data.swap_data);
|
||||
let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data;
|
||||
let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data;
|
||||
|
||||
let x_axis: Axis<'_, String> = Axis::default().bounds([0.0, TIME_STARTS_FROM as f64]);
|
||||
|
||||
|
@ -839,8 +841,8 @@ impl Painter {
|
|||
fn draw_network_graph<B: backend::Backend>(
|
||||
&self, f: &mut Frame<'_, B>, app_state: &app::App, draw_loc: Rect,
|
||||
) {
|
||||
let network_data_rx: &[(f64, f64)] = &(app_state.canvas_data.network_data_rx);
|
||||
let network_data_tx: &[(f64, f64)] = &(app_state.canvas_data.network_data_tx);
|
||||
let network_data_rx: &[(f64, f64)] = &app_state.canvas_data.network_data_rx;
|
||||
let network_data_tx: &[(f64, f64)] = &app_state.canvas_data.network_data_tx;
|
||||
|
||||
let x_axis: Axis<'_, String> = Axis::default().bounds([0.0, 60_000.0]);
|
||||
let y_axis: Axis<'_, &str> = Axis::default()
|
||||
|
@ -938,7 +940,7 @@ impl Painter {
|
|||
|
||||
// Calculate widths
|
||||
let width_ratios: Vec<f64> = vec![0.25, 0.25, 0.25, 0.25];
|
||||
let lens: &Vec<usize> = &NETWORK_HEADERS_LENS;
|
||||
let lens: &[usize] = &NETWORK_HEADERS_LENS;
|
||||
let width = f64::from(draw_loc.width);
|
||||
|
||||
let variable_intrinsic_results =
|
||||
|
@ -967,12 +969,12 @@ impl Painter {
|
|||
fn draw_temp_table<B: backend::Backend>(
|
||||
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
|
||||
) {
|
||||
let temp_sensor_data: &[Vec<String>] = &(app_state.canvas_data.temp_sensor_data);
|
||||
let temp_sensor_data: &[Vec<String>] = &app_state.canvas_data.temp_sensor_data;
|
||||
|
||||
let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64;
|
||||
let start_position = get_start_position(
|
||||
num_rows,
|
||||
&(app_state.app_scroll_positions.scroll_direction),
|
||||
&app_state.app_scroll_positions.scroll_direction,
|
||||
&mut app_state
|
||||
.app_scroll_positions
|
||||
.temp_scroll_state
|
||||
|
@ -984,7 +986,7 @@ impl Painter {
|
|||
app_state.is_resized,
|
||||
);
|
||||
|
||||
let sliced_vec = &(temp_sensor_data[start_position as usize..]);
|
||||
let sliced_vec = &temp_sensor_data[start_position as usize..];
|
||||
let mut temp_row_counter: i64 = 0;
|
||||
|
||||
let temperature_rows = sliced_vec.iter().map(|temp_row| {
|
||||
|
@ -1064,11 +1066,11 @@ impl Painter {
|
|||
fn draw_disk_table<B: backend::Backend>(
|
||||
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
|
||||
) {
|
||||
let disk_data: &[Vec<String>] = &(app_state.canvas_data.disk_data);
|
||||
let disk_data: &[Vec<String>] = &app_state.canvas_data.disk_data;
|
||||
let num_rows = max(0, i64::from(draw_loc.height) - 5) as u64;
|
||||
let start_position = get_start_position(
|
||||
num_rows,
|
||||
&(app_state.app_scroll_positions.scroll_direction),
|
||||
&app_state.app_scroll_positions.scroll_direction,
|
||||
&mut app_state
|
||||
.app_scroll_positions
|
||||
.disk_scroll_state
|
||||
|
@ -1162,27 +1164,27 @@ impl Painter {
|
|||
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
|
||||
) {
|
||||
let width = max(0, draw_loc.width as i64 - 34) as u64; // TODO: [REFACTOR] Hard coding this is terrible.
|
||||
let query = app_state.get_current_search_query().as_str();
|
||||
let grapheme_indices = UnicodeSegmentation::grapheme_indices(query, true).rev(); // Reverse due to us wanting to draw from back -> front
|
||||
let cursor_position = app_state.get_cursor_position();
|
||||
let right_border = min(UnicodeWidthStr::width(query), width as usize);
|
||||
let char_cursor_position = app_state.get_char_cursor_position();
|
||||
|
||||
let mut itx = 0;
|
||||
let mut query_with_cursor: Vec<Text<'_>> = if let app::WidgetPosition::ProcessSearch =
|
||||
app_state.current_widget_selected
|
||||
{
|
||||
let mut res = Vec::new();
|
||||
if cursor_position >= query.len() {
|
||||
res.push(Text::styled(
|
||||
" ",
|
||||
self.colours.currently_selected_text_style,
|
||||
))
|
||||
}
|
||||
let start_position: usize = get_search_start_position(
|
||||
width as usize,
|
||||
&app_state.process_search_state.search_state.cursor_direction,
|
||||
&mut app_state.process_search_state.search_state.cursor_bar,
|
||||
char_cursor_position,
|
||||
app_state.is_resized,
|
||||
);
|
||||
|
||||
res.extend(
|
||||
grapheme_indices
|
||||
let query = app_state.get_current_search_query().as_str();
|
||||
let grapheme_indices = UnicodeSegmentation::grapheme_indices(query, true);
|
||||
let mut current_grapheme_posn = 0;
|
||||
let query_with_cursor: Vec<Text<'_>> =
|
||||
if let app::WidgetPosition::ProcessSearch = app_state.current_widget_selected {
|
||||
let mut res = grapheme_indices
|
||||
.filter_map(|grapheme| {
|
||||
if itx >= right_border {
|
||||
current_grapheme_posn += UnicodeWidthStr::width(grapheme.1);
|
||||
|
||||
if current_grapheme_posn <= start_position {
|
||||
None
|
||||
} else {
|
||||
let styled = if grapheme.0 == cursor_position {
|
||||
|
@ -1190,32 +1192,34 @@ impl Painter {
|
|||
} else {
|
||||
Text::styled(grapheme.1, self.colours.text_style)
|
||||
};
|
||||
itx += UnicodeWidthStr::width(grapheme.1);
|
||||
Some(styled)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
res
|
||||
} else {
|
||||
// This is easier - we just need to get a range of graphemes, rather than
|
||||
// dealing with possibly inserting a cursor (as none is shown!)
|
||||
grapheme_indices
|
||||
.filter_map(|grapheme| {
|
||||
if itx >= right_border {
|
||||
None
|
||||
} else {
|
||||
let styled = Text::styled(grapheme.1, self.colours.text_style);
|
||||
itx += UnicodeWidthStr::width(grapheme.1);
|
||||
Some(styled)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
if cursor_position >= query.len() {
|
||||
res.push(Text::styled(
|
||||
" ",
|
||||
self.colours.currently_selected_text_style,
|
||||
))
|
||||
}
|
||||
|
||||
// I feel like this is most definitely not the efficient way of doing this but eh
|
||||
query_with_cursor.reverse();
|
||||
res
|
||||
} else {
|
||||
// This is easier - we just need to get a range of graphemes, rather than
|
||||
// dealing with possibly inserting a cursor (as none is shown!)
|
||||
grapheme_indices
|
||||
.filter_map(|grapheme| {
|
||||
current_grapheme_posn += UnicodeWidthStr::width(grapheme.1);
|
||||
if current_grapheme_posn <= start_position {
|
||||
None
|
||||
} else {
|
||||
let styled = Text::styled(grapheme.1, self.colours.text_style);
|
||||
Some(styled)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let mut search_text = vec![if app_state.is_grouped() {
|
||||
Text::styled("Search by Name: ", self.colours.table_header_style)
|
||||
|
@ -1348,7 +1352,7 @@ impl Painter {
|
|||
|
||||
let position = get_start_position(
|
||||
num_rows,
|
||||
&(app_state.app_scroll_positions.scroll_direction),
|
||||
&app_state.app_scroll_positions.scroll_direction,
|
||||
&mut app_state
|
||||
.app_scroll_positions
|
||||
.process_scroll_state
|
||||
|
@ -1367,7 +1371,7 @@ impl Painter {
|
|||
position
|
||||
};
|
||||
|
||||
let sliced_vec = &(process_data[start_position as usize..]);
|
||||
let sliced_vec = &process_data[start_position as usize..];
|
||||
let mut process_counter: i64 = 0;
|
||||
|
||||
// Draw!
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
mod colour_utils;
|
||||
use colour_utils::*;
|
||||
use tui::style::{Color, Modifier, Style};
|
||||
|
||||
use colour_utils::*;
|
||||
|
||||
use crate::{constants::*, utils::error};
|
||||
|
||||
mod colour_utils;
|
||||
|
||||
pub struct CanvasColours {
|
||||
pub currently_selected_text_colour: Color,
|
||||
pub currently_selected_bg_colour: Color,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use crate::utils::{error, gen_util::*};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use tui::style::{Color, Style};
|
||||
|
||||
use crate::utils::{error, gen_util::*};
|
||||
|
||||
const GOLDEN_RATIO: f32 = 0.618_034; // Approx, good enough for use (also Clippy gets mad if it's too long)
|
||||
pub const STANDARD_FIRST_COLOUR: Color = Color::LightMagenta;
|
||||
pub const STANDARD_SECOND_COLOUR: Color = Color::LightYellow;
|
||||
|
|
|
@ -72,11 +72,43 @@ pub fn get_variable_intrinsic_widths(
|
|||
|
||||
#[allow(dead_code, unused_variables)]
|
||||
pub fn get_search_start_position(
|
||||
num_rows: u64, scroll_direction: &app::ScrollDirection, scroll_position_bar: &mut u64,
|
||||
currently_selected_position: u64, is_resized: bool,
|
||||
) -> u64 {
|
||||
//TODO: [Scroll] Gotta fix this too lol
|
||||
0
|
||||
num_columns: usize, cursor_direction: &app::CursorDirection, cursor_bar: &mut usize,
|
||||
current_cursor_position: usize, is_resized: bool,
|
||||
) -> usize {
|
||||
if is_resized {
|
||||
*cursor_bar = 0;
|
||||
}
|
||||
|
||||
match cursor_direction {
|
||||
app::CursorDirection::RIGHT => {
|
||||
if current_cursor_position < *cursor_bar + num_columns {
|
||||
// If, using previous_scrolled_position, we can see the element
|
||||
// (so within that and + num_rows) just reuse the current previously scrolled position
|
||||
*cursor_bar
|
||||
} else if current_cursor_position >= num_columns {
|
||||
// Else if the current position past the last element visible in the list, omit
|
||||
// until we can see that element
|
||||
*cursor_bar = current_cursor_position - num_columns;
|
||||
*cursor_bar
|
||||
} else {
|
||||
// Else, if it is not past the last element visible, do not omit anything
|
||||
0
|
||||
}
|
||||
}
|
||||
app::CursorDirection::LEFT => {
|
||||
if current_cursor_position <= *cursor_bar {
|
||||
// If it's past the first element, then show from that element downwards
|
||||
*cursor_bar = current_cursor_position;
|
||||
*cursor_bar
|
||||
} else if current_cursor_position >= *cursor_bar + num_columns {
|
||||
*cursor_bar = current_cursor_position - num_columns;
|
||||
*cursor_bar
|
||||
} else {
|
||||
// Else, don't change what our start position is from whatever it is set to!
|
||||
*cursor_bar
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_start_position(
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
//! This mainly concerns converting collected data into things that the canvas
|
||||
//! can actually handle.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use constants::*;
|
||||
|
||||
use crate::{
|
||||
app::{
|
||||
data_farmer,
|
||||
|
@ -10,8 +14,6 @@ use crate::{
|
|||
constants,
|
||||
utils::gen_util::{get_exact_byte_values, get_simple_byte_values},
|
||||
};
|
||||
use constants::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ConvertedNetworkData {
|
||||
|
|
57
src/main.rs
57
src/main.rs
|
@ -1,15 +1,22 @@
|
|||
#![warn(rust_2018_idioms)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
extern crate log;
|
||||
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
boxed::Box,
|
||||
io::{stdout, Write},
|
||||
panic::{self, PanicInfo},
|
||||
sync::mpsc,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
event::{
|
||||
|
@ -20,17 +27,17 @@ use crossterm::{
|
|||
style::Print,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
|
||||
use std::{
|
||||
boxed::Box,
|
||||
io::{stdout, Write},
|
||||
panic::{self, PanicInfo},
|
||||
sync::mpsc,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use tui::{backend::CrosstermBackend, Terminal};
|
||||
|
||||
use app::{
|
||||
data_harvester::{self, processes::ProcessSorting},
|
||||
App,
|
||||
};
|
||||
use constants::*;
|
||||
use data_conversion::*;
|
||||
use utils::error::{self, BottomError};
|
||||
|
||||
pub mod app;
|
||||
mod utils {
|
||||
pub mod error;
|
||||
|
@ -41,14 +48,6 @@ mod canvas;
|
|||
mod constants;
|
||||
mod data_conversion;
|
||||
|
||||
use app::{
|
||||
data_harvester::{self, processes::ProcessSorting},
|
||||
App,
|
||||
};
|
||||
use constants::*;
|
||||
use data_conversion::*;
|
||||
use utils::error::{self, BottomError};
|
||||
|
||||
enum Event<I, J> {
|
||||
KeyInput(I),
|
||||
MouseInput(J),
|
||||
|
@ -398,6 +397,8 @@ fn handle_key_event_or_break(
|
|||
KeyCode::Char('u') => app.clear_search(),
|
||||
KeyCode::Char('a') => app.skip_cursor_beginning(),
|
||||
KeyCode::Char('e') => app.skip_cursor_end(),
|
||||
// Can't do now, CTRL+BACKSPACE doesn't work and graphemes
|
||||
// are hard to iter while truncating last (eloquently).
|
||||
// KeyCode::Backspace => app.skip_word_backspace(),
|
||||
_ => {}
|
||||
}
|
||||
|
@ -496,22 +497,22 @@ fn get_temperature_option(
|
|||
} else if let Some(flags) = &config.flags {
|
||||
if let Some(temp_type) = &flags.temperature_type {
|
||||
// Give lowest priority to config.
|
||||
match temp_type.as_str() {
|
||||
return match temp_type.as_str() {
|
||||
"fahrenheit" | "f" => {
|
||||
return Ok(data_harvester::temperature::TemperatureType::Fahrenheit);
|
||||
Ok(data_harvester::temperature::TemperatureType::Fahrenheit)
|
||||
}
|
||||
"kelvin" | "k" => {
|
||||
return Ok(data_harvester::temperature::TemperatureType::Kelvin);
|
||||
Ok(data_harvester::temperature::TemperatureType::Kelvin)
|
||||
}
|
||||
"celsius" | "c" => {
|
||||
return Ok(data_harvester::temperature::TemperatureType::Celsius);
|
||||
Ok(data_harvester::temperature::TemperatureType::Celsius)
|
||||
}
|
||||
_ => {
|
||||
return Err(BottomError::ConfigError(
|
||||
Err(BottomError::ConfigError(
|
||||
"Invalid temperature type. Please have the value be of the form \
|
||||
<kelvin|k|celsius|c|fahrenheit|f>"
|
||||
.to_string(),
|
||||
));
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -717,7 +718,7 @@ fn generate_config_colours(config: &Config, painter: &mut canvas::Painter) -> er
|
|||
painter.colours.set_avg_cpu_colour(avg_cpu_color)?;
|
||||
}
|
||||
|
||||
if let Some(cpu_core_colors) = &(colours.cpu_core_colors) {
|
||||
if let Some(cpu_core_colors) = &colours.cpu_core_colors {
|
||||
painter.colours.set_cpu_colours(cpu_core_colors)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::process::Command;
|
||||
|
||||
use assert_cmd::prelude::*;
|
||||
use predicates::prelude::*;
|
||||
use std::process::Command;
|
||||
|
||||
// These tests are mostly here just to ensure that invalid results will be caught when passing arguments...
|
||||
|
||||
|
|
Loading…
Reference in a new issue