Fixed cursor issue.

This commit is contained in:
ClementTsang 2020-02-27 23:39:05 -05:00
parent 8a95f91442
commit 818d920835
4 changed files with 143 additions and 55 deletions

View File

@ -8,8 +8,8 @@ use data_farmer::*;
use crate::{canvas, constants, utils::error::Result}; use crate::{canvas, constants, utils::error::Result};
mod process_killer; mod process_killer;
use unicode_segmentation::{GraphemeCursor}; use unicode_segmentation::GraphemeCursor;
use unicode_width::UnicodeWidthStr; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
const MAX_SEARCH_LENGTH: usize = 200; const MAX_SEARCH_LENGTH: usize = 200;
@ -33,7 +33,7 @@ pub enum ScrollDirection {
} }
#[derive(Debug)] #[derive(Debug)]
pub enum SearchDirection { pub enum CursorDirection {
LEFT, LEFT,
RIGHT, RIGHT,
} }
@ -73,6 +73,10 @@ pub struct AppSearchState {
pub is_blank_search: bool, pub is_blank_search: bool,
pub is_invalid_search: bool, pub is_invalid_search: bool,
pub grapheme_cursor: GraphemeCursor, pub grapheme_cursor: GraphemeCursor,
pub cursor_direction: CursorDirection,
pub cursor_bar: usize,
/// This represents the position in terms of CHARACTERS, not graphemes
pub char_cursor_position: usize,
} }
impl Default for AppSearchState { impl Default for AppSearchState {
@ -84,11 +88,21 @@ impl Default for AppSearchState {
is_invalid_search: false, is_invalid_search: false,
is_blank_search: true, is_blank_search: true,
grapheme_cursor: GraphemeCursor::new(0, 0, true), grapheme_cursor: GraphemeCursor::new(0, 0, true),
cursor_direction: CursorDirection::RIGHT,
cursor_bar: 0,
char_cursor_position: 0,
} }
} }
} }
impl AppSearchState { impl AppSearchState {
/// Returns a reset but still enabled app search state
pub fn reset() -> Self {
let mut app_search_state = AppSearchState::default();
app_search_state.is_enabled = true;
app_search_state
}
pub fn is_invalid_or_blank_search(&self) -> bool { pub fn is_invalid_or_blank_search(&self) -> bool {
self.is_blank_search || self.is_invalid_search self.is_blank_search || self.is_invalid_search
} }
@ -549,6 +563,10 @@ impl App {
.cur_cursor() .cur_cursor()
} }
pub fn get_char_cursor_position(&self) -> usize {
self.process_search_state.search_state.char_cursor_position
}
/// 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.delete_dialog_state.is_showing_dd { if self.delete_dialog_state.is_showing_dd {
@ -620,19 +638,14 @@ impl App {
#[allow(unused_variables)] #[allow(unused_variables)]
pub fn skip_word_backspace(&mut self) { pub fn skip_word_backspace(&mut self) {
if let WidgetPosition::ProcessSearch = self.current_widget_selected { if let WidgetPosition::ProcessSearch = self.current_widget_selected {
if self.process_search_state.search_state.is_enabled { if self.process_search_state.search_state.is_enabled {}
}
} }
} }
pub fn clear_search(&mut self) { pub fn clear_search(&mut self) {
if let WidgetPosition::ProcessSearch = self.current_widget_selected { if let WidgetPosition::ProcessSearch = self.current_widget_selected {
self.process_search_state.search_state.grapheme_cursor =
GraphemeCursor::new(0, 0, true);
self.process_search_state.search_state.current_search_query = String::default();
self.process_search_state.search_state.is_blank_search = true;
self.process_search_state.search_state.is_invalid_search = false;
self.update_process_gui = true; self.update_process_gui = true;
self.process_search_state.search_state = AppSearchState::reset();
} }
} }
@ -663,7 +676,8 @@ impl App {
if self.process_search_state.search_state.is_enabled && self.get_cursor_position() > 0 { if self.process_search_state.search_state.is_enabled && self.get_cursor_position() > 0 {
self.search_walk_back(self.get_cursor_position()); self.search_walk_back(self.get_cursor_position());
self.process_search_state let removed_char = self
.process_search_state
.search_state .search_state
.current_search_query .current_search_query
.remove(self.get_cursor_position()); .remove(self.get_cursor_position());
@ -677,6 +691,9 @@ impl App {
true, true,
); );
self.process_search_state.search_state.char_cursor_position -=
UnicodeWidthChar::width(removed_char).unwrap_or(0);
self.update_regex(); self.update_regex();
self.update_process_gui = true; self.update_process_gui = true;
} }
@ -710,7 +727,15 @@ impl App {
pub fn on_left_key(&mut self) { pub fn on_left_key(&mut self) {
if !self.is_in_dialog() { if !self.is_in_dialog() {
if let WidgetPosition::ProcessSearch = self.current_widget_selected { if let WidgetPosition::ProcessSearch = self.current_widget_selected {
let prev_cursor = self.get_cursor_position();
self.search_walk_back(self.get_cursor_position()); self.search_walk_back(self.get_cursor_position());
if self.get_cursor_position() < prev_cursor {
let str_slice = &self.process_search_state.search_state.current_search_query
[self.get_cursor_position()..prev_cursor];
self.process_search_state.search_state.char_cursor_position -=
UnicodeWidthStr::width(str_slice);
self.process_search_state.search_state.cursor_direction = CursorDirection::LEFT;
}
} }
} else if self.delete_dialog_state.is_showing_dd && !self.delete_dialog_state.is_on_yes { } else if self.delete_dialog_state.is_showing_dd && !self.delete_dialog_state.is_on_yes {
self.delete_dialog_state.is_on_yes = true; self.delete_dialog_state.is_on_yes = true;
@ -720,7 +745,16 @@ impl App {
pub fn on_right_key(&mut self) { pub fn on_right_key(&mut self) {
if !self.is_in_dialog() { if !self.is_in_dialog() {
if let WidgetPosition::ProcessSearch = self.current_widget_selected { if let WidgetPosition::ProcessSearch = self.current_widget_selected {
let prev_cursor = self.get_cursor_position();
self.search_walk_forward(self.get_cursor_position()); self.search_walk_forward(self.get_cursor_position());
if self.get_cursor_position() > prev_cursor {
let str_slice = &self.process_search_state.search_state.current_search_query
[prev_cursor..self.get_cursor_position()];
self.process_search_state.search_state.char_cursor_position +=
UnicodeWidthStr::width(str_slice);
self.process_search_state.search_state.cursor_direction =
CursorDirection::RIGHT;
}
} }
} else if self.delete_dialog_state.is_showing_dd && self.delete_dialog_state.is_on_yes { } else if self.delete_dialog_state.is_showing_dd && self.delete_dialog_state.is_on_yes {
self.delete_dialog_state.is_on_yes = false; self.delete_dialog_state.is_on_yes = false;
@ -738,6 +772,8 @@ impl App {
.len(), .len(),
true, true,
); );
self.process_search_state.search_state.char_cursor_position = 0;
self.process_search_state.search_state.cursor_direction = CursorDirection::LEFT;
} }
} }
} }
@ -756,6 +792,14 @@ impl App {
.len(), .len(),
true, true,
); );
self.process_search_state.search_state.char_cursor_position =
UnicodeWidthStr::width(
self.process_search_state
.search_state
.current_search_query
.as_str(),
);
self.process_search_state.search_state.cursor_direction = CursorDirection::RIGHT;
} }
} }
} }
@ -840,6 +884,10 @@ impl App {
true, true,
); );
self.search_walk_forward(self.get_cursor_position()); self.search_walk_forward(self.get_cursor_position());
self.process_search_state.search_state.char_cursor_position +=
UnicodeWidthChar::width(caught_char).unwrap_or(0);
self.update_regex(); self.update_regex();
self.update_process_gui = true; self.update_process_gui = true;
} }

View File

@ -4,7 +4,7 @@ use crate::{
data_conversion::{ConvertedCpuData, ConvertedProcessData}, data_conversion::{ConvertedCpuData, ConvertedProcessData},
utils::error, utils::error,
}; };
use std::cmp::{max, min}; use std::cmp::max;
use std::collections::HashMap; use std::collections::HashMap;
use tui::{ use tui::{
backend, backend,
@ -1162,27 +1162,31 @@ impl Painter {
&self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect, &self, f: &mut Frame<'_, B>, app_state: &mut app::App, draw_loc: Rect,
) { ) {
let width = max(0, draw_loc.width as i64 - 34) as u64; // TODO: [REFACTOR] Hard coding this is terrible. let width = max(0, draw_loc.width as i64 - 34) as u64; // TODO: [REFACTOR] Hard coding this is terrible.
let query = app_state.get_current_search_query().as_str();
let grapheme_indices = UnicodeSegmentation::grapheme_indices(query, true).rev(); // Reverse due to us wanting to draw from back -> front
let cursor_position = app_state.get_cursor_position(); let cursor_position = app_state.get_cursor_position();
let right_border = min(UnicodeWidthStr::width(query), width as usize); let char_cursor_position = app_state.get_char_cursor_position();
let mut itx = 0; let start_position: usize = get_search_start_position(
let mut query_with_cursor: Vec<Text<'_>> = if let app::WidgetPosition::ProcessSearch = width as usize,
app_state.current_widget_selected &app_state.process_search_state.search_state.cursor_direction,
{ &mut app_state.process_search_state.search_state.cursor_bar,
let mut res = Vec::new(); char_cursor_position,
if cursor_position >= query.len() { app_state.is_resized,
res.push(Text::styled( );
" ",
self.colours.currently_selected_text_style,
))
}
res.extend( let query = app_state.get_current_search_query().as_str();
grapheme_indices debug!(
"query: {}, width: {}, cursor: {}, start position: {}",
query, width, char_cursor_position, start_position
);
let grapheme_indices = UnicodeSegmentation::grapheme_indices(query, true);
let mut current_grapheme_posn = 0;
let query_with_cursor: Vec<Text<'_>> =
if let app::WidgetPosition::ProcessSearch = app_state.current_widget_selected {
let mut res = grapheme_indices
.filter_map(|grapheme| { .filter_map(|grapheme| {
if itx >= right_border { current_grapheme_posn += UnicodeWidthStr::width(grapheme.1);
if current_grapheme_posn <= start_position {
None None
} else { } else {
let styled = if grapheme.0 == cursor_position { let styled = if grapheme.0 == cursor_position {
@ -1190,32 +1194,34 @@ impl Painter {
} else { } else {
Text::styled(grapheme.1, self.colours.text_style) Text::styled(grapheme.1, self.colours.text_style)
}; };
itx += UnicodeWidthStr::width(grapheme.1);
Some(styled) Some(styled)
} }
}) })
.collect::<Vec<_>>(), .collect::<Vec<_>>();
);
res if cursor_position >= query.len() {
} else { res.push(Text::styled(
// This is easier - we just need to get a range of graphemes, rather than " ",
// dealing with possibly inserting a cursor (as none is shown!) self.colours.currently_selected_text_style,
grapheme_indices ))
.filter_map(|grapheme| { }
if itx >= right_border {
None
} else {
let styled = Text::styled(grapheme.1, self.colours.text_style);
itx += UnicodeWidthStr::width(grapheme.1);
Some(styled)
}
})
.collect::<Vec<_>>()
};
// I feel like this is most definitely not the efficient way of doing this but eh res
query_with_cursor.reverse(); } else {
// 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 = Text::styled(grapheme.1, self.colours.text_style);
Some(styled)
}
})
.collect::<Vec<_>>()
};
let mut search_text = vec![if app_state.is_grouped() { let mut search_text = vec![if app_state.is_grouped() {
Text::styled("Search by Name: ", self.colours.table_header_style) Text::styled("Search by Name: ", self.colours.table_header_style)

View File

@ -72,11 +72,43 @@ pub fn get_variable_intrinsic_widths(
#[allow(dead_code, unused_variables)] #[allow(dead_code, unused_variables)]
pub fn get_search_start_position( pub fn get_search_start_position(
num_rows: u64, scroll_direction: &app::ScrollDirection, scroll_position_bar: &mut u64, num_columns: usize, cursor_direction: &app::CursorDirection, cursor_bar: &mut usize,
currently_selected_position: u64, is_resized: bool, current_cursor_position: usize, is_resized: bool,
) -> u64 { ) -> usize {
//TODO: [Scroll] Gotta fix this too lol if is_resized {
0 *cursor_bar = 0;
}
match cursor_direction {
app::CursorDirection::RIGHT => {
if current_cursor_position < *cursor_bar + num_columns {
// If, using previous_scrolled_position, we can see the element
// (so within that and + num_rows) just reuse the current previously scrolled position
*cursor_bar
} else if current_cursor_position >= num_columns {
// Else if the current position past the last element visible in the list, omit
// until we can see that element
*cursor_bar = current_cursor_position - num_columns;
*cursor_bar
} else {
// Else, if it is not past the last element visible, do not omit anything
0
}
}
app::CursorDirection::LEFT => {
if current_cursor_position <= *cursor_bar {
// If it's past the first element, then show from that element downwards
*cursor_bar = current_cursor_position;
*cursor_bar
} else if current_cursor_position >= *cursor_bar + num_columns {
*cursor_bar = current_cursor_position - num_columns;
*cursor_bar
} else {
// Else, don't change what our start position is from whatever it is set to!
*cursor_bar
}
}
}
} }
pub fn get_start_position( pub fn get_start_position(

View File

@ -399,6 +399,8 @@ fn handle_key_event_or_break(
KeyCode::Char('u') => app.clear_search(), KeyCode::Char('u') => app.clear_search(),
KeyCode::Char('a') => app.skip_cursor_beginning(), KeyCode::Char('a') => app.skip_cursor_beginning(),
KeyCode::Char('e') => app.skip_cursor_end(), KeyCode::Char('e') => app.skip_cursor_end(),
// Can't do now, CTRL+BACKSPACE doesn't work and graphemes
// are hard to iter while truncating last (eloquently).
// KeyCode::Backspace => app.skip_word_backspace(), // KeyCode::Backspace => app.skip_word_backspace(),
_ => {} _ => {}
} }