diff --git a/userspace/term/src/main.rs b/userspace/term/src/main.rs index 9d26a0b5..7c9ab187 100644 --- a/userspace/term/src/main.rs +++ b/userspace/term/src/main.rs @@ -85,12 +85,15 @@ impl DrawState { dt.fill(default_bg); } + if cursor_dirty { state.buffer.set_row_dirty(self.old_cursor.row); } let bytes_per_line = (self.font.width() as usize + 7) / 8; - for (i, row) in state.buffer.dirty_rows() { + let scroll = state.adjust_scroll(); + let cursor_visible = scroll == 0; + state.buffer.visible_rows_mut(scroll, |i, row| { let cy = i * fh; for (j, cell) in row.cells().enumerate() { @@ -132,27 +135,32 @@ impl DrawState { y += 1; } } - } + }); + // for (i, row) in state.buffer.dirty_rows() { + // } // TODO check if there's a character under cursor - let cx = state.cursor.col * fw; - let cy = state.cursor.row * fh; + if cursor_visible { + let cx = state.cursor.col * fw; + let cy = state.cursor.row * fh; - // Fill block cursor - for y in 0..fh { - let off = (cy + y) * self.width + cx; - dt[off..off + fw].fill(default_fg); - } - - if !self.focused { - // Remove cursor center - for y in 1..fh - 1 { - let off = (cy + y) * self.width + cx + 1; - dt[off..off + fw - 2].fill(default_bg); + // Fill block cursor + for y in 0..fh { + let off = (cy + y) * self.width + cx; + dt[off..off + fw].fill(default_fg); } + + if !self.focused { + // Remove cursor center + for y in 1..fh - 1 { + let off = (cy + y) * self.width + cx + 1; + dt[off..off + fw - 2].fill(default_bg); + } + } + + self.old_cursor = state.cursor; } - self.old_cursor = state.cursor; self.force_redraw = false; self.focus_changed = false; } @@ -211,32 +219,56 @@ impl Terminal<'_> { EventOutcome::Redraw }); + let state_c = state.clone(); let pty_master_c = pty_master.clone(); window.set_on_key_input(move |ev| { + let mut s = state_c.lock().unwrap(); let mut pty_master = pty_master_c.lock().unwrap(); + let mut need_redraw = false; // TODO error reporting from handlers if let Some(input) = ev.input { pty_master.write_all(&[input as u8]).unwrap(); + need_redraw = s.scroll_end(); } else { match (ev.modifiers, ev.key) { (KeyModifiers::NONE, KeyboardKey::Escape) => { pty_master.write_all(b"\x1B").unwrap(); + need_redraw = s.scroll_end(); } (KeyModifiers::NONE, KeyboardKey::Backspace) => { pty_master.write_all(&[termios.chars.erase]).unwrap(); + need_redraw = s.scroll_end(); } (KeyModifiers::CTRL, KeyboardKey::Char(b'c')) => { pty_master.write_all(&[termios.chars.interrupt]).unwrap(); + need_redraw = s.scroll_end(); } (KeyModifiers::CTRL, KeyboardKey::Char(b'd')) => { pty_master.write_all(&[termios.chars.eof]).unwrap(); + need_redraw = s.scroll_end(); + } + (KeyModifiers::SHIFT, KeyboardKey::PageUp) => { + need_redraw = s.scroll_up(); + } + (KeyModifiers::SHIFT, KeyboardKey::PageDown) => { + need_redraw = s.scroll_down(); + } + (KeyModifiers::SHIFT, KeyboardKey::Home) => { + need_redraw = s.scroll_home(); + } + (KeyModifiers::SHIFT, KeyboardKey::End) => { + need_redraw = s.scroll_end(); } _ => (), } } - EventOutcome::None + if need_redraw { + EventOutcome::Redraw + } else { + EventOutcome::None + } }); let draw_state_c = draw_state.clone(); diff --git a/userspace/term/src/state.rs b/userspace/term/src/state.rs index 655284ef..96940a7c 100644 --- a/userspace/term/src/state.rs +++ b/userspace/term/src/state.rs @@ -1,3 +1,5 @@ +use std::collections::VecDeque; + use crate::attr::{CellAttributes, Color}; #[derive(Clone, Copy, Debug)] @@ -13,9 +15,11 @@ pub struct GridRow { } pub struct Buffer { + scrollback: VecDeque, rows: Vec, width: usize, height: usize, + scrollback_limit: usize, } #[derive(Clone, Copy, PartialEq, Eq)] @@ -36,6 +40,7 @@ pub struct State { esc_state: EscapeState, esc_args: Vec, + scroll: usize, pub cursor: Cursor, #[allow(unused)] saved_cursor: Option, @@ -106,6 +111,8 @@ impl Buffer { pub fn new(width: usize, height: usize, bg: Color) -> Self { Self { rows: vec![GridRow::new(width, bg); height], + scrollback: VecDeque::new(), + scrollback_limit: 1024, width, height, } @@ -115,15 +122,30 @@ impl Buffer { self.rows.fill(GridRow::new(self.width, bg)); } - pub fn dirty_rows(&mut self) -> impl Iterator { - self.rows.iter_mut().enumerate().filter_map(|(i, row)| { + pub fn iter_rows_mut(&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); + } + } + + pub fn visible_rows_mut(&mut self, scroll: usize, mut handler: F) { + self.iter_rows_mut(scroll, |y, row| { if row.dirty { row.dirty = false; - Some((i, row)) - } else { - None + handler(y, row); } - }) + }); } pub fn resize(&mut self, width: usize, height: usize, bg: Color) { @@ -142,6 +164,11 @@ impl Buffer { } pub fn scroll_once(&mut self, bg: Color) { + self.scrollback.push_front(self.rows[0].clone()); + if self.scrollback.len() >= self.scrollback_limit { + self.scrollback.pop_back(); + } + for i in 1..self.height { self.rows[i - 1] = self.rows[i].clone(); self.rows[i - 1].dirty = true; @@ -156,6 +183,10 @@ impl Buffer { pub fn set_row_dirty(&mut self, row: usize) { self.rows[row].dirty = true; } + + pub fn invalidate_rows(&mut self, scroll: usize) { + self.iter_rows_mut(scroll, |_, row| row.dirty = true); + } } impl State { @@ -173,6 +204,7 @@ impl State { esc_state: EscapeState::Normal, cursor: Cursor { row: 0, col: 0 }, + scroll: 0, saved_cursor: None, default_attributes, @@ -183,6 +215,7 @@ impl State { pub fn resize(&mut self, width: usize, height: usize) { self.buffer .resize(width, height, self.default_attributes.bg); + self.scroll = 0; if self.cursor.row >= height { self.cursor.row = height - 1; @@ -370,4 +403,63 @@ impl State { EscapeState::Csi => self.handle_ctlseq_byte(ch), } } + + 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 + } + } }