Made search look prettier and organized it a bit... also added match whole word functionality.
This commit is contained in:
parent
1360296b4e
commit
fc3a2e69ec
152
src/app.rs
152
src/app.rs
|
@ -46,8 +46,64 @@ pub struct AppScrollWidgetState {
|
|||
pub widget_scroll_position: i64,
|
||||
}
|
||||
|
||||
/// AppSearchState only deals with the search's state.
|
||||
pub struct AppSearchState {}
|
||||
/// AppSearchState only deals with the search's current settings and state.
|
||||
pub struct AppSearchState {
|
||||
current_search_query: String,
|
||||
searching_pid: bool,
|
||||
ignore_case: bool,
|
||||
current_regex: std::result::Result<regex::Regex, regex::Error>,
|
||||
current_cursor_position: usize,
|
||||
match_word: bool,
|
||||
use_regex: bool,
|
||||
}
|
||||
|
||||
impl Default for AppSearchState {
|
||||
fn default() -> Self {
|
||||
AppSearchState {
|
||||
current_search_query: String::default(),
|
||||
searching_pid: false,
|
||||
ignore_case: false,
|
||||
current_regex: BASE_REGEX.clone(),
|
||||
current_cursor_position: 0,
|
||||
match_word: false,
|
||||
use_regex: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppSearchState {
|
||||
pub fn toggle_ignore_case(&mut self) {
|
||||
self.ignore_case = !self.ignore_case;
|
||||
}
|
||||
|
||||
pub fn toggle_search_whole_word(&mut self) {
|
||||
self.match_word = !self.match_word;
|
||||
}
|
||||
|
||||
pub fn toggle_search_regex(&mut self) {
|
||||
self.use_regex = !self.use_regex;
|
||||
}
|
||||
|
||||
pub fn toggle_search_with_pid(&mut self) {
|
||||
self.searching_pid = !self.searching_pid;
|
||||
}
|
||||
|
||||
pub fn is_ignoring_case(&self) -> bool {
|
||||
self.ignore_case
|
||||
}
|
||||
|
||||
pub fn is_searching_whole_word(&self) -> bool {
|
||||
self.match_word
|
||||
}
|
||||
|
||||
pub fn is_searching_with_regex(&self) -> bool {
|
||||
self.use_regex
|
||||
}
|
||||
|
||||
pub fn is_searching_with_pid(&self) -> bool {
|
||||
self.searching_pid
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: [OPT] Group like fields together... this is kinda gross to step through
|
||||
pub struct App {
|
||||
|
@ -84,12 +140,8 @@ pub struct App {
|
|||
pub canvas_data: canvas::DisplayableData,
|
||||
enable_grouping: bool,
|
||||
enable_searching: bool,
|
||||
current_search_query: String,
|
||||
searching_pid: bool,
|
||||
pub ignore_case: bool,
|
||||
current_regex: std::result::Result<regex::Regex, regex::Error>,
|
||||
current_cursor_position: usize,
|
||||
pub data_collection: DataCollection,
|
||||
pub search_state: AppSearchState,
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
@ -130,12 +182,8 @@ impl App {
|
|||
canvas_data: canvas::DisplayableData::default(),
|
||||
enable_grouping: false,
|
||||
enable_searching: false,
|
||||
current_search_query: String::default(),
|
||||
searching_pid: false,
|
||||
ignore_case: false,
|
||||
current_regex: BASE_REGEX.clone(), //TODO: [OPT] seems like a thing we can switch to lifetimes to avoid cloning
|
||||
current_cursor_position: 0,
|
||||
data_collection: DataCollection::default(),
|
||||
search_state: AppSearchState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,8 +195,8 @@ impl App {
|
|||
self.current_widget_selected = WidgetPosition::Process;
|
||||
self.enable_searching = false;
|
||||
}
|
||||
self.current_search_query = String::new();
|
||||
self.searching_pid = false;
|
||||
self.search_state.current_search_query = String::new();
|
||||
self.search_state.searching_pid = false;
|
||||
self.to_delete_process_list = None;
|
||||
self.dd_err = None;
|
||||
}
|
||||
|
@ -189,7 +237,13 @@ impl App {
|
|||
match self.current_widget_selected {
|
||||
WidgetPosition::Process => self.toggle_grouping(),
|
||||
WidgetPosition::Disk => {}
|
||||
WidgetPosition::ProcessSearch => self.toggle_ignore_case(),
|
||||
WidgetPosition::ProcessSearch => {
|
||||
if self.search_state.is_searching_with_pid() {
|
||||
self.search_with_name();
|
||||
} else {
|
||||
self.search_with_pid();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -226,7 +280,7 @@ impl App {
|
|||
pub fn search_with_pid(&mut self) {
|
||||
if !self.is_in_dialog() && self.is_searching() {
|
||||
if let WidgetPosition::ProcessSearch = self.current_widget_selected {
|
||||
self.searching_pid = true;
|
||||
self.search_state.searching_pid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -234,43 +288,50 @@ impl App {
|
|||
pub fn search_with_name(&mut self) {
|
||||
if !self.is_in_dialog() && self.is_searching() {
|
||||
if let WidgetPosition::ProcessSearch = self.current_widget_selected {
|
||||
self.searching_pid = false;
|
||||
self.search_state.searching_pid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_searching_with_pid(&self) -> bool {
|
||||
self.searching_pid
|
||||
}
|
||||
|
||||
pub fn get_current_search_query(&self) -> &String {
|
||||
&self.current_search_query
|
||||
&self.search_state.current_search_query
|
||||
}
|
||||
|
||||
pub fn toggle_ignore_case(&mut self) {
|
||||
if !self.is_in_dialog() && self.is_searching() {
|
||||
if let WidgetPosition::ProcessSearch = self.current_widget_selected {
|
||||
self.ignore_case = !self.ignore_case;
|
||||
self.search_state.toggle_ignore_case();
|
||||
self.update_regex();
|
||||
self.update_process_gui = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_regex(&mut self) {
|
||||
self.current_regex = if self.current_search_query.is_empty() {
|
||||
pub fn update_regex(&mut self) {
|
||||
self.search_state.current_regex = if self.search_state.current_search_query.is_empty() {
|
||||
BASE_REGEX.clone()
|
||||
} else if self.ignore_case {
|
||||
regex::Regex::new(&(format!("(?i){}", self.current_search_query)))
|
||||
} else {
|
||||
regex::Regex::new(&(self.current_search_query))
|
||||
let mut final_regex_string = self.search_state.current_search_query.clone();
|
||||
|
||||
if !self.search_state.is_searching_with_regex() {
|
||||
final_regex_string = regex::escape(&final_regex_string);
|
||||
}
|
||||
|
||||
if self.search_state.is_searching_whole_word() {
|
||||
final_regex_string = format!("^{}$", final_regex_string);
|
||||
}
|
||||
if self.search_state.is_ignoring_case() {
|
||||
final_regex_string = format!("(?i){}", final_regex_string);
|
||||
}
|
||||
|
||||
regex::Regex::new(&final_regex_string)
|
||||
};
|
||||
self.previous_process_position = 0;
|
||||
self.currently_selected_process_position = 0;
|
||||
}
|
||||
|
||||
pub fn get_cursor_position(&self) -> usize {
|
||||
self.current_cursor_position
|
||||
self.search_state.current_cursor_position
|
||||
}
|
||||
|
||||
/// One of two functions allowed to run while in a dialog...
|
||||
|
@ -292,10 +353,11 @@ impl App {
|
|||
|
||||
pub fn on_backspace(&mut self) {
|
||||
if let WidgetPosition::ProcessSearch = self.current_widget_selected {
|
||||
if self.current_cursor_position > 0 {
|
||||
self.current_cursor_position -= 1;
|
||||
self.current_search_query
|
||||
.remove(self.current_cursor_position);
|
||||
if self.search_state.current_cursor_position > 0 {
|
||||
self.search_state.current_cursor_position -= 1;
|
||||
self.search_state
|
||||
.current_search_query
|
||||
.remove(self.search_state.current_cursor_position);
|
||||
|
||||
self.update_regex();
|
||||
self.update_process_gui = true;
|
||||
|
@ -304,7 +366,7 @@ impl App {
|
|||
}
|
||||
|
||||
pub fn get_current_regex_matcher(&self) -> &std::result::Result<regex::Regex, regex::Error> {
|
||||
&self.current_regex
|
||||
&self.search_state.current_regex
|
||||
}
|
||||
|
||||
pub fn on_up_key(&mut self) {
|
||||
|
@ -328,8 +390,8 @@ impl App {
|
|||
pub fn on_left_key(&mut self) {
|
||||
if !self.is_in_dialog() {
|
||||
if let WidgetPosition::ProcessSearch = self.current_widget_selected {
|
||||
if self.current_cursor_position > 0 {
|
||||
self.current_cursor_position -= 1;
|
||||
if self.search_state.current_cursor_position > 0 {
|
||||
self.search_state.current_cursor_position -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -338,8 +400,10 @@ impl App {
|
|||
pub fn on_right_key(&mut self) {
|
||||
if !self.is_in_dialog() {
|
||||
if let WidgetPosition::ProcessSearch = self.current_widget_selected {
|
||||
if self.current_cursor_position < self.current_search_query.len() {
|
||||
self.current_cursor_position += 1;
|
||||
if self.search_state.current_cursor_position
|
||||
< self.search_state.current_search_query.len()
|
||||
{
|
||||
self.search_state.current_cursor_position += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -348,7 +412,7 @@ impl App {
|
|||
pub fn skip_cursor_beginning(&mut self) {
|
||||
if !self.is_in_dialog() {
|
||||
if let WidgetPosition::ProcessSearch = self.current_widget_selected {
|
||||
self.current_cursor_position = 0;
|
||||
self.search_state.current_cursor_position = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -356,7 +420,8 @@ impl App {
|
|||
pub fn skip_cursor_end(&mut self) {
|
||||
if !self.is_in_dialog() {
|
||||
if let WidgetPosition::ProcessSearch = self.current_widget_selected {
|
||||
self.current_cursor_position = self.current_search_query.len();
|
||||
self.search_state.current_cursor_position =
|
||||
self.search_state.current_search_query.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -374,9 +439,10 @@ impl App {
|
|||
self.last_key_press = current_key_press_inst;
|
||||
|
||||
if let WidgetPosition::ProcessSearch = self.current_widget_selected {
|
||||
self.current_search_query
|
||||
.insert(self.current_cursor_position, caught_char);
|
||||
self.current_cursor_position += 1;
|
||||
self.search_state
|
||||
.current_search_query
|
||||
.insert(self.search_state.current_cursor_position, caught_char);
|
||||
self.search_state.current_cursor_position += 1;
|
||||
|
||||
self.update_regex();
|
||||
|
||||
|
|
|
@ -376,10 +376,24 @@ pub fn draw_data<B: backend::Backend>(
|
|||
let processes_chunk = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(0)
|
||||
.constraints([Constraint::Percentage(25), Constraint::Percentage(75)].as_ref())
|
||||
.constraints(
|
||||
if (bottom_chunks[1].height as f64 * 0.25) as u16 >= 4 {
|
||||
[Constraint::Percentage(75), Constraint::Percentage(25)]
|
||||
} else {
|
||||
let required = if bottom_chunks[1].height < 10 {
|
||||
bottom_chunks[1].height / 2
|
||||
} else {
|
||||
5
|
||||
};
|
||||
let remaining = bottom_chunks[1].height - required;
|
||||
[Constraint::Length(remaining), Constraint::Length(required)]
|
||||
}
|
||||
.as_ref(),
|
||||
)
|
||||
.split(bottom_chunks[1]);
|
||||
draw_search_field(&mut f, app_state, processes_chunk[0]);
|
||||
draw_processes_table(&mut f, app_state, processes_chunk[1]);
|
||||
|
||||
draw_processes_table(&mut f, app_state, processes_chunk[0]);
|
||||
draw_search_field(&mut f, app_state, processes_chunk[1]);
|
||||
} else {
|
||||
draw_processes_table(&mut f, app_state, bottom_chunks[1]);
|
||||
}
|
||||
|
@ -873,7 +887,7 @@ fn draw_disk_table<B: backend::Backend>(
|
|||
fn draw_search_field<B: backend::Backend>(
|
||||
f: &mut Frame<B>, app_state: &mut app::App, draw_loc: Rect,
|
||||
) {
|
||||
let width = draw_loc.width - 18; // TODO [SEARCH] this is hard-coded... ew
|
||||
let width = max(0, draw_loc.width as i64 - 20) as u64; // TODO [SEARCH] this is hard-coded... ew
|
||||
let query = app_state.get_current_search_query();
|
||||
let shrunk_query = if query.len() < width as usize {
|
||||
query
|
||||
|
@ -909,36 +923,66 @@ fn draw_search_field<B: backend::Backend>(
|
|||
}
|
||||
}
|
||||
|
||||
let mut search_text = vec![
|
||||
if app_state.is_searching_with_pid() {
|
||||
Text::styled("\nPID", Style::default().fg(TABLE_HEADER_COLOUR))
|
||||
let mut search_text = vec![if app_state.search_state.is_searching_with_pid() {
|
||||
Text::styled(
|
||||
"Search by PID (Tab for Name): ",
|
||||
Style::default().fg(TABLE_HEADER_COLOUR),
|
||||
)
|
||||
} else {
|
||||
Text::styled(
|
||||
"Search by Name (Tab for PID): ",
|
||||
Style::default().fg(TABLE_HEADER_COLOUR),
|
||||
)
|
||||
}];
|
||||
|
||||
// Text options shamelessly stolen from VS Code.
|
||||
let option_text = vec![
|
||||
Text::styled("\n\n", Style::default().fg(TABLE_HEADER_COLOUR)),
|
||||
Text::styled(
|
||||
"Match Case (Alt+C)",
|
||||
Style::default().fg(TABLE_HEADER_COLOUR),
|
||||
),
|
||||
if !app_state.search_state.is_ignoring_case() {
|
||||
Text::styled("[*]", Style::default().fg(TABLE_HEADER_COLOUR))
|
||||
} else {
|
||||
Text::styled("\nName", Style::default().fg(TABLE_HEADER_COLOUR))
|
||||
Text::styled("[ ]", Style::default().fg(TABLE_HEADER_COLOUR))
|
||||
},
|
||||
if app_state.ignore_case {
|
||||
Text::styled(" (Ignore Case): ", Style::default().fg(TABLE_HEADER_COLOUR))
|
||||
Text::styled(" ", Style::default().fg(TABLE_HEADER_COLOUR)),
|
||||
Text::styled(
|
||||
"Match Whole Word (Alt+W)",
|
||||
Style::default().fg(TABLE_HEADER_COLOUR),
|
||||
),
|
||||
if app_state.search_state.is_searching_whole_word() {
|
||||
Text::styled("[*]", Style::default().fg(TABLE_HEADER_COLOUR))
|
||||
} else {
|
||||
Text::styled(": ", Style::default().fg(TABLE_HEADER_COLOUR))
|
||||
Text::styled("[ ]", Style::default().fg(TABLE_HEADER_COLOUR))
|
||||
},
|
||||
Text::styled(" ", Style::default().fg(TABLE_HEADER_COLOUR)),
|
||||
Text::styled(
|
||||
"Use Regex (Alt+R)",
|
||||
Style::default().fg(TABLE_HEADER_COLOUR),
|
||||
),
|
||||
if app_state.search_state.is_searching_with_regex() {
|
||||
Text::styled("[*]", Style::default().fg(TABLE_HEADER_COLOUR))
|
||||
} else {
|
||||
Text::styled("[ ]", Style::default().fg(TABLE_HEADER_COLOUR))
|
||||
},
|
||||
];
|
||||
|
||||
search_text.extend(query_with_cursor);
|
||||
search_text.extend(option_text);
|
||||
|
||||
// TODO: [SEARCH] Gotta make this easier to understand... it's pretty ugly cramming controls like this
|
||||
Paragraph::new(search_text.iter())
|
||||
.block(
|
||||
Block::default()
|
||||
.title("Search (Esc or Ctrl-f to close)")
|
||||
.borders(Borders::ALL)
|
||||
.border_style(if app_state.get_current_regex_matcher().is_err() {
|
||||
Style::default().fg(Color::Red)
|
||||
} else {
|
||||
match app_state.current_widget_selected {
|
||||
app::WidgetPosition::ProcessSearch => *CANVAS_HIGHLIGHTED_BORDER_STYLE,
|
||||
_ => *CANVAS_BORDER_STYLE,
|
||||
}
|
||||
}),
|
||||
)
|
||||
.block(Block::default().borders(Borders::ALL).border_style(
|
||||
if app_state.get_current_regex_matcher().is_err() {
|
||||
Style::default().fg(Color::Red)
|
||||
} else {
|
||||
match app_state.current_widget_selected {
|
||||
app::WidgetPosition::ProcessSearch => *CANVAS_HIGHLIGHTED_BORDER_STYLE,
|
||||
_ => *CANVAS_BORDER_STYLE,
|
||||
}
|
||||
},
|
||||
))
|
||||
.style(Style::default().fg(Color::Gray))
|
||||
.alignment(Alignment::Left)
|
||||
.wrap(false)
|
||||
|
|
28
src/main.rs
28
src/main.rs
|
@ -133,7 +133,7 @@ fn main() -> error::Result<()> {
|
|||
|
||||
// Set default search method
|
||||
if matches.is_present("CASE_INSENSITIVE_DEFAULT") {
|
||||
app.ignore_case = true;
|
||||
app.search_state.toggle_ignore_case();
|
||||
}
|
||||
|
||||
// Set up up tui and crossterm
|
||||
|
@ -257,8 +257,6 @@ fn main() -> error::Result<()> {
|
|||
KeyCode::Right => app.move_right(),
|
||||
KeyCode::Up => app.move_up(),
|
||||
KeyCode::Down => app.move_down(),
|
||||
KeyCode::Char('p') => app.search_with_pid(),
|
||||
KeyCode::Char('n') => app.search_with_name(),
|
||||
KeyCode::Char('r') => {
|
||||
if rtx.send(ResetEvent::Reset).is_ok() {
|
||||
app.reset();
|
||||
|
@ -276,6 +274,28 @@ fn main() -> error::Result<()> {
|
|||
KeyCode::Down => app.move_down(),
|
||||
_ => {}
|
||||
}
|
||||
} else if let KeyModifiers::ALT = event.modifiers {
|
||||
match event.code {
|
||||
KeyCode::Char('c') => {
|
||||
if app.is_in_search_widget() {
|
||||
app.search_state.toggle_ignore_case();
|
||||
app.update_regex();
|
||||
}
|
||||
}
|
||||
KeyCode::Char('w') => {
|
||||
if app.is_in_search_widget() {
|
||||
app.search_state.toggle_search_whole_word();
|
||||
app.update_regex();
|
||||
}
|
||||
}
|
||||
KeyCode::Char('r') => {
|
||||
if app.is_in_search_widget() {
|
||||
app.search_state.toggle_search_regex();
|
||||
app.update_regex();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -417,7 +437,7 @@ fn update_final_process_list(app: &mut app::App) {
|
|||
.iter()
|
||||
.filter(|(_pid, process)| {
|
||||
if let Ok(matcher) = app.get_current_regex_matcher() {
|
||||
if app.is_searching_with_pid() {
|
||||
if app.search_state.is_searching_with_pid() {
|
||||
matcher.is_match(&process.pid.to_string())
|
||||
} else {
|
||||
matcher.is_match(&process.name)
|
||||
|
|
Loading…
Reference in New Issue