2025-02-13 13:10:06 +02:00
|
|
|
use std::collections::VecDeque;
|
|
|
|
|
2023-12-28 10:39:21 +02:00
|
|
|
use crate::attr::{CellAttributes, Color};
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
|
|
pub struct GridCell {
|
|
|
|
pub char: char,
|
|
|
|
pub attrs: CellAttributes,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct GridRow {
|
|
|
|
cols: Vec<GridCell>,
|
|
|
|
dirty: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Buffer {
|
2025-02-13 13:10:06 +02:00
|
|
|
scrollback: VecDeque<GridRow>,
|
2023-12-28 10:39:21 +02:00
|
|
|
rows: Vec<GridRow>,
|
|
|
|
width: usize,
|
|
|
|
height: usize,
|
2025-02-13 13:10:06 +02:00
|
|
|
scrollback_limit: usize,
|
2023-12-28 10:39:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
|
|
pub struct Cursor {
|
|
|
|
pub row: usize,
|
|
|
|
pub col: usize,
|
|
|
|
}
|
|
|
|
|
2025-03-02 17:27:26 +02:00
|
|
|
struct CsiState {
|
|
|
|
byte: u8,
|
|
|
|
}
|
|
|
|
|
2023-12-28 10:39:21 +02:00
|
|
|
enum EscapeState {
|
|
|
|
Normal,
|
|
|
|
Escape,
|
2025-03-02 17:27:26 +02:00
|
|
|
Csi(CsiState),
|
2023-12-28 10:39:21 +02:00
|
|
|
}
|
|
|
|
|
2025-03-01 15:23:33 +02:00
|
|
|
#[derive(Default)]
|
|
|
|
struct Utf8Decoder {
|
|
|
|
buffer: [u8; 4],
|
|
|
|
len: usize,
|
|
|
|
}
|
|
|
|
|
2023-12-28 10:39:21 +02:00
|
|
|
pub struct State {
|
|
|
|
pub buffer: Buffer,
|
|
|
|
|
2025-03-01 15:23:33 +02:00
|
|
|
utf8_decode: Utf8Decoder,
|
2023-12-28 10:39:21 +02:00
|
|
|
esc_state: EscapeState,
|
|
|
|
esc_args: Vec<u32>,
|
|
|
|
|
2025-02-13 13:10:06 +02:00
|
|
|
scroll: usize,
|
2023-12-28 10:39:21 +02:00
|
|
|
pub cursor: Cursor,
|
2025-03-02 17:27:26 +02:00
|
|
|
pub cursor_visible: bool,
|
|
|
|
pub alternate: bool,
|
2023-12-28 10:39:21 +02:00
|
|
|
#[allow(unused)]
|
|
|
|
saved_cursor: Option<Cursor>,
|
|
|
|
|
|
|
|
pub default_attributes: CellAttributes,
|
|
|
|
pub attributes: CellAttributes,
|
|
|
|
}
|
|
|
|
|
2025-03-01 15:23:33 +02:00
|
|
|
impl Utf8Decoder {
|
|
|
|
pub fn push(&mut self, byte: u8) -> Option<char> {
|
|
|
|
self.buffer[self.len] = byte;
|
|
|
|
self.len += 1;
|
|
|
|
if let Ok(str) = std::str::from_utf8(&self.buffer[..self.len]) {
|
|
|
|
let ch = str.chars().next().unwrap();
|
|
|
|
self.len = 0;
|
|
|
|
Some(ch)
|
|
|
|
} else {
|
|
|
|
if self.len == 4 {
|
|
|
|
// Got 4 bytes and could not decode a single character
|
|
|
|
self.len = 0;
|
|
|
|
Some('\u{25A1}')
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-28 10:39:21 +02:00
|
|
|
impl GridCell {
|
|
|
|
pub fn new(char: char, attrs: CellAttributes) -> Self {
|
|
|
|
Self { char, attrs }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn empty(bg: Color) -> Self {
|
|
|
|
Self {
|
|
|
|
char: '\0',
|
|
|
|
attrs: CellAttributes {
|
|
|
|
fg: Color::Black,
|
|
|
|
bg,
|
|
|
|
bright: false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GridRow {
|
|
|
|
pub fn new(width: usize, bg: Color) -> Self {
|
|
|
|
Self {
|
|
|
|
cols: vec![GridCell::empty(bg); width],
|
|
|
|
dirty: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_cell(&mut self, col: usize, char: char, attrs: CellAttributes) {
|
|
|
|
self.cols[col] = GridCell::new(char, attrs);
|
|
|
|
self.dirty = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_dirty(&self) -> bool {
|
|
|
|
self.dirty
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn clear_dirty(&mut self) {
|
|
|
|
self.dirty = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn cells(&self) -> impl Iterator<Item = &GridCell> {
|
|
|
|
self.cols.iter()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn clear(&mut self, bg: Color) {
|
|
|
|
self.cols.fill(GridCell::empty(bg));
|
|
|
|
self.dirty = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn erase_to_right(&mut self, start: usize, bg: Color) {
|
|
|
|
self.cols[start..].fill(GridCell::empty(bg));
|
|
|
|
self.dirty = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn resize(&mut self, width: usize, bg: Color) {
|
|
|
|
self.cols.resize(width, GridCell::empty(bg));
|
|
|
|
self.dirty = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Buffer {
|
|
|
|
pub fn new(width: usize, height: usize, bg: Color) -> Self {
|
|
|
|
Self {
|
|
|
|
rows: vec![GridRow::new(width, bg); height],
|
2025-02-13 13:10:06 +02:00
|
|
|
scrollback: VecDeque::new(),
|
|
|
|
scrollback_limit: 1024,
|
2023-12-28 10:39:21 +02:00
|
|
|
width,
|
|
|
|
height,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn clear(&mut self, bg: Color) {
|
|
|
|
self.rows.fill(GridRow::new(self.width, bg));
|
|
|
|
}
|
|
|
|
|
2025-02-13 13:10:06 +02:00
|
|
|
pub fn iter_rows_mut<F: FnMut(usize, &mut GridRow)>(&mut self, scroll: usize, mut handler: F) {
|
|
|
|
let scroll = scroll.min(self.scrollback.len());
|
|
|
|
let non_scroll = self.height.saturating_sub(scroll);
|
|
|
|
let scroll_end = self.height - non_scroll;
|
|
|
|
|
|
|
|
for y in 0..scroll_end {
|
|
|
|
let i = scroll - 1 - y;
|
|
|
|
let row = &mut self.scrollback[i];
|
|
|
|
handler(y, row);
|
|
|
|
}
|
|
|
|
for i in 0..non_scroll {
|
|
|
|
let y = scroll + i;
|
|
|
|
let row = &mut self.rows[i];
|
|
|
|
handler(y, row);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-01 15:23:33 +02:00
|
|
|
pub fn visible_rows_mut<F: FnMut(usize, &mut GridRow)>(
|
|
|
|
&mut self,
|
|
|
|
scroll: usize,
|
|
|
|
mut handler: F,
|
|
|
|
) {
|
2025-02-13 13:10:06 +02:00
|
|
|
self.iter_rows_mut(scroll, |y, row| {
|
2023-12-28 10:39:21 +02:00
|
|
|
if row.dirty {
|
|
|
|
row.dirty = false;
|
2025-02-13 13:10:06 +02:00
|
|
|
handler(y, row);
|
2023-12-28 10:39:21 +02:00
|
|
|
}
|
2025-02-13 13:10:06 +02:00
|
|
|
});
|
2023-12-28 10:39:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn resize(&mut self, width: usize, height: usize, bg: Color) {
|
|
|
|
self.rows.resize(height, GridRow::new(width, bg));
|
|
|
|
for row in self.rows.iter_mut() {
|
|
|
|
row.resize(width, bg);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.width = width;
|
|
|
|
self.height = height;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_cell(&mut self, cur: Cursor, cell: GridCell) {
|
|
|
|
self.rows[cur.row].cols[cur.col] = cell;
|
|
|
|
self.rows[cur.row].dirty = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn scroll_once(&mut self, bg: Color) {
|
2025-02-13 13:10:06 +02:00
|
|
|
self.scrollback.push_front(self.rows[0].clone());
|
|
|
|
if self.scrollback.len() >= self.scrollback_limit {
|
|
|
|
self.scrollback.pop_back();
|
|
|
|
}
|
|
|
|
|
2023-12-28 10:39:21 +02:00
|
|
|
for i in 1..self.height {
|
|
|
|
self.rows[i - 1] = self.rows[i].clone();
|
|
|
|
self.rows[i - 1].dirty = true;
|
|
|
|
}
|
|
|
|
self.rows[self.height - 1] = GridRow::new(self.width, bg);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn erase_row(&mut self, row: usize, bg: Color) {
|
|
|
|
self.rows[row].clear(bg);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_row_dirty(&mut self, row: usize) {
|
2025-02-18 11:27:54 +02:00
|
|
|
if row >= self.rows.len() {
|
|
|
|
return;
|
|
|
|
}
|
2023-12-28 10:39:21 +02:00
|
|
|
self.rows[row].dirty = true;
|
|
|
|
}
|
2025-02-13 13:10:06 +02:00
|
|
|
|
|
|
|
pub fn invalidate_rows(&mut self, scroll: usize) {
|
|
|
|
self.iter_rows_mut(scroll, |_, row| row.dirty = true);
|
|
|
|
}
|
2023-12-28 10:39:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl State {
|
|
|
|
pub fn new(width: usize, height: usize) -> Self {
|
|
|
|
let default_attributes = CellAttributes {
|
|
|
|
fg: Color::White,
|
|
|
|
bg: Color::Black,
|
|
|
|
bright: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
Self {
|
|
|
|
buffer: Buffer::new(width, height, default_attributes.bg),
|
2025-03-01 15:23:33 +02:00
|
|
|
utf8_decode: Utf8Decoder::default(),
|
2023-12-28 10:39:21 +02:00
|
|
|
|
|
|
|
esc_args: Vec::new(),
|
|
|
|
esc_state: EscapeState::Normal,
|
|
|
|
|
|
|
|
cursor: Cursor { row: 0, col: 0 },
|
2025-03-02 17:27:26 +02:00
|
|
|
cursor_visible: true,
|
|
|
|
alternate: false,
|
2025-02-13 13:10:06 +02:00
|
|
|
scroll: 0,
|
2023-12-28 10:39:21 +02:00
|
|
|
saved_cursor: None,
|
|
|
|
|
|
|
|
default_attributes,
|
|
|
|
attributes: default_attributes,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-03 15:24:50 +02:00
|
|
|
pub fn clear(&mut self) {
|
|
|
|
self.buffer.clear(self.attributes.bg);
|
|
|
|
}
|
|
|
|
|
2023-12-28 10:39:21 +02:00
|
|
|
pub fn resize(&mut self, width: usize, height: usize) {
|
|
|
|
self.buffer
|
|
|
|
.resize(width, height, self.default_attributes.bg);
|
2025-02-13 13:10:06 +02:00
|
|
|
self.scroll = 0;
|
2023-12-28 10:39:21 +02:00
|
|
|
|
|
|
|
if self.cursor.row >= height {
|
|
|
|
self.cursor.row = height - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.cursor.col >= width {
|
|
|
|
self.cursor.col = width - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-01 15:23:33 +02:00
|
|
|
fn putc_normal(&mut self, ch: char) -> bool {
|
2023-12-28 10:39:21 +02:00
|
|
|
let mut redraw = match ch {
|
2025-03-01 15:23:33 +02:00
|
|
|
// c if c >= 127 => {
|
|
|
|
// let attr = CellAttributes {
|
|
|
|
// fg: Color::Black,
|
|
|
|
// bg: Color::Red,
|
|
|
|
// bright: false,
|
|
|
|
// };
|
|
|
|
// self.buffer.set_cell(self.cursor, GridCell::new('?', attr));
|
|
|
|
// self.cursor.col += 1;
|
|
|
|
// true
|
|
|
|
// }
|
|
|
|
'\x1B' => {
|
2023-12-28 10:39:21 +02:00
|
|
|
self.esc_state = EscapeState::Escape;
|
|
|
|
self.esc_args.clear();
|
|
|
|
self.esc_args.push(0);
|
|
|
|
return false;
|
|
|
|
}
|
2025-03-01 15:23:33 +02:00
|
|
|
'\r' => {
|
2023-12-28 10:39:21 +02:00
|
|
|
self.buffer.rows[self.cursor.row].dirty = true;
|
|
|
|
self.cursor.col = 0;
|
|
|
|
true
|
|
|
|
}
|
2025-03-01 15:23:33 +02:00
|
|
|
'\n' => {
|
2023-12-28 10:39:21 +02:00
|
|
|
self.buffer.rows[self.cursor.row].dirty = true;
|
|
|
|
self.cursor.row += 1;
|
|
|
|
self.cursor.col = 0;
|
|
|
|
true
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
self.buffer
|
|
|
|
.set_cell(self.cursor, GridCell::new(ch as char, self.attributes));
|
|
|
|
self.cursor.col += 1;
|
|
|
|
true
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-03-02 17:27:26 +02:00
|
|
|
if self.alternate {
|
|
|
|
self.cursor.col = self.cursor.col.min(self.buffer.width - 1);
|
|
|
|
self.cursor.row = self.cursor.row.min(self.buffer.height - 1);
|
|
|
|
}
|
|
|
|
|
2023-12-28 10:39:21 +02:00
|
|
|
if self.cursor.col >= self.buffer.width {
|
|
|
|
if self.cursor.row < self.buffer.height {
|
|
|
|
self.buffer.rows[self.cursor.row].dirty = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.cursor.row += 1;
|
|
|
|
self.cursor.col = 0;
|
|
|
|
redraw = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
while self.cursor.row >= self.buffer.height {
|
|
|
|
self.buffer.scroll_once(self.default_attributes.bg);
|
|
|
|
self.cursor.row -= 1;
|
|
|
|
redraw = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
redraw
|
|
|
|
}
|
|
|
|
|
2025-03-02 17:27:26 +02:00
|
|
|
fn handle_ctlseq(&mut self, byte: u8, c: char) -> bool {
|
2023-12-28 10:39:21 +02:00
|
|
|
let redraw = match c {
|
2025-03-02 17:27:26 +02:00
|
|
|
'h' if byte == b'?' => match self.esc_args.get(0).copied().unwrap_or(0) {
|
|
|
|
25 => {
|
|
|
|
// Cursor visible
|
|
|
|
self.cursor_visible = true;
|
|
|
|
true
|
|
|
|
}
|
|
|
|
1049 => {
|
|
|
|
// Enter alternate mode
|
|
|
|
self.alternate = true;
|
|
|
|
true
|
|
|
|
}
|
|
|
|
_ => false,
|
|
|
|
},
|
|
|
|
'l' if byte == b'?' => match self.esc_args.get(0).copied().unwrap_or(0) {
|
|
|
|
25 => {
|
|
|
|
// Cursor not visible
|
|
|
|
self.cursor_visible = false;
|
|
|
|
true
|
|
|
|
}
|
|
|
|
1049 => {
|
|
|
|
// Leave alternate mode
|
|
|
|
self.alternate = false;
|
|
|
|
true
|
|
|
|
}
|
|
|
|
_ => false,
|
|
|
|
},
|
|
|
|
|
2023-12-28 10:39:21 +02:00
|
|
|
// Move back one character
|
2025-03-01 15:23:33 +02:00
|
|
|
'D' => {
|
2023-12-28 10:39:21 +02:00
|
|
|
if self.cursor.col > 0 {
|
|
|
|
self.buffer.set_row_dirty(self.cursor.row);
|
|
|
|
self.cursor.col -= 1;
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Character attributes
|
2025-03-01 15:23:33 +02:00
|
|
|
'm' => match self.esc_args[0] {
|
2023-12-28 10:39:21 +02:00
|
|
|
0 => {
|
|
|
|
self.attributes = self.default_attributes;
|
|
|
|
false
|
|
|
|
}
|
|
|
|
1 => {
|
|
|
|
self.attributes.bright = true;
|
|
|
|
false
|
|
|
|
}
|
|
|
|
30..=39 => {
|
|
|
|
let vt_color = self.esc_args[0] % 10;
|
|
|
|
if vt_color == 9 {
|
|
|
|
self.attributes.fg = Color::Black;
|
|
|
|
} else {
|
|
|
|
self.attributes.fg = Color::from_esc(vt_color);
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
40..=49 => {
|
|
|
|
let vt_color = self.esc_args[0] % 10;
|
|
|
|
if vt_color == 9 {
|
|
|
|
self.attributes.bg = Color::Black;
|
|
|
|
} else {
|
|
|
|
self.attributes.bg = Color::from_esc(vt_color);
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
_ => false,
|
|
|
|
},
|
|
|
|
// Move cursor to position
|
2025-03-01 15:23:33 +02:00
|
|
|
'f' => {
|
2023-12-28 10:39:21 +02:00
|
|
|
let row = self.esc_args[0].clamp(1, self.buffer.height as u32) - 1;
|
|
|
|
let col = self.esc_args[1].clamp(1, self.buffer.width as u32) - 1;
|
|
|
|
|
|
|
|
self.buffer.set_row_dirty(self.cursor.row);
|
|
|
|
self.cursor = Cursor {
|
|
|
|
row: row as _,
|
|
|
|
col: col as _,
|
|
|
|
};
|
|
|
|
|
|
|
|
true
|
|
|
|
}
|
2025-03-03 15:24:50 +02:00
|
|
|
// Move cursor to home position (0; 0)
|
|
|
|
'H' => {
|
|
|
|
self.buffer.set_row_dirty(self.cursor.row);
|
|
|
|
self.cursor = Cursor { row: 0, col: 0 };
|
|
|
|
true
|
|
|
|
}
|
2023-12-28 10:39:21 +02:00
|
|
|
// Clear rows/columns/screen
|
2025-03-01 15:23:33 +02:00
|
|
|
'J' => match self.esc_args[0] {
|
2023-12-28 10:39:21 +02:00
|
|
|
// Erase lines down
|
|
|
|
0 => false,
|
|
|
|
// Erase lines up
|
|
|
|
1 => false,
|
|
|
|
// Erase all
|
|
|
|
2 => {
|
|
|
|
self.buffer.clear(self.attributes.bg);
|
|
|
|
true
|
|
|
|
}
|
|
|
|
_ => false,
|
|
|
|
},
|
2025-03-01 15:23:33 +02:00
|
|
|
'K' => match self.esc_args[0] {
|
2023-12-28 10:39:21 +02:00
|
|
|
// Erase to right
|
|
|
|
0 => {
|
|
|
|
self.buffer.rows[self.cursor.row]
|
|
|
|
.erase_to_right(self.cursor.col, self.attributes.bg);
|
|
|
|
true
|
|
|
|
}
|
|
|
|
// Erase All
|
|
|
|
2 => {
|
|
|
|
self.buffer.erase_row(self.cursor.row, self.attributes.bg);
|
|
|
|
true
|
|
|
|
}
|
|
|
|
_ => false,
|
|
|
|
},
|
|
|
|
_ => false,
|
|
|
|
};
|
|
|
|
|
|
|
|
self.esc_state = EscapeState::Normal;
|
|
|
|
redraw
|
|
|
|
}
|
|
|
|
|
2025-03-02 17:27:26 +02:00
|
|
|
fn handle_ctlseq_byte(&mut self, byte: u8, c: char) -> bool {
|
2023-12-28 10:39:21 +02:00
|
|
|
match c {
|
2025-03-02 17:27:26 +02:00
|
|
|
'?' if byte == 0 => {
|
|
|
|
self.esc_state = EscapeState::Csi(CsiState { byte: b'?' });
|
|
|
|
false
|
|
|
|
}
|
2025-03-01 15:23:33 +02:00
|
|
|
c if let Some(digit) = c.to_digit(10) => {
|
2023-12-28 10:39:21 +02:00
|
|
|
let arg = self.esc_args.last_mut().unwrap();
|
|
|
|
*arg *= 10;
|
2025-03-01 15:23:33 +02:00
|
|
|
*arg += digit;
|
2023-12-28 10:39:21 +02:00
|
|
|
false
|
|
|
|
}
|
2025-03-01 15:23:33 +02:00
|
|
|
';' => {
|
2023-12-28 10:39:21 +02:00
|
|
|
self.esc_args.push(0);
|
|
|
|
false
|
|
|
|
}
|
2025-03-02 17:27:26 +02:00
|
|
|
_ => self.handle_ctlseq(byte, c),
|
2023-12-28 10:39:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_shell_output(&mut self, ch: u8) -> bool {
|
2025-03-01 15:23:33 +02:00
|
|
|
if let Some(ch) = self.utf8_decode.push(ch) {
|
|
|
|
match self.esc_state {
|
|
|
|
EscapeState::Normal => self.putc_normal(ch),
|
|
|
|
EscapeState::Escape => match ch {
|
|
|
|
'[' => {
|
2025-03-02 17:27:26 +02:00
|
|
|
self.esc_state = EscapeState::Csi(CsiState { byte: 0 });
|
2025-03-01 15:23:33 +02:00
|
|
|
false
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
self.esc_state = EscapeState::Normal;
|
|
|
|
false
|
|
|
|
}
|
|
|
|
},
|
2025-03-02 17:27:26 +02:00
|
|
|
EscapeState::Csi(CsiState { byte }) => self.handle_ctlseq_byte(byte, ch),
|
2025-03-01 15:23:33 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
false
|
2023-12-28 10:39:21 +02:00
|
|
|
}
|
|
|
|
}
|
2025-02-13 13:10:06 +02:00
|
|
|
|
|
|
|
pub fn invalidate_current_viewport(&mut self) {
|
|
|
|
self.buffer.invalidate_rows(self.scroll);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn adjust_scroll(&mut self) -> usize {
|
|
|
|
if self.scroll > self.buffer.scrollback.len() {
|
|
|
|
self.scroll = self.buffer.scrollback.len();
|
|
|
|
}
|
|
|
|
|
|
|
|
self.scroll
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn scroll_up(&mut self) -> bool {
|
|
|
|
let max = self.buffer.scrollback.len();
|
|
|
|
if max == 0 {
|
|
|
|
self.scroll = 0;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
let amount = max.min(8);
|
|
|
|
if amount != 0 {
|
|
|
|
self.scroll += amount;
|
|
|
|
self.invalidate_current_viewport();
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn scroll_down(&mut self) -> bool {
|
|
|
|
let amount = self.scroll.min(8);
|
|
|
|
if amount != 0 {
|
|
|
|
self.scroll -= amount;
|
|
|
|
self.invalidate_current_viewport();
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn scroll_home(&mut self) -> bool {
|
|
|
|
if self.scroll != self.buffer.scrollback.len() {
|
|
|
|
self.scroll = self.buffer.scrollback.len();
|
|
|
|
self.invalidate_current_viewport();
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn scroll_end(&mut self) -> bool {
|
|
|
|
if self.scroll != 0 {
|
|
|
|
self.scroll = 0;
|
|
|
|
self.invalidate_current_viewport();
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2023-12-28 10:39:21 +02:00
|
|
|
}
|