mod cell;
mod cursor;
mod escape;
mod line;
mod render;
mod ui_command;
use cell::*;
use cursor::*;
use escape::{EscapeSequence, EscapeState};
use line::*;
pub use ui_command::*;
use std::collections::VecDeque;
#[derive(Debug)]
pub struct ScreenBuffer {
width: u16,
height: u16,
lines: VecDeque<Line>,
view_start: usize,
cursor_pos: Position,
selection_start: Option<(u16, usize)>,
selection_end: Option<(u16, usize)>,
max_scrollback: usize,
escape_state: EscapeState,
escape_sequence: EscapeSequence,
last_render: Option<tokio::time::Instant>,
needs_render: bool,
}
impl ScreenBuffer {
pub fn new(width: u16, height: u16, max_scrollback: usize) -> Self {
let mut buffer = Self {
width,
height,
lines: VecDeque::new(),
view_start: 0,
cursor_pos: Position::home(),
selection_start: None,
selection_end: None,
max_scrollback,
last_render: None,
needs_render: false,
escape_state: EscapeState::Normal,
escape_sequence: EscapeSequence::new(),
};
buffer.lines.push_back(Line::new(width as usize));
buffer
}
fn set_char_at_cursor(&mut self, ch: char) {
while self.cursor_pos.y >= self.lines.len() {
self.lines.push_back(Line::new(self.width as usize));
}
if let Some(line) = self.lines.get_mut(self.cursor_pos.y)
&& (self.cursor_pos.x as usize) < line.len()
{
line.set_char(self.cursor_pos.x as usize, ch);
}
}
fn clear_from_cursor_to_sol(&mut self) {
if let Some(line) = self.lines.get_mut(self.cursor_pos.y) {
line.reset_to_idx(self.cursor_pos.x as usize);
}
}
fn clear_from_cursor_to_sos(&mut self) {
self.clear_from_cursor_to_sol();
for line in self
.lines
.range_mut(self.view_start..=self.cursor_pos.y - 1)
{
line.reset();
}
}
fn clear_from_cursor_to_eol(&mut self) {
if let Some(line) = self.lines.get_mut(self.cursor_pos.y) {
line.reset_from_idx(self.cursor_pos.x as usize);
}
}
fn clear_from_cursor_to_eos(&mut self) {
self.clear_from_cursor_to_eol();
for line in self.lines.range_mut(self.cursor_pos.y + 1..) {
line.reset();
}
}
fn clear_whole_line(&mut self) {
if let Some(line) = self.lines.get_mut(self.cursor_pos.y) {
line.reset();
}
}
fn new_line(&mut self) {
self.set_cursor_pos((0, self.cursor_pos.y + 1));
if self.cursor_pos.y >= self.lines.len() {
self.lines.push_back(Line::new(self.width as usize));
}
while self.lines.len() > self.max_scrollback {
self.lines.pop_front();
if self.cursor_pos.y > 0 {
self.cursor_pos.y -= 1;
}
if self.view_start > 0 {
self.view_start -= 1;
}
}
}
#[allow(dead_code)]
pub fn get_stats(&self) -> BufferStats {
let total_lines = self.lines.len();
BufferStats {
total_lines,
view_start: self.view_start,
view_end: (self.view_start + self.height as usize).min(total_lines),
cursor_line: self.cursor_pos.y,
has_selection: self.selection_start.is_some() && self.selection_end.is_some(),
}
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct BufferStats {
pub total_lines: usize,
pub view_start: usize,
pub view_end: usize,
pub cursor_line: usize,
pub has_selection: bool,
}