use crate::ansi::TerminalOps;
use super::{Cell, Grid, Scrollback};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CursorStyle {
Block,
Underline,
Bar,
}
impl CursorStyle {
pub fn from_config(value: &str) -> Self {
match value.to_ascii_lowercase().as_str() {
"underline" => Self::Underline,
"bar" | "beam" => Self::Bar,
_ => Self::Block,
}
}
pub fn from_dec_style(value: usize) -> Self {
match value {
3 | 4 => Self::Underline,
5 | 6 => Self::Bar,
_ => Self::Block,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum MouseTracking {
#[default]
Off,
X10,
Normal,
ButtonMotion,
AnyMotion,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum MouseEncoding {
#[default]
Normal,
Utf8,
Sgr,
SgrPixels,
Urxvt,
}
pub struct TerminalState {
pub grid: Grid,
pub scrollback: Scrollback,
primary_grid: Option<Grid>,
in_alt_screen: bool,
app_cursor: bool,
bracketed_paste: bool,
cursor_visible: bool,
cursor_style: CursorStyle,
mouse_tracking: MouseTracking,
mouse_encoding: MouseEncoding,
focus_reporting: bool,
title: String,
clipboard_text: Option<String>,
hyperlinks: Vec<String>,
active_hyperlink_id: u32,
}
impl TerminalState {
pub fn new(cols: usize, rows: usize, scrollback_lines: usize) -> Self {
Self {
grid: Grid::new(cols, rows),
scrollback: Scrollback::new_scrollback(scrollback_lines),
primary_grid: None,
in_alt_screen: false,
app_cursor: false,
bracketed_paste: false,
cursor_visible: true,
cursor_style: CursorStyle::Block,
mouse_tracking: MouseTracking::Off,
mouse_encoding: MouseEncoding::Normal,
focus_reporting: false,
title: String::new(),
clipboard_text: None,
hyperlinks: Vec::new(),
active_hyperlink_id: 0,
}
}
pub fn resize(&mut self, cols: usize, rows: usize) {
self.grid.resize(cols, rows);
if let Some(primary) = &mut self.primary_grid {
primary.resize(cols, rows);
}
}
pub fn set_scrollback_limit(&mut self, max_lines: usize) {
self.scrollback.set_max_lines(max_lines);
}
pub fn in_alt_screen(&self) -> bool {
self.in_alt_screen
}
pub fn app_cursor(&self) -> bool {
self.app_cursor
}
pub fn bracketed_paste(&self) -> bool {
self.bracketed_paste
}
pub fn cursor_visible(&self) -> bool {
self.cursor_visible
}
pub fn cursor_style(&self) -> CursorStyle {
self.cursor_style
}
pub fn set_cursor_style_config(&mut self, style: CursorStyle) {
self.cursor_style = style;
}
pub fn mouse_tracking(&self) -> MouseTracking {
self.mouse_tracking
}
pub fn mouse_sgr(&self) -> bool {
matches!(
self.mouse_encoding,
MouseEncoding::Sgr | MouseEncoding::SgrPixels
)
}
pub fn mouse_encoding(&self) -> MouseEncoding {
self.mouse_encoding
}
pub fn mouse_reporting(&self) -> bool {
self.mouse_tracking != MouseTracking::Off
}
pub fn focus_reporting(&self) -> bool {
self.focus_reporting
}
pub fn title(&self) -> &str {
&self.title
}
pub fn clipboard_text(&self) -> Option<&str> {
self.clipboard_text.as_deref()
}
pub fn active_hyperlink(&self) -> Option<&str> {
self.hyperlink_uri(self.active_hyperlink_id)
}
pub fn active_hyperlink_id(&self) -> u32 {
self.active_hyperlink_id
}
pub fn hyperlink_uri(&self, hyperlink_id: u32) -> Option<&str> {
if hyperlink_id == 0 {
return None;
}
self.hyperlinks
.get(hyperlink_id.saturating_sub(1) as usize)
.map(String::as_str)
}
fn hyperlink_id_for(&mut self, uri: &str) -> u32 {
if let Some(index) = self.hyperlinks.iter().position(|existing| existing == uri) {
return (index + 1) as u32;
}
if self.hyperlinks.len() >= u32::MAX as usize {
return 0;
}
self.hyperlinks.push(uri.to_string());
self.hyperlinks.len() as u32
}
}
impl TerminalOps for TerminalState {
fn put_cell(&mut self, cell: Cell) {
let cell = if self.active_hyperlink_id == 0 {
cell
} else {
cell.with_hyperlink_id(self.active_hyperlink_id)
};
if self.in_alt_screen {
self.grid.put_cell(cell, None);
} else {
self.grid.put_cell(cell, Some(&mut self.scrollback));
}
}
fn put_ascii_bytes(
&mut self,
bytes: &[u8],
fg: super::cell::CellColor,
bg: super::cell::CellColor,
attrs: super::cell::CellAttrs,
) {
if self.in_alt_screen {
self.grid
.put_ascii_bytes(bytes, fg, bg, attrs, self.active_hyperlink_id, None);
} else {
self.grid.put_ascii_bytes(
bytes,
fg,
bg,
attrs,
self.active_hyperlink_id,
Some(&mut self.scrollback),
);
}
}
fn newline(&mut self, blank: Cell) {
if self.in_alt_screen {
self.grid.newline_with(None, &blank);
} else {
self.grid.newline_with(Some(&mut self.scrollback), &blank);
}
}
fn carriage_return(&mut self) {
self.grid.carriage_return();
}
fn backspace(&mut self) {
self.grid.backspace();
}
fn tab(&mut self) {
self.grid.tab();
}
fn clear_all(&mut self, blank: Cell) {
self.grid.clear_all_with(&blank);
}
fn clear_display_forward(&mut self, blank: Cell) {
self.grid.clear_display_forward_with(&blank);
}
fn clear_display_backward(&mut self, blank: Cell) {
self.grid.clear_display_backward_with(&blank);
}
fn clear_line_forward(&mut self, blank: Cell) {
self.grid.clear_line_forward_with(&blank);
}
fn clear_line_from_start(&mut self, blank: Cell) {
self.grid.clear_line_from_start_with(&blank);
}
fn clear_line(&mut self, blank: Cell) {
self.grid.clear_line_with(&blank);
}
fn cursor_up(&mut self, n: usize) {
self.grid.cursor_up(n);
}
fn cursor_down(&mut self, n: usize) {
self.grid.cursor_down(n);
}
fn cursor_next_line(&mut self, n: usize) {
self.grid.cursor_next_line(n);
}
fn cursor_prev_line(&mut self, n: usize) {
self.grid.cursor_prev_line(n);
}
fn cursor_forward(&mut self, n: usize) {
self.grid.cursor_forward(n);
}
fn cursor_backward(&mut self, n: usize) {
self.grid.cursor_backward(n);
}
fn cursor_horizontal_absolute(&mut self, col: usize) {
self.grid.cursor_horizontal_absolute(col);
}
fn cursor_vertical_absolute(&mut self, row: usize) {
self.grid.cursor_vertical_absolute(row);
}
fn cursor_position(&mut self, row: usize, col: usize) {
self.grid.cursor_position(row, col);
}
fn set_scroll_region(&mut self, top_1: usize, bot_1: usize) {
self.grid.set_scroll_region(top_1, bot_1);
}
fn insert_chars(&mut self, n: usize, blank: Cell) {
self.grid.insert_chars_with(n, &blank);
}
fn delete_chars(&mut self, n: usize, blank: Cell) {
self.grid.delete_chars_with(n, &blank);
}
fn erase_chars(&mut self, n: usize, blank: Cell) {
self.grid.erase_chars_with(n, &blank);
}
fn insert_lines(&mut self, n: usize, blank: Cell) {
self.grid.insert_lines_with(n, &blank);
}
fn delete_lines(&mut self, n: usize, blank: Cell) {
self.grid.delete_lines_with(n, &blank);
}
fn scroll_up(&mut self, n: usize, blank: Cell) {
self.grid.scroll_up_with(n, None, &blank);
}
fn scroll_down(&mut self, n: usize, blank: Cell) {
self.grid.scroll_down_with(n, &blank);
}
fn repeat_preceding_cell(&mut self, n: usize) {
if self.in_alt_screen {
self.grid.repeat_preceding_cell(n, None);
} else {
self.grid
.repeat_preceding_cell(n, Some(&mut self.scrollback));
}
}
fn save_cursor(&mut self) {
self.grid.save_cursor();
}
fn restore_cursor(&mut self) {
self.grid.restore_cursor();
}
fn enter_alt_screen(&mut self) {
if self.in_alt_screen {
return;
}
let (cols, rows) = (self.grid.cols, self.grid.rows);
let primary = std::mem::replace(&mut self.grid, Grid::new(cols, rows));
self.primary_grid = Some(primary);
self.in_alt_screen = true;
}
fn exit_alt_screen(&mut self) {
if !self.in_alt_screen {
return;
}
if let Some(primary) = self.primary_grid.take() {
self.grid = primary;
}
self.in_alt_screen = false;
}
fn set_app_cursor(&mut self, enabled: bool) {
self.app_cursor = enabled;
}
fn set_bracketed_paste(&mut self, enabled: bool) {
self.bracketed_paste = enabled;
}
fn set_cursor_visible(&mut self, visible: bool) {
self.cursor_visible = visible;
}
fn set_cursor_style(&mut self, style: CursorStyle) {
self.cursor_style = style;
}
fn set_mouse_tracking(&mut self, tracking: MouseTracking) {
self.mouse_tracking = tracking;
}
fn set_mouse_sgr(&mut self, enabled: bool) {
self.mouse_encoding = if enabled {
MouseEncoding::Sgr
} else {
MouseEncoding::Normal
};
}
fn set_mouse_encoding(&mut self, encoding: MouseEncoding) {
self.mouse_encoding = encoding;
}
fn set_focus_reporting(&mut self, enabled: bool) {
self.focus_reporting = enabled;
}
fn set_title(&mut self, title: String) {
self.title = title;
}
fn set_clipboard_text(&mut self, text: Option<String>) {
self.clipboard_text = text;
}
fn set_active_hyperlink(&mut self, uri: Option<String>) {
self.active_hyperlink_id = uri
.as_deref()
.map(|uri| self.hyperlink_id_for(uri))
.unwrap_or(0);
}
}