diff --git a/src/main.rs b/src/main.rs index 7982f93..c067e55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,6 +57,7 @@ mod config; mod stats; mod icon; mod quick_actions; +mod trait_ext; #[cfg(feature = "img")] mod imgview; diff --git a/src/term.rs b/src/term.rs index a62bf58..2d32773 100644 --- a/src/term.rs +++ b/src/term.rs @@ -6,8 +6,10 @@ use termion::screen::AlternateScreen; use termion::raw::{IntoRawMode, RawTerminal}; use parse_ansi::parse_bytes; +use crate::unicode_width::{UnicodeWidthStr, UnicodeWidthChar}; use crate::fail::{HResult, ErrorLog}; +use crate::trait_ext::ExtractResult; pub type TermMode = AlternateScreen>>; @@ -203,7 +205,7 @@ pub fn size() -> HResult<(usize, usize)> { pub fn sized_string(string: &str, xsize: u16) -> String { string.chars().fold("".to_string(), |acc, ch| { - let width: usize = unicode_width::UnicodeWidthStr::width_cjk(acc.as_str()); + let width: usize = unicode_width::UnicodeWidthStr::width(acc.as_str()); if width + 1 >= xsize as usize { acc } else { @@ -212,46 +214,99 @@ pub fn sized_string(string: &str, xsize: u16) -> String { }) } -fn is_ansi(ansi_pos: &Vec<(usize, usize)>, char_pos: &usize) -> bool { - ansi_pos.iter().fold(false, |is_ansi, (start, end)| { - if char_pos >= start && char_pos <= end { - true - } else { is_ansi } - }) +#[derive(Debug)] +enum Token<'a> { + Text(&'a str), + Ansi(&'a str) } -fn ansi_len_at(ansi_pos: &Vec<(usize, usize)>, char_pos: &usize) -> usize { - ansi_pos.iter().fold(0, |len, (start, end)| { - if char_pos >= start && char_pos <= end { - len + (char_pos - start) - } else if char_pos >= end { - len + (end - start) - } else { - len - } - }) +fn get_tokens(string: &str) -> Vec { + let mut tokens = parse_bytes(string.as_bytes()) + .fold((Vec::new(), 0), |(mut tokens, last_tok), ansi_pos| { + if last_tok == 0 { + // first iteration + if ansi_pos.start() != 0 { + // there is text before first ansi code + tokens.push(Token::Text(&string[0..ansi_pos.start()])); + } + tokens.push(Token::Ansi(&string[ansi_pos.start()..ansi_pos.end()])); + (tokens, ansi_pos.end()) + } else if last_tok == ansi_pos.start() { + // next token is another ansi code + tokens.push(Token::Ansi(&string[ansi_pos.start()..ansi_pos.end()])); + (tokens, ansi_pos.end()) + } else { + // there is text before the next ansi code + tokens.push(Token::Text(&string[last_tok..ansi_pos.start()])); + tokens.push(Token::Ansi(&string[ansi_pos.start()..ansi_pos.end()])); + (tokens, ansi_pos.end()) + } + }); + + // last part is just text, add it to tokens + if string.len() > tokens.1 { + tokens.0.push(Token::Text(&string[tokens.1..string.len()])); + } + + tokens.0 } + pub fn sized_string_u(string: &str, xsize: usize) -> String { - let ansi_pos = parse_bytes(string.as_bytes()).map(|m| { - (m.start(), m.end()) - }).collect(); + let tokens = get_tokens(&string); - let sized = string.chars().fold(String::new(), |acc, ch| { - let width: usize = unicode_width::UnicodeWidthStr::width_cjk(acc.as_str()); - let ansi_len = ansi_len_at(&ansi_pos, &acc.len()); - let unprinted = acc.len() - width; + let sized = tokens.iter().try_fold((String::new(), 0), |(mut sized, width), token| { + let (tok, tok_width) = match token { + Token::Text(text) => { + let tok_str = text; + let tok_width = text.width(); + (tok_str, tok_width) + }, + Token::Ansi(ansi) => (ansi, 0) + }; - if width + unprinted >= xsize + ansi_len + 1{ - acc + // adding this token makes string larger than xsise + if width + tok_width > xsize { + let chars_left = xsize + 1 - width; + + // fill up with chars from token until xsize is reached + let fillup = tok.chars().try_fold((String::new(), 0), + |(mut fillup, fillup_width), chr| { + let chr_width = chr.width().unwrap_or(0); + + if fillup_width + chr_width > chars_left { + Err((fillup, fillup_width)) + } else { + fillup.push(chr); + Ok((fillup, fillup_width + chr_width)) + } + }); + + let (fillup, fillup_width) = fillup.extract(); + sized.push_str(&fillup); + + // we're done here, stop looping + Err((sized, width + fillup_width)) } else { - acc + &ch.to_string() + sized.push_str(&tok); + Ok((sized, width + tok_width)) } }); - let ansi_len = ansi_len_at(&ansi_pos, &sized.len()); - let padded = format!("{:padding$}", sized, padding=xsize + ansi_len + 1); - padded + + + let (mut sized_str, sized_width) = sized.extract(); + + // pad out string + if sized_width < xsize { + let padding = xsize-sized_width; + for _ in 0..padding { + sized_str += " "; + } + } + + + sized_str } diff --git a/src/trait_ext.rs b/src/trait_ext.rs new file mode 100644 index 0000000..350138b --- /dev/null +++ b/src/trait_ext.rs @@ -0,0 +1,12 @@ +pub trait ExtractResult { + fn extract(self) -> T; +} + +impl ExtractResult for Result { + fn extract(self) -> T { + match self { + Ok(val) => val, + Err(val) => val + } + } +}