diff --git a/src/chat.rs b/src/chat.rs index 8b251fa..84f67a6 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1,10 +1,10 @@ use std::{cmp::{max, min}, error::Error, io::{stdout, Write}, sync::{atomic::{AtomicUsize, Ordering}, Arc, RwLock}, thread, time::{Duration, SystemTime}}; use colored::{Color, Colorize}; -use crossterm::{cursor::{MoveLeft, MoveRight}, event::{self, Event, KeyCode, KeyModifiers, MouseEventKind}, terminal::{disable_raw_mode, enable_raw_mode}}; +use crossterm::{cursor::{MoveLeft, MoveRight}, event::{self, Event, KeyCode, KeyModifiers, MouseEventKind}, terminal::{self, disable_raw_mode, enable_raw_mode}}; use rand::random; -use crate::IP_REGEX; +use crate::{util::string_chunks, IP_REGEX}; use super::{proto::read_messages, util::sanitize_text, COLORED_USERNAMES, DATE_REGEX, config::Context, proto::send_message}; @@ -88,11 +88,30 @@ Press enter to close")?; } -pub fn print_console(context: Arc, messages: Vec, input: &str) -> Result<(), Box> { +pub fn print_console(ctx: Arc, messages: Vec, input: &str) -> Result<(), Box> { + let (width, height) = terminal::size()?; + let (width, height) = (width as usize, height as usize); + + let scroll = ctx.scroll.load(Ordering::SeqCst); + let scroll = (1f64 - scroll as f64 / messages.len() as f64) * (height - 1) as f64; + let scroll = scroll as usize; + let text = format!( - "{}{}\r\n> {}", - "\r\n".repeat(context.max_messages - messages.len()), - messages.join("\r\n"), + "{}\r\n> {}", + messages[messages.len()-height-1..].into_iter() + .flat_map(|o| string_chunks(&o, width as usize - 1)) + .enumerate() + .map(|(i, (s, l))| { + format!("{}{}{}", + s, + " ".repeat(width - 1 - l), + if i == scroll { + "#" + } else { + "|" + } + ) + }).collect::>().join("\r\n"), input ); @@ -333,6 +352,13 @@ fn poll_events(ctx: Arc) -> Result<(), Box> { write!(stdout(), "{}", &data).unwrap(); stdout().lock().flush().unwrap(); }, + Event::Resize(_, _) => { + print_console( + ctx.clone(), + messages.messages(), + &input.read().unwrap() + )?; + }, Event::Mouse(data) => { match data.kind { MouseEventKind::ScrollUp => { diff --git a/src/util.rs b/src/util.rs index f6a6ad8..9197ddc 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,13 +1,62 @@ -use std::io::{stdin, stdout, BufRead, Write}; +use std::{collections::HashSet, io::{stdin, stdout, BufRead, Write}, ops::Range}; +use lazy_static::lazy_static; use regex::Regex; +lazy_static! { + static ref ANSI_REGEX: Regex = Regex::new(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").unwrap(); + static ref CONTROL_CHARS_REGEX: Regex = Regex::new(r"[\x00-\x1F\x7F]").unwrap(); +} + +fn get_matches(regex: &Regex, text: &str) -> Vec> { + regex.find_iter(text).map(|mat| mat.range()).collect() +} + +pub fn string_chunks(text: &str, width: usize) -> Vec<(String, usize)> { + let mut norm: Vec = vec![true; text.chars().count()]; + + for range in get_matches(&ANSI_REGEX, text) { + for i in range { + if let Some(index) = text.char_indices().position(|x| x.0 == i) { + norm[index] = false; + } + } + } + for range in get_matches(&CONTROL_CHARS_REGEX, text) { + for i in range { + if let Some(index) = text.char_indices().position(|x| x.0 == i) { + norm[index] = false; + } + } + } + + let mut now_chunk = String::new(); + let mut chunks = Vec::new(); + let mut length = 0; + + for (i, b) in norm.iter().enumerate() { + if *b { + length += 1; + } + + now_chunk.push(text.chars().skip(i).next().unwrap()); + + if length == width { + chunks.push((now_chunk.clone(), length)); + now_chunk.clear(); + length = 0; + } + } + if !now_chunk.is_empty() { + chunks.push((now_chunk.clone(), length)); + } + + chunks +} pub fn sanitize_text(input: &str) -> String { - let ansi_regex = Regex::new(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").unwrap(); - let control_chars_regex = Regex::new(r"[\x00-\x1F\x7F]").unwrap(); - let without_ansi = ansi_regex.replace_all(input, ""); - let cleaned_text = control_chars_regex.replace_all(&without_ansi, ""); + let without_ansi = ANSI_REGEX.replace_all(input, ""); + let cleaned_text = CONTROL_CHARS_REGEX.replace_all(&without_ansi, ""); cleaned_text.into_owned() }