zellij/src/client/panes/terminal_pane.rs
Aram Drevekenin ceb2ebbd2b
fix(terminal): persiste vte state (#269)
* fix(terminal): persist vte state

* style(fmt): rustfmt
2021-04-14 13:52:45 +02:00

327 lines
12 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::tab::Pane;
use ::nix::pty::Winsize;
use ::std::os::unix::io::RawFd;
use std::fmt::Debug;
use crate::panes::grid::Grid;
use crate::panes::terminal_character::{
CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
};
use crate::pty_bus::VteBytes;
#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
pub enum PaneId {
Terminal(RawFd),
Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct?
}
/// Contains the position and size of a [`Pane`], or more generally of any terminal, measured
/// in character rows and columns.
#[derive(Clone, Copy, Debug, Default)]
pub struct PositionAndSize {
pub x: usize,
pub y: usize,
pub rows: usize,
pub columns: usize,
}
impl From<Winsize> for PositionAndSize {
fn from(winsize: Winsize) -> PositionAndSize {
PositionAndSize {
columns: winsize.ws_col as usize,
rows: winsize.ws_row as usize,
..Default::default()
}
}
}
pub struct TerminalPane {
pub grid: Grid,
pub pid: RawFd,
pub selectable: bool,
pub position_and_size: PositionAndSize,
pub position_and_size_override: Option<PositionAndSize>,
pub max_height: Option<usize>,
vte_parser: vte::Parser,
}
impl Pane for TerminalPane {
fn x(&self) -> usize {
self.get_x()
}
fn y(&self) -> usize {
self.get_y()
}
fn rows(&self) -> usize {
self.get_rows()
}
fn columns(&self) -> usize {
self.get_columns()
}
fn reset_size_and_position_override(&mut self) {
self.position_and_size_override = None;
self.reflow_lines();
}
fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) {
self.position_and_size.columns = position_and_size.columns;
self.position_and_size.rows = position_and_size.rows;
self.reflow_lines();
}
fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) {
let position_and_size_override = PositionAndSize {
x,
y,
rows: size.rows,
columns: size.columns,
};
self.position_and_size_override = Some(position_and_size_override);
self.reflow_lines();
}
fn handle_pty_bytes(&mut self, bytes: VteBytes) {
for byte in bytes.iter() {
self.vte_parser.advance(&mut self.grid, *byte);
}
}
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
// (x, y)
self.grid.cursor_coordinates()
}
fn adjust_input_to_terminal(&self, input_bytes: Vec<u8>) -> Vec<u8> {
// there are some cases in which the terminal state means that input sent to it
// needs to be adjusted.
// here we match against those cases - if need be, we adjust the input and if not
// we send back the original input
match input_bytes.as_slice() {
[27, 91, 68] => {
// left arrow
if self.grid.cursor_key_mode {
// please note that in the line below, there is an ANSI escape code (27) at the beginning of the string,
// some editors will not show this
return "OD".as_bytes().to_vec();
}
}
[27, 91, 67] => {
// right arrow
if self.grid.cursor_key_mode {
// please note that in the line below, there is an ANSI escape code (27) at the beginning of the string,
// some editors will not show this
return "OC".as_bytes().to_vec();
}
}
[27, 91, 65] => {
// up arrow
if self.grid.cursor_key_mode {
// please note that in the line below, there is an ANSI escape code (27) at the beginning of the string,
// some editors will not show this
return "OA".as_bytes().to_vec();
}
}
[27, 91, 66] => {
// down arrow
if self.grid.cursor_key_mode {
// please note that in the line below, there is an ANSI escape code (27) at the beginning of the string,
// some editors will not show this
return "OB".as_bytes().to_vec();
}
}
_ => {}
};
input_bytes
}
fn position_and_size_override(&self) -> Option<PositionAndSize> {
self.position_and_size_override
}
fn should_render(&self) -> bool {
self.grid.should_render
}
fn set_should_render(&mut self, should_render: bool) {
self.grid.should_render = should_render;
}
fn selectable(&self) -> bool {
self.selectable
}
fn set_selectable(&mut self, selectable: bool) {
self.selectable = selectable;
}
fn set_max_height(&mut self, max_height: usize) {
self.max_height = Some(max_height);
}
fn set_invisible_borders(&mut self, _invisible_borders: bool) {
unimplemented!();
}
fn max_height(&self) -> Option<usize> {
self.max_height
}
fn render(&mut self) -> Option<String> {
// FIXME:
// the below conditional is commented out because it causes several bugs:
// 1. When panes are resized or tabs are switched the previous contents of the screen stick
// around
// 2. When there are wide characters in a pane, since we don't yet handle them properly,
// the spill over to the pane to the right
// if self.should_render || cfg!(test) {
if true {
let mut vte_output = String::new();
let buffer_lines = &self.read_buffer_as_lines();
let display_cols = self.get_columns();
let mut character_styles = CharacterStyles::new();
if self.grid.clear_viewport_before_rendering {
for line_index in 0..self.grid.height {
let x = self.get_x();
let y = self.get_y();
vte_output.push_str(&format!(
"\u{1b}[{};{}H\u{1b}[m",
y + line_index + 1,
x + 1
)); // goto row/col and reset styles
for _col_index in 0..self.grid.width {
vte_output.push(EMPTY_TERMINAL_CHARACTER.character);
}
}
self.grid.clear_viewport_before_rendering = false;
}
for (row, line) in buffer_lines.iter().enumerate() {
let x = self.get_x();
let y = self.get_y();
vte_output.push_str(&format!("\u{1b}[{};{}H\u{1b}[m", y + row + 1, x + 1)); // goto row/col and reset styles
for (col, t_character) in line.iter().enumerate() {
if col < display_cols {
// in some cases (eg. while resizing) some characters will spill over
// before they are corrected by the shell (for the prompt) or by reflowing
// lines
if let Some(new_styles) =
character_styles.update_and_return_diff(&t_character.styles)
{
// the terminal keeps the previous styles as long as we're in the same
// line, so we only want to update the new styles here (this also
// includes resetting previous styles as needed)
vte_output.push_str(&new_styles.to_string());
}
vte_output.push(t_character.character);
}
}
character_styles.clear();
}
self.grid.should_render = false;
Some(vte_output)
} else {
None
}
}
fn pid(&self) -> PaneId {
PaneId::Terminal(self.pid)
}
fn reduce_height_down(&mut self, count: usize) {
self.position_and_size.y += count;
self.position_and_size.rows -= count;
self.reflow_lines();
}
fn increase_height_down(&mut self, count: usize) {
self.position_and_size.rows += count;
self.reflow_lines();
}
fn increase_height_up(&mut self, count: usize) {
self.position_and_size.y -= count;
self.position_and_size.rows += count;
self.reflow_lines();
}
fn reduce_height_up(&mut self, count: usize) {
self.position_and_size.rows -= count;
self.reflow_lines();
}
fn reduce_width_right(&mut self, count: usize) {
self.position_and_size.x += count;
self.position_and_size.columns -= count;
self.reflow_lines();
}
fn reduce_width_left(&mut self, count: usize) {
self.position_and_size.columns -= count;
self.reflow_lines();
}
fn increase_width_left(&mut self, count: usize) {
self.position_and_size.x -= count;
self.position_and_size.columns += count;
self.reflow_lines();
}
fn increase_width_right(&mut self, count: usize) {
self.position_and_size.columns += count;
self.reflow_lines();
}
fn push_down(&mut self, count: usize) {
self.position_and_size.y += count;
}
fn push_right(&mut self, count: usize) {
self.position_and_size.x += count;
}
fn pull_left(&mut self, count: usize) {
self.position_and_size.x -= count;
}
fn pull_up(&mut self, count: usize) {
self.position_and_size.y -= count;
}
fn scroll_up(&mut self, count: usize) {
self.grid.move_viewport_up(count);
self.grid.should_render = true;
}
fn scroll_down(&mut self, count: usize) {
self.grid.move_viewport_down(count);
self.grid.should_render = true;
}
fn clear_scroll(&mut self) {
self.grid.reset_viewport();
self.grid.should_render = true;
}
}
impl TerminalPane {
pub fn new(pid: RawFd, position_and_size: PositionAndSize) -> TerminalPane {
let grid = Grid::new(position_and_size.rows, position_and_size.columns);
TerminalPane {
pid,
grid,
selectable: true,
position_and_size,
position_and_size_override: None,
max_height: None,
vte_parser: vte::Parser::new(),
}
}
pub fn get_x(&self) -> usize {
match self.position_and_size_override {
Some(position_and_size_override) => position_and_size_override.x,
None => self.position_and_size.x as usize,
}
}
pub fn get_y(&self) -> usize {
match self.position_and_size_override {
Some(position_and_size_override) => position_and_size_override.y,
None => self.position_and_size.y as usize,
}
}
pub fn get_columns(&self) -> usize {
match &self.position_and_size_override.as_ref() {
Some(position_and_size_override) => position_and_size_override.columns,
None => self.position_and_size.columns as usize,
}
}
pub fn get_rows(&self) -> usize {
match &self.position_and_size_override.as_ref() {
Some(position_and_size_override) => position_and_size_override.rows,
None => self.position_and_size.rows as usize,
}
}
fn reflow_lines(&mut self) {
let rows = self.get_rows();
let columns = self.get_columns();
self.grid.change_size(rows, columns);
}
pub fn read_buffer_as_lines(&self) -> Vec<Vec<TerminalCharacter>> {
self.grid.as_character_lines()
}
#[cfg(test)]
pub fn cursor_coordinates(&self) -> Option<(usize, usize)> {
// (x, y)
self.grid.cursor_coordinates()
}
}