Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Adds tree view #223

Merged
merged 21 commits into from
Sep 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cargo-husky/hooks/pre-push
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/bin/sh

set -e

echo "Running pre-push hook:"

echo "Executing: cargo +nightly clippy -- -D clippy::all"
Expand Down
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,13 @@
"fract",
"gnueabihf",
"gotop",
"gotop's",
"gtop",
"haase",
"heim",
"hjkl",
"htop",
"indexmap",
"libc",
"markdownlint",
"memb",
Expand All @@ -62,6 +65,7 @@
"nvme",
"paren",
"pmem",
"ppid",
"prepush",
"processthreadsapi",
"regexes",
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- [#220](https://github.com/ClementTsang/bottom/pull/220): Add ability to hide specific temperature and disk entries via config.

- [#223](https://github.com/ClementTsang/bottom/pull/223): Add tree mode for processes.

### Changes

- [#213](https://github.com/ClementTsang/bottom/pull/213), [#214](https://github.com/ClementTsang/bottom/pull/214): Updated help descriptions, added auto-complete generation.
Expand Down
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ ctrlc = {version = "3.1", features = ["termination"]}
clap = "2.33"
dirs = "3.0.1"
futures = "0.3.5"
indexmap = "1.6.0"
itertools = "0.9.0"
libc = "0.2"
regex = "1.3"
Expand Down
47 changes: 33 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ A cross-platform graphical process/system monitor with a customizable interface
- [Processes](#processes)
- [Process searching](#process-searching)
- [Process sorting](#process-sorting)
- [Tree mode](#tree-mode)
- [Zoom](#zoom)
- [Expanding](#expanding)
- [Basic mode](#basic-mode)
Expand Down Expand Up @@ -246,23 +247,24 @@ Run using `btm`.
| `s, F6` | Open process sort widget |
| `I` | Invert current sort |
| `%` | Toggle between values and percentages for memory usage |
| `t`, `F5` | Toggle tree mode |

#### Process search bindings

| | |
| ------------ | -------------------------------------------- |
| `Tab` | Toggle between searching by PID or name |
| `Esc` | Close the search widget (retains the filter) |
| `Ctrl-a` | Skip to the start of the search query |
| `Ctrl-e` | Skip to the end of the search query |
| `Ctrl-u` | Clear the current search query |
| `Backspace` | Delete the character behind the cursor |
| `Delete` | Delete the character at the cursor |
| `Alt-c`/`F1` | Toggle matching case |
| `Alt-w`/`F2` | Toggle matching the entire word |
| `Alt-r`/`F3` | Toggle using regex |
| `Left` | Move cursor left |
| `Right` | Move cursor right |
| | |
| ------------- | -------------------------------------------- |
| `Tab` | Toggle between searching by PID or name |
| `Esc` | Close the search widget (retains the filter) |
| `Ctrl-a` | Skip to the start of the search query |
| `Ctrl-e` | Skip to the end of the search query |
| `Ctrl-u` | Clear the current search query |
| `Backspace` | Delete the character behind the cursor |
| `Delete` | Delete the character at the cursor |
| `Alt-c`, `F1` | Toggle matching case |
| `Alt-w`, `F2` | Toggle matching the entire word |
| `Alt-r`, `F3` | Toggle using regex |
| `Left` | Move cursor left |
| `Right` | Move cursor right |

### Process sort bindings

Expand Down Expand Up @@ -424,6 +426,23 @@ You can sort the processes list by any column you want by pressing `s` while on

![sorting](assets/sort.png)

#### Tree mode

Use `t` or `F5` to toggle tree mode in a process widget. This is somewhat similar to htop's tree
mode.

![Standard tree](assets/trees_1.png)

Sorting works as well, but it is done per groups of siblings. For example, by CPU%:

![Standard tree](assets/trees_2.png)

You can also still filter processes. Branches that entirely do not match the query are pruned out,
but if a branch contains an element that does match the query, any non-matching elements will instead
just be greyed out, so the tree structure is still maintained:

![Standard tree](assets/trees_3.png)

### Zoom

Using the `+`/`-` keys or the scroll wheel will move the current time intervals of the currently selected widget, and `=` to reset the zoom levels to the default.
Expand Down
Binary file added assets/trees_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/trees_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/trees_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
111 changes: 67 additions & 44 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub use states::*;
use crate::{
canvas, constants,
utils::error::{BottomError, Result},
Pid,
};

pub mod data_farmer;
Expand Down Expand Up @@ -67,7 +68,7 @@ pub struct App {
pub dd_err: Option<String>,

#[builder(default, setter(skip))]
to_delete_process_list: Option<(String, Vec<u32>)>,
to_delete_process_list: Option<(String, Vec<Pid>)>,

#[builder(default = false, setter(skip))]
pub is_frozen: bool,
Expand Down Expand Up @@ -265,37 +266,40 @@ impl App {
.proc_state
.get_mut_widget_state(self.current_widget.widget_id)
{
// Toggles process widget grouping state
proc_widget_state.is_grouped = !(proc_widget_state.is_grouped);

// Forcefully switch off column if we were on it...
if (proc_widget_state.is_grouped
&& proc_widget_state.process_sorting_type
== data_harvester::processes::ProcessSorting::Pid)
|| (!proc_widget_state.is_grouped
// Do NOT allow when in tree mode!
if !proc_widget_state.is_tree_mode {
// Toggles process widget grouping state
proc_widget_state.is_grouped = !(proc_widget_state.is_grouped);

// Forcefully switch off column if we were on it...
if (proc_widget_state.is_grouped
&& proc_widget_state.process_sorting_type
== data_harvester::processes::ProcessSorting::Count)
{
proc_widget_state.process_sorting_type =
data_harvester::processes::ProcessSorting::CpuPercent; // Go back to default, negate PID for group
proc_widget_state.process_sorting_reverse = true;
}
== data_harvester::processes::ProcessSorting::Pid)
|| (!proc_widget_state.is_grouped
&& proc_widget_state.process_sorting_type
== data_harvester::processes::ProcessSorting::Count)
{
proc_widget_state.process_sorting_type =
data_harvester::processes::ProcessSorting::CpuPercent; // Go back to default, negate PID for group
proc_widget_state.is_process_sort_descending = true;
}

proc_widget_state
.columns
.column_mapping
.get_mut(&processes::ProcessSorting::State)
.unwrap()
.enabled = !(proc_widget_state.is_grouped);
proc_widget_state
.columns
.column_mapping
.get_mut(&processes::ProcessSorting::State)
.unwrap()
.enabled = !(proc_widget_state.is_grouped);

proc_widget_state
.columns
.toggle(&processes::ProcessSorting::Count);
proc_widget_state
.columns
.toggle(&processes::ProcessSorting::Pid);
proc_widget_state
.columns
.toggle(&processes::ProcessSorting::Count);
proc_widget_state
.columns
.toggle(&processes::ProcessSorting::Pid);

self.proc_state.force_update = Some(self.current_widget.widget_id);
self.proc_state.force_update = Some(self.current_widget.widget_id);
}
}
}
_ => {}
Expand Down Expand Up @@ -384,8 +388,8 @@ impl App {
};

if let Some(proc_widget_state) = self.proc_state.get_mut_widget_state(widget_id) {
proc_widget_state.process_sorting_reverse =
!proc_widget_state.process_sorting_reverse;
proc_widget_state.is_process_sort_descending =
!proc_widget_state.is_process_sort_descending;

self.proc_state.force_update = Some(widget_id);
}
Expand Down Expand Up @@ -483,6 +487,24 @@ impl App {
}
}

pub fn toggle_tree_mode(&mut self) {
if let Some(proc_widget_state) = self
.proc_state
.widget_states
.get_mut(&(self.current_widget.widget_id))
{
proc_widget_state.is_tree_mode = !proc_widget_state.is_tree_mode;

if proc_widget_state.is_tree_mode {
// We enabled... set PID sort type to ascending.
proc_widget_state.process_sorting_type = processes::ProcessSorting::Pid;
proc_widget_state.is_process_sort_descending = false;
}

self.proc_state.force_update = Some(self.current_widget.widget_id);
}
}

/// 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 {
Expand Down Expand Up @@ -889,7 +911,7 @@ impl App {
if proc_widget_state.scroll_state.current_scroll_position
< corresponding_filtered_process_list.len()
{
let current_process: (String, Vec<u32>);
let current_process: (String, Vec<Pid>);
if self.is_grouped(self.current_widget.widget_id) {
if let Some(process) = &corresponding_filtered_process_list
.get(proc_widget_state.scroll_state.current_scroll_position)
Expand Down Expand Up @@ -1069,13 +1091,13 @@ impl App {
{
match proc_widget_state.process_sorting_type {
processes::ProcessSorting::CpuPercent => {
proc_widget_state.process_sorting_reverse =
!proc_widget_state.process_sorting_reverse
proc_widget_state.is_process_sort_descending =
!proc_widget_state.is_process_sort_descending
}
_ => {
proc_widget_state.process_sorting_type =
processes::ProcessSorting::CpuPercent;
proc_widget_state.process_sorting_reverse = true;
proc_widget_state.is_process_sort_descending = true;
}
}
self.proc_state.force_update = Some(self.current_widget.widget_id);
Expand All @@ -1092,13 +1114,13 @@ impl App {
{
match proc_widget_state.process_sorting_type {
processes::ProcessSorting::MemPercent => {
proc_widget_state.process_sorting_reverse =
!proc_widget_state.process_sorting_reverse
proc_widget_state.is_process_sort_descending =
!proc_widget_state.is_process_sort_descending
}
_ => {
proc_widget_state.process_sorting_type =
processes::ProcessSorting::MemPercent;
proc_widget_state.process_sorting_reverse = true;
proc_widget_state.is_process_sort_descending = true;
}
}
self.proc_state.force_update = Some(self.current_widget.widget_id);
Expand All @@ -1116,13 +1138,13 @@ impl App {
if !proc_widget_state.is_grouped {
match proc_widget_state.process_sorting_type {
processes::ProcessSorting::Pid => {
proc_widget_state.process_sorting_reverse =
!proc_widget_state.process_sorting_reverse
proc_widget_state.is_process_sort_descending =
!proc_widget_state.is_process_sort_descending
}
_ => {
proc_widget_state.process_sorting_type =
processes::ProcessSorting::Pid;
proc_widget_state.process_sorting_reverse = false;
proc_widget_state.is_process_sort_descending = false;
}
}
self.proc_state.force_update = Some(self.current_widget.widget_id);
Expand Down Expand Up @@ -1168,8 +1190,8 @@ impl App {
match proc_widget_state.process_sorting_type {
processes::ProcessSorting::ProcessName
| processes::ProcessSorting::Command => {
proc_widget_state.process_sorting_reverse =
!proc_widget_state.process_sorting_reverse
proc_widget_state.is_process_sort_descending =
!proc_widget_state.is_process_sort_descending
}
_ => {
proc_widget_state.process_sorting_type =
Expand All @@ -1178,7 +1200,7 @@ impl App {
} else {
processes::ProcessSorting::ProcessName
};
proc_widget_state.process_sorting_reverse = false;
proc_widget_state.is_process_sort_descending = false;
}
}
self.proc_state.force_update = Some(self.current_widget.widget_id);
Expand All @@ -1194,6 +1216,7 @@ impl App {
'L' | 'D' => self.move_widget_selection(&WidgetDirection::Right),
'K' | 'W' => self.move_widget_selection(&WidgetDirection::Up),
'J' | 'S' => self.move_widget_selection(&WidgetDirection::Down),
't' => self.toggle_tree_mode(),
'+' => self.zoom_in(),
'-' => self.zoom_out(),
'=' => self.reset_zoom(),
Expand Down Expand Up @@ -1228,7 +1251,7 @@ impl App {
}
}

pub fn get_to_delete_processes(&self) -> Option<(String, Vec<u32>)> {
pub fn get_to_delete_processes(&self) -> Option<(String, Vec<Pid>)> {
self.to_delete_process_list.clone()
}

Expand Down
2 changes: 1 addition & 1 deletion src/app/data_harvester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ pub struct DataCollector {
pub data: Data,
sys: System,
#[cfg(target_os = "linux")]
pid_mapping: HashMap<u32, processes::PrevProcDetails>,
pid_mapping: HashMap<crate::Pid, processes::PrevProcDetails>,
#[cfg(target_os = "linux")]
prev_idle: f64,
#[cfg(target_os = "linux")]
Expand Down
Loading