mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-04-08 17:05:59 +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 |
|
||||
| ++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) |
|
||||
| ++ctrl+a++ | Skip to the start 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.
|
||||
fn move_to_widget(&mut self, direction: MovementDirection) -> EventResult {
|
||||
let layout_tree = &mut self.layout_tree;
|
||||
let previous_selected = self.selected_widget;
|
||||
if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) {
|
||||
match move_widget_selection(layout_tree, widget, self.selected_widget, direction) {
|
||||
MoveWidgetResult::ForceRedraw(new_widget_id) => {
|
||||
self.selected_widget = new_widget_id;
|
||||
EventResult::Redraw
|
||||
}
|
||||
MoveWidgetResult::NodeId(new_widget_id) => {
|
||||
self.selected_widget = new_widget_id;
|
||||
match if self.is_expanded {
|
||||
move_expanded_widget_selection(
|
||||
&mut self.widget_lookup_map,
|
||||
self.selected_widget,
|
||||
direction,
|
||||
)
|
||||
} else {
|
||||
let layout_tree = &mut self.layout_tree;
|
||||
|
||||
if previous_selected != self.selected_widget {
|
||||
EventResult::Redraw
|
||||
} else {
|
||||
EventResult::NoRedraw
|
||||
}
|
||||
move_widget_selection(
|
||||
layout_tree,
|
||||
&mut self.widget_lookup_map,
|
||||
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() {
|
||||
if widget.does_border_intersect_mouse(&event) {
|
||||
let result = widget.handle_mouse_event(event);
|
||||
|
||||
let new_id;
|
||||
match widget.selectable_type() {
|
||||
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 => {
|
||||
let result = widget.handle_mouse_event(event);
|
||||
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.
|
||||
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,
|
||||
/// 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,
|
||||
}
|
||||
|
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 enum_dispatch::enum_dispatch;
|
||||
use indextree::NodeId;
|
||||
use tui::{backend::Backend, layout::Rect, widgets::TableState, Frame};
|
||||
|
||||
use crate::{
|
||||
@ -160,7 +159,6 @@ pub trait Widget {
|
||||
pub enum SelectableType {
|
||||
Selectable,
|
||||
Unselectable,
|
||||
Redirect(NodeId),
|
||||
}
|
||||
|
||||
/// The "main" widgets that are used by bottom to display information!
|
||||
@ -182,6 +180,26 @@ pub enum TmpBottomWidget {
|
||||
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:
|
||||
/// - [`DialogState::Hidden`] - the dialog is currently not showing.
|
||||
/// - [`DialogState::Shown`] - the dialog is showing.
|
||||
|
@ -285,6 +285,24 @@ impl TextInput {
|
||||
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 {
|
||||
@ -299,22 +317,8 @@ impl Component for TextInput {
|
||||
fn handle_key_event(&mut self, event: KeyEvent) -> ComponentEventResult {
|
||||
if event.modifiers.is_empty() || event.modifiers == KeyModifiers::SHIFT {
|
||||
match event.code {
|
||||
KeyCode::Left => {
|
||||
let original_cursor = self.cursor.cur_cursor();
|
||||
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::Left => self.move_left(),
|
||||
KeyCode::Right => self.move_right(),
|
||||
KeyCode::Backspace => self.clear_previous_grapheme(),
|
||||
KeyCode::Delete => self.clear_current_grapheme(),
|
||||
KeyCode::Char(c) => self.insert_character(c),
|
||||
@ -349,6 +353,8 @@ impl Component for TextInput {
|
||||
match event.code {
|
||||
KeyCode::Char('b') => self.move_word_back(),
|
||||
KeyCode::Char('f') => self.move_word_forward(),
|
||||
KeyCode::Char('h') => self.move_left(),
|
||||
KeyCode::Char('l') => self.move_right(),
|
||||
_ => ComponentEventResult::Unhandled,
|
||||
}
|
||||
} else {
|
||||
|
@ -12,14 +12,15 @@ use tui::{
|
||||
|
||||
use crate::{
|
||||
app::{
|
||||
does_bound_intersect_coordinate, event::ComponentEventResult, Component, SelectableType,
|
||||
Widget,
|
||||
does_bound_intersect_coordinate,
|
||||
event::{ComponentEventResult, SelectionAction},
|
||||
Component, Widget,
|
||||
},
|
||||
canvas::Painter,
|
||||
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)]
|
||||
pub struct Carousel {
|
||||
index: usize,
|
||||
@ -109,7 +110,7 @@ impl Carousel {
|
||||
.direction(tui::layout::Direction::Vertical)
|
||||
.split(area);
|
||||
|
||||
self.set_bounds(split_area[0]);
|
||||
self.set_bounds(area);
|
||||
|
||||
if let Some((_prev_id, prev_element_name)) = self.get_prev() {
|
||||
let prev_arrow_text = Spans::from(Span::styled(
|
||||
@ -198,11 +199,15 @@ impl Widget for Carousel {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn selectable_type(&self) -> SelectableType {
|
||||
if let Some(node) = self.get_currently_selected() {
|
||||
SelectableType::Redirect(node)
|
||||
} else {
|
||||
SelectableType::Unselectable
|
||||
}
|
||||
fn handle_widget_selection_left(&mut self) -> SelectionAction {
|
||||
// Override to move to the left widget
|
||||
self.decrement_index();
|
||||
SelectionAction::Handled
|
||||
}
|
||||
|
||||
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 crate::{
|
||||
app::{Component, Widget},
|
||||
app::{Component, SelectableType, Widget},
|
||||
options::layout_options::LayoutRule,
|
||||
};
|
||||
|
||||
@ -35,8 +35,6 @@ impl Empty {
|
||||
|
||||
impl Component for Empty {
|
||||
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()
|
||||
}
|
||||
|
||||
@ -55,4 +53,8 @@ impl Widget for Empty {
|
||||
fn height(&self) -> LayoutRule {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn selectable_type(&self) -> SelectableType {
|
||||
SelectableType::Unselectable
|
||||
}
|
||||
}
|
||||
|
@ -984,6 +984,24 @@ impl ProcessManager {
|
||||
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) {
|
||||
self.show_sort = false;
|
||||
if let ProcessManagerSelection::Sort = self.selected {
|
||||
@ -1081,7 +1099,7 @@ impl Component for ProcessManager {
|
||||
return self.open_search();
|
||||
}
|
||||
KeyCode::Char('%') => {
|
||||
// Handle switching memory usage type
|
||||
return self.toggle_memory();
|
||||
}
|
||||
KeyCode::Char('+') => {
|
||||
// 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.
|
||||
// So, I need the height AFTER wrapping.
|
||||
// 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);
|
||||
|
||||
@ -334,15 +336,6 @@ impl Painter {
|
||||
);
|
||||
|
||||
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 {
|
||||
let remaining_area: Rect =
|
||||
carousel.draw_carousel(painter, f, area);
|
||||
@ -357,7 +350,7 @@ impl Painter {
|
||||
painter,
|
||||
f,
|
||||
remaining_area,
|
||||
selected_id == to_draw_node,
|
||||
selected_id == node,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
@ -140,6 +140,7 @@ impl KillDialog for Painter {
|
||||
} else {
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
// TODO: Can probably make this const.
|
||||
let signal_text;
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
|
@ -1,9 +1,6 @@
|
||||
use crate::options::ConfigColours;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
// Default widget ID
|
||||
pub const DEFAULT_WIDGET_ID: u64 = 56709;
|
||||
|
||||
// How long to store 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_HELP_TEXT: [[&str; 2]; 48] = [
|
||||
["Tab", "Toggle between searching for PID and name"],
|
||||
pub const SEARCH_HELP_TEXT: [[&str; 2]; 47] = [
|
||||
["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"],
|
||||
|
Loading…
x
Reference in New Issue
Block a user