summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorClement Tsang <34804052+ClementTsang@users.noreply.github.com>2022-12-31 05:51:59 -0500
committerGitHub <noreply@github.com>2022-12-31 05:51:59 -0500
commit3b5774117f7a7aa5e99f6553e989688f4362e8e2 (patch)
treed3f3a589591c509d095348f97437cee51376dbfe /src
parentefcf2bde2961d9b820609b56ddc77c666cb3b5bb (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.rs165
-rw-r--r--src/app/query.rs2
-rw-r--r--src/app/states.rs244
-rw-r--r--src/canvas/drawing_utils.rs39
-rw-r--r--src/canvas/widgets/process_table.rs104
-rw-r--r--src/components/data_table/state.rs21
-rw-r--r--src/utils/gen_util.rs36
-rw-r--r--src/widgets/process_table.rs77
8 files changed, 372 insertions, 316 deletions
diff --git a/src/app.rs b/src/app.rs
index a2bffe95..78a64b00 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -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