summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastian Thiel <sebastian.thiel@icloud.com>2023-12-22 08:53:40 +0100
committerSebastian Thiel <sebastian.thiel@icloud.com>2023-12-22 10:52:49 +0100
commit8fae93966f916291bece3e5673ca83cefa702069 (patch)
tree05eddc4582ff2c3f782e305540a03a81ae6a33b5
parentb431ec38f318a50a1b636e72ffed768e9ba1e4c5 (diff)
refactor shortening
* use single-char ellipsis * use unicode width computation instead of counting chars, validate multi-block strings do not crash * copy-on-write
-rw-r--r--Cargo.lock1
-rw-r--r--Cargo.toml5
-rw-r--r--src/interactive/widgets/entries.rs67
3 files changed, 52 insertions, 21 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 2ff61c4..1a02b92 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -334,6 +334,7 @@ dependencies = [
"trash",
"tui-react",
"unicode-segmentation",
+ "unicode-width",
"wild",
]
diff --git a/Cargo.toml b/Cargo.toml
index 11aa9a0..880abdd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,7 +14,7 @@ default = ["tui-crossplatform", "trash-move"]
tui-unix = ["crosstermion/tui-react-termion", "tui-shared"]
tui-crossplatform = ["crosstermion/tui-react-crossterm", "tui-shared"]
-tui-shared = ["tui", "tui-react", "open", "unicode-segmentation"]
+tui-shared = ["tui", "tui-react", "open", "unicode-segmentation", "unicode-width"]
trash-move = ["trash"]
[dependencies]
@@ -32,6 +32,7 @@ chrono = { version = "0.4.31", default-features = false, features = ["std"] }
# 'tui' related
unicode-segmentation = { version = "1.3.0", optional = true }
+unicode-width = { version = "0.1.5", optional = true }
crosstermion = { version = "0.12.0", default-features = false, optional = true }
tui = { package = "ratatui", version = "0.24.0", optional = true, default-features = false }
tui-react = { version = "0.21.0", optional = true }
@@ -54,7 +55,7 @@ panic = 'abort'
incremental = false
overflow-checks = false
lto = "fat"
-codegen-units = 1
+#codegen-units = 1
build-override = { opt-level = 3 }
[dev-dependencies]
diff --git a/src/interactive/widgets/entries.rs b/src/interactive/widgets/entries.rs
index 5763da3..9ec877d 100644
--- a/src/interactive/widgets/entries.rs
+++ b/src/interactive/widgets/entries.rs
@@ -6,7 +6,7 @@ use crate::interactive::{
use chrono::DateTime;
use dua::traverse::TreeIndex;
use itertools::Itertools;
-use std::borrow::Borrow;
+use std::borrow::{Borrow, Cow};
use std::ops::Deref;
use std::time::SystemTime;
use tui::{
@@ -22,6 +22,8 @@ use tui_react::{
util::{block_width, rect},
List, ListProps,
};
+use unicode_segmentation::UnicodeSegmentation;
+use unicode_width::UnicodeWidthStr;
pub struct EntriesProps<'a> {
pub current_path: String,
@@ -112,8 +114,8 @@ impl Entries {
.sum(),
) as usize;
- let shorten_name = shorten_string_middle(
- name_with_prefix(name.to_string_lossy().deref(), *is_dir).as_str(),
+ let shorten_name = shorten_input(
+ name_with_prefix(name.to_string_lossy().deref(), *is_dir).into(),
available_width,
);
@@ -347,35 +349,62 @@ fn show_count_column(sort_mode: &SortMode) -> bool {
)
}
-fn shorten_string_middle(input: &str, width: usize) -> String {
- let ellipsis = "...";
- let ellipsis_len = ellipsis.chars().count();
+/// Note that this implementation isn't correct as `width` is the amount of blocks to display,
+/// which is not what we are actually counting when adding graphemes to the output string.
+fn shorten_input(input: Cow<'_, str>, width: usize) -> Cow<'_, str> {
+ const ELLIPSIS: char = '…';
+ const ELLIPSIS_LEN: usize = 1;
+ const EXTENDED: bool = true;
- if input.chars().count() <= width {
- return input.to_string();
+ let total_count = input.width();
+ if total_count <= width {
+ return input;
}
- if ellipsis.chars().count() > width {
- return "".to_string();
+ if ELLIPSIS_LEN > width {
+ return Cow::Borrowed("");
}
- let chars_per_half = (width - ellipsis_len) / 2;
+ let graphemes_per_half = (width - ELLIPSIS_LEN) / 2;
- let first_half: String = input.chars().take(chars_per_half).collect();
- let second_half_start = input.chars().count() - chars_per_half;
- let second_half: String = input.chars().skip(second_half_start).collect();
+ let mut out = String::with_capacity(width);
+ let mut g = input.graphemes(EXTENDED);
- first_half + ellipsis + &second_half
+ out.extend(g.by_ref().take(graphemes_per_half));
+ out.push(ELLIPSIS);
+ out.extend(g.skip(total_count - graphemes_per_half * 2));
+
+ Cow::Owned(out)
}
#[cfg(test)]
mod entries_test {
- use super::shorten_string_middle;
+ use super::shorten_input;
#[test]
fn test_shorten_string_middle() {
- assert_eq!(shorten_string_middle("12345678", 7), "12...78".to_string());
- assert_eq!(shorten_string_middle("12345678", 3), "...".to_string());
- assert_eq!(shorten_string_middle("12345678", 2), "".to_string());
+ let numbers = "12345678";
+ let graphemes = "你好😁你好";
+ for (input, target_length, expected) in [
+ (numbers, 8, numbers),
+ (numbers, 7, "123…678"),
+ (numbers, 3, "1…8"),
+ (numbers, 2, "…"),
+ (numbers, 1, "…"),
+ (numbers, 0, ""),
+ // multi-block strings are handled incorrectly, but at least it doesn't crash.
+ (graphemes, 0, ""),
+ (graphemes, 1, "…"),
+ (graphemes, 3, "你…"),
+ (graphemes, 4, "你…"),
+ (graphemes, 5, "你好…"),
+ (graphemes, 6, "你好…"),
+ (graphemes, 7, "你好😁…"),
+ (graphemes, 8, "你好😁…"),
+ (graphemes, 9, "你好😁你…"),
+ (graphemes, 10, "你好😁你好"),
+ ] {
+ assert_eq!(shorten_input(input.into(), target_length), expected);
+ }
}
}