mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-27 23:54:14 +02:00
refactor: cover almost all keybinds except killing processes
This commit is contained in:
parent
abcca77c1d
commit
35ec66eaa7
@ -198,7 +198,6 @@ Note that key bindings are generally case-sensitive.
|
|||||||
| ------------------------------------- | -------------------------------------------- |
|
| ------------------------------------- | -------------------------------------------- |
|
||||||
| ++left++ <br/> ++h++ <br/> ++alt+h++ | Moves the cursor left |
|
| ++left++ <br/> ++h++ <br/> ++alt+h++ | Moves the cursor left |
|
||||||
| ++right++ <br/> ++l++ <br/> ++alt+l++ | Moves the cursor right |
|
| ++right++ <br/> ++l++ <br/> ++alt+l++ | Moves the cursor right |
|
||||||
| ++tab++ | Toggle between searching by PID or name |
|
|
||||||
| ++esc++ | Close the search widget (retains the filter) |
|
| ++esc++ | Close the search widget (retains the filter) |
|
||||||
| ++ctrl+a++ | Skip to the start of the search query |
|
| ++ctrl+a++ | Skip to the start of the search query |
|
||||||
| ++ctrl+e++ | Skip to the end of the search query |
|
| ++ctrl+e++ | Skip to the end of the search query |
|
||||||
|
80
src/app.rs
80
src/app.rs
@ -308,26 +308,36 @@ impl AppState {
|
|||||||
|
|
||||||
/// Moves to a widget.
|
/// Moves to a widget.
|
||||||
fn move_to_widget(&mut self, direction: MovementDirection) -> EventResult {
|
fn move_to_widget(&mut self, direction: MovementDirection) -> EventResult {
|
||||||
let layout_tree = &mut self.layout_tree;
|
match if self.is_expanded {
|
||||||
let previous_selected = self.selected_widget;
|
move_expanded_widget_selection(
|
||||||
if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) {
|
&mut self.widget_lookup_map,
|
||||||
match move_widget_selection(layout_tree, widget, self.selected_widget, direction) {
|
self.selected_widget,
|
||||||
MoveWidgetResult::ForceRedraw(new_widget_id) => {
|
direction,
|
||||||
self.selected_widget = new_widget_id;
|
)
|
||||||
EventResult::Redraw
|
} else {
|
||||||
}
|
let layout_tree = &mut self.layout_tree;
|
||||||
MoveWidgetResult::NodeId(new_widget_id) => {
|
|
||||||
self.selected_widget = new_widget_id;
|
|
||||||
|
|
||||||
if previous_selected != self.selected_widget {
|
move_widget_selection(
|
||||||
EventResult::Redraw
|
layout_tree,
|
||||||
} else {
|
&mut self.widget_lookup_map,
|
||||||
EventResult::NoRedraw
|
self.selected_widget,
|
||||||
}
|
direction,
|
||||||
|
)
|
||||||
|
} {
|
||||||
|
MoveWidgetResult::ForceRedraw(new_widget_id) => {
|
||||||
|
self.selected_widget = new_widget_id;
|
||||||
|
EventResult::Redraw
|
||||||
|
}
|
||||||
|
MoveWidgetResult::NodeId(new_widget_id) => {
|
||||||
|
let previous_selected = self.selected_widget;
|
||||||
|
self.selected_widget = new_widget_id;
|
||||||
|
|
||||||
|
if previous_selected != self.selected_widget {
|
||||||
|
EventResult::Redraw
|
||||||
|
} else {
|
||||||
|
EventResult::NoRedraw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
EventResult::NoRedraw
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,33 +458,29 @@ impl AppState {
|
|||||||
for (id, widget) in self.widget_lookup_map.iter_mut() {
|
for (id, widget) in self.widget_lookup_map.iter_mut() {
|
||||||
if widget.does_border_intersect_mouse(&event) {
|
if widget.does_border_intersect_mouse(&event) {
|
||||||
let result = widget.handle_mouse_event(event);
|
let result = widget.handle_mouse_event(event);
|
||||||
|
|
||||||
let new_id;
|
|
||||||
match widget.selectable_type() {
|
match widget.selectable_type() {
|
||||||
SelectableType::Selectable => {
|
SelectableType::Selectable => {
|
||||||
new_id = *id;
|
let was_id_already_selected = self.selected_widget == *id;
|
||||||
|
self.selected_widget = *id;
|
||||||
|
|
||||||
|
if was_id_already_selected {
|
||||||
|
returned_result = self.convert_widget_event_result(result);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// If the weren't equal, *force* a redraw, and correct the layout tree.
|
||||||
|
correct_layout_last_selections(
|
||||||
|
&mut self.layout_tree,
|
||||||
|
self.selected_widget,
|
||||||
|
);
|
||||||
|
let _ = self.convert_widget_event_result(result);
|
||||||
|
returned_result = EventResult::Redraw;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SelectableType::Unselectable => {
|
SelectableType::Unselectable => {
|
||||||
let result = widget.handle_mouse_event(event);
|
let result = widget.handle_mouse_event(event);
|
||||||
return self.convert_widget_event_result(result);
|
return self.convert_widget_event_result(result);
|
||||||
}
|
}
|
||||||
SelectableType::Redirect(redirected_id) => {
|
|
||||||
new_id = redirected_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let was_id_already_selected = self.selected_widget == new_id;
|
|
||||||
self.selected_widget = new_id;
|
|
||||||
|
|
||||||
if was_id_already_selected {
|
|
||||||
returned_result = self.convert_widget_event_result(result);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
// If the weren't equal, *force* a redraw, and correct the layout tree.
|
|
||||||
correct_layout_last_selections(&mut self.layout_tree, self.selected_widget);
|
|
||||||
let _ = self.convert_widget_event_result(result);
|
|
||||||
returned_result = EventResult::Redraw;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,8 @@ pub enum ComponentEventResult {
|
|||||||
|
|
||||||
/// How a widget should handle a widget selection request.
|
/// How a widget should handle a widget selection request.
|
||||||
pub enum SelectionAction {
|
pub enum SelectionAction {
|
||||||
/// This event occurs if the widget internally handled the selection action. A redraw is required.
|
/// This occurs if the widget internally handled the selection action. A redraw is required.
|
||||||
Handled,
|
Handled,
|
||||||
/// This event occurs if the widget did not handle the selection action; the caller must handle it.
|
/// This occurs if the widget did not handle the selection action; the caller must handle it.
|
||||||
NotHandled,
|
NotHandled,
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,7 @@
|
|||||||
use std::time::Instant;
|
use std::{fmt::Debug, time::Instant};
|
||||||
|
|
||||||
use crossterm::event::{KeyEvent, MouseEvent};
|
use crossterm::event::{KeyEvent, MouseEvent};
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use indextree::NodeId;
|
|
||||||
use tui::{backend::Backend, layout::Rect, widgets::TableState, Frame};
|
use tui::{backend::Backend, layout::Rect, widgets::TableState, Frame};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -160,7 +159,6 @@ pub trait Widget {
|
|||||||
pub enum SelectableType {
|
pub enum SelectableType {
|
||||||
Selectable,
|
Selectable,
|
||||||
Unselectable,
|
Unselectable,
|
||||||
Redirect(NodeId),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The "main" widgets that are used by bottom to display information!
|
/// The "main" widgets that are used by bottom to display information!
|
||||||
@ -182,6 +180,26 @@ pub enum TmpBottomWidget {
|
|||||||
Empty,
|
Empty,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for TmpBottomWidget {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::MemGraph(_) => write!(f, "MemGraph"),
|
||||||
|
Self::TempTable(_) => write!(f, "TempTable"),
|
||||||
|
Self::DiskTable(_) => write!(f, "DiskTable"),
|
||||||
|
Self::CpuGraph(_) => write!(f, "CpuGraph"),
|
||||||
|
Self::NetGraph(_) => write!(f, "NetGraph"),
|
||||||
|
Self::OldNetGraph(_) => write!(f, "OldNetGraph"),
|
||||||
|
Self::ProcessManager(_) => write!(f, "ProcessManager"),
|
||||||
|
Self::BatteryTable(_) => write!(f, "BatteryTable"),
|
||||||
|
Self::BasicCpu(_) => write!(f, "BasicCpu"),
|
||||||
|
Self::BasicMem(_) => write!(f, "BasicMem"),
|
||||||
|
Self::BasicNet(_) => write!(f, "BasicNet"),
|
||||||
|
Self::Carousel(_) => write!(f, "Carousel"),
|
||||||
|
Self::Empty(_) => write!(f, "Empty"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The states a dialog can be in. Consists of either:
|
/// The states a dialog can be in. Consists of either:
|
||||||
/// - [`DialogState::Hidden`] - the dialog is currently not showing.
|
/// - [`DialogState::Hidden`] - the dialog is currently not showing.
|
||||||
/// - [`DialogState::Shown`] - the dialog is showing.
|
/// - [`DialogState::Shown`] - the dialog is showing.
|
||||||
|
@ -285,6 +285,24 @@ impl TextInput {
|
|||||||
area,
|
area,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn move_left(&mut self) -> ComponentEventResult {
|
||||||
|
let original_cursor = self.cursor.cur_cursor();
|
||||||
|
if self.move_back() == original_cursor {
|
||||||
|
ComponentEventResult::NoRedraw
|
||||||
|
} else {
|
||||||
|
ComponentEventResult::Redraw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_right(&mut self) -> ComponentEventResult {
|
||||||
|
let original_cursor = self.cursor.cur_cursor();
|
||||||
|
if self.move_forward() == original_cursor {
|
||||||
|
ComponentEventResult::NoRedraw
|
||||||
|
} else {
|
||||||
|
ComponentEventResult::Redraw
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for TextInput {
|
impl Component for TextInput {
|
||||||
@ -299,22 +317,8 @@ impl Component for TextInput {
|
|||||||
fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
|
fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
|
||||||
if event.modifiers.is_empty() || event.modifiers == KeyModifiers::SHIFT {
|
if event.modifiers.is_empty() || event.modifiers == KeyModifiers::SHIFT {
|
||||||
match event.code {
|
match event.code {
|
||||||
KeyCode::Left => {
|
KeyCode::Left => self.move_left(),
|
||||||
let original_cursor = self.cursor.cur_cursor();
|
KeyCode::Right => self.move_right(),
|
||||||
if self.move_back() == original_cursor {
|
|
||||||
ComponentEventResult::NoRedraw
|
|
||||||
} else {
|
|
||||||
ComponentEventResult::Redraw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Right => {
|
|
||||||
let original_cursor = self.cursor.cur_cursor();
|
|
||||||
if self.move_forward() == original_cursor {
|
|
||||||
ComponentEventResult::NoRedraw
|
|
||||||
} else {
|
|
||||||
ComponentEventResult::Redraw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Backspace => self.clear_previous_grapheme(),
|
KeyCode::Backspace => self.clear_previous_grapheme(),
|
||||||
KeyCode::Delete => self.clear_current_grapheme(),
|
KeyCode::Delete => self.clear_current_grapheme(),
|
||||||
KeyCode::Char(c) => self.insert_character(c),
|
KeyCode::Char(c) => self.insert_character(c),
|
||||||
@ -349,6 +353,8 @@ impl Component for TextInput {
|
|||||||
match event.code {
|
match event.code {
|
||||||
KeyCode::Char('b') => self.move_word_back(),
|
KeyCode::Char('b') => self.move_word_back(),
|
||||||
KeyCode::Char('f') => self.move_word_forward(),
|
KeyCode::Char('f') => self.move_word_forward(),
|
||||||
|
KeyCode::Char('h') => self.move_left(),
|
||||||
|
KeyCode::Char('l') => self.move_right(),
|
||||||
_ => ComponentEventResult::Unhandled,
|
_ => ComponentEventResult::Unhandled,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -12,14 +12,15 @@ use tui::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
does_bound_intersect_coordinate, event::ComponentEventResult, Component, SelectableType,
|
does_bound_intersect_coordinate,
|
||||||
Widget,
|
event::{ComponentEventResult, SelectionAction},
|
||||||
|
Component, Widget,
|
||||||
},
|
},
|
||||||
canvas::Painter,
|
canvas::Painter,
|
||||||
options::layout_options::LayoutRule,
|
options::layout_options::LayoutRule,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A container that "holds"" multiple [`BottomWidget`]s through their [`NodeId`]s.
|
/// A container that "holds" multiple [`BottomWidget`]s through their [`NodeId`]s.
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
pub struct Carousel {
|
pub struct Carousel {
|
||||||
index: usize,
|
index: usize,
|
||||||
@ -109,7 +110,7 @@ impl Carousel {
|
|||||||
.direction(tui::layout::Direction::Vertical)
|
.direction(tui::layout::Direction::Vertical)
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
self.set_bounds(split_area[0]);
|
self.set_bounds(area);
|
||||||
|
|
||||||
if let Some((_prev_id, prev_element_name)) = self.get_prev() {
|
if let Some((_prev_id, prev_element_name)) = self.get_prev() {
|
||||||
let prev_arrow_text = Spans::from(Span::styled(
|
let prev_arrow_text = Spans::from(Span::styled(
|
||||||
@ -198,11 +199,15 @@ impl Widget for Carousel {
|
|||||||
self.height
|
self.height
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selectable_type(&self) -> SelectableType {
|
fn handle_widget_selection_left(&mut self) -> SelectionAction {
|
||||||
if let Some(node) = self.get_currently_selected() {
|
// Override to move to the left widget
|
||||||
SelectableType::Redirect(node)
|
self.decrement_index();
|
||||||
} else {
|
SelectionAction::Handled
|
||||||
SelectableType::Unselectable
|
}
|
||||||
}
|
|
||||||
|
fn handle_widget_selection_right(&mut self) -> SelectionAction {
|
||||||
|
// Override to move to the right widget
|
||||||
|
self.increment_index();
|
||||||
|
SelectionAction::Handled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use tui::layout::Rect;
|
use tui::layout::Rect;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{Component, Widget},
|
app::{Component, SelectableType, Widget},
|
||||||
options::layout_options::LayoutRule,
|
options::layout_options::LayoutRule,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -35,8 +35,6 @@ impl Empty {
|
|||||||
|
|
||||||
impl Component for Empty {
|
impl Component for Empty {
|
||||||
fn bounds(&self) -> Rect {
|
fn bounds(&self) -> Rect {
|
||||||
// TODO: Maybe think of how to store this without making it available for clicking. Separate bounds out to the layout? Might
|
|
||||||
// need to keep the bounds calculations for some components, so maybe implement it specifically for them.
|
|
||||||
Rect::default()
|
Rect::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,4 +53,8 @@ impl Widget for Empty {
|
|||||||
fn height(&self) -> LayoutRule {
|
fn height(&self) -> LayoutRule {
|
||||||
self.height
|
self.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn selectable_type(&self) -> SelectableType {
|
||||||
|
SelectableType::Unselectable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -984,6 +984,24 @@ impl ProcessManager {
|
|||||||
ComponentEventResult::Signal(ReturnSignal::Update)
|
ComponentEventResult::Signal(ReturnSignal::Update)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toggle_memory(&mut self) -> ComponentEventResult {
|
||||||
|
if matches!(
|
||||||
|
self.process_table.columns()[3].sort_type,
|
||||||
|
ProcessSortType::MemPercent
|
||||||
|
) {
|
||||||
|
self.process_table
|
||||||
|
.set_column(ProcessSortColumn::new(ProcessSortType::Mem), 3);
|
||||||
|
} else {
|
||||||
|
self.process_table
|
||||||
|
.set_column(ProcessSortColumn::new(ProcessSortType::MemPercent), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate row cache.
|
||||||
|
self.process_table.invalidate_cached_columns(); // TODO: This should be automatically called somehow after sets/removes to avoid forgetting it - maybe do a queue system?
|
||||||
|
|
||||||
|
ComponentEventResult::Signal(ReturnSignal::Update)
|
||||||
|
}
|
||||||
|
|
||||||
fn hide_sort(&mut self) {
|
fn hide_sort(&mut self) {
|
||||||
self.show_sort = false;
|
self.show_sort = false;
|
||||||
if let ProcessManagerSelection::Sort = self.selected {
|
if let ProcessManagerSelection::Sort = self.selected {
|
||||||
@ -1081,7 +1099,7 @@ impl Component for ProcessManager {
|
|||||||
return self.open_search();
|
return self.open_search();
|
||||||
}
|
}
|
||||||
KeyCode::Char('%') => {
|
KeyCode::Char('%') => {
|
||||||
// Handle switching memory usage type
|
return self.toggle_memory();
|
||||||
}
|
}
|
||||||
KeyCode::Char('+') => {
|
KeyCode::Char('+') => {
|
||||||
// Expand a branch
|
// Expand a branch
|
||||||
|
@ -215,6 +215,8 @@ impl Painter {
|
|||||||
// line-wrapping is NOT the same as taking the width of the text and dividing by width.
|
// line-wrapping is NOT the same as taking the width of the text and dividing by width.
|
||||||
// So, I need the height AFTER wrapping.
|
// So, I need the height AFTER wrapping.
|
||||||
// See: https://github.com/fdehau/tui-rs/pull/349. Land this after this pushes to release.
|
// See: https://github.com/fdehau/tui-rs/pull/349. Land this after this pushes to release.
|
||||||
|
//
|
||||||
|
// ADDENDUM: I could probably use the same textwrap trick I did with the help menu for this.
|
||||||
|
|
||||||
let dd_text = self.get_dd_spans(app_state);
|
let dd_text = self.get_dd_spans(app_state);
|
||||||
|
|
||||||
@ -334,15 +336,6 @@ impl Painter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if let Some(widget) = lookup_map.get_mut(&node) {
|
if let Some(widget) = lookup_map.get_mut(&node) {
|
||||||
// debug!(
|
|
||||||
// "Original bound: {:?}, offset_x: {}, offset_y: {}, area: {:?}, widget: {}",
|
|
||||||
// bound,
|
|
||||||
// offset_x,
|
|
||||||
// offset_y,
|
|
||||||
// area,
|
|
||||||
// widget.get_pretty_name()
|
|
||||||
// );
|
|
||||||
|
|
||||||
if let TmpBottomWidget::Carousel(carousel) = widget {
|
if let TmpBottomWidget::Carousel(carousel) = widget {
|
||||||
let remaining_area: Rect =
|
let remaining_area: Rect =
|
||||||
carousel.draw_carousel(painter, f, area);
|
carousel.draw_carousel(painter, f, area);
|
||||||
@ -357,7 +350,7 @@ impl Painter {
|
|||||||
painter,
|
painter,
|
||||||
f,
|
f,
|
||||||
remaining_area,
|
remaining_area,
|
||||||
selected_id == to_draw_node,
|
selected_id == node,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -140,6 +140,7 @@ impl KillDialog for Painter {
|
|||||||
} else {
|
} else {
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
{
|
{
|
||||||
|
// TODO: Can probably make this const.
|
||||||
let signal_text;
|
let signal_text;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
use crate::options::ConfigColours;
|
use crate::options::ConfigColours;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
// Default widget ID
|
|
||||||
pub const DEFAULT_WIDGET_ID: u64 = 56709;
|
|
||||||
|
|
||||||
// How long to store data.
|
// How long to store data.
|
||||||
pub const STALE_MAX_MILLISECONDS: u64 = 600 * 1000; // Keep 10 minutes of data.
|
pub const STALE_MAX_MILLISECONDS: u64 = 600 * 1000; // Keep 10 minutes of data.
|
||||||
|
|
||||||
@ -320,8 +317,7 @@ pub const PROCESS_HELP_TEXT: [[&str; 2]; 14] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
pub const SEARCH_TEXT_HELP_TITLE: &str = "Process Search";
|
pub const SEARCH_TEXT_HELP_TITLE: &str = "Process Search";
|
||||||
pub const SEARCH_HELP_TEXT: [[&str; 2]; 48] = [
|
pub const SEARCH_HELP_TEXT: [[&str; 2]; 47] = [
|
||||||
["Tab", "Toggle between searching for PID and name"],
|
|
||||||
["Esc", "Close the search widget (retains the filter)"],
|
["Esc", "Close the search widget (retains the filter)"],
|
||||||
["Ctrl-a", "Skip to the start of the search query"],
|
["Ctrl-a", "Skip to the start of the search query"],
|
||||||
["Ctrl-e", "Skip to the end of the search query"],
|
["Ctrl-e", "Skip to the end of the search query"],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user