diff --git a/CHANGELOG.md b/CHANGELOG.md index 3935221c..fae70500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#406](https://github.com/ClementTsang/bottom/pull/406): Adds the Nord colour scheme, as well as a light variant. +- [#409](https://github.com/ClementTsang/bottom/pull/409): Adds `Ctrl-w` and `Ctrl-h` shortcuts in search, to delete a word and delete a character respectively. + ## Changes - [#372](https://github.com/ClementTsang/bottom/pull/372): Hides the SWAP graph and legend in normal mode if SWAP is 0. diff --git a/README.md b/README.md index 9078bfd5..4388bc97 100644 --- a/README.md +++ b/README.md @@ -333,6 +333,8 @@ Use `btm --help` for more information. | `Ctrl-a` | Skip to the start of the search query | | `Ctrl-e` | Skip to the end of the search query | | `Ctrl-u` | Clear the current search query | +| `Ctrl-w` | Delete a word behind the cursor | +| `Ctrl-h` | Delete the character behind the cursor | | `Backspace` | Delete the character behind the cursor | | `Delete` | Delete the character at the cursor | | `Alt-c`, `F1` | Toggle matching case | diff --git a/src/app.rs b/src/app.rs index b10570b8..7b9d0758 100644 --- a/src/app.rs +++ b/src/app.rs @@ -725,17 +725,22 @@ impl App { .current_search_query .len() { + let current_cursor = proc_widget_state.get_search_cursor_position(); proc_widget_state + .search_walk_forward(proc_widget_state.get_search_cursor_position()); + + let _removed_chars: String = proc_widget_state .process_search_state .search_state .current_search_query - .remove(proc_widget_state.get_search_cursor_position()); + .drain(current_cursor..proc_widget_state.get_search_cursor_position()) + .collect(); proc_widget_state .process_search_state .search_state .grapheme_cursor = GraphemeCursor::new( - proc_widget_state.get_search_cursor_position(), + current_cursor, proc_widget_state .process_search_state .search_state @@ -769,14 +774,16 @@ impl App { .is_enabled && proc_widget_state.get_search_cursor_position() > 0 { + let current_cursor = proc_widget_state.get_search_cursor_position(); proc_widget_state .search_walk_back(proc_widget_state.get_search_cursor_position()); - let removed_char = proc_widget_state + let removed_chars: String = proc_widget_state .process_search_state .search_state .current_search_query - .remove(proc_widget_state.get_search_cursor_position()); + .drain(proc_widget_state.get_search_cursor_position()..current_cursor) + .collect(); proc_widget_state .process_search_state @@ -794,7 +801,8 @@ impl App { proc_widget_state .process_search_state .search_state - .char_cursor_position -= UnicodeWidthChar::width(removed_char).unwrap_or(0); + .char_cursor_position -= UnicodeWidthStr::width(removed_chars.as_str()); + proc_widget_state .process_search_state .search_state @@ -1167,6 +1175,82 @@ impl App { } } + pub fn clear_previous_word(&mut self) { + if let BottomWidgetType::ProcSearch = self.current_widget.widget_type { + if let Some(proc_widget_state) = self + .proc_state + .widget_states + .get_mut(&(self.current_widget.widget_id - 1)) + { + // Traverse backwards from the current cursor location until you hit non-whitespace characters, + // then continue to traverse (and delete) backwards until you hit a whitespace character. Halt. + + // So... first, let's get our current cursor position using graphemes... + let end_index = proc_widget_state.get_char_cursor_position(); + + // Then, let's crawl backwards until we hit our location, and store the "head"... + let query = proc_widget_state.get_current_search_query(); + let mut start_index = 0; + let mut saw_non_whitespace = false; + + for (itx, c) in query + .chars() + .rev() + .enumerate() + .skip(query.len() - end_index) + { + if c.is_whitespace() { + if saw_non_whitespace { + start_index = query.len() - itx; + break; + } + } else { + saw_non_whitespace = true; + } + } + + let removed_chars: String = proc_widget_state + .process_search_state + .search_state + .current_search_query + .drain(start_index..end_index) + .collect(); + + proc_widget_state + .process_search_state + .search_state + .grapheme_cursor = GraphemeCursor::new( + start_index, + proc_widget_state + .process_search_state + .search_state + .current_search_query + .len(), + true, + ); + + proc_widget_state + .process_search_state + .search_state + .char_cursor_position -= UnicodeWidthStr::width(removed_chars.as_str()); + + proc_widget_state + .process_search_state + .search_state + .cursor_direction = CursorDirection::Left; + + proc_widget_state.update_query(); + self.proc_state.force_update = Some(self.current_widget.widget_id - 1); + + // Now, convert this range into a String-friendly range and remove it all at once! + + // Now make sure to also update our current cursor positions... + + self.proc_state.force_update = Some(self.current_widget.widget_id - 1); + } + } + } + pub fn start_dd(&mut self) { self.reset_multi_tap_keys(); diff --git a/src/canvas/widgets/process_table.rs b/src/canvas/widgets/process_table.rs index b49dfea4..bacba555 100644 --- a/src/canvas/widgets/process_table.rs +++ b/src/canvas/widgets/process_table.rs @@ -549,7 +549,7 @@ impl ProcessTableWidget for Painter { }) .collect::>(); - if cursor_position >= query.len() { + if cursor_position == query.len() { res.push(Span::styled(" ", currently_selected_text_style)) } @@ -558,17 +558,7 @@ impl ProcessTableWidget for Painter { // This is easier - we just need to get a range of graphemes, rather than // dealing with possibly inserting a cursor (as none is shown!) - grapheme_indices - .filter_map(|grapheme| { - current_grapheme_posn += UnicodeWidthStr::width(grapheme.1); - if current_grapheme_posn <= start_position { - None - } else { - let styled = Span::styled(grapheme.1, text_style); - Some(styled) - } - }) - .collect::>() + vec![Span::styled(query.to_string(), text_style)] } } @@ -622,6 +612,7 @@ impl ProcessTableWidget for Painter { }, )]; search_vec.extend(query_with_cursor); + search_vec })]; diff --git a/src/constants.rs b/src/constants.rs index 3edcbc2c..70131acb 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -271,13 +271,15 @@ pub const PROCESS_HELP_TEXT: [&str; 14] = [ "+, -, click Collapse/expand a branch while in tree mode", ]; -pub const SEARCH_HELP_TEXT: [&str; 46] = [ +pub const SEARCH_HELP_TEXT: [&str; 48] = [ "4 - Process search widget", "Tab Toggle between searching for PID and 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", "Ctrl-u Clear the current search query", + "Ctrl-w Delete a word behind the cursor", + "Ctrl-h Delete the character behind the cursor", "Backspace Delete the character behind the cursor", "Delete Delete the character at the cursor", "Alt-c, F1 Toggle matching case", diff --git a/src/lib.rs b/src/lib.rs index 120c1c10..7b15d37f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -157,6 +157,8 @@ pub fn handle_key_event_or_break( KeyCode::Char('a') => app.skip_cursor_beginning(), KeyCode::Char('e') => app.skip_cursor_end(), KeyCode::Char('u') => app.clear_search(), + KeyCode::Char('w') => app.clear_previous_word(), + KeyCode::Char('h') => app.on_backspace(), // KeyCode::Char('j') => {}, // Move down // KeyCode::Char('k') => {}, // Move up // KeyCode::Char('h') => {}, // Move right