From 584cc989cfdf37cd11a2e885e42ddabaccda7dec Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 29 Mar 2020 19:47:46 +0800 Subject: [PATCH] Import plenty of utilities from prodash into tui-react --- Cargo.lock | 12 ++-- tui-react/Cargo.toml | 4 +- tui-react/src/lib.rs | 129 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 138 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91e4ee10..bc7732a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,9 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "backtrace" -version = "0.3.45" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8" +checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e" dependencies = [ "backtrace-sys", "cfg-if", @@ -40,9 +40,9 @@ dependencies = [ [[package]] name = "backtrace-sys" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69" +checksum = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118" dependencies = [ "cc", "libc", @@ -601,10 +601,12 @@ dependencies = [ [[package]] name = "tui-react" -version = "0.2.1" +version = "0.2.2" dependencies = [ "log", "tui", + "unicode-segmentation", + "unicode-width", ] [[package]] diff --git a/tui-react/Cargo.toml b/tui-react/Cargo.toml index 341bd417..d41605b3 100644 --- a/tui-react/Cargo.toml +++ b/tui-react/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tui-react" -version = "0.2.1" +version = "0.2.2" authors = ["Sebastian Thiel "] edition = "2018" repository = "https://github.com/Byron/dua-cli" @@ -11,3 +11,5 @@ license = "MIT" [dependencies] tui = "0.8.0" log = "0.4.6" +unicode-segmentation = "1.6.0" +unicode-width = "0.1.7" diff --git a/tui-react/src/lib.rs b/tui-react/src/lib.rs index b7d5b943..de9fad1a 100644 --- a/tui-react/src/lib.rs +++ b/tui-react/src/lib.rs @@ -7,7 +7,9 @@ pub use list::*; pub use terminal::*; use std::iter::repeat; -use tui::{self, buffer::Buffer, layout::Rect, style::Color}; +use tui::{self, buffer::Buffer, layout::Rect, style::Color, style::Style}; +use unicode_segmentation::UnicodeSegmentation; +use unicode_width::UnicodeWidthStr; pub fn fill_background_to_right(mut s: String, entire_width: u16) -> String { match (s.len(), entire_width as usize) { @@ -27,3 +29,128 @@ pub fn fill_background(area: Rect, buf: &mut Buffer, color: Color) { } } } + +pub fn draw_text_with_ellipsis_nowrap<'a>( + bound: Rect, + buf: &mut Buffer, + text: impl AsRef, + style: impl Into>, +) -> u16 { + let s = style.into(); + let t = text.as_ref(); + let mut graphemes = t.graphemes(true); + let mut total_width = 0; + { + let mut ellipsis_candidate_x = None; + let mut x_offset = 0; + for (g, mut x) in graphemes.by_ref().zip(bound.left()..bound.right()) { + let width = g.width(); + total_width += width; + + x += x_offset; + let cell = buf.get_mut(x, bound.y); + if x + 1 == bound.right() { + ellipsis_candidate_x = Some(x); + } + cell.symbol = g.into(); + if let Some(s) = s { + cell.style = s; + } + + x_offset += width.saturating_sub(1) as u16; + if x + x_offset >= bound.right() { + break; + } + let x = x as usize; + for x in x + 1..x + width { + let i = buf.index_of(x as u16, bound.y); + buf.content[i].reset(); + } + } + if let (Some(_), Some(x)) = (graphemes.next(), ellipsis_candidate_x) { + buf.get_mut(x, bound.y).symbol = "…".into(); + } + } + total_width as u16 +} + +pub fn draw_text_nowrap_fn( + bound: Rect, + buf: &mut Buffer, + t: impl AsRef, + mut s: impl FnMut(&str, u16, u16) -> Style, +) { + if bound.width == 0 { + return; + } + for (g, x) in t.as_ref().graphemes(true).zip(bound.left()..bound.right()) { + let cell = buf.get_mut(x, bound.y); + cell.symbol = g.into(); + cell.style = s(&cell.symbol, x, bound.y); + } +} + +pub mod util { + use unicode_segmentation::UnicodeSegmentation; + use unicode_width::UnicodeWidthStr; + + pub fn sanitize_offset(offset: u16, num_items: usize, num_displayable_lines: u16) -> u16 { + offset.min((num_items.saturating_sub(num_displayable_lines as usize)) as u16) + } + + #[derive(Default)] + pub struct GraphemeCountWriter(pub usize); + + impl std::io::Write for GraphemeCountWriter { + fn write(&mut self, buf: &[u8]) -> Result { + self.0 += String::from_utf8_lossy(buf).graphemes(true).count(); + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<(), std::io::Error> { + Ok(()) + } + } + + pub fn block_width(s: &str) -> u16 { + s.graphemes(true).map(|g| g.width()).sum::() as u16 + } + + pub mod rect { + use tui::layout::Rect; + + /// A safe version of Rect::intersection that doesn't suffer from underflows + pub fn intersect(lhs: Rect, rhs: Rect) -> Rect { + let x1 = lhs.x.max(rhs.x); + let y1 = lhs.y.max(rhs.y); + let x2 = lhs.right().min(rhs.right()); + let y2 = lhs.bottom().min(rhs.bottom()); + Rect { + x: x1, + y: y1, + width: x2.saturating_sub(x1), + height: y2.saturating_sub(y1), + } + } + + pub fn offset_x(r: Rect, offset: u16) -> Rect { + Rect { + x: r.x + offset, + width: r.width.saturating_sub(offset), + ..r + } + } + + pub fn snap_to_right(bound: Rect, new_width: u16) -> Rect { + offset_x(bound, bound.width.saturating_sub(new_width)) + } + + pub fn line_bound(bound: Rect, line: usize) -> Rect { + Rect { + y: bound.y + line as u16, + height: 1, + ..bound + } + } + } +}