Mostly done the base display and control logic for search, now need to implement search filter logic

This commit is contained in:
ClementTsang 2020-01-12 15:41:27 -05:00
parent ffafba2374
commit 2bb1333d04
4 changed files with 239 additions and 127 deletions

View File

@ -68,7 +68,7 @@ Run using `btm`.
#### General #### General
- `Ctrl-q`, `Ctrl-c` to quit. - `q`, `Ctrl-c` to quit. Note if you are currently in the search widget, `q` will not work so you can still type.
- `Ctrl-r` to reset the screen and reset all collected data. - `Ctrl-r` to reset the screen and reset all collected data.
@ -102,7 +102,7 @@ Run using `btm`.
- `Tab` to group together processes with the same name. Disables PID sorting. `dd` will now kill all processes covered by that name. - `Tab` to group together processes with the same name. Disables PID sorting. `dd` will now kill all processes covered by that name.
- `Ctrl-f` or `/` to toggle a search box for finding a process. By default this searches for process name, press `p` or `n` within the search bar to switch between searching for PID and name respectively. - `Ctrl-f` or `/` to toggle a search box for finding a process. Press `Ctrl-p` or `Ctrl-n` within the search bar widget to switch between searching for PID and name respectively. Press `Esc` or `Ctrl-f` to close it.
### Mouse actions ### Mouse actions

View File

@ -14,6 +14,7 @@ pub enum ApplicationPosition {
Temp, Temp,
Network, Network,
Process, Process,
ProcessSearch,
} }
#[derive(Debug)] #[derive(Debug)]
@ -58,7 +59,8 @@ pub struct App {
pub canvas_data: canvas::CanvasData, pub canvas_data: canvas::CanvasData,
enable_grouping: bool, enable_grouping: bool,
enable_searching: bool, enable_searching: bool,
current_search_phrase: String, current_search_query: String,
searching_pid: bool,
} }
impl App { impl App {
@ -99,7 +101,8 @@ impl App {
canvas_data: canvas::CanvasData::default(), canvas_data: canvas::CanvasData::default(),
enable_grouping: false, enable_grouping: false,
enable_searching: false, enable_searching: false,
current_search_phrase: String::default(), current_search_query: String::default(),
searching_pid: false,
} }
} }
@ -107,11 +110,29 @@ impl App {
self.reset_multi_tap_keys(); self.reset_multi_tap_keys();
self.show_help = false; self.show_help = false;
self.show_dd = false; self.show_dd = false;
if self.enable_searching {
self.current_application_position = ApplicationPosition::Process;
self.enable_searching = false; self.enable_searching = false;
}
self.current_search_query = String::new();
self.searching_pid = false;
self.to_delete_process_list = None; self.to_delete_process_list = None;
self.dd_err = None; self.dd_err = None;
} }
pub fn on_esc(&mut self) {
self.reset_multi_tap_keys();
if self.is_in_dialog() {
self.show_help = false;
self.show_dd = false;
self.to_delete_process_list = None;
self.dd_err = None;
} else if self.enable_searching {
self.current_application_position = ApplicationPosition::Process;
self.enable_searching = false;
}
}
fn reset_multi_tap_keys(&mut self) { fn reset_multi_tap_keys(&mut self) {
self.awaiting_second_char = false; self.awaiting_second_char = false;
self.second_char = ' '; self.second_char = ' ';
@ -144,8 +165,19 @@ impl App {
pub fn toggle_searching(&mut self) { pub fn toggle_searching(&mut self) {
if !self.is_in_dialog() { if !self.is_in_dialog() {
if let ApplicationPosition::Process = self.current_application_position { match self.current_application_position {
self.enable_searching = !(self.enable_searching); ApplicationPosition::Process | ApplicationPosition::ProcessSearch => {
if self.enable_searching {
// Toggle off
self.enable_searching = false;
self.current_application_position = ApplicationPosition::Process;
} else {
// Toggle on
self.enable_searching = true;
self.current_application_position = ApplicationPosition::ProcessSearch;
}
}
_ => {}
} }
} }
} }
@ -154,6 +186,38 @@ impl App {
self.enable_searching self.enable_searching
} }
pub fn is_in_search_widget(&self) -> bool {
if let ApplicationPosition::ProcessSearch = self.current_application_position {
true
} else {
false
}
}
pub fn search_with_pid(&mut self) {
if !self.is_in_dialog() && self.is_searching() {
if let ApplicationPosition::ProcessSearch = self.current_application_position {
self.searching_pid = true;
}
}
}
pub fn search_with_name(&mut self) {
if !self.is_in_dialog() && self.is_searching() {
if let ApplicationPosition::ProcessSearch = self.current_application_position {
self.searching_pid = false;
}
}
}
pub fn is_searching_with_pid(&self) -> bool {
self.searching_pid
}
pub fn get_current_search_phrase(&self) -> &String {
&self.current_search_query
}
/// One of two functions allowed to run while in a dialog... /// One of two functions allowed to run while in a dialog...
pub fn on_enter(&mut self) { pub fn on_enter(&mut self) {
if self.show_dd { if self.show_dd {
@ -171,6 +235,12 @@ impl App {
} }
} }
pub fn on_backspace(&mut self) {
if let ApplicationPosition::ProcessSearch = self.current_application_position {
self.current_search_query.pop();
}
}
pub fn on_char_key(&mut self, caught_char: char) { pub fn on_char_key(&mut self, caught_char: char) {
// Forbid any char key presses when showing a dialog box... // Forbid any char key presses when showing a dialog box...
if !self.is_in_dialog() { if !self.is_in_dialog() {
@ -183,18 +253,18 @@ impl App {
} }
self.last_key_press = current_key_press_inst; self.last_key_press = current_key_press_inst;
if let ApplicationPosition::ProcessSearch = self.current_application_position {
self.current_search_query.push(caught_char);
} else {
match caught_char { match caught_char {
'/' => { '/' => {
if let ApplicationPosition::Process = self.current_application_position {
self.toggle_searching(); self.toggle_searching();
} }
}
'd' => { 'd' => {
if let ApplicationPosition::Process = self.current_application_position { if let ApplicationPosition::Process = self.current_application_position {
if self.awaiting_second_char && self.second_char == 'd' { if self.awaiting_second_char && self.second_char == 'd' {
self.awaiting_second_char = false; self.awaiting_second_char = false;
self.second_char = ' '; self.second_char = ' ';
let current_process = if self.is_grouped() { let current_process = if self.is_grouped() {
let mut res: Vec<ConvertedProcessData> = Vec::new(); let mut res: Vec<ConvertedProcessData> = Vec::new();
for pid in &self.canvas_data.grouped_process_data for pid in &self.canvas_data.grouped_process_data
@ -206,7 +276,6 @@ impl App {
.process_data .process_data
.iter() .iter()
.find(|p| p.pid == *pid); .find(|p| p.pid == *pid);
if let Some(process) = result { if let Some(process) = result {
res.push((*process).clone()); res.push((*process).clone());
} }
@ -302,12 +371,12 @@ impl App {
} }
_ => {} _ => {}
} }
if self.awaiting_second_char && caught_char != self.second_char { if self.awaiting_second_char && caught_char != self.second_char {
self.awaiting_second_char = false; self.awaiting_second_char = false;
} }
} }
} }
}
pub fn kill_highlighted_process(&mut self) -> Result<()> { pub fn kill_highlighted_process(&mut self) -> Result<()> {
// Technically unnecessary but this is a good check... // Technically unnecessary but this is a good check...
@ -332,13 +401,15 @@ impl App {
// CPU -(down)> MEM // CPU -(down)> MEM
// MEM -(down)> Network, -(right)> TEMP // MEM -(down)> Network, -(right)> TEMP
// TEMP -(down)> Disk, -(left)> MEM, -(up)> CPU // TEMP -(down)> Disk, -(left)> MEM, -(up)> CPU
// Disk -(down)> Processes, -(left)> MEM, -(up)> TEMP // Disk -(down)> Processes OR PROC_SEARCH, -(left)> MEM, -(up)> TEMP
// Network -(up)> MEM, -(right)> PROC // Network -(up)> MEM, -(right)> PROC
// PROC -(up)> Disk, -(left)> Network // PROC -(up)> Disk OR PROC_SEARCH if enabled, -(left)> Network
// PROC_SEARCH -(up)> Disk, -(down)> PROC, -(left)> Network
pub fn on_left(&mut self) { pub fn on_left(&mut self) {
if !self.is_in_dialog() { if !self.is_in_dialog() {
self.current_application_position = match self.current_application_position { self.current_application_position = match self.current_application_position {
ApplicationPosition::Process => ApplicationPosition::Network, ApplicationPosition::Process => ApplicationPosition::Network,
ApplicationPosition::ProcessSearch => ApplicationPosition::Network,
ApplicationPosition::Disk => ApplicationPosition::Mem, ApplicationPosition::Disk => ApplicationPosition::Mem,
ApplicationPosition::Temp => ApplicationPosition::Mem, ApplicationPosition::Temp => ApplicationPosition::Mem,
_ => self.current_application_position, _ => self.current_application_position,
@ -363,7 +434,14 @@ impl App {
self.current_application_position = match self.current_application_position { self.current_application_position = match self.current_application_position {
ApplicationPosition::Mem => ApplicationPosition::Cpu, ApplicationPosition::Mem => ApplicationPosition::Cpu,
ApplicationPosition::Network => ApplicationPosition::Mem, ApplicationPosition::Network => ApplicationPosition::Mem,
ApplicationPosition::Process => ApplicationPosition::Disk, ApplicationPosition::Process => {
if self.is_searching() {
ApplicationPosition::ProcessSearch
} else {
ApplicationPosition::Disk
}
}
ApplicationPosition::ProcessSearch => ApplicationPosition::Disk,
ApplicationPosition::Temp => ApplicationPosition::Cpu, ApplicationPosition::Temp => ApplicationPosition::Cpu,
ApplicationPosition::Disk => ApplicationPosition::Temp, ApplicationPosition::Disk => ApplicationPosition::Temp,
_ => self.current_application_position, _ => self.current_application_position,
@ -378,7 +456,14 @@ impl App {
ApplicationPosition::Cpu => ApplicationPosition::Mem, ApplicationPosition::Cpu => ApplicationPosition::Mem,
ApplicationPosition::Mem => ApplicationPosition::Network, ApplicationPosition::Mem => ApplicationPosition::Network,
ApplicationPosition::Temp => ApplicationPosition::Disk, ApplicationPosition::Temp => ApplicationPosition::Disk,
ApplicationPosition::Disk => ApplicationPosition::Process, ApplicationPosition::Disk => {
if self.is_searching() {
ApplicationPosition::ProcessSearch
} else {
ApplicationPosition::Process
}
}
ApplicationPosition::ProcessSearch => ApplicationPosition::Process,
_ => self.current_application_position, _ => self.current_application_position,
}; };
self.reset_multi_tap_keys(); self.reset_multi_tap_keys();

View File

@ -7,7 +7,7 @@ use std::cmp::max;
use tui::{ use tui::{
backend, backend,
layout::{Alignment, Constraint, Direction, Layout, Rect}, layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style}, style::{Color, Style},
terminal::Frame, terminal::Frame,
widgets::{Axis, Block, Borders, Chart, Dataset, Marker, Paragraph, Row, Table, Text, Widget}, widgets::{Axis, Block, Borders, Chart, Dataset, Marker, Paragraph, Row, Table, Text, Widget},
Terminal, Terminal,
@ -17,6 +17,7 @@ const TEXT_COLOUR: Color = Color::Gray;
const GRAPH_COLOUR: Color = Color::Gray; const GRAPH_COLOUR: Color = Color::Gray;
const BORDER_STYLE_COLOUR: Color = Color::Gray; const BORDER_STYLE_COLOUR: Color = Color::Gray;
const HIGHLIGHTED_BORDER_STYLE_COLOUR: Color = Color::LightBlue; const HIGHLIGHTED_BORDER_STYLE_COLOUR: Color = Color::LightBlue;
const TABLE_HEADER_COLOUR: Color = Color::LightBlue;
const GOLDEN_RATIO: f32 = 0.618_034; // Approx, good enough for use (also Clippy gets mad if it's too long) const GOLDEN_RATIO: f32 = 0.618_034; // Approx, good enough for use (also Clippy gets mad if it's too long)
// Headers // Headers
@ -30,7 +31,7 @@ const FORCE_MIN_THRESHOLD: usize = 5;
lazy_static! { lazy_static! {
static ref HELP_TEXT: [Text<'static>; 17] = [ static ref HELP_TEXT: [Text<'static>; 17] = [
Text::raw("\nGeneral Keybindings\n"), Text::raw("\nGeneral Keybindings\n"),
Text::raw("Ctrl-q, Ctrl-c to quit.\n"), Text::raw("q, Ctrl-c to quit. Note if you are currently in the search widget, `q` will not work.\n"),
Text::raw("Ctrl-r to reset all data.\n"), Text::raw("Ctrl-r to reset all data.\n"),
Text::raw("f to toggle freezing and unfreezing the display.\n"), Text::raw("f to toggle freezing and unfreezing the display.\n"),
Text::raw( Text::raw(
@ -46,7 +47,7 @@ lazy_static! {
Text::raw("p to sort by PID.\n"), Text::raw("p to sort by PID.\n"),
Text::raw("n to sort by process name.\n"), Text::raw("n to sort by process name.\n"),
Text::raw("Tab to group together processes with the same name.\n"), Text::raw("Tab to group together processes with the same name.\n"),
Text::raw("Ctrl-f or / to toggle searching for a process. Use p and n to toggle between searching for PID and name.\n"), Text::raw("Ctrl-f or / to toggle searching for a process. Use Ctrl-p and Ctrl-n to toggle between searching for PID and name.\n"),
Text::raw("\nFor startup flags, type in \"btm -h\".") Text::raw("\nFor startup flags, type in \"btm -h\".")
]; ];
static ref COLOUR_LIST: Vec<Color> = gen_n_colours(constants::NUM_COLOURS); static ref COLOUR_LIST: Vec<Color> = gen_n_colours(constants::NUM_COLOURS);
@ -500,7 +501,7 @@ fn draw_cpu_legend<B: backend::Backend>(
_ => *CANVAS_BORDER_STYLE, _ => *CANVAS_BORDER_STYLE,
}, },
)) ))
.header_style(Style::default().fg(Color::LightBlue)) .header_style(Style::default().fg(TABLE_HEADER_COLOUR))
.widths( .widths(
&(intrinsic_widths &(intrinsic_widths
.into_iter() .into_iter()
@ -692,7 +693,7 @@ fn draw_network_labels<B: backend::Backend>(
_ => *CANVAS_BORDER_STYLE, _ => *CANVAS_BORDER_STYLE,
}, },
)) ))
.header_style(Style::default().fg(Color::LightBlue)) .header_style(Style::default().fg(TABLE_HEADER_COLOUR))
.widths( .widths(
&(intrinsic_widths &(intrinsic_widths
.into_iter() .into_iter()
@ -759,7 +760,7 @@ fn draw_temp_table<B: backend::Backend>(
_ => *CANVAS_BORDER_STYLE, _ => *CANVAS_BORDER_STYLE,
}), }),
) )
.header_style(Style::default().fg(Color::LightBlue)) .header_style(Style::default().fg(TABLE_HEADER_COLOUR))
.widths( .widths(
&(intrinsic_widths &(intrinsic_widths
.into_iter() .into_iter()
@ -824,11 +825,7 @@ fn draw_disk_table<B: backend::Backend>(
_ => *CANVAS_BORDER_STYLE, _ => *CANVAS_BORDER_STYLE,
}), }),
) )
.header_style( .header_style(Style::default().fg(TABLE_HEADER_COLOUR))
Style::default()
.fg(Color::LightBlue)
.modifier(Modifier::BOLD),
)
.widths( .widths(
&(intrinsic_widths &(intrinsic_widths
.into_iter() .into_iter()
@ -841,7 +838,28 @@ fn draw_disk_table<B: backend::Backend>(
fn draw_search_field<B: backend::Backend>( fn draw_search_field<B: backend::Backend>(
f: &mut Frame<B>, app_state: &mut app::App, draw_loc: Rect, f: &mut Frame<B>, app_state: &mut app::App, draw_loc: Rect,
) { ) {
// TODO: Search field let search_text = [
if app_state.is_searching_with_pid() {
Text::styled("\nPID: ", Style::default().fg(TABLE_HEADER_COLOUR))
} else {
Text::styled("\nName: ", Style::default().fg(TABLE_HEADER_COLOUR))
},
Text::raw(app_state.get_current_search_phrase()),
];
Paragraph::new(search_text.iter())
.block(
Block::default()
.title("Search (Ctrl-p and Ctrl-n to switch search types, Esc or Ctrl-f to close)")
.borders(Borders::ALL)
.border_style(match app_state.current_application_position {
app::ApplicationPosition::ProcessSearch => *CANVAS_HIGHLIGHTED_BORDER_STYLE,
_ => *CANVAS_BORDER_STYLE,
}),
)
.style(Style::default().fg(Color::Gray))
.alignment(Alignment::Left)
.wrap(true) // TODO: We want this to keep going right... slicing?
.render(f, draw_loc);
} }
fn draw_processes_table<B: backend::Backend>( fn draw_processes_table<B: backend::Backend>(
@ -952,7 +970,7 @@ fn draw_processes_table<B: backend::Backend>(
_ => *CANVAS_BORDER_STYLE, _ => *CANVAS_BORDER_STYLE,
}), }),
) )
.header_style(Style::default().fg(Color::LightBlue)) .header_style(Style::default().fg(TABLE_HEADER_COLOUR))
.widths( .widths(
&(intrinsic_widths &(intrinsic_widths
.into_iter() .into_iter()

View File

@ -212,27 +212,36 @@ fn main() -> error::Result<()> {
Event::KeyInput(event) => { Event::KeyInput(event) => {
if event.modifiers.is_empty() { if event.modifiers.is_empty() {
// If only a code, and no modifiers, don't bother... // If only a code, and no modifiers, don't bother...
// Required to catch for while typing
if event.code == KeyCode::Char('q') && !app.is_in_search_widget() {
break;
}
match event.code { match event.code {
KeyCode::End => app.skip_to_last(), KeyCode::End => app.skip_to_last(),
KeyCode::Home => app.skip_to_first(), KeyCode::Home => app.skip_to_first(),
KeyCode::Up => app.decrement_position_count(), KeyCode::Up => app.decrement_position_count(),
KeyCode::Down => app.increment_position_count(), KeyCode::Down => app.increment_position_count(),
KeyCode::Char(character) => app.on_char_key(character), KeyCode::Char(character) => app.on_char_key(character),
KeyCode::Esc => app.reset(), KeyCode::Esc => app.on_esc(),
KeyCode::Enter => app.on_enter(), KeyCode::Enter => app.on_enter(),
KeyCode::Tab => app.on_tab(), KeyCode::Tab => app.on_tab(),
KeyCode::Backspace => app.on_backspace(),
_ => {} _ => {}
} }
} else { } else {
// Otherwise, track the modifier as well... // Otherwise, track the modifier as well...
if let KeyModifiers::CONTROL = event.modifiers { if let KeyModifiers::CONTROL = event.modifiers {
match event.code { match event.code {
KeyCode::Char('c') | KeyCode::Char('q') => break, KeyCode::Char('c') => break,
KeyCode::Char('f') => app.on_char_key('/'), // Note that this is fine for now, assuming '/' does not do anything other than search. KeyCode::Char('f') => app.toggle_searching(), // Note that this is fine for now, assuming '/' does not do anything other than search.
KeyCode::Left | KeyCode::Char('h') => app.on_left(), KeyCode::Left | KeyCode::Char('h') => app.on_left(),
KeyCode::Right | KeyCode::Char('l') => app.on_right(), KeyCode::Right | KeyCode::Char('l') => app.on_right(),
KeyCode::Up | KeyCode::Char('k') => app.on_up(), KeyCode::Up | KeyCode::Char('k') => app.on_up(),
KeyCode::Down | KeyCode::Char('j') => app.on_down(), KeyCode::Down | KeyCode::Char('j') => app.on_down(),
KeyCode::Char('p') => app.search_with_pid(),
KeyCode::Char('n') => app.search_with_name(),
KeyCode::Char('r') => { KeyCode::Char('r') => {
if rtx.send(ResetEvent::Reset).is_ok() { if rtx.send(ResetEvent::Reset).is_ok() {
app.reset(); app.reset();