diff options
author | Clement Tsang <34804052+ClementTsang@users.noreply.github.com> | 2022-12-31 05:51:59 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-12-31 05:51:59 -0500 |
commit | 3b5774117f7a7aa5e99f6553e989688f4362e8e2 (patch) | |
tree | d3f3a589591c509d095348f97437cee51376dbfe /src | |
parent | efcf2bde2961d9b820609b56ddc77c666cb3b5bb (diff) |
bug: fix search scrolling with wider Unicode characters. (#938)
This should help make search scrolling more reliable larger Unicode characters like CJK/flag characters.
Diffstat (limited to 'src')
-rw-r--r-- | src/app.rs | 165 | ||||
-rw-r--r-- | src/app/query.rs | 2 | ||||
-rw-r--r-- | src/app/states.rs | 244 | ||||
-rw-r--r-- | src/canvas/drawing_utils.rs | 39 | ||||
-rw-r--r-- | src/canvas/widgets/process_table.rs | 104 | ||||
-rw-r--r-- | src/components/data_table/state.rs | 21 | ||||
-rw-r--r-- | src/utils/gen_util.rs | 36 | ||||
-rw-r--r-- | src/widgets/process_table.rs | 77 |
8 files changed, 372 insertions, 316 deletions
@@ -11,7 +11,6 @@ use layout_manager::*; pub use states::*; use typed_builder::*; use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation}; -use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; use crate::widgets::{ProcWidget, ProcWidgetMode}; use crate::{ @@ -32,8 +31,6 @@ pub mod states; use frozen_state::FrozenState; -const MAX_SEARCH_LENGTH: usize = 200; // FIXME: Remove this limit, it's unnecessary. - #[derive(Debug, Clone)] pub enum AxisScaling { Log, @@ -504,23 +501,21 @@ impl App { { if is_in_search_widget { if proc_widget_state.proc_search.search_state.is_enabled - && proc_widget_state.get_search_cursor_position() + && proc_widget_state.cursor_char_index() < proc_widget_state .proc_search .search_state .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 current_cursor = proc_widget_state.cursor_char_index(); + proc_widget_state.search_walk_forward(); - let _removed_chars: String = proc_widget_state + let _ = proc_widget_state .proc_search .search_state .current_search_query - .drain(current_cursor..proc_widget_state.get_search_cursor_position()) - .collect(); + .drain(current_cursor..proc_widget_state.cursor_char_index()); proc_widget_state.proc_search.search_state.grapheme_cursor = GraphemeCursor::new( @@ -552,22 +547,21 @@ impl App { { if is_in_search_widget && proc_widget_state.proc_search.search_state.is_enabled - && proc_widget_state.get_search_cursor_position() > 0 + && proc_widget_state.cursor_char_index() > 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 current_cursor = proc_widget_state.cursor_char_index(); + proc_widget_state.search_walk_back(); - let removed_chars: String = proc_widget_state + // Remove the indices in between. + let _ = proc_widget_state .proc_search .search_state .current_search_query - .drain(proc_widget_state.get_search_cursor_position()..current_cursor) - .collect(); + .drain(proc_widget_state.cursor_char_index()..current_cursor); proc_widget_state.proc_search.search_state.grapheme_cursor = GraphemeCursor::new( - proc_widget_state.get_search_cursor_position(), + proc_widget_state.cursor_char_index(), proc_widget_state .proc_search .search_state @@ -576,11 +570,6 @@ impl App { true, ); - proc_widget_state - .proc_search - .search_state - .char_cursor_position -= UnicodeWidthStr::width(removed_chars.as_str()); - proc_widget_state.proc_search.search_state.cursor_direction = CursorDirection::Left; @@ -684,19 +673,9 @@ impl App { .get_mut_widget_state(self.current_widget.widget_id - 1) { if is_in_search_widget { - let prev_cursor = proc_widget_state.get_search_cursor_position(); - proc_widget_state - .search_walk_back(proc_widget_state.get_search_cursor_position()); - if proc_widget_state.get_search_cursor_position() < prev_cursor { - let str_slice = &proc_widget_state - .proc_search - .search_state - .current_search_query - [proc_widget_state.get_search_cursor_position()..prev_cursor]; - proc_widget_state - .proc_search - .search_state - .char_cursor_position -= UnicodeWidthStr::width(str_slice); + let prev_cursor = proc_widget_state.cursor_char_index(); + proc_widget_state.search_walk_back(); + if proc_widget_state.cursor_char_index() < prev_cursor { proc_widget_state.proc_search.search_state.cursor_direction = CursorDirection::Left; } @@ -753,20 +732,9 @@ impl App { .get_mut_widget_state(self.current_widget.widget_id - 1) { if is_in_search_widget { - let prev_cursor = proc_widget_state.get_search_cursor_position(); - proc_widget_state.search_walk_forward( - proc_widget_state.get_search_cursor_position(), - ); - if proc_widget_state.get_search_cursor_position() > prev_cursor { - let str_slice = &proc_widget_state - .proc_search - .search_state - .current_search_query - [prev_cursor..proc_widget_state.get_search_cursor_position()]; - proc_widget_state - .proc_search - .search_state - .char_cursor_position += UnicodeWidthStr::width(str_slice); + let prev_cursor = proc_widget_state.cursor_char_index(); + proc_widget_state.search_walk_forward(); + if proc_widget_state.cursor_char_index() > prev_cursor { proc_widget_state.proc_search.search_state.cursor_direction = CursorDirection::Right; } @@ -932,10 +900,7 @@ impl App { .len(), true, ); - proc_widget_state - .proc_search - .search_state - .char_cursor_position = 0; + proc_widget_state.proc_search.search_state.cursor_direction = CursorDirection::Left; } @@ -954,30 +919,14 @@ impl App { .get_mut(&(self.current_widget.widget_id - 1)) { if is_in_search_widget { - proc_widget_state.proc_search.search_state.grapheme_cursor = - GraphemeCursor::new( - proc_widget_state - .proc_search - .search_state - .current_search_query - .len(), - proc_widget_state - .proc_search - .search_state - .current_search_query - .len(), - true, - ); - proc_widget_state + let query_len = proc_widget_state .proc_search .search_state - .char_cursor_position = UnicodeWidthStr::width( - proc_widget_state - .proc_search - .search_state - .current_search_query - .as_str(), - ); + .current_search_query + .len(); + + proc_widget_state.proc_search.search_state.grapheme_cursor = + GraphemeCursor::new(query_len, query_len, true); proc_widget_state.proc_search.search_state.cursor_direction = CursorDirection::Right; } @@ -1008,11 +957,11 @@ impl App { // 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(); + // So... first, let's get our current cursor position in terms of char indices. + let end_index = proc_widget_state.cursor_char_index(); // Then, let's crawl backwards until we hit our location, and store the "head"... - let query = proc_widget_state.get_current_search_query(); + let query = proc_widget_state.current_search_query(); let mut start_index = 0; let mut saw_non_whitespace = false; @@ -1032,12 +981,11 @@ impl App { } } - let removed_chars: String = proc_widget_state + let _ = proc_widget_state .proc_search .search_state .current_search_query - .drain(start_index..end_index) - .collect(); + .drain(start_index..end_index); proc_widget_state.proc_search.search_state.grapheme_cursor = GraphemeCursor::new( start_index, @@ -1049,11 +997,6 @@ impl App { true, ); - proc_widget_state - .proc_search - .search_state - .char_cursor_position -= UnicodeWidthStr::width(removed_chars.as_str()); - proc_widget_state.proc_search.search_state.cursor_direction = CursorDirection::Left; proc_widget_state.update_query(); @@ -1113,25 +1056,16 @@ impl App { .widget_states .get_mut(&(self.current_widget.widget_id - 1)) { - if is_in_search_widget - && proc_widget_state.is_search_enabled() - && UnicodeWidthStr::width( - proc_widget_state - .proc_search - .search_state - .current_search_query - .as_str(), - ) <= MAX_SEARCH_LENGTH - { + if is_in_search_widget && proc_widget_state.is_search_enabled() { proc_widget_state .proc_search .search_state .current_search_query - .insert(proc_widget_state.get_search_cursor_position(), caught_char); + .insert(proc_widget_state.cursor_char_index(), caught_char); proc_widget_state.proc_search.search_state.grapheme_cursor = GraphemeCursor::new( - proc_widget_state.get_search_cursor_position(), + proc_widget_state.cursor_char_index(), proc_widget_state .proc_search .search_state @@ -1139,14 +1073,7 @@ impl App { .len(), true, ); - proc_widget_state - .search_walk_forward(proc_widget_state.get_search_cursor_position()); - - proc_widget_state - .proc_search - .search_state - .char_cursor_position += - UnicodeWidthChar::width(caught_char).unwrap_or(0); + proc_widget_state.search_walk_forward(); proc_widget_state.update_query(); proc_widget_state.proc_search.search_state.cursor_direction = @@ -2724,22 +2651,10 @@ impl App { .widget_states .get_mut(&(self.current_widget.widget_id - 1)) { - let curr_width = UnicodeWidthStr::width( - proc_widget_state - .proc_search - .search_state - .current_search_query - .as_str(), - ); - let paste_width = UnicodeWidthStr::width(paste.as_str()); let num_runes = UnicodeSegmentation::graphemes(paste.as_str(), true).count(); - if is_in_search_widget - && proc_widget_state.is_search_enabled() - && curr_width + paste_width <= MAX_SEARCH_LENGTH - { - let paste_char_width = paste.len(); - let left_bound = proc_widget_state.get_search_cursor_position(); + if is_in_search_widget && proc_widget_state.is_search_enabled() { + let left_bound = proc_widget_state.cursor_char_index(); let curr_query = &mut proc_widget_state .proc_search @@ -2752,15 +2667,9 @@ impl App { GraphemeCursor::new(left_bound, curr_query.len(), true); for _ in 0..num_runes { - let cursor = proc_widget_state.get_search_cursor_position(); - proc_widget_state.search_walk_forward(cursor); + proc_widget_state.search_walk_forward(); } - proc_widget_state - .proc_search - .search_state - .char_cursor_position += paste_char_width; - proc_widget_state.update_query(); proc_widget_state.proc_search.search_state.cursor_direction = CursorDirection::Right; diff --git a/src/app/query.rs b/src/app/query.rs index 0eca1b75..a894577e 100644 --- a/src/app/query.rs +++ b/src/app/query.rs @@ -253,7 +253,7 @@ pub fn parse_query( if content == "=" { // Check next string if possible if let Some(queue_next) = query.pop_front() { - // TODO: [Query, ???] Need to consider the following cases: + // TODO: [Query] Need to consider the following cases: // - (test) // - (test // - test) diff --git a/src/app/states.rs b/src/app/states.rs index a24a52f1..e4141e4f 100644 --- a/src/app/states.rs +++ b/src/app/states.rs @@ -1,10 +1,12 @@ -use std::{collections::HashMap, time::Instant}; +use std::{collections::HashMap, ops::Range, time::Instant}; -use unicode_segmentation::GraphemeCursor; +use indexmap::IndexMap; +use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete, UnicodeSegmentation}; use crate::{ app::{layout_manager::BottomWidgetType, query::*}, constants, + utils::gen_util::str_width, widgets::{ BatteryWidgetState, CpuWidgetState, DiskTableWidget, MemWidgetState, NetWidgetState, ProcWidget, TempWidgetState, @@ -71,10 +73,11 @@ pub struct AppSearchState { pub is_invalid_search: bool, 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, - /// The query + + pub display_start_char_index: usize, + pub size_mappings: IndexMap<usize, Range<usize>>, + + /// The query. TODO: Merge this as one enum. pub query: Option<Query>, pub error_message: Option<String>, } @@ -88,8 +91,8 @@ impl Default for AppSearchState { is_blank_search: true, grapheme_cursor: GraphemeCursor::new(0, 0, true), cursor_direction: CursorDirection::Right, - cursor_bar: 0, - char_cursor_position: 0, + display_start_char_index: 0, + size_mappings: IndexMap::default(), query: None, error_message: None, } @@ -109,6 +112,142 @@ impl AppSearchState { pub fn is_invalid_or_blank_search(&self) -> bool { self.is_blank_search || self.is_invalid_search } + + /// Sets the starting grapheme index to draw from. + pub fn get_start_position(&mut self, available_width: usize, is_force_redraw: bool) { + // Remember - the number of columns != the number of grapheme slots/sizes, you + // cannot use index to determine this reliably! + + let start_index = if is_force_redraw { + 0 + } else { + self.display_start_char_index + }; + let cursor_index = self.grapheme_cursor.cur_cursor(); + + if let Some(start_range) = self.size_mappings.get(&start_index) { + let cursor_range = self + .size_mappings + .get(&cursor_index) + .cloned() + .unwrap_or_else(|| { + self.size_mappings + .last() + .map(|(_, r)| r.end..(r.end + 1)) + .unwrap_or(start_range.end..(start_range.end + 1)) + }); + + // Cases to handle in both cases: + // - The current start index can show the cursor's word. + // - The current start index cannot show the cursor's word. + // + // What differs is how we "scroll" based on the cursor movement direction. + + self.display_start_char_index = match self.cursor_direction { + CursorDirection::Right => { + if start_range.start + available_width >= cursor_range.end { + // Use the current index. + start_index + } else if cursor_range.end >= available_width { + // If the current position is past the last visible element, skip until we see it. + + let mut index = 0; + for i in 0..(cursor_index + 1) { + if let Some(r) = self.size_mappings.get(&i) { + if r.start + available_width >= cursor_range.end { + index = i; + break; + } + } + } + + index + } else { + 0 + } + } + CursorDirection::Left => { + if cursor_range.start < start_range.end { + let mut index = 0; + for i in cursor_index..(self.current_search_query.len()) { + if let Some(r) = self.size_mappings.get(&i) { + if r.start + available_width >= cursor_range.end { + index = i; + break; + } + } + } + index + } else { + start_index + } + } + }; + } else { + // If we fail here somehow, just reset to 0 index + scroll left. + self.display_start_char_index = 0; + self.cursor_direction = CursorDirection::Left; + }; + } + + pub(crate) fn walk_forward(&mut self) { + // TODO: Add tests for this. + let start_position = self.grapheme_cursor.cur_cursor(); + let chunk = &self.current_search_query[start_position..]; + + match self.grapheme_cursor.next_boundary(chunk, start_position) { + Ok(_) => {} + Err(err) => match err { + GraphemeIncomplete::PreContext(ctx) => { + // Provide the entire string as context. Not efficient but should resolve failures. + self.grapheme_cursor + .provide_context(&self.current_search_query[0..ctx], 0); + + self.grapheme_cursor + .next_boundary(chunk, start_position) + .unwrap(); + } + _ => Err(err).unwrap(), + }, + } + } + + pub(crate) fn walk_backward(&mut self) { + // TODO: Add tests for this. + let start_position = self.grapheme_cursor.cur_cursor(); + let chunk = &self.current_search_query[..start_position]; + + match self.grapheme_cursor.prev_boundary(chunk, 0) { + Ok(_) => {} + Err(err) => match err { + GraphemeIncomplete::PreContext(ctx) => { + // Provide the entire string as context. Not efficient but should resolve failures. + self.grapheme_cursor + .provide_context(&self.current_search_query[0..ctx], 0); + + self.grapheme_cursor.prev_boundary(chunk, 0).unwrap(); + } + _ => Err(err).unwrap(), + }, + } + } + + pub(crate) fn update_sizes(&mut self) { + self.size_mappings.clear(); + let mut curr_offset = 0; + for (index, grapheme) in + UnicodeSegmentation::grapheme_indices(self.current_search_query.as_str(), true) + { + let width = str_width(grapheme); + let end = curr_offset + width; + + self.size_mappings.insert(index, curr_offset..end); + + curr_offset = end; + } + + self.size_mappings.shrink_to_fit(); + } } pub struct ProcState { @@ -266,3 +405,92 @@ pub struct ParagraphScrollState { pub current_scroll_index: u16, pub max_scroll_index: u16, } + +#[cfg(test)] +mod test { + use super::*; + + fn move_right(state: &mut AppSearchState) { + state.walk_forward(); + state.cursor_direction = CursorDirection::Right; + } + + fn move_left(state: &mut AppSearchState) { + state.walk_backward(); + state.cursor_direction = CursorDirection::Left; + } + + #[test] + fn search_cursor_moves() { + let mut state = AppSearchState::default(); + state.current_search_query = "Hi, 你好! 🇦🇶".to_string(); + state.grapheme_cursor = GraphemeCursor::new(0, state.current_search_query.len(), true); + state.update_sizes(); + + // Moving right. + state.get_start_position(4, false); + assert_eq!(state.grapheme_cursor.cur_cursor(), 0); + assert_eq!(state.display_start_char_index, 0); + + move_right(&mut state); + state.get_start_position(4, false); + assert_eq!(state.grapheme_cursor.cur_cursor(), 1); + assert_eq!(state.display_start_char_index, 0); + + move_right(&mut state); + state.get_start_position(4, false); + assert_eq!(state.grapheme_cursor.cur_cursor(), 2); + assert_eq!(state.display_start_char_index, 0); + + move_right(&mut state); + state.get_start_position(4, false); + assert_eq!(state.grapheme_cursor.cur_cursor(), 3); + assert_eq!(state.display_start_char_index, 0); + + move_right(&mut state); + state.get_start_position(4, false); + assert_eq!(state.grapheme_cursor.cur_cursor(), 4); + assert_eq!(state.display_start_char_index, 2); + + move_right(&mut state); + state.get_start_position(4, false); + assert_eq!(state.grapheme_cursor.cur_cursor(), 7); + assert_eq!(state.display_start_char_index, 4); + + move_right(&mut state); + state.get_start_position(4, false); + assert_eq!(state.grapheme_cursor.cur_cursor(), 10); + assert_eq!(state.display_start_char_index, 7); + + move_right(&mut state); + move_right(&mut state); + state.get_start_position(4, false); + assert_eq!(state.grapheme_cursor.cur_cursor(), 12); + assert_eq!(state.display_start_char_index, 10); + + // Moving left. + move_left(&mut state); + state.get_start_position(4, false); + assert_eq!(state.grapheme_cursor.cur_cursor(), 11); + assert_eq!(state.display_start_char_index, 10); + + move_left(&mut state); + move_left(&mut state); + state.get_start_position(4, false); + assert_eq!(state.grapheme_cursor.cur_cursor(), 7); + assert_eq!(state.display_start_char_index, 7); + + move_left(&mut state); + move_left(&mut state); + move_left(&mut state); + move_left(&mut state); + state.get_start_position(4, false); + assert_eq!(state.grapheme_cursor.cur_cursor(), 1); + assert_eq!(state.display_start_char_index, 1); + + move_left(&mut state); + state.get_start_position(4, false); + assert_eq!(state.grapheme_cursor.cur_cursor(), 0); + assert_eq!(state.display_start_char_index, 0); + } +} diff --git a/src/canvas/drawing_utils.rs b/src/canvas/drawing_utils.rs index 8d29b9a5..8a658929 100644 --- a/src/canvas/drawing_utils.rs +++ b/src/canvas/drawing_utils.rs @@ -2,45 +2,6 @@ use std::{cmp::min, time::Instant}; use tui::layout::Rect; -use crate::app::CursorDirection; - -pub fn get_search_start_position( - num_columns: usize, cursor_direction: &CursorDirection, cursor_bar: &mut usize, - current_cursor_position: usize, is_force_redraw: bool, -) -> usize { - if is_force_redraw { - *cursor_bar = 0; - } - - match cursor_direction { - 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 - } - } - 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; - } else if current_cursor_position >= *cursor_bar + num_columns { - *cursor_bar = current_cursor_position - num_columns; - } - // Else, don't change what our start position is from whatever it is set to! - *cursor_bar - } - } -} - /// Calculate how many bars are to be drawn within basic mode's components. pub fn calculate_basic_use_bars(use_percentage: f64, num_bars_available: usize) -> usize { min( diff --git a/src/canvas/widgets/process_table.rs b/src/canvas/widgets/process_table.rs index ca6fd54e..9b0c23ea 100644 --- a/src/canvas/widgets/process_table.rs +++ b/src/canvas/widgets/process_table.rs @@ -1,16 +1,16 @@ use tui::{ backend::Backend, layout::{Alignment, Constraint, Direction, Layout, Rect}, + style::Style, terminal::Frame, text::{Span, Spans}, widgets::{Block, Borders, Paragraph}, }; -use unicode_segmentation::{GraphemeIndices, UnicodeSegmentation}; -use unicode_width::UnicodeWidthStr; +use unicode_segmentation::UnicodeSegmentation; use crate::{ - app::App, - canvas::{drawing_utils::get_search_start_position, Painter}, + app::{App, AppSearchState}, + canvas::Painter, components::data_table::{DrawInfo, SelectionState}, constants::*, }; @@ -68,8 +68,6 @@ impl Painter { /// Draws the process sort box. /// - `widget_id` represents the widget ID of the process widget itself.an - /// - /// This should not be directly called. fn draw_processes_table<B: Backend>( &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, ) { @@ -99,38 +97,42 @@ impl Painter { /// Draws the process search field. /// - `widget_id` represents the widget ID of the search box itself --- NOT the process widget /// state that is stored. - /// - /// This should not be directly called. fn draw_search_field<B: Backend>( &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, draw_border: bool, widget_id: u64, ) { - fn build_query<'a>( - is_on_widget: bool, grapheme_indices: GraphemeIndices<'a>, start_position: usize, - cursor_position: usize, query: &str, currently_selected_text_style: tui::style::Style, - text_style: tui::style::Style, - ) -> Vec<Span<'a>> { - let mut current_grapheme_pos = 0; + fn build_query_span( + search_state: &AppSearchState, available_width: usize, is_on_widget: bool, + currently_selected_text_style: Style, text_style: Style, + ) -> Vec<Span<'_>> { + let start_index = search_state.display_start_char_index; + let cursor_index = search_state.grapheme_cursor.cur_cursor(); + let mut current_width = 0; + let query = search_state.current_search_query.as_str(); if is_on_widget { - let mut res = grapheme_indices - .filter_map(|grapheme| { - current_grapheme_pos += UnicodeWidthStr::width(grapheme.1); - - if current_grapheme_pos <= start_position { - None + let mut res = Vec::with_capacity(available_width); + for ((index, grapheme), lengths) in + UnicodeSegmentation::grapheme_indices(query, true) + .zip(search_state.size_mappings.values()) + { + if index < start_index { + continue; + } else if current_width > available_width { + break; + } else { + let styled = if index == cursor_index { + Span::styled(grapheme, currently_selected_text_style) } else { - let styled = if grapheme.0 == cursor_position { - Span::styled(grapheme.1, currently_selected_text_style) - } else { - Span::styled(grapheme.1, text_style) - }; - Some(styled) - } - }) - .collect::<Vec<_>>(); - - if cursor_position == query.len() { + Span::styled(grapheme, text_style) + }; + + res.push(styled); + current_width += lengths.end - lengths.start; + } + } + + if cursor_index == query.len() { res.push(Span::styled(" ", currently_selected_text_style)) } @@ -143,44 +145,36 @@ impl Painter { } } - // TODO: Make the cursor scroll back if there's space! if let Some(proc_widget_state) = app_state.proc_state.widget_states.get_mut(&(widget_id - 1)) { let is_on_widget = widget_id == app_state.current_widget.widget_id; let num_columns = usize::from(draw_loc.width); - let search_title = "> "; - - let num_chars_for_text = search_title.len(); - let cursor_position = proc_widget_state.get_search_cursor_position(); - let current_cursor_position = proc_widget_state.get_char_cursor_position(); - - let start_position: usize = get_search_start_position( - num_columns - num_chars_for_text - 5, - &proc_widget_state.proc_search.search_state.cursor_direction, - &mut proc_widget_state.proc_search.search_state.cursor_bar, - current_cursor_position, - app_state.is_force_redraw, - ); + const SEARCH_TITLE: &str = "> "; + let offset = if draw_border { 4 } else { 2 }; // width of 3 removed for >_| + let available_width = if num_columns > (offset + 3) { + num_columns - offset + } else { + num_columns + }; - let query = proc_widget_state.get_current_search_query().as_str(); - let grapheme_indices = UnicodeSegmentation::grapheme_indices(query, true); + proc_widget_state + .proc_search + .search_state + .get_start_position(available_width, app_state.is_force_redraw); - // TODO: [CURSOR] blank cursor if not selected // TODO: [CURSOR] blinking cursor? - let query_with_cursor = build_query( + let query_with_cursor = build_query_span( + &proc_widget_state.proc_search.search_state, + available_width, is_on_widget, - grapheme_indices, - start_posi |