use crate::term::BufWrite as _; use std::convert::TryInto as _; #[derive(Clone, Debug)] pub struct Grid { size: Size, pos: Pos, saved_pos: Pos, rows: Vec, scroll_top: u16, scroll_bottom: u16, origin_mode: bool, saved_origin_mode: bool, scrollback: std::collections::VecDeque, scrollback_len: usize, scrollback_offset: usize, } impl Grid { pub fn new(size: Size, scrollback_len: usize) -> Self { Self { size, pos: Pos::default(), saved_pos: Pos::default(), rows: vec![crate::row::Row::new(size.cols); size.rows as usize], scroll_top: 0, scroll_bottom: size.rows - 1, origin_mode: false, saved_origin_mode: false, scrollback: std::collections::VecDeque::new(), scrollback_len, scrollback_offset: 0, } } fn new_row(&self) -> crate::row::Row { crate::row::Row::new(self.size.cols) } pub fn clear(&mut self) { self.pos = Pos::default(); self.saved_pos = Pos::default(); for row in self.drawing_rows_mut() { row.clear(crate::attrs::Attrs::default()); } self.scroll_top = 0; self.scroll_bottom = self.size.rows - 1; self.origin_mode = false; self.saved_origin_mode = false; } pub fn size(&self) -> Size { self.size } pub fn set_size(&mut self, size: Size) { if size.cols != self.size.cols { for row in &mut self.rows { row.wrap(false); } } if self.scroll_bottom == self.size.rows - 1 { self.scroll_bottom = size.rows - 1; } self.size = size; for row in &mut self.rows { row.resize(size.cols as usize, crate::cell::Cell::default()); } self.rows.resize(size.rows as usize, self.new_row()); if self.scroll_bottom >= size.rows { self.scroll_bottom = size.rows - 1; } if self.scroll_bottom < self.scroll_top { self.scroll_top = 0; } self.row_clamp_top(false); self.row_clamp_bottom(false); self.col_clamp(); } pub fn pos(&self) -> Pos { self.pos } pub fn set_pos(&mut self, mut pos: Pos) { if self.origin_mode { pos.row = pos.row.saturating_add(self.scroll_top); } self.pos = pos; self.row_clamp_top(self.origin_mode); self.row_clamp_bottom(self.origin_mode); self.col_clamp(); } pub fn save_cursor(&mut self) { self.saved_pos = self.pos; self.saved_origin_mode = self.origin_mode; } pub fn restore_cursor(&mut self) { self.pos = self.saved_pos; self.origin_mode = self.saved_origin_mode; } pub fn visible_rows(&self) -> impl Iterator { let scrollback_len = self.scrollback.len(); let rows_len = self.rows.len(); self.scrollback .iter() .skip(scrollback_len - self.scrollback_offset) .chain(self.rows.iter().take(rows_len - self.scrollback_offset)) } pub fn drawing_rows(&self) -> impl Iterator { self.rows.iter() } pub fn drawing_rows_mut( &mut self, ) -> impl Iterator { self.rows.iter_mut() } pub fn visible_row(&self, pos: Pos) -> Option<&crate::row::Row> { self.visible_rows().nth(pos.row as usize) } pub fn drawing_row(&self, pos: Pos) -> Option<&crate::row::Row> { self.drawing_rows().nth(pos.row as usize) } pub fn drawing_row_mut( &mut self, pos: Pos, ) -> Option<&mut crate::row::Row> { self.drawing_rows_mut().nth(pos.row as usize) } pub fn current_row_mut(&mut self) -> &mut crate::row::Row { self.drawing_row_mut(self.pos) .expect("cursor not pointing to a cell") } pub fn visible_cell(&self, pos: Pos) -> Option<&crate::cell::Cell> { self.visible_row(pos).and_then(|r| r.get(pos.col)) } pub fn drawing_cell(&self, pos: Pos) -> Option<&crate::cell::Cell> { self.drawing_row(pos).and_then(|r| r.get(pos.col)) } pub fn drawing_cell_mut( &mut self, pos: Pos, ) -> Option<&mut crate::cell::Cell> { self.drawing_row_mut(pos).and_then(|r| r.get_mut(pos.col)) } pub fn current_cell(&self) -> &crate::cell::Cell { self.drawing_cell(self.pos) .expect("cursor not pointing to a cell") } pub fn current_cell_mut(&mut self) -> &mut crate::cell::Cell { self.drawing_cell_mut(self.pos) .expect("cursor not pointing to a cell") } pub fn scrollback_len(&self) -> usize { self.scrollback_len } pub fn scrollback(&self) -> usize { self.scrollback_offset } pub fn set_scrollback(&mut self, rows: usize) { self.scrollback_offset = rows.min(self.scrollback.len()); } pub fn write_contents(&self, contents: &mut String) { let mut wrapping = false; for row in self.visible_rows() { row.write_contents(contents, 0, self.size.cols, wrapping); if !row.wrapped() { contents.push_str("\n"); } wrapping = row.wrapped(); } while contents.ends_with('\n') { contents.truncate(contents.len() - 1); } } pub fn write_contents_formatted( &self, contents: &mut Vec, ) -> crate::attrs::Attrs { crate::term::ClearAttrs::default().write_buf(contents); crate::term::ClearScreen::default().write_buf(contents); let mut prev_attrs = crate::attrs::Attrs::default(); let mut prev_pos = Pos::default(); let mut wrapping = false; for (i, row) in self.visible_rows().enumerate() { let i = i.try_into().unwrap(); let (new_pos, new_attrs) = row.write_contents_formatted( contents, 0, self.size.cols, i, wrapping, prev_pos, prev_attrs, ); prev_pos = new_pos; prev_attrs = new_attrs; wrapping = row.wrapped(); } // writing a character to the last column of a row doesn't wrap the // cursor immediately - it waits until the next character is actually // drawn. it is only possible for the cursor to have this kind of // position after drawing a character though, so if we end in this // position, we need to redraw the character at the end of the row. if prev_pos != self.pos && self.pos.col >= self.size.cols { let mut pos = Pos { row: self.pos.row, col: self.size.cols - 1, }; if self.visible_cell(pos).unwrap().is_wide_continuation() { pos.col = self.size.cols - 2; } let cell = self.visible_cell(pos).unwrap(); if cell.has_contents() { crate::term::MoveFromTo::new(prev_pos, pos) .write_buf(contents); contents.extend(cell.contents().as_bytes()); } else { // if the cell doesn't have contents, we can't have gotten // here by drawing a character in the last column. this means // that as far as i'm aware, we have to have reached here from // a newline when we were already after the end of an earlier // row. in the case where we are already after the end of an // earlier row, we can just write a few newlines, otherwise we // also need to do the same as above to get ourselves to after // the end of a row. let orig_row = pos.row; let mut found = false; for i in (0..orig_row).rev() { pos.row = i; pos.col = self.size.cols - 1; if self.visible_cell(pos).unwrap().is_wide_continuation() { pos.col = self.size.cols - 2; } let cell = self.visible_cell(pos).unwrap(); if cell.has_contents() { if prev_pos.row != i || prev_pos.col < self.size.cols { crate::term::MoveFromTo::new(prev_pos, pos) .write_buf(contents); contents.extend(cell.contents().as_bytes()); } contents.extend( "\n".repeat((orig_row - i) as usize).as_bytes(), ); found = true; break; } } // this can happen if you get the cursor off the end of a row, // and then do something to clear the end of the current row // without moving the cursor (IL, DL, ED, EL, etc). we know // there can't be something in the last column because we // would have caught that above, so it should be safe to // overwrite it. if !found { pos.row = orig_row; crate::term::MoveFromTo::new(prev_pos, pos) .write_buf(contents); contents.push(b' '); crate::term::SaveCursor::default().write_buf(contents); crate::term::Backspace::default().write_buf(contents); crate::term::EraseChar::new(1).write_buf(contents); crate::term::RestoreCursor::default().write_buf(contents); } } } else { crate::term::MoveFromTo::new(prev_pos, self.pos) .write_buf(contents); } prev_attrs } pub fn write_contents_diff( &self, contents: &mut Vec, prev: &Self, mut prev_attrs: crate::attrs::Attrs, ) -> crate::attrs::Attrs { let mut prev_pos = prev.pos; let mut wrapping = false; for (i, (row, prev_row)) in self.visible_rows().zip(prev.visible_rows()).enumerate() { let i = i.try_into().unwrap(); let (new_pos, new_attrs) = row.write_contents_diff( contents, prev_row, 0, self.size.cols, i, wrapping, prev_pos, prev_attrs, ); prev_pos = new_pos; prev_attrs = new_attrs; wrapping = row.wrapped(); } // writing a character to the last column of a row doesn't wrap the // cursor immediately - it waits until the next character is actually // drawn. it is only possible for the cursor to have this kind of // position after drawing a character though, so if we end in this // position, we need to redraw the character at the end of the row. if prev_pos != self.pos && self.pos.col >= self.size.cols { let mut pos = Pos { row: self.pos.row, col: self.size.cols - 1, }; if self.visible_cell(pos).unwrap().is_wide_continuation() { pos.col = self.size.cols - 2; } let cell = self.visible_cell(pos).unwrap(); if cell.has_contents() { crate::term::MoveFromTo::new(prev_pos, pos) .write_buf(contents); contents.extend(cell.contents().as_bytes()); } else { // if the cell doesn't have contents, we can't have gotten // here by drawing a character in the last column. this means // that as far as i'm aware, we have to have reached here from // a newline when we were already after the end of an earlier // row. in the case where we are already after the end of an // earlier row, we can just write a few newlines, otherwise we // also need to do the same as above to get ourselves to after // the end of a row. let orig_row = pos.row; let mut found = false; for i in (0..orig_row).rev() { pos.row = i; pos.col = self.size.cols - 1; if self.visible_cell(pos).unwrap().is_wide_continuation() { pos.col = self.size.cols - 2; } let cell = self.visible_cell(pos).unwrap(); if cell.has_contents() { if prev_pos.row != i || prev_pos.col < self.size.cols { crate::term::MoveFromTo::new(prev_pos, pos) .write_buf(contents); contents.extend(cell.contents().as_bytes()); } contents.extend( "\n".repeat((orig_row - i) as usize).as_bytes(), ); found = true; break; } } // this can happen if you get the cursor off the end of a row, // and then do something to clear the end of the current row // without moving the cursor (IL, DL, ED, EL, etc). we know // there can't be something in the last column because we // would have caught that above, so it should be safe to // overwrite it. if !found { pos.row = orig_row; crate::term::MoveFromTo::new(prev_pos, pos) .write_buf(contents); contents.push(b' '); crate::term::SaveCursor::default().write_buf(contents); crate::term::Backspace::default().write_buf(contents); crate::term::EraseChar::new(1).write_buf(contents); crate::term::RestoreCursor::default().write_buf(contents); } } } else { crate::term::MoveFromTo::new(prev_pos, self.pos) .write_buf(contents); } prev_attrs } pub fn erase_all(&mut self, attrs: crate::attrs::Attrs) { for row in self.drawing_rows_mut() { row.clear(attrs); } } pub fn erase_all_forward(&mut self, attrs: crate::attrs::Attrs) { let pos = self.pos; for row in self.drawing_rows_mut().skip(pos.row as usize + 1) { row.clear(attrs); } self.erase_row_forward(attrs); } pub fn erase_all_backward(&mut self, attrs: crate::attrs::Attrs) { let pos = self.pos; for row in self.drawing_rows_mut().take(pos.row as usize) { row.clear(attrs); } self.erase_row_backward(attrs); } pub fn erase_row(&mut self, attrs: crate::attrs::Attrs) { self.current_row_mut().clear(attrs); } pub fn erase_row_forward(&mut self, attrs: crate::attrs::Attrs) { let size = self.size; let pos = self.pos; let row = self.current_row_mut(); for col in pos.col..size.cols { row.erase(col as usize, attrs); } } pub fn erase_row_backward(&mut self, attrs: crate::attrs::Attrs) { let size = self.size; let pos = self.pos; let row = self.current_row_mut(); for col in 0..=pos.col.min(size.cols - 1) { row.erase(col as usize, attrs); } } pub fn insert_cells(&mut self, count: u16) { let size = self.size; let pos = self.pos; let wide = pos.col < size.cols && self.current_cell().is_wide_continuation(); let row = self.current_row_mut(); for _ in 0..count { if wide { row.get_mut(pos.col).unwrap().set_wide_continuation(false); } row.insert(pos.col as usize, crate::cell::Cell::default()); if wide { row.get_mut(pos.col).unwrap().set_wide_continuation(true); } } row.truncate(size.cols as usize); } pub fn delete_cells(&mut self, count: u16) { let size = self.size; let pos = self.pos; let row = self.current_row_mut(); for _ in 0..(count.min(size.cols - pos.col)) { row.remove(pos.col as usize); } row.resize(size.cols as usize, crate::cell::Cell::default()); } pub fn erase_cells(&mut self, count: u16, attrs: crate::attrs::Attrs) { let size = self.size; let pos = self.pos; let row = self.current_row_mut(); for col in pos.col..((pos.col + count).min(size.cols)) { row.clear_wide(col); row.erase(col as usize, attrs); } } pub fn insert_lines(&mut self, count: u16) { for _ in 0..count { self.rows.remove(self.scroll_bottom as usize); self.rows.insert(self.pos.row as usize, self.new_row()); } } pub fn delete_lines(&mut self, count: u16) { for _ in 0..(count.min(self.size.rows - self.pos.row)) { self.rows .insert(self.scroll_bottom as usize + 1, self.new_row()); self.rows.remove(self.pos.row as usize); } } pub fn scroll_up(&mut self, count: u16) { for _ in 0..(count.min(self.size.rows - self.scroll_top)) { self.rows .insert(self.scroll_bottom as usize + 1, self.new_row()); let removed = self.rows.remove(self.scroll_top as usize); if self.scrollback_len > 0 && !self.scroll_region_active() { self.scrollback.push_back(removed); while self.scrollback.len() > self.scrollback_len { self.scrollback.pop_front(); } if self.scrollback_offset > 0 { self.scrollback_offset = self.scrollback.len().min(self.scrollback_offset + 1); } } } } pub fn scroll_down(&mut self, count: u16) { for _ in 0..count { self.rows.remove(self.scroll_bottom as usize); self.rows.insert(self.scroll_top as usize, self.new_row()); } } pub fn set_scroll_region(&mut self, top: u16, bottom: u16) { let bottom = bottom.min(self.size().rows - 1); if top < bottom { self.scroll_top = top; self.scroll_bottom = bottom; } else { self.scroll_top = 0; self.scroll_bottom = self.size().rows - 1; } self.pos.row = self.scroll_top; self.pos.col = 0; } fn in_scroll_region(&self) -> bool { self.pos.row >= self.scroll_top && self.pos.row <= self.scroll_bottom } fn scroll_region_active(&self) -> bool { self.scroll_top != 0 || self.scroll_bottom != self.size.rows - 1 } pub fn set_origin_mode(&mut self, mode: bool) { self.origin_mode = mode; self.set_pos(Pos { row: 0, col: 0 }) } pub fn row_inc_clamp(&mut self, count: u16) { let in_scroll_region = self.in_scroll_region(); self.pos.row = self.pos.row.saturating_add(count); self.row_clamp_bottom(in_scroll_region); } pub fn row_inc_scroll(&mut self, count: u16) { let in_scroll_region = self.in_scroll_region(); self.pos.row = self.pos.row.saturating_add(count); let lines = self.row_clamp_bottom(in_scroll_region); self.scroll_up(lines); } pub fn row_dec_clamp(&mut self, count: u16) { let in_scroll_region = self.in_scroll_region(); self.pos.row = self.pos.row.saturating_sub(count); self.row_clamp_top(in_scroll_region); } pub fn row_dec_scroll(&mut self, count: u16) { let in_scroll_region = self.in_scroll_region(); // need to account for clamping by both row_clamp_top and by // saturating_sub let extra_lines = if count > self.pos.row { count - self.pos.row } else { 0 }; self.pos.row = self.pos.row.saturating_sub(count); let lines = self.row_clamp_top(in_scroll_region); self.scroll_down(lines + extra_lines); } pub fn row_set(&mut self, i: u16) { self.pos.row = i; } pub fn col_inc(&mut self, count: u16) { self.pos.col = self.pos.col.saturating_add(count); } pub fn col_inc_clamp(&mut self, count: u16) { self.pos.col = self.pos.col.saturating_add(count); self.col_clamp(); } pub fn col_dec(&mut self, count: u16) { self.pos.col = self.pos.col.saturating_sub(count); } pub fn col_tab(&mut self) { self.pos.col -= self.pos.col % 8; self.pos.col += 8; self.col_clamp(); } pub fn col_set(&mut self, i: u16) { self.pos.col = i; self.col_clamp(); } pub fn col_wrap(&mut self, width: u16, wrap: bool) { if self.pos.col > self.size.cols - width { self.current_row_mut().wrap(wrap); self.pos.col = 0; self.row_inc_scroll(1); } } fn row_clamp_top(&mut self, limit_to_scroll_region: bool) -> u16 { if limit_to_scroll_region && self.pos.row < self.scroll_top { let rows = self.scroll_top - self.pos.row; self.pos.row = self.scroll_top; rows } else { 0 } } fn row_clamp_bottom(&mut self, limit_to_scroll_region: bool) -> u16 { let bottom = if limit_to_scroll_region { self.scroll_bottom } else { self.size.rows - 1 }; if self.pos.row > bottom { let rows = self.pos.row - bottom; self.pos.row = bottom; rows } else { 0 } } fn col_clamp(&mut self) { if self.pos.col > self.size.cols - 1 { self.pos.col = self.size.cols - 1; } } } #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] pub struct Size { pub rows: u16, pub cols: u16, } #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] pub struct Pos { pub row: u16, pub col: u16, }