From 7b6b7992ab92eff0b27c9fe973def73dfa7d68c3 Mon Sep 17 00:00:00 2001 From: Thomas Forgione Date: Sun, 22 Oct 2023 17:21:31 +0200 Subject: [PATCH] Cleaning --- demo.sh | 4 +- src/lib.rs | 515 ++++++++-------------------------------------------- src/tile.rs | 445 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 524 insertions(+), 440 deletions(-) create mode 100644 src/tile.rs diff --git a/demo.sh b/demo.sh index b7253cf..cac71d3 100755 --- a/demo.sh +++ b/demo.sh @@ -7,7 +7,7 @@ rand() { iterations=$(rand 5 10) for i in $(seq 1 "$iterations"); do - color="\x1B[3$(rand 0 6)m" + color="\x1B[3$(rand 1 6)m" echo -e "$color$(rand 1 100)\x1b[0m" - sleep $(rand 1 2) + sleep 0.$(rand 1 2) done diff --git a/src/lib.rs b/src/lib.rs index 0048dab..b21c5d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,45 @@ -use std::io::{self, stdin, stdout, Read, Write}; -use std::process::Stdio; -use std::sync::mpsc::{channel, Sender}; +use std::io::{self, stdin, stdout, Write}; +use std::sync::mpsc::channel; use std::{env, thread}; -use pty_process::blocking::Command; -use pty_process::blocking::Pty; - use termion::event::{Event, Key, MouseButton, MouseEvent}; use termion::input::{MouseTerminal, TermRead}; use termion::raw::IntoRawMode; use termion::screen::IntoAlternateScreen; use termion::terminal_size; -use termion::{clear, color, cursor, style}; +use termion::{clear, cursor}; + +pub mod tile; + +use tile::{Tile, TileBuilder}; + +/// Draws a box from (x1, y1) to (x2, y2). +pub fn rect((x1, y1): (u16, u16), (x2, y2): (u16, u16)) -> String { + let mut buffer = vec![]; + + buffer.push(format!("{}┌", cursor::Goto(x1, y1))); + + for _ in (x1 + 1)..x2 { + buffer.push(format!("─")); + } + + buffer.push(format!("┐")); + + for y in (y1 + 1)..y2 { + buffer.push(format!("{}│", cursor::Goto(x1, y))); + buffer.push(format!("{}│", cursor::Goto(x2, y))); + } + + buffer.push(format!("{}└", cursor::Goto(x1, y2))); + + for _ in (x1 + 1)..x2 { + buffer.push(format!("─")); + } + + buffer.push(format!("┘")); + + buffer.join("") +} /// Returns the length of a string containing colors and styles. pub fn str_len(s: &str) -> u16 { @@ -109,173 +137,6 @@ pub fn sub_str<'a>(s: &'a str, start: u16, end: u16) -> &'a str { &s[real_start..real_end] } -#[cfg(test)] -mod test { - use termion::color; - - use crate::str_len; - - #[test] - fn test_str_len_1() { - let string = format!( - "{}Hello{} {}World{}", - color::Red.fg_str(), - color::Reset.fg_str(), - color::Green.fg_str(), - color::Reset.fg_str(), - ); - - assert_eq!(str_len(&string), 11); - } -} - -/// A tile with a command running inside it. -#[derive(Debug)] -pub struct Tile { - /// The command that should be executed in the tile. - pub command: Vec, - - /// Content of the command's stdout and stderr. - /// - /// We put both stdout and stderr here to avoid dealing with order between stdout and stderr. - pub stdout: String, - - /// The cursor where stdout should write. - /// - /// If None, stdout should push at the end of the string. - pub cursor: Option, - - /// The number of chars in stdout. - pub len: usize, - - /// The sender for the communication with the multiview. - pub sender: Sender, - - /// Coordinates of the tile. - pub coords: (u16, u16), - - /// The number of lines that the stdout is scrolled. - pub scroll: isize, - - /// The number of lines that stdout will print. - pub max_scroll: isize, -} - -impl Tile { - /// Creates a new empty tile. - pub fn new(command: &[String], i: u16, j: u16, sender: Sender) -> Tile { - Tile { - command: command - .into_iter() - .map(|x| x.to_string()) - .collect::>(), - stdout: String::new(), - len: 0, - cursor: None, - sender, - coords: (i, j), - scroll: 0, - max_scroll: 0, - } - } - - /// Starts the commands. - pub fn start(&mut self, width: u16, height: u16) { - let command = self - .command - .iter() - .map(|x| x.to_string()) - .collect::>(); - - let coords = self.coords; - let clone = command.clone(); - let sender = self.sender.clone(); - - thread::spawn(move || { - let pty = Pty::new().unwrap(); - pty.resize(pty_process::Size::new(height - 4, width - 4)) - .unwrap(); - - let mut child = Command::new(&clone[0]) - .args(&clone[1..]) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn(&pty.pts().unwrap()) - .unwrap(); - - let mut stdout = child.stdout.take().unwrap(); - let mut stderr = child.stderr.take().unwrap(); - let stderr_sender = sender.clone(); - - let coords = coords; - - thread::spawn(move || loop { - let mut buffer = [0; 4096]; - let result = stderr.read(&mut buffer); - - match result { - Ok(0) => break, - - Ok(n) => { - stderr_sender - .send(Msg::Stderr( - coords.0, - coords.1, - String::from_utf8_lossy(&buffer[0..n]).to_string(), - )) - .unwrap(); - } - - Err(_) => break, - } - }); - - loop { - let mut buffer = [0; 4096]; - let result = stdout.read(&mut buffer); - - match result { - Ok(0) => break, - - Ok(n) => { - sender - .send(Msg::Stderr( - coords.0, - coords.1, - String::from_utf8_lossy(&buffer[0..n]).to_string(), - )) - .unwrap(); - } - - Err(_) => break, - } - } - - sender - .send(Msg::Stdout(coords.0, coords.1, String::from("\n"))) - .unwrap(); - - let code = child.wait().unwrap().code().unwrap(); - - let exit_string = format!( - "{}{}Command exited with return code {}{}\n", - style::Bold, - if code == 0 { - color::Green.fg_str() - } else { - color::Red.fg_str() - }, - code, - style::Reset, - ); - - sender - .send(Msg::Stdout(coords.0, coords.1, exit_string)) - .unwrap(); - }); - } -} - /// Multiple applications running in a single terminal. struct Multiview { /// The stdout on which the multiview will be rendererd. @@ -333,191 +194,27 @@ impl Multiview { self.refresh_ui = true; } - /// Draws a box from (x1, y1) to (x2, y2). - pub fn rect(&self, (x1, y1): (u16, u16), (x2, y2): (u16, u16)) -> String { - let mut buffer = vec![]; - - buffer.push(format!("{}┌", cursor::Goto(x1, y1))); - - for _ in (x1 + 1)..x2 { - buffer.push(format!("─")); - } - - buffer.push(format!("┐")); - - for y in (y1 + 1)..y2 { - buffer.push(format!("{}│", cursor::Goto(x1, y))); - buffer.push(format!("{}│", cursor::Goto(x2, y))); - } - - buffer.push(format!("{}└", cursor::Goto(x1, y2))); - - for _ in (x1 + 1)..x2 { - buffer.push(format!("─")); - } - - buffer.push(format!("┘")); - - buffer.join("") - } - /// Renders the border and the title of a tile. - pub fn first_render_tile(&self, (i, j): (u16, u16), term_size: (u16, u16)) -> String { - let w = term_size.0 / self.tiles[0].len() as u16; - let h = term_size.1 / self.tiles.len() as u16; - - let x1 = j * w + 1; - let y1 = i * h + 1; - - let x2 = (j + 1) * w; - let y2 = (i + 1) * h; - + pub fn render_tile_border(&self, (i, j): (u16, u16)) -> String { let tile = &self.tile((i, j)); - let command_str = tile.command.join(" "); - - let mut buffer = vec![]; - - let max_title_len = - (term_size.0 / self.tiles[0].len() as u16) - 4 - "Command: ".len() as u16; - - let command_str = if command_str.len() > max_title_len as usize { - format!( - "{}...", - &command_str[0 as usize..max_title_len as usize - 3] - ) - } else { - command_str - }; - - buffer.push(format!( - "{}{} {}Command: {}{}{}", - color::Reset.fg_str(), - cursor::Goto(x1 + 1, y1 + 1), - style::Bold, - command_str, - style::Reset, - cursor::Goto(x1 + 2, y1 + 3), - )); - - if self.selected == (i, j) { - buffer.push(format!("{}", color::Green.fg_str())); - } - buffer.push(self.rect((x1, y1), (x2, y2))); - buffer.push(format!("{}├", cursor::Goto(x1, y1 + 2))); - - for _ in (x1 + 1)..x2 { - buffer.push(format!("─")); - } - - buffer.push(format!("{}┤", cursor::Goto(x2, y1 + 2))); - - buffer.join("") + tile.render_border(self.selected == ((i, j))) } /// Renders the (x, y) tile. - pub fn render_tile(&mut self, (i, j): (u16, u16), term_size: (u16, u16)) -> String { - let w = term_size.0 / self.tiles[0].len() as u16; - let h = term_size.1 / self.tiles.len() as u16; - - let x1 = j * w + 1; - let y1 = i * h + 1; - - let tile = &self.tile((i, j)); - - let mut buffer = vec![]; - - let mut counting = true; - let mut line_index = 0; - let mut current_char_index = 0; - let scroll = tile.scroll as u16; - - buffer.push(format!("{}", cursor::Goto(x1 + 2, y1 + 3))); - - for c in tile.stdout.chars() { - if c == '\x1b' { - counting = false; - } - - match c { - '\n' => { - line_index += 1; - let old_current_char_index = current_char_index; - current_char_index = 0; - - if line_index >= scroll && line_index < h + scroll - 4 { - if old_current_char_index < w { - let mut spaces = String::new(); - for _ in old_current_char_index..w - 3 { - spaces.push(' '); - } - buffer.push(spaces); - } - - buffer.push(format!( - "{}", - cursor::Goto(x1 + 2, y1 + 3 + line_index as u16 - scroll) - )); - } - } - - _ => { - if counting { - current_char_index += 1; - } - - if current_char_index == w - 3 { - line_index += 1; - current_char_index = 1; - - if line_index >= scroll && line_index < h + scroll - 4 { - buffer.push(format!( - "{}", - cursor::Goto(x1 + 2, y1 + 3 + line_index as u16 - scroll) - )); - } - } - - if line_index >= scroll && line_index < h + scroll - 4 { - buffer.push(format!("{}", c)); - } - } - } - - if c == 'm' { - counting = true; - } - } - - if current_char_index == 0 { - let mut spaces = format!("{}", cursor::Goto(x1 + 2, y1 + h - 2)); - for _ in 0..w - 3 { - spaces.push(' '); - } - buffer.push(spaces); - } - - let tile = self.tile_mut((i, j)); - if tile.max_scroll != line_index as isize { - tile.max_scroll = line_index as isize; - tile.scroll = tile.max_scroll - h as isize + 5; - if tile.scroll < 0 { - tile.scroll = 0; - } - } - - buffer.push(format!("{}", style::Reset)); - buffer.join("") + pub fn render_tile_content(&mut self, (i, j): (u16, u16)) -> String { + let tile = self.tile((i, j)); + tile.render_content() } /// Renders all the tiles of the multiview. - pub fn render(&mut self, term_size: (u16, u16)) -> io::Result<()> { + pub fn render(&mut self) -> io::Result<()> { let mut buffer = vec![]; for i in 0..self.tiles.len() { for j in 0..self.tiles[0].len() { if self.refresh_ui { - buffer.push(self.first_render_tile((i as u16, j as u16), term_size)); + buffer.push(self.render_tile_border((i as u16, j as u16))); } - buffer.push(self.render_tile((i as u16, j as u16), term_size)); + buffer.push(self.render_tile_content((i as u16, j as u16))); } } @@ -544,83 +241,7 @@ impl Multiview { /// Push a string into a tile's stdout. pub fn push_stdout(&mut self, (i, j): (u16, u16), content: String) { let tile = self.tile_mut((i, j)); - - let mut clear_line_counter = 0; - - for c in content.chars() { - // Check if we're running into \x1b[K - clear_line_counter = match (c, clear_line_counter) { - ('\x1b', _) => 1, - ('[', 1) => 2, - ('K', 2) => 3, - _ => 0, - }; - - match (clear_line_counter, tile.cursor) { - (3, Some(cursor)) => { - // Find the size of the string until the next '\n' or end - let mut counter = 0; - loop { - counter += 1; - - // TODO fix utf8 - if tile.stdout.len() <= counter + cursor - || &tile.stdout[cursor + counter..cursor + counter + 1] == "\n" - { - break; - } - } - - tile.stdout - .replace_range((cursor - 2)..(cursor + counter), ""); - tile.len -= 2 + counter; - tile.cursor = None; - continue; - } - _ => (), - } - - if c == '\r' { - // Set cursor at the right place - let mut index = tile.len; - let mut reverse = tile.stdout.chars().rev(); - - loop { - match reverse.next() { - Some('\n') | None => break, - _ => index -= 1, - } - } - - tile.cursor = Some(index); - } else { - let new_cursor = match tile.cursor { - Some(index) => { - if c == '\n' { - tile.stdout.push(c); - tile.len += 1; - None - } else { - // TODO fix utf8 - tile.stdout.replace_range(index..index + 1, &c.to_string()); - if index + 1 == tile.len { - None - } else { - Some(index + 1) - } - } - } - - None => { - tile.stdout.push(c); - tile.len += 1; - None - } - }; - - tile.cursor = new_cursor; - } - } + tile.push_stdout(content); } /// Push a string into a tile's stderr. @@ -638,10 +259,10 @@ impl Drop for Multiview { /// An event that can be sent in channels. pub enum Msg { /// An stdout line arrived. - Stdout(u16, u16, String), + Stdout((u16, u16), String), /// An stderr line arrived. - Stderr(u16, u16, String), + Stderr((u16, u16), String), /// A click occured. Click(u16, u16), @@ -669,7 +290,32 @@ pub fn main() -> io::Result<()> { .map(|(i, tiles)| { tiles .into_iter() - .map(|(j, tile)| Tile::new(tile, i as u16, j as u16, sender.clone())) + .map(|(j, tile)| ((i, j), tile)) + .collect::>() + }) + .collect::>(); + + let term_size = terminal_size()?; + + let tile_size = ( + term_size.0 / tiles[0].len() as u16, + term_size.1 / tiles.len() as u16, + ); + + let tiles = tiles + .into_iter() + .map(|row| { + row.into_iter() + .map(|((i, j), tile)| { + TileBuilder::new() + .command(tile.into()) + .coords((i as u16, j as u16)) + .position((j as u16 * tile_size.0 + 1, i as u16 * tile_size.1 + 1)) + .size(tile_size) + .sender(sender.clone()) + .build() + .unwrap() + }) .collect::>() }) .collect::>(); @@ -679,19 +325,12 @@ pub fn main() -> io::Result<()> { let stdout = stdout.into_alternate_screen()?; let stdout = MouseTerminal::from(stdout); - let term_size = terminal_size()?; - let mut multiview = Multiview::new(stdout, tiles)?; - multiview.render(term_size)?; - - let tile_size = ( - term_size.0 / multiview.tiles[0].len() as u16, - term_size.1 / multiview.tiles.len() as u16, - ); + multiview.render()?; for row in &mut multiview.tiles { for tile in row { - tile.start(tile_size.0, tile_size.1); + tile.start(); } } @@ -716,8 +355,8 @@ pub fn main() -> io::Result<()> { loop { match receiver.recv() { - Ok(Msg::Stdout(i, j, line)) => multiview.push_stdout((i, j), line), - Ok(Msg::Stderr(i, j, line)) => multiview.push_stderr((i, j), line), + Ok(Msg::Stdout(coords, line)) => multiview.push_stdout(coords, line), + Ok(Msg::Stderr(coords, line)) => multiview.push_stderr(coords, line), Ok(Msg::Click(x, y)) => multiview.select_tile((x, y), term_size), Ok(Msg::ScrollDown) => multiview.scroll_down(), Ok(Msg::ScrollUp) => multiview.scroll_up(), @@ -725,7 +364,7 @@ pub fn main() -> io::Result<()> { Err(_) => (), } - multiview.render(term_size)?; + multiview.render()?; } Ok(()) diff --git a/src/tile.rs b/src/tile.rs new file mode 100644 index 0000000..69139db --- /dev/null +++ b/src/tile.rs @@ -0,0 +1,445 @@ +//! This module contains everything related to tiles. + +use std::io::Read; +use std::process::Stdio; +use std::sync::mpsc::Sender; +use std::thread; + +use pty_process::blocking::Command; +use pty_process::blocking::Pty; + +use termion::{color, cursor, style}; + +use crate::{rect, Msg}; + +/// A helper to build tiles. +pub struct TileBuilder { + /// The command that the tile will run. + pub command: Option>, + + /// The coordinates of the tile. + pub coords: Option<(u16, u16)>, + + /// The top left corner of the tile. + pub position: Option<(u16, u16)>, + + /// The size of the tile. + pub size: Option<(u16, u16)>, + + /// The sender to communicate with the main view. + pub sender: Option>, +} + +impl TileBuilder { + /// Creates an empty tile builder. + pub fn new() -> TileBuilder { + TileBuilder { + command: None, + coords: None, + position: None, + size: None, + sender: None, + } + } + + /// Sets the command of the tile. + pub fn command(self, command: Vec) -> TileBuilder { + let mut s = self; + s.command = Some(command); + s + } + + /// Sets the coordinates of the tile. + pub fn coords(self, coords: (u16, u16)) -> TileBuilder { + let mut s = self; + s.coords = Some(coords); + s + } + + /// Sets the position of the tile. + pub fn position(self, position: (u16, u16)) -> TileBuilder { + let mut s = self; + s.position = Some(position); + s + } + + /// Sets the size of the tile. + pub fn size(self, size: (u16, u16)) -> TileBuilder { + let mut s = self; + s.size = Some(size); + s + } + + /// Sets the sender of the tile. + pub fn sender(self, sender: Sender) -> TileBuilder { + let mut s = self; + s.sender = Some(sender); + s + } + + /// Builds the tile. + pub fn build(self) -> Option { + let (x, y) = self.position?; + let (w, h) = self.size?; + + Some(Tile { + command: self.command?, + coords: self.coords?, + outer_position: (x, y), + inner_position: (x + 2, y + 3), + outer_size: (w, h), + inner_size: (w - 4, h - 5), + sender: self.sender?, + stdout: String::new(), + cursor: None, + len: 0, + scroll: 0, + number_lines: 0, + }) + } +} + +/// A tile with a command running inside it. +#[derive(Debug)] +pub struct Tile { + /// The command that should be executed in the tile. + pub command: Vec, + + /// Content of the command's stdout and stderr. + /// + /// We put both stdout and stderr here to avoid dealing with order between stdout and stderr. + pub stdout: String, + + /// The cursor where stdout should write. + /// + /// If None, stdout should push at the end of the string. + pub cursor: Option, + + /// The number of chars in stdout. + pub len: usize, + + /// The sender for the communication with the multiview. + pub sender: Sender, + + /// Coordinates of the tile. + pub coords: (u16, u16), + + /// Top left corner of the tile. + pub outer_position: (u16, u16), + + /// Top left corner of the content of the tile. + pub inner_position: (u16, u16), + + /// Size of the tile. + pub outer_size: (u16, u16), + + /// Size of the inside of the tile. + pub inner_size: (u16, u16), + + /// The number of lines that the stdout is scrolled. + pub scroll: isize, + + /// The number of lines that stdout will print. + pub number_lines: isize, +} + +impl Tile { + /// Starts the commands. + pub fn start(&mut self) { + let command = self + .command + .iter() + .map(|x| x.to_string()) + .collect::>(); + + let coords = self.coords; + let clone = command.clone(); + let size = self.inner_size; + let sender = self.sender.clone(); + + thread::spawn(move || { + let pty = Pty::new().unwrap(); + pty.resize(pty_process::Size::new(size.1, size.0)).unwrap(); + + let mut child = Command::new(&clone[0]) + .args(&clone[1..]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn(&pty.pts().unwrap()) + .unwrap(); + + let mut stdout = child.stdout.take().unwrap(); + let mut stderr = child.stderr.take().unwrap(); + let stderr_sender = sender.clone(); + + let coords = coords; + + thread::spawn(move || loop { + let mut buffer = [0; 4096]; + let result = stderr.read(&mut buffer); + + match result { + Ok(0) => break, + + Ok(n) => { + stderr_sender + .send(Msg::Stderr( + coords, + String::from_utf8_lossy(&buffer[0..n]).to_string(), + )) + .unwrap(); + } + + Err(_) => break, + } + }); + + loop { + let mut buffer = [0; 4096]; + let result = stdout.read(&mut buffer); + + match result { + Ok(0) => break, + + Ok(n) => { + sender + .send(Msg::Stderr( + coords, + String::from_utf8_lossy(&buffer[0..n]).to_string(), + )) + .unwrap(); + } + + Err(_) => break, + } + } + + sender + .send(Msg::Stdout(coords, String::from("\n"))) + .unwrap(); + + let code = child.wait().unwrap().code().unwrap(); + + let exit_string = format!( + "{}{}Command exited with return code {}{}\n", + style::Bold, + if code == 0 { + color::Green.fg_str() + } else { + color::Red.fg_str() + }, + code, + style::Reset, + ); + + sender.send(Msg::Stdout(coords, exit_string)).unwrap(); + }); + } + + /// Push content into the stdout of the tile. + pub fn push_stdout(&mut self, content: String) { + let mut clear_line_counter = 0; + + for c in content.chars() { + // Check if we're running into \x1b[K + clear_line_counter = match (c, clear_line_counter) { + ('\x1b', _) => 1, + ('[', 1) => 2, + ('K', 2) => 3, + _ => 0, + }; + + match (clear_line_counter, self.cursor) { + (3, Some(cursor)) => { + // Find the size of the string until the next '\n' or end + let mut counter = 0; + loop { + counter += 1; + + // TODO fix utf8 + if self.stdout.len() <= counter + cursor + || &self.stdout[cursor + counter..cursor + counter + 1] == "\n" + { + break; + } + } + + self.stdout + .replace_range((cursor - 2)..(cursor + counter), ""); + self.len -= 2 + counter; + self.cursor = None; + continue; + } + _ => (), + } + + if c == '\r' { + // Set cursor at the right place + let mut index = self.len; + let mut reverse = self.stdout.chars().rev(); + + loop { + match reverse.next() { + Some('\n') | None => break, + _ => index -= 1, + } + } + + self.cursor = Some(index); + } else { + let new_cursor = match self.cursor { + Some(index) => { + if c == '\n' { + self.stdout.push(c); + self.len += 1; + None + } else { + // TODO fix utf8 + self.stdout.replace_range(index..index + 1, &c.to_string()); + if index + 1 == self.len { + None + } else { + Some(index + 1) + } + } + } + + None => { + self.stdout.push(c); + self.len += 1; + None + } + }; + + self.cursor = new_cursor; + } + } + } + + /// Renders the borders of the tile. + pub fn render_border(&self, selected: bool) -> String { + let (x, y) = self.outer_position; + let (w, h) = self.outer_size; + + let command_str = self.command.join(" "); + + let mut buffer = vec![]; + + let max_title_len = self.inner_size.1 - "Command: ".len() as u16; + + let command_str = if command_str.len() > max_title_len as usize { + format!( + "{}...", + &command_str[0 as usize..max_title_len as usize - 3] + ) + } else { + command_str + }; + + buffer.push(format!( + "{}{} {}Command: {}{}{}", + color::Reset.fg_str(), + cursor::Goto(x + 1, y + 1), + style::Bold, + command_str, + style::Reset, + cursor::Goto(x + 2, y + 3), + )); + + if selected { + buffer.push(format!("{}", color::Green.fg_str())); + } + + buffer.push(rect((x, y), (x + w - 1, y + h - 1))); + buffer.push(format!("{}├", cursor::Goto(x, y + 2))); + + for _ in (x + 1)..(x + w) { + buffer.push(format!("─")); + } + + buffer.push(format!("{}┤", cursor::Goto(x + w - 1, y + 2))); + + buffer.join("") + } + + /// Renders the content of the tile. + pub fn render_content(&self) -> String { + let (x, y) = self.inner_position; + let (w, h) = self.inner_size; + + let mut buffer = vec![]; + + let mut counting = true; + let mut line_index = 0; + let mut current_char_index = 0; + let scroll = self.scroll as u16; + + buffer.push(format!("{}", cursor::Goto(x, y))); + + for c in self.stdout.chars() { + if c == '\x1b' { + counting = false; + } + + match c { + '\n' => { + line_index += 1; + let old_current_char_index = current_char_index; + current_char_index = 0; + + if line_index >= scroll && line_index < h + scroll { + if old_current_char_index < w { + let mut spaces = String::new(); + for _ in old_current_char_index..w { + spaces.push(' '); + } + buffer.push(spaces); + } + + buffer.push(format!( + "{}", + cursor::Goto(x, y + line_index as u16 - scroll) + )); + } + } + + _ => { + if counting { + current_char_index += 1; + } + + if current_char_index == w { + line_index += 1; + current_char_index = 1; + + if line_index >= scroll && line_index < h + scroll { + buffer.push(format!( + "{}", + cursor::Goto(x, y + line_index as u16 - scroll) + )); + } + } + + if line_index >= scroll && line_index < h + scroll { + buffer.push(format!("{}", c)); + } + } + } + + if c == 'm' { + counting = true; + } + } + + if current_char_index == 0 { + let mut spaces = format!("{}", cursor::Goto(x, y + h)); + for _ in 0..w { + spaces.push(' '); + } + buffer.push(spaces); + } + + buffer.push(format!("{}", style::Reset)); + buffer.join("") + } +}