fix(formatting): prevent crashes on files with multibyte characters (#51)

* Fix crash when truncating to middle of a character

* Fix alignment of file names with wide characters

* Respect use ::formatting convention
This commit is contained in:
Renée Kooi
2020-06-26 14:03:04 +02:00
committed by GitHub
parent 4d3ac2b45c
commit 120058d817
4 changed files with 43 additions and 7 deletions

1
Cargo.lock generated
View File

@@ -191,6 +191,7 @@ dependencies = [
"structopt 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
"tui 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", "tui 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]

View File

@@ -17,6 +17,7 @@ jwalk = "0.5"
signal-hook = "0.1.10" signal-hook = "0.1.10"
structopt = "0.3" structopt = "0.3"
filesize = "0.2.0" filesize = "0.2.0"
unicode-width = "0.1.7"
[dev-dependencies] [dev-dependencies]
insta = "0.16.0" insta = "0.16.0"

View File

@@ -1,11 +1,31 @@
use ::std::iter::FromIterator;
use ::unicode_width::UnicodeWidthChar;
fn truncate_iter_to_unicode_width<Input, Collect>(iter: Input, width: usize) -> Collect
where
Input: Iterator<Item = char>,
Collect: FromIterator<char>,
{
let mut chunk_width = 0;
iter.take_while(|ch| {
chunk_width += ch.width().unwrap_or(0);
chunk_width <= width
})
.collect()
}
pub fn truncate_middle(row: &str, max_length: u16) -> String { pub fn truncate_middle(row: &str, max_length: u16) -> String {
if max_length < 6 { if max_length < 6 {
let mut res = String::from(row); truncate_iter_to_unicode_width(row.chars(), max_length as usize)
res.truncate(max_length as usize);
res
} else if row.len() as u16 > max_length { } else if row.len() as u16 > max_length {
let first_slice = &row[0..(max_length as usize / 2) - 2]; let split_point = (max_length as usize / 2) - 2;
let second_slice = &row[(row.len() - (max_length / 2) as usize + 2)..row.len()]; let first_slice = truncate_iter_to_unicode_width::<_, String>(row.chars(), split_point);
let second_slice =
truncate_iter_to_unicode_width::<_, Vec<_>>(row.chars().rev(), split_point)
.into_iter()
.rev()
.collect::<String>();
if max_length % 2 == 0 { if max_length % 2 == 0 {
format!("{}[...]{}", first_slice, second_slice) format!("{}[...]{}", first_slice, second_slice)
} else { } else {
@@ -25,3 +45,16 @@ pub fn truncate_end(row: &str, max_len: u16) -> String {
row.to_string() row.to_string()
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn truncate_middle_char_boundary() {
assert_eq!(
truncate_middle("굿걸 - 누가 방송국을 털었나 E06.mp4", 44),
"굿걸 - 누가 방송국을[...]국을 털었나 E06.mp4",
);
}
}

View File

@@ -1,6 +1,7 @@
use ::tui::buffer::Buffer; use ::tui::buffer::Buffer;
use ::tui::layout::Rect; use ::tui::layout::Rect;
use ::tui::style::{Color, Modifier, Style}; use ::tui::style::{Color, Modifier, Style};
use ::unicode_width::UnicodeWidthStr;
use crate::state::tiles::{FileType, Tile}; use crate::state::tiles::{FileType, Tile};
use crate::ui::format::{truncate_middle, DisplaySize, DisplaySizeRounded}; use crate::ui::format::{truncate_middle, DisplaySize, DisplaySizeRounded};
@@ -167,11 +168,11 @@ pub fn draw_filled_rect(buf: &mut Buffer, fill_style: Style, rect: &Rect) {
pub fn draw_tile_text_on_grid(buf: &mut Buffer, tile: &Tile, selected: bool) { pub fn draw_tile_text_on_grid(buf: &mut Buffer, tile: &Tile, selected: bool) {
let first_line = tile_first_line(&tile); let first_line = tile_first_line(&tile);
let first_line_length = first_line.chars().count() as u16; let first_line_length = first_line.width() as u16;
let first_line_start_position = let first_line_start_position =
((tile.width - first_line_length) as f64 / 2.0).ceil() as u16 + tile.x; ((tile.width - first_line_length) as f64 / 2.0).ceil() as u16 + tile.x;
let second_line = tile_second_line(&tile); let second_line = tile_second_line(&tile);
let second_line_length = second_line.chars().count(); let second_line_length = second_line.width();
let second_line_start_position = let second_line_start_position =
((tile.width - second_line_length as u16) as f64 / 2.0).ceil() as u16 + tile.x; ((tile.width - second_line_length as u16) as f64 / 2.0).ceil() as u16 + tile.x;
let (background_style, first_line_style, second_line_style) = tile_style(&tile, selected); let (background_style, first_line_style, second_line_style) = tile_style(&tile, selected);