mirror of
https://github.com/ClementTsang/bottom
synced 2024-11-10 14:44:18 +00:00
feature: Add scroll indicator to keep track of table position in widgets. (#333)
Adds the option to enable an "out of" indicator for scrollable table widgets (using --show_table_scroll_position).
This commit is contained in:
parent
a9c1197075
commit
3260ff4663
10 changed files with 214 additions and 55 deletions
2
.github/workflows/deployment.yml
vendored
2
.github/workflows/deployment.yml
vendored
|
@ -203,6 +203,8 @@ jobs:
|
|||
run: |
|
||||
strip target/${{ matrix.triple.target }}/release/btm
|
||||
|
||||
# TODO: Strip ARM
|
||||
|
||||
- name: Bundle release and completion (Windows)
|
||||
if: matrix.triple.os == 'windows-2019'
|
||||
shell: bash
|
||||
|
|
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.6.0] - Unreleased
|
||||
|
||||
## Features
|
||||
|
||||
- [#333](https://github.com/ClementTsang/bottom/pull/333): Adds an "out of" indicator that can be enabled using `--show_table_scroll_position` to help keep track of scrolled position.
|
||||
|
||||
## Changes
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
## [0.5.3] - 2020-11-26
|
||||
|
||||
## Bug Fixes
|
||||
|
|
52
README.md
52
README.md
|
@ -238,6 +238,7 @@ Use `btm --help` for more information.
|
|||
--mem_as_value Defaults to showing process memory usage by value.
|
||||
-r, --rate <MS> Sets a refresh rate in ms.
|
||||
-R, --regex Enables regex by default.
|
||||
--show_table_scroll_position Shows the scroll position tracker in table widgets
|
||||
-d, --time_delta <MS> The amount in ms changed upon zooming.
|
||||
-T, --tree Defaults to showing the process widget in tree mode.
|
||||
--use_old_network_legend DEPRECATED - uses the older network legend.
|
||||
|
@ -528,31 +529,32 @@ The following options can be set under `[flags]` to achieve the same effect as p
|
|||
|
||||
These are the following supported flag config values, which correspond to the flag of the same name described in [Flags](#flags):
|
||||
|
||||
| Field | Type |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------- |
|
||||
| `hide_avg_cpu` | Boolean |
|
||||
| `dot_marker` | Boolean |
|
||||
| `left_legend` | Boolean |
|
||||
| `current_usage` | Boolean |
|
||||
| `group_processes` | Boolean |
|
||||
| `case_sensitive` | Boolean |
|
||||
| `whole_word` | Boolean |
|
||||
| `regex` | Boolean |
|
||||
| `show_disabled_data` | Boolean |
|
||||
| `basic` | Boolean |
|
||||
| `hide_table_count` | Boolean |
|
||||
| `use_old_network_legend` | Boolean |
|
||||
| `battery` | Boolean |
|
||||
| `rate` | Unsigned Int (represents milliseconds) |
|
||||
| `default_time_value` | Unsigned Int (represents milliseconds) |
|
||||
| `time_delta` | Unsigned Int (represents milliseconds) |
|
||||
| `temperature_type` | String (one of ["k", "f", "c", "kelvin", "fahrenheit", "celsius"]) |
|
||||
| `default_widget_type` | String (one of ["cpu", "proc", "net", "temp", "mem", "disk"], same as layout options) |
|
||||
| `default_widget_count` | Unsigned Int (represents which `default_widget_type`) |
|
||||
| `disable_click` | Boolean |
|
||||
| `color` | String (one of ["default", "default-light", "gruvbox", "gruvbox-light"]) |
|
||||
| `mem_as_value` | Boolean |
|
||||
| `tree` | Boolean |
|
||||
| Field | Type |
|
||||
| ---------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| `hide_avg_cpu` | Boolean |
|
||||
| `dot_marker` | Boolean |
|
||||
| `left_legend` | Boolean |
|
||||
| `current_usage` | Boolean |
|
||||
| `group_processes` | Boolean |
|
||||
| `case_sensitive` | Boolean |
|
||||
| `whole_word` | Boolean |
|
||||
| `regex` | Boolean |
|
||||
| `show_disabled_data` | Boolean |
|
||||
| `basic` | Boolean |
|
||||
| `hide_table_count` | Boolean |
|
||||
| `use_old_network_legend` | Boolean |
|
||||
| `battery` | Boolean |
|
||||
| `rate` | Unsigned Int (represents milliseconds) |
|
||||
| `default_time_value` | Unsigned Int (represents milliseconds) |
|
||||
| `time_delta` | Unsigned Int (represents milliseconds) |
|
||||
| `temperature_type` | String (one of ["k", "f", "c", "kelvin", "fahrenheit", "celsius"]) |
|
||||
| `default_widget_type` | String (one of ["cpu", "proc", "net", "temp", "mem", "disk"], same as layout options) |
|
||||
| `default_widget_count` | Unsigned Int (represents which `default_widget_type`) |
|
||||
| `disable_click` | Boolean |
|
||||
| `color` | String (one of ["default", "default-light", "gruvbox", "gruvbox-light"]) |
|
||||
| `mem_as_value` | Boolean |
|
||||
| `tree` | Boolean |
|
||||
| `show_table_scroll_position` | Boolean |
|
||||
|
||||
#### Theming
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ pub struct AppConfigFields {
|
|||
pub table_gap: u16,
|
||||
pub disable_click: bool,
|
||||
pub no_write: bool,
|
||||
pub show_table_scroll_position: bool,
|
||||
}
|
||||
|
||||
/// For filtering out information
|
||||
|
|
|
@ -868,11 +868,11 @@ pub struct BottomWidget {
|
|||
#[builder(default = None)]
|
||||
pub parent_reflector: Option<(WidgetDirection, u64)>,
|
||||
|
||||
/// Top left corner when drawn, for mouse click detection
|
||||
/// Top left corner when drawn, for mouse click detection. (x, y)
|
||||
#[builder(default = None)]
|
||||
pub top_left_corner: Option<(u16, u16)>,
|
||||
|
||||
/// Bottom right corner when drawn, for mouse click detection
|
||||
/// Bottom right corner when drawn, for mouse click detection. (x, y)
|
||||
#[builder(default = None)]
|
||||
pub bottom_right_corner: Option<(u16, u16)>,
|
||||
}
|
||||
|
|
|
@ -42,7 +42,6 @@ impl DiskTableWidget for Painter {
|
|||
) {
|
||||
let recalculate_column_widths = app_state.should_get_widget_bounds();
|
||||
if let Some(disk_widget_state) = app_state.disk_state.widget_states.get_mut(&widget_id) {
|
||||
let disk_data: &mut [Vec<String>] = &mut app_state.canvas_data.disk_data;
|
||||
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
|
||||
0
|
||||
} else {
|
||||
|
@ -65,7 +64,7 @@ impl DiskTableWidget for Painter {
|
|||
.current_scroll_position
|
||||
.saturating_sub(start_position),
|
||||
));
|
||||
let sliced_vec = &disk_data[start_position..];
|
||||
let sliced_vec = &app_state.canvas_data.disk_data[start_position..];
|
||||
|
||||
// Calculate widths
|
||||
let hard_widths = [None, None, Some(4), Some(6), Some(6), Some(7), Some(7)];
|
||||
|
@ -154,7 +153,6 @@ impl DiskTableWidget for Painter {
|
|||
Row::Data(truncated_data)
|
||||
});
|
||||
|
||||
// TODO: This seems to be bugged? The selected text style gets "stuck"? I think this gets fixed with tui 0.10?
|
||||
let (border_style, highlight_style) = if is_on_widget {
|
||||
(
|
||||
self.colours.highlighted_border_style,
|
||||
|
@ -164,22 +162,62 @@ impl DiskTableWidget for Painter {
|
|||
(self.colours.border_style, self.colours.text_style)
|
||||
};
|
||||
|
||||
let title_base = if app_state.app_config_fields.show_table_scroll_position {
|
||||
let title_string = format!(
|
||||
" Disk ({} of {}) ",
|
||||
disk_widget_state
|
||||
.scroll_state
|
||||
.current_scroll_position
|
||||
.saturating_add(1),
|
||||
app_state.canvas_data.disk_data.len()
|
||||
);
|
||||
|
||||
if title_string.len() <= draw_loc.width as usize {
|
||||
title_string
|
||||
} else {
|
||||
" Disk ".to_string()
|
||||
}
|
||||
} else {
|
||||
" Disk ".to_string()
|
||||
};
|
||||
|
||||
let title = if app_state.is_expanded {
|
||||
const TITLE_BASE: &str = " Disk ── Esc to go back ";
|
||||
const ESCAPE_ENDING: &str = "── Esc to go back ";
|
||||
|
||||
let (chosen_title_base, expanded_title_base) = {
|
||||
let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING);
|
||||
|
||||
if temp_title_base.len() > draw_loc.width as usize {
|
||||
(
|
||||
" Disk ".to_string(),
|
||||
format!("{}{}", " Disk ".to_string(), ESCAPE_ENDING),
|
||||
)
|
||||
} else {
|
||||
(title_base, temp_title_base)
|
||||
}
|
||||
};
|
||||
|
||||
Spans::from(vec![
|
||||
Span::styled(" Disk ", self.colours.widget_title_style),
|
||||
Span::styled(chosen_title_base, self.colours.widget_title_style),
|
||||
Span::styled(
|
||||
format!(
|
||||
"─{}─ Esc to go back ",
|
||||
"─".repeat(usize::from(draw_loc.width).saturating_sub(
|
||||
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
|
||||
))
|
||||
"─".repeat(
|
||||
usize::from(draw_loc.width).saturating_sub(
|
||||
UnicodeSegmentation::graphemes(
|
||||
expanded_title_base.as_str(),
|
||||
true
|
||||
)
|
||||
.count()
|
||||
+ 2
|
||||
)
|
||||
)
|
||||
),
|
||||
border_style,
|
||||
),
|
||||
])
|
||||
} else {
|
||||
Spans::from(Span::styled(" Disk ", self.colours.widget_title_style))
|
||||
Spans::from(Span::styled(title_base, self.colours.widget_title_style))
|
||||
};
|
||||
|
||||
let disk_block = if draw_border {
|
||||
|
|
|
@ -207,6 +207,33 @@ impl ProcessTableWidget for Painter {
|
|||
(self.colours.border_style, self.colours.text_style)
|
||||
};
|
||||
|
||||
let title_base = if app_state.app_config_fields.show_table_scroll_position {
|
||||
if let Some(finalized_process_data) = app_state
|
||||
.canvas_data
|
||||
.finalized_process_data_map
|
||||
.get(&widget_id)
|
||||
{
|
||||
let title = format!(
|
||||
" Processes ({} of {}) ",
|
||||
proc_widget_state
|
||||
.scroll_state
|
||||
.current_scroll_position
|
||||
.saturating_add(1),
|
||||
finalized_process_data.len()
|
||||
);
|
||||
|
||||
if title.len() <= draw_loc.width as usize {
|
||||
title
|
||||
} else {
|
||||
" Processes ".to_string()
|
||||
}
|
||||
} else {
|
||||
" Processes ".to_string()
|
||||
}
|
||||
} else {
|
||||
" Processes ".to_string()
|
||||
};
|
||||
|
||||
let title = if app_state.is_expanded
|
||||
&& !proc_widget_state
|
||||
.process_search_state
|
||||
|
@ -214,21 +241,42 @@ impl ProcessTableWidget for Painter {
|
|||
.is_enabled
|
||||
&& !proc_widget_state.is_sort_open
|
||||
{
|
||||
const TITLE_BASE: &str = " Processes ── Esc to go back ";
|
||||
const ESCAPE_ENDING: &str = "── Esc to go back ";
|
||||
|
||||
let (chosen_title_base, expanded_title_base) = {
|
||||
let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING);
|
||||
|
||||
if temp_title_base.len() > draw_loc.width as usize {
|
||||
(
|
||||
" Processes ".to_string(),
|
||||
format!("{}{}", " Processes ".to_string(), ESCAPE_ENDING),
|
||||
)
|
||||
} else {
|
||||
(title_base, temp_title_base)
|
||||
}
|
||||
};
|
||||
|
||||
Spans::from(vec![
|
||||
Span::styled(" Processes ", self.colours.widget_title_style),
|
||||
Span::styled(chosen_title_base, self.colours.widget_title_style),
|
||||
Span::styled(
|
||||
format!(
|
||||
"─{}─ Esc to go back ",
|
||||
"─".repeat(usize::from(draw_loc.width).saturating_sub(
|
||||
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
|
||||
))
|
||||
"─".repeat(
|
||||
usize::from(draw_loc.width).saturating_sub(
|
||||
UnicodeSegmentation::graphemes(
|
||||
expanded_title_base.as_str(),
|
||||
true
|
||||
)
|
||||
.count()
|
||||
+ 2
|
||||
)
|
||||
)
|
||||
),
|
||||
border_style,
|
||||
),
|
||||
])
|
||||
} else {
|
||||
Spans::from(Span::styled(" Processes ", self.colours.widget_title_style))
|
||||
Spans::from(Span::styled(title_base, self.colours.widget_title_style))
|
||||
};
|
||||
|
||||
let process_block = if draw_border {
|
||||
|
@ -281,6 +329,7 @@ impl ProcessTableWidget for Painter {
|
|||
disabled,
|
||||
)
|
||||
});
|
||||
|
||||
let proc_table_state = &mut proc_widget_state.scroll_state.table_state;
|
||||
proc_table_state.select(Some(
|
||||
proc_widget_state
|
||||
|
@ -434,7 +483,6 @@ impl ProcessTableWidget for Painter {
|
|||
}
|
||||
});
|
||||
|
||||
// FIXME: gotop's "x out of y" thing is really nice to help keep track of the scroll position. Add to everything?
|
||||
f.render_stateful_widget(
|
||||
Table::new(process_headers.iter(), process_rows)
|
||||
.block(process_block)
|
||||
|
|
|
@ -42,8 +42,6 @@ impl TempTableWidget for Painter {
|
|||
) {
|
||||
let recalculate_column_widths = app_state.should_get_widget_bounds();
|
||||
if let Some(temp_widget_state) = app_state.temp_state.widget_states.get_mut(&widget_id) {
|
||||
let temp_sensor_data: &mut [Vec<String>] = &mut app_state.canvas_data.temp_sensor_data;
|
||||
|
||||
let table_gap = if draw_loc.height < TABLE_GAP_HEIGHT_LIMIT {
|
||||
0
|
||||
} else {
|
||||
|
@ -66,7 +64,7 @@ impl TempTableWidget for Painter {
|
|||
.current_scroll_position
|
||||
.saturating_sub(start_position),
|
||||
));
|
||||
let sliced_vec = &temp_sensor_data[start_position..];
|
||||
let sliced_vec = &app_state.canvas_data.temp_sensor_data[start_position..];
|
||||
|
||||
// Calculate widths
|
||||
let hard_widths = [None, None];
|
||||
|
@ -153,25 +151,62 @@ impl TempTableWidget for Painter {
|
|||
(self.colours.border_style, self.colours.text_style)
|
||||
};
|
||||
|
||||
let title_base = if app_state.app_config_fields.show_table_scroll_position {
|
||||
let title_string = format!(
|
||||
" Temperatures ({} of {}) ",
|
||||
temp_widget_state
|
||||
.scroll_state
|
||||
.current_scroll_position
|
||||
.saturating_add(1),
|
||||
app_state.canvas_data.temp_sensor_data.len()
|
||||
);
|
||||
|
||||
if title_string.len() <= draw_loc.width as usize {
|
||||
title_string
|
||||
} else {
|
||||
" Temperatures ".to_string()
|
||||
}
|
||||
} else {
|
||||
" Temperatures ".to_string()
|
||||
};
|
||||
|
||||
let title = if app_state.is_expanded {
|
||||
const TITLE_BASE: &str = " Temperatures ── Esc to go back ";
|
||||
const ESCAPE_ENDING: &str = "── Esc to go back ";
|
||||
|
||||
let (chosen_title_base, expanded_title_base) = {
|
||||
let temp_title_base = format!("{}{}", title_base, ESCAPE_ENDING);
|
||||
|
||||
if temp_title_base.len() > draw_loc.width as usize {
|
||||
(
|
||||
" Temperatures ".to_string(),
|
||||
format!("{}{}", " Temperatures ".to_string(), ESCAPE_ENDING),
|
||||
)
|
||||
} else {
|
||||
(title_base, temp_title_base)
|
||||
}
|
||||
};
|
||||
|
||||
Spans::from(vec![
|
||||
Span::styled(" Temperatures ", self.colours.widget_title_style),
|
||||
Span::styled(chosen_title_base, self.colours.widget_title_style),
|
||||
Span::styled(
|
||||
format!(
|
||||
"─{}─ Esc to go back ",
|
||||
"─".repeat(usize::from(draw_loc.width).saturating_sub(
|
||||
UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2
|
||||
))
|
||||
"─".repeat(
|
||||
usize::from(draw_loc.width).saturating_sub(
|
||||
UnicodeSegmentation::graphemes(
|
||||
expanded_title_base.as_str(),
|
||||
true
|
||||
)
|
||||
.count()
|
||||
+ 2
|
||||
)
|
||||
)
|
||||
),
|
||||
border_style,
|
||||
),
|
||||
])
|
||||
} else {
|
||||
Spans::from(Span::styled(
|
||||
" Temperatures ",
|
||||
self.colours.widget_title_style,
|
||||
))
|
||||
Spans::from(Span::styled(title_base, self.colours.widget_title_style))
|
||||
};
|
||||
|
||||
let temp_block = if draw_border {
|
||||
|
|
|
@ -135,6 +135,13 @@ Hides the spacing between table headers and entries.\n\n",
|
|||
"\
|
||||
Completely hides the time scaling from being shown.\n\n",
|
||||
);
|
||||
let show_table_scroll_position = Arg::with_name("show_table_scroll_position")
|
||||
.long("show_table_scroll_position")
|
||||
.help("Shows the scroll position tracker in table widgets")
|
||||
.long_help(
|
||||
"\
|
||||
Shows the list scroll position tracker in the widget title for table widgets.\n\n",
|
||||
);
|
||||
let left_legend = Arg::with_name("left_legend")
|
||||
.short("l")
|
||||
.long("left_legend")
|
||||
|
@ -366,6 +373,7 @@ Defaults to showing the process widget in tree mode.\n\n",
|
|||
.arg(hide_avg_cpu)
|
||||
.arg(hide_table_gap)
|
||||
.arg(hide_time)
|
||||
.arg(show_table_scroll_position)
|
||||
.arg(left_legend)
|
||||
// .arg(no_write)
|
||||
.arg(rate)
|
||||
|
|
|
@ -147,6 +147,9 @@ pub struct ConfigFlags {
|
|||
|
||||
#[builder(default, setter(strip_option))]
|
||||
pub tree: Option<bool>,
|
||||
|
||||
#[builder(default, setter(strip_option))]
|
||||
show_table_scroll_position: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
|
||||
|
@ -388,6 +391,7 @@ pub fn build_app(
|
|||
disable_click: get_disable_click(matches, config),
|
||||
// no_write: get_no_write(matches, config),
|
||||
no_write: false,
|
||||
show_table_scroll_position: get_show_table_scroll_position(matches, config),
|
||||
};
|
||||
|
||||
let used_widgets = UsedWidgets {
|
||||
|
@ -972,3 +976,14 @@ fn get_is_default_tree(matches: &clap::ArgMatches<'static>, config: &Config) ->
|
|||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn get_show_table_scroll_position(matches: &clap::ArgMatches<'static>, config: &Config) -> bool {
|
||||
if matches.is_present("show_table_scroll_position") {
|
||||
return true;
|
||||
} else if let Some(flags) = &config.flags {
|
||||
if let Some(show_table_scroll_position) = flags.show_table_scroll_position {
|
||||
return show_table_scroll_position;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue