From 5d6abab1dee6a7fc6bddd0cccd14d2adf70c6d60 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 20 May 2026 21:15:33 +0300 Subject: [PATCH] various: fix TUI navigation performance; unicode rendering Signed-off-by: NotAShelf Change-Id: I027f00979bd5f354e3ea0257e4b8d8bf6a6a6964 --- src/commands/list.rs | 63 ++++++++++++++++++++++++-------------------- src/db/mod.rs | 16 +++++------ 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/src/commands/list.rs b/src/commands/list.rs index 369949c..3a0b3b9 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs @@ -168,7 +168,6 @@ impl TuiState { } else { self.cursor + 1 }; - self.dirty = true; } /// Move the cursor up by one, wrapping to `total - 1` at the top. @@ -181,7 +180,6 @@ impl TuiState { } else { self.cursor - 1 }; - self.dirty = true; } /// Resize the window (e.g. terminal resized). Marks dirty so the @@ -515,30 +513,43 @@ impl SqliteClipboardDb { .enumerate() .map(|(i, entry)| { let mut preview = String::new(); - let mut width = 0; + let mut pwidth = 0usize; for g in entry.1.graphemes(true) { - let g_width = UnicodeWidthStr::width(g); - if width + g_width > preview_col { + let gw = UnicodeWidthStr::width(g); + if pwidth + gw > preview_col { preview.push('…'); + pwidth += 1; break; } preview.push_str(g); - width += g_width; + pwidth += gw; } - let mut mime = String::new(); - let mut mwidth = 0; - for g in entry.2.graphemes(true) { - let g_width = UnicodeWidthStr::width(g); - if mwidth + g_width > mime_col { - mime.push('…'); - break; - } - mime.push_str(g); - mwidth += g_width; + let preview_pad = preview_col.saturating_sub(pwidth); + for _ in 0..preview_pad { + preview.push(' '); } + let mut mime_trunc = String::new(); + let mut mwidth = 0usize; + for g in entry.2.graphemes(true) { + let gw = UnicodeWidthStr::width(g); + if mwidth + gw > mime_col { + mime_trunc.push('…'); + mwidth += 1; + break; + } + mime_trunc.push_str(g); + mwidth += gw; + } + let mime_pad = mime_col.saturating_sub(mwidth); + let mime_padded = if mime_pad > 0 { + format!("{}{mime_trunc}", " ".repeat(mime_pad)) + } else { + mime_trunc + }; + + let id = entry.0; let mut spans = Vec::new(); - let (id, preview, mime) = entry; if Some(i) == selected { spans.push(Span::styled( highlight_symbol, @@ -554,23 +565,23 @@ impl SqliteClipboardDb { )); spans.push(Span::raw(" ")); spans.push(Span::styled( - format!("{preview:mime_col$}"), + mime_padded, Style::default().fg(Color::Green), )); } else { spans.push(Span::raw(" ")); spans.push(Span::raw(format!("{id:>id_col$}"))); spans.push(Span::raw(" ")); - spans.push(Span::raw(format!("{preview:mime_col$}"))); + spans.push(Span::raw(mime_padded)); } ListItem::new(Line::from(spans)) }) @@ -635,15 +646,9 @@ impl SqliteClipboardDb { if actions.search_backspace { let new_query = tui .search_query - .chars() + .char_indices() .next_back() - .map(|_| { - tui - .search_query - .chars() - .take(tui.search_query.len() - 1) - .collect::() - }) + .map(|(i, _)| tui.search_query[..i].to_string()) .unwrap_or_default(); if tui.set_search(new_query) { // Search changed, refresh count and reset diff --git a/src/db/mod.rs b/src/db/mod.rs index 8ff5f8d..357d13f 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -77,6 +77,7 @@ use regex::Regex; use rusqlite::{Connection, OptionalExtension, params}; use serde::{Deserialize, Serialize}; use thiserror::Error; +use unicode_width::UnicodeWidthChar; pub const DEFAULT_MAX_ENTRY_SIZE: usize = 5_000_000; @@ -1076,19 +1077,16 @@ pub fn preview_entry(data: &[u8], mime: Option<&str>, width: u32) -> String { return trimmed.to_string(); } - // Only allocate new string if we need to replace whitespace let mut result = String::with_capacity(width as usize + 1); - for (char_count, c) in trimmed.chars().enumerate() { - if char_count >= width as usize { + let mut disp = 0usize; + for c in trimmed.chars() { + let cw = UnicodeWidthChar::width(c).unwrap_or(1); + if disp + cw > width as usize { result.push('…'); break; } - - if c.is_whitespace() { - result.push(' '); - } else { - result.push(c); - } + result.push(if c.is_whitespace() { ' ' } else { c }); + disp += cw; } return result; }