summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorClementTsang <cjhtsang@uwaterloo.ca>2020-02-02 23:15:24 -0500
committerClementTsang <cjhtsang@uwaterloo.ca>2020-02-02 23:15:28 -0500
commitfc3a2e69ec834b209ee6f4e1dbf97b4d1cc96c81 (patch)
tree56d302e4d75e1a3ab0029293129f6aa241ebd356 /src
parent1360296b4eaf5d833da590c082e4b6fc44bbd276 (diff)
Made search look prettier and organized it a bit... also added match whole word functionality.
Diffstat (limited to 'src')
-rw-r--r--src/app.rs152
-rw-r--r--src/canvas.rs94
-rw-r--r--src/main.rs28
3 files changed, 202 insertions, 72 deletions
diff --git a/src/app.rs b/src/app.rs
index 57dbaf89..455b3437 100644
--- a/src/app.rs
+++ b/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();
diff --git a/src/canvas.rs b/src/canvas.rs
index 321d9ddd..962d6d40 100644
--- a/src/canvas.rs
+++ b/src/canvas.rs
@@ -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("[ ]", 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("\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(
+ "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))
+ 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)
diff --git a/src/main.rs b/src/main.rs
index 90454c1c..12c9cc88 100644
--- a/src/main.rs
+++ b/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)