diff options
| author | a-kenji <aks.kenji@protonmail.com> | 2021-05-18 10:05:15 +0200 |
|---|---|---|
| committer | a-kenji <aks.kenji@protonmail.com> | 2021-05-18 10:05:15 +0200 |
| commit | bcbde9fbb5bffe4e1334acc1270314517938cac5 (patch) | |
| tree | 8a18820a32392fb4a8a132a27740299ac4849f84 /src | |
| parent | Fix Clippy Suggestions (diff) | |
| parent | Merge pull request #510 from zellij-org/speedup-ci (diff) | |
| download | sl0ppij-bcbde9fbb5bffe4e1334acc1270314517938cac5.tar.gz sl0ppij-bcbde9fbb5bffe4e1334acc1270314517938cac5.tar.xz sl0ppij-bcbde9fbb5bffe4e1334acc1270314517938cac5.zip | |
Merge branch 'main' of https://github.com/zellij-org/zellij into layout-path-506
Diffstat (limited to 'src')
86 files changed, 74 insertions, 13752 deletions
diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index b8a61344..00000000 --- a/src/cli.rs +++ /dev/null @@ -1,55 +0,0 @@ -use super::common::utils::consts::{ZELLIJ_CONFIG_DIR_ENV, ZELLIJ_CONFIG_FILE_ENV}; -use crate::common::input::options::Options; -use crate::common::setup::Setup; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use structopt::StructOpt; - -#[derive(StructOpt, Default, Debug, Clone, Serialize, Deserialize)] -#[structopt(name = "zellij")] -pub struct CliArgs { - /// Maximum panes on screen, caution: opening more panes will close old ones - #[structopt(long)] - pub max_panes: Option<usize>, - - /// Change where zellij looks for layouts and plugins - #[structopt(long, parse(from_os_str))] - pub data_dir: Option<PathBuf>, - - /// Run server listening at the specified socket path - #[structopt(long, parse(from_os_str))] - pub server: Option<PathBuf>, - - /// Path to a layout yaml file - #[structopt(short, long, parse(from_os_str))] - pub layout: Option<PathBuf>, - - /// Path to a layout yaml file - #[structopt(long, parse(from_os_str))] - pub layout_path: Option<PathBuf>, - - /// Change where zellij looks for the configuration - #[structopt(short, long, env=ZELLIJ_CONFIG_FILE_ENV, parse(from_os_str))] - pub config: Option<PathBuf>, - - /// Change where zellij looks for the configuration - #[structopt(long, env=ZELLIJ_CONFIG_DIR_ENV, parse(from_os_str))] - pub config_dir: Option<PathBuf>, - - #[structopt(subcommand)] - pub option: Option<ConfigCli>, - - #[structopt(short, long)] - pub debug: bool, -} - -#[derive(Debug, StructOpt, Clone, Serialize, Deserialize)] -pub enum ConfigCli { - /// Change the behaviour of zellij - #[structopt(name = "options")] - Options(Options), - - /// Setup zellij and check its configuration - #[structopt(name = "setup")] - Setup(Setup), -} diff --git a/src/client/boundaries.rs b/src/client/boundaries.rs deleted file mode 100644 index 86fb37fc..00000000 --- a/src/client/boundaries.rs +++ /dev/null @@ -1,630 +0,0 @@ -use crate::tab::Pane; -use crate::utils::shared::colors; -use ansi_term::Colour::{Fixed, RGB}; -use std::collections::HashMap; -use zellij_tile::data::{InputMode, Palette, PaletteColor}; - -use std::fmt::{Display, Error, Formatter}; -pub mod boundary_type { - pub const TOP_RIGHT: &str = "┐"; - pub const VERTICAL: &str = "│"; - pub const HORIZONTAL: &str = "─"; - pub const TOP_LEFT: &str = "┌"; - pub const BOTTOM_RIGHT: &str = "┘"; - pub const BOTTOM_LEFT: &str = "└"; - pub const VERTICAL_LEFT: &str = "┤"; - pub const VERTICAL_RIGHT: &str = "├"; - pub const HORIZONTAL_DOWN: &str = "┬"; - pub const HORIZONTAL_UP: &str = "┴"; - pub const CROSS: &str = "┼"; -} - -pub type BoundaryType = &'static str; // easy way to refer to boundary_type above - -#[derive(Clone, Copy, Debug)] -pub struct BoundarySymbol { - boundary_type: BoundaryType, - invisible: bool, - color: Option<PaletteColor>, -} - -impl BoundarySymbol { - pub fn new(boundary_type: BoundaryType) -> Self { - BoundarySymbol { - boundary_type, - invisible: false, - color: Some(PaletteColor::EightBit(colors::GRAY)), - } - } - pub fn invisible(mut self) -> Self { - self.invisible = true; - self - } - pub fn color(&mut self, color: Option<PaletteColor>) -> Self { - self.color = color; - *self - } -} - -impl Display for BoundarySymbol { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - match self.invisible { - true => write!(f, " "), - false => match self.color { - Some(color) => match color { - PaletteColor::Rgb((r, g, b)) => { - write!(f, "{}", RGB(r, g, b).paint(self.boundary_type)) - } - PaletteColor::EightBit(color) => { - write!(f, "{}", Fixed(color).paint(self.boundary_type)) - } - }, - None => write!(f, "{}", self.boundary_type), - }, - } - } -} - -fn combine_symbols( - current_symbol: BoundarySymbol, - next_symbol: BoundarySymbol, -) -> Option<BoundarySymbol> { - use boundary_type::*; - let invisible = current_symbol.invisible || next_symbol.invisible; - let color = current_symbol.color.or(next_symbol.color); - match (current_symbol.boundary_type, next_symbol.boundary_type) { - (CROSS, _) | (_, CROSS) => { - // (┼, *) or (*, ┼) => Some(┼) - let boundary_type = CROSS; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (TOP_RIGHT, TOP_RIGHT) => { - // (┐, ┐) => Some(┐) - let boundary_type = TOP_RIGHT; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (TOP_RIGHT, VERTICAL) | (TOP_RIGHT, BOTTOM_RIGHT) | (TOP_RIGHT, VERTICAL_LEFT) => { - // (┐, │) => Some(┤) - // (┐, ┘) => Some(┤) - // (─, ┤) => Some(┤) - let boundary_type = VERTICAL_LEFT; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (TOP_RIGHT, HORIZONTAL) | (TOP_RIGHT, TOP_LEFT) | (TOP_RIGHT, HORIZONTAL_DOWN) => { - // (┐, ─) => Some(┬) - // (┐, ┌) => Some(┬) - // (┐, ┬) => Some(┬) - let boundary_type = HORIZONTAL_DOWN; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (TOP_RIGHT, BOTTOM_LEFT) | (TOP_RIGHT, VERTICAL_RIGHT) | (TOP_RIGHT, HORIZONTAL_UP) => { - // (┐, └) => Some(┼) - // (┐, ├) => Some(┼) - // (┐, ┴) => Some(┼) - let boundary_type = CROSS; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (HORIZONTAL, HORIZONTAL) => { - // (─, ─) => Some(─) - let boundary_type = HORIZONTAL; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (HORIZONTAL, VERTICAL) | (HORIZONTAL, VERTICAL_LEFT) | (HORIZONTAL, VERTICAL_RIGHT) => { - // (─, │) => Some(┼) - // (─, ┤) => Some(┼) - // (─, ├) => Some(┼) - let boundary_type = CROSS; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (HORIZONTAL, TOP_LEFT) | (HORIZONTAL, HORIZONTAL_DOWN) => { - // (─, ┌) => Some(┬) - // (─, ┬) => Some(┬) - let boundary_type = HORIZONTAL_DOWN; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (HORIZONTAL, BOTTOM_RIGHT) | (HORIZONTAL, BOTTOM_LEFT) | (HORIZONTAL, HORIZONTAL_UP) => { - // (─, ┘) => Some(┴) - // (─, └) => Some(┴) - // (─, ┴) => Some(┴) - let boundary_type = HORIZONTAL_UP; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (VERTICAL, VERTICAL) => { - // (│, │) => Some(│) - let boundary_type = VERTICAL; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (VERTICAL, TOP_LEFT) | (VERTICAL, BOTTOM_LEFT) | (VERTICAL, VERTICAL_RIGHT) => { - // (│, ┌) => Some(├) - // (│, └) => Some(├) - // (│, ├) => Some(├) - let boundary_type = VERTICAL_RIGHT; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (VERTICAL, BOTTOM_RIGHT) | (VERTICAL, VERTICAL_LEFT) => { - // (│, ┘) => Some(┤) - // (│, ┤) => Some(┤) - let boundary_type = VERTICAL_LEFT; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (VERTICAL, HORIZONTAL_DOWN) | (VERTICAL, HORIZONTAL_UP) => { - // (│, ┬) => Some(┼) - // (│, ┴) => Some(┼) - let boundary_type = CROSS; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (TOP_LEFT, TOP_LEFT) => { - // (┌, ┌) => Some(┌) - let boundary_type = TOP_LEFT; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (TOP_LEFT, BOTTOM_RIGHT) | (TOP_LEFT, VERTICAL_LEFT) | (TOP_LEFT, HORIZONTAL_UP) => { - // (┌, ┘) => Some(┼) - // (┌, ┤) => Some(┼) - // (┌, ┴) => Some(┼) - let boundary_type = CROSS; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (TOP_LEFT, BOTTOM_LEFT) | (TOP_LEFT, VERTICAL_RIGHT) => { - // (┌, └) => Some(├) - // (┌, ├) => Some(├) - let boundary_type = VERTICAL_RIGHT; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (TOP_LEFT, HORIZONTAL_DOWN) => { - // (┌, ┬) => Some(┬) - let boundary_type = HORIZONTAL_DOWN; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (BOTTOM_RIGHT, BOTTOM_RIGHT) => { - // (┘, ┘) => Some(┘) - let boundary_type = BOTTOM_RIGHT; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (BOTTOM_RIGHT, BOTTOM_LEFT) | (BOTTOM_RIGHT, HORIZONTAL_UP) => { - // (┘, └) => Some(┴) - // (┘, ┴) => Some(┴) - let boundary_type = HORIZONTAL_UP; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (BOTTOM_RIGHT, VERTICAL_LEFT) => { - // (┘, ┤) => Some(┤) - let boundary_type = VERTICAL_LEFT; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (BOTTOM_RIGHT, VERTICAL_RIGHT) | (BOTTOM_RIGHT, HORIZONTAL_DOWN) => { - // (┘, ├) => Some(┼) - // (┘, ┬) => Some(┼) - let boundary_type = CROSS; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (BOTTOM_LEFT, BOTTOM_LEFT) => { - // (└, └) => Some(└) - let boundary_type = BOTTOM_LEFT; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (BOTTOM_LEFT, VERTICAL_LEFT) | (BOTTOM_LEFT, HORIZONTAL_DOWN) => { - // (└, ┤) => Some(┼) - // (└, ┬) => Some(┼) - let boundary_type = CROSS; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (BOTTOM_LEFT, VERTICAL_RIGHT) => { - // (└, ├) => Some(├) - let boundary_type = VERTICAL_RIGHT; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (BOTTOM_LEFT, HORIZONTAL_UP) => { - // (└, ┴) => Some(┴) - let boundary_type = HORIZONTAL_UP; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (VERTICAL_LEFT, VERTICAL_LEFT) => { - // (┤, ┤) => Some(┤) - let boundary_type = VERTICAL_LEFT; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (VERTICAL_LEFT, VERTICAL_RIGHT) - | (VERTICAL_LEFT, HORIZONTAL_DOWN) - | (VERTICAL_LEFT, HORIZONTAL_UP) => { - // (┤, ├) => Some(┼) - // (┤, ┬) => Some(┼) - // (┤, ┴) => Some(┼) - let boundary_type = CROSS; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (VERTICAL_RIGHT, VERTICAL_RIGHT) => { - // (├, ├) => Some(├) - let boundary_type = VERTICAL_RIGHT; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (VERTICAL_RIGHT, HORIZONTAL_DOWN) | (VERTICAL_RIGHT, HORIZONTAL_UP) => { - // (├, ┬) => Some(┼) - // (├, ┴) => Some(┼) - let boundary_type = CROSS; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (HORIZONTAL_DOWN, HORIZONTAL_DOWN) => { - // (┬, ┬) => Some(┬) - let boundary_type = HORIZONTAL_DOWN; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (HORIZONTAL_DOWN, HORIZONTAL_UP) => { - // (┬, ┴) => Some(┼) - let boundary_type = CROSS; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (HORIZONTAL_UP, HORIZONTAL_UP) => { - // (┴, ┴) => Some(┴) - let boundary_type = HORIZONTAL_UP; - Some(BoundarySymbol { - boundary_type, - invisible, - color, - }) - } - (_, _) => combine_symbols(next_symbol, current_symbol), - } -} - -#[derive(PartialEq, Eq, Hash, Debug)] -pub struct Coordinates { - x: usize, - y: usize, -} - -impl Coordinates { - pub fn new(x: usize, y: usize) -> Self { - Coordinates { x, y } - } -} - -pub trait Rect { - fn x(&self) -> usize; - fn y(&self) -> usize; - fn rows(&self) -> usize; - fn columns(&self) -> usize; - fn right_boundary_x_coords(&self) -> usize { - self.x() + self.columns() - } - fn bottom_boundary_y_coords(&self) -> usize { - self.y() + self.rows() - } - fn is_directly_right_of(&self, other: &Self) -> bool { - self.x() == other.x() + other.columns() + 1 - } - fn is_directly_left_of(&self, other: &Self) -> bool { - self.x() + self.columns() + 1 == other.x() - } - fn is_directly_below(&self, other: &Self) -> bool { - self.y() == other.y() + other.rows() + 1 - } - fn is_directly_above(&self, other: &Self) -> bool { - self.y() + self.rows() + 1 == other.y() - } - fn horizontally_overlaps_with(&self, other: &Self) -> bool { - (self.y() >= other.y() && self.y() <= (other.y() + other.rows())) - || ((self.y() + self.rows()) <= (other.y() + other.rows()) - && (self.y() + self.rows()) > other.y()) - || (self.y() <= other.y() && (self.y() + self.rows() >= (other.y() + other.rows()))) - || (other.y() <= self.y() && (other.y() + other.rows() >= (self.y() + self.rows()))) - } - fn get_horizontal_overlap_with(&self, other: &Self) -> usize { - std::cmp::min(self.y() + self.rows(), other.y() + other.rows()) - - std::cmp::max(self.y(), other.y()) - } - fn vertically_overlaps_with(&self, other: &Self) -> bool { - (self.x() >= other.x() && self.x() <= (other.x() + other.columns())) - || ((self.x() + self.columns()) <= (other.x() + other.columns()) - && (self.x() + self.columns()) > other.x()) - || (self.x() <= other.x() - && (self.x() + self.columns() >= (other.x() + other.columns()))) - || (other.x() <= self.x() - && (other.x() + other.columns() >= (self.x() + self.columns()))) - } - fn get_vertical_overlap_with(&self, other: &Self) -> usize { - std::cmp::min(self.x() + self.columns(), other.x() + other.columns()) - - std::cmp::max(self.x(), other.x()) - } -} - -pub struct Boundaries { - columns: usize, - rows: usize, - // boundary_characters: HashMap<Coordinates, BoundaryType>, - boundary_characters: HashMap<Coordinates, BoundarySymbol>, -} - -impl Boundaries { - pub fn new(columns: u16, rows: u16) -> Self { - let columns = columns as usize; - let rows = rows as usize; - Boundaries { - columns, - rows, - boundary_characters: HashMap::new(), - } - } - pub fn add_rect(&mut self, rect: &dyn Pane, input_mode: InputMode, palette: Option<Palette>) { - let color = match palette.is_some() { - true => match input_mode { - InputMode::Normal | InputMode::Locked => Some(palette.unwrap().green), - _ => Some(palette.unwrap().orange), - }, - false => None, - }; - if rect.x() > 0 { - let boundary_x_coords = rect.x() - 1; - let first_row_coordinates = self.rect_right_boundary_row_start(rect); - let last_row_coordinates = self.rect_right_boundary_row_end(rect); - for row in first_row_coordinates..last_row_coordinates { - let coordinates = Coordinates::new(boundary_x_coords, row); - let mut symbol_to_add = if row == first_row_coordinates && row != 0 { - BoundarySymbol::new(boundary_type::TOP_LEFT).color(color) - } else if row == last_row_coordinates - 1 && row != self.rows - 1 { - BoundarySymbol::new(boundary_type::BOTTOM_LEFT).color(color) - } else { - BoundarySymbol::new(boundary_type::VERTICAL).color(color) - }; - if rect.invisible_borders() { - symbol_to_add = symbol_to_add.invisible(); - } - let next_symbol = self - .boundary_characters - .remove(&coordinates) - .and_then(|current_symbol| combine_symbols(current_symbol, symbol_to_add)) - .unwrap_or(symbol_to_add); - self.boundary_characters.insert(coordinates, next_symbol); - } - } - if rect.y() > 0 { - let boundary_y_coords = rect.y() - 1; - let first_col_coordinates = self.rect_bottom_boundary_col_start(rect); - let last_col_coordinates = self.rect_bottom_boundary_col_end(rect); - for col in first_col_coordinates..last_col_coordinates { - let coordinates = Coordinates::new(col, boundary_y_coords); - let mut symbol_to_add = if col == first_col_coordinates && col != 0 { - BoundarySymbol::new(boundary_type::TOP_LEFT).color(color) - } else if col == last_col_coordinates - 1 && col != self.columns - 1 { - BoundarySymbol::new(boundary_type::TOP_RIGHT).color(color) - } else { - BoundarySymbol::new(boundary_type::HORIZONTAL).color(color) - }; - if rect.invisible_borders() { - symbol_to_add = symbol_to_add.invisible(); - } - let next_symbol = self - .boundary_characters - .remove(&coordinates) - .and_then(|current_symbol| combine_symbols(current_symbol, symbol_to_add)) - .unwrap_or(symbol_to_add); - self.boundary_characters.insert(coordinates, next_symbol); - } - } - if self.rect_right_boundary_is_before_screen_edge(rect) { - // let boundary_x_coords = self.rect_right_boundary_x_coords(rect); - let boundary_x_coords = rect.right_boundary_x_coords(); - let first_row_coordinates = self.rect_right_boundary_row_start(rect); - let last_row_coordinates = self.rect_right_boundary_row_end(rect); - for row in first_row_coordinates..last_row_coordinates { - let coordinates = Coordinates::new(boundary_x_coords, row); - let mut symbol_to_add = if row == first_row_coordinates && row != 0 { - BoundarySymbol::new(boundary_type::TOP_RIGHT).color(color) - } else if row == last_row_coordinates - 1 && row != self.rows - 1 { - BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color) - } else { - BoundarySymbol::new(boundary_type::VERTICAL).color(color) - }; - if rect.invisible_borders() { - symbol_to_add = symbol_to_add.invisible(); - } - let next_symbol = self - .boundary_characters - .remove(&coordinates) - .and_then(|current_symbol| combine_symbols(current_symbol, symbol_to_add)) - .unwrap_or(symbol_to_add); - self.boundary_characters.insert(coordinates, next_symbol); - } - } - if self.rect_bottom_boundary_is_before_screen_edge(rect) { - let boundary_y_coords = rect.bottom_boundary_y_coords(); - let first_col_coordinates = self.rect_bottom_boundary_col_start(rect); - let last_col_coordinates = self.rect_bottom_boundary_col_end(rect); - for col in first_col_coordinates..last_col_coordinates { - let coordinates = Coordinates::new(col, boundary_y_coords); - let mut symbol_to_add = if col == first_col_coordinates && col != 0 { - BoundarySymbol::new(boundary_type::BOTTOM_LEFT).color(color) - } else if col == last_col_coordinates - 1 && col != self.columns - 1 { - BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color) - } else { - BoundarySymbol::new(boundary_type::HORIZONTAL).color(color) - }; - if rect.invisible_borders() { - symbol_to_add = symbol_to_add.invisible(); - } - let next_symbol = self - .boundary_characters - .remove(&coordinates) - .and_then(|current_symbol| combine_symbols(current_symbol, symbol_to_add)) - .unwrap_or(symbol_to_add); - self.boundary_characters.insert(coordinates, next_symbol); - } - } - } - pub fn vte_output(&self) -> String { - let mut vte_output = String::new(); - for (coordinates, boundary_character) in &self.boundary_characters { - vte_output.push_str(&format!( - "\u{1b}[{};{}H\u{1b}[m{}", - coordinates.y + 1, - coordinates.x + 1, - boundary_character - )); // goto row/col + boundary character - } - vte_output - } - fn rect_right_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool { - rect.x() + rect.columns() < self.columns - } - fn rect_bottom_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool { - rect.y() + rect.rows() < self.rows - } - fn rect_right_boundary_row_start(&self, rect: &dyn Pane) -> usize { - if rect.y() == 0 { - 0 - } else { - rect.y() - 1 - } - } - fn rect_right_boundary_row_end(&self, rect: &dyn Pane) -> usize { - let rect_bottom_row = rect.y() + rect.rows(); - // we do this because unless we're on the screen edge, we'd like to go one extra row to - // connect to whatever boundary is beneath us - if rect_bottom_row == self.rows { - rect_bottom_row - } else { - rect_bottom_row + 1 - } - } - fn rect_bottom_boundary_col_start(&self, rect: &dyn Pane) -> usize { - if rect.x() == 0 { - 0 - } else { - rect.x() - 1 - } - } - fn rect_bottom_boundary_col_end(&self, rect: &dyn Pane) -> usize { - let rect_right_col = rect.x() + rect.columns(); - // we do this because unless we're on the screen edge, we'd like to go one extra column to - // connect to whatever boundary is right of us - if rect_right_col == self.columns { - rect_right_col - } else { - rect_right_col + 1 - } - } -} diff --git a/src/client/layout.rs b/src/client/layout.rs deleted file mode 100644 index 592481dc..00000000 --- a/src/client/layout.rs +++ /dev/null @@ -1,230 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::path::{Path, PathBuf}; -use std::{fs::File, io::prelude::*}; - -use crate::panes::PositionAndSize; - -fn split_space_to_parts_vertically( - space_to_split: &PositionAndSize, - sizes: Vec<Option<SplitSize>>, -) -> Vec<PositionAndSize> { - let mut split_parts = Vec::new(); - let mut current_x_position = space_to_split.x; - let mut current_width = 0; - let max_width = space_to_split.columns - (sizes.len() - 1); // minus space for gaps - - let mut parts_to_grow = Vec::new(); - - // First fit in the parameterized sizes - for size in sizes { - let (columns, max_columns) = match size { - Some(SplitSize::Percent(percent)) => { - ((max_width as f32 * (percent as f32 / 100.0)) as usize, None) - } // TODO: round properly - Some(SplitSize::Fixed(size)) => (size as usize, Some(size as usize)), - None => { - parts_to_grow.push(current_x_position); - ( - 1, // This is grown later on - None, - ) - } - }; - split_parts.push(PositionAndSize { - x: current_x_position, - y: space_to_split.y, - columns, - rows: space_to_split.rows, - max_columns, - ..Default::default() - }); - current_width += columns; - current_x_position += columns + 1; // 1 for gap - } - - if current_width > max_width { - panic!("Layout contained too many columns to fit onto the screen!"); - } - - let mut last_flexible_index = split_parts.len() - 1; - if let Some(new_columns) = (max_width - current_width).checked_div(parts_to_grow.len()) { - current_width = 0; - current_x_position = 0; - for (idx, part) in split_parts.iter_mut().enumerate() { - part.x = current_x_position; - if parts_to_grow.contains(&part.x) { - part.columns = new_columns; - last_flexible_index = idx; - } - current_width += part.columns; - current_x_position += part.columns + 1; // 1 for gap - } - } - - if current_width < max_width { - // we have some extra space left, let's add it to the last flexible part - let extra = max_width - current_width; - let mut last_part = split_parts.get_mut(last_flexible_index).unwrap(); - last_part.columns += extra; - for part in (&mut split_parts[last_flexible_index + 1..]).iter_mut() { - part.x += extra; - } - } - split_parts -} - -fn split_space_to_parts_horizontally( - space_to_split: &PositionAndSize, - sizes: Vec<Option<SplitSize>>, -) -> Vec<PositionAndSize> { - let mut split_parts = Vec::new(); - let mut current_y_position = space_to_split.y; - let mut current_height = 0; - let max_height = space_to_split.rows - (sizes.len() - 1); // minus space for gaps - - let mut parts_to_grow = Vec::new(); - - for size in sizes { - let (rows, max_rows) = match size { - Some(SplitSize::Percent(percent)) => ( - (max_height as f32 * (percent as f32 / 100.0)) as usize, - None, - ), // TODO: round properly - Some(SplitSize::Fixed(size)) => (size as usize, Some(size as usize)), - None => { - parts_to_grow.push(current_y_position); - ( - 1, // This is grown later on - None, - ) - } - }; - split_parts.push(PositionAndSize { - x: space_to_split.x, - y: current_y_position, - columns: space_to_split.columns, - rows, - max_rows, - ..Default::default() - }); - current_height += rows; - current_y_position += rows + 1; // 1 for gap - } - - if current_height > max_height { - panic!("Layout contained too many rows to fit onto the screen!"); - } - - let mut last_flexible_index = split_parts.len() - 1; - if let Some(new_rows) = (max_height - current_height).checked_div(parts_to_grow.len()) { - current_height = 0; - current_y_position = 0; - - for (idx, part) in split_parts.iter_mut().enumerate() { - part.y = current_y_position; - if parts_to_grow.contains(&part.y) { - part.rows = new_rows; - last_flexible_index = idx; - } - current_height += part.rows; - current_y_position += part.rows + 1; // 1 for gap - } - } - - if current_height < max_height { - // we have some extra space left, let's add it to the last flexible part - let extra = max_height - current_height; - let mut last_part = split_parts.get_mut(last_flexible_index).unwrap(); - last_part.rows += extra; - for part in (&mut split_parts[last_flexible_index + 1..]).iter_mut() { - part.y += extra; - } - } - split_parts -} - -fn split_space( - space_to_split: &PositionAndSize, - layout: &Layout, -) -> Vec<(Layout, PositionAndSize)> { - let mut pane_positions = Vec::new(); - let sizes: Vec<Option<SplitSize>> = layout.parts.iter().map(|part| part.split_size).collect(); - - let split_parts = match layout.direction { - Direction::Vertical => split_space_to_parts_vertically(space_to_split, sizes), - Direction::Horizontal => split_space_to_parts_horizontally(space_to_split, sizes), - }; - for (i, part) in layout.parts.iter().enumerate() { - let part_position_and_size = split_parts.get(i).unwrap(); - if !part.parts.is_empty() { - let mut part_positions = split_space(&part_position_and_size, part); - pane_positions.append(&mut part_positions); - } else { - pane_positions.push((part.clone(), *part_position_and_size)); - } - } - pane_positions -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub enum Direction { - Horizontal, - Vertical, -} - -#[derive(Debug, Serialize, Deserialize, Clone, Copy)] -pub enum SplitSize { - Percent(u8), // 1 to 100 - Fixed(u16), // An absolute number of columns or rows -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Layout { - pub direction: Direction, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub parts: Vec<Layout>, - #[serde(skip_serializing_if = "Option::is_none")] - pub split_size: Option<SplitSize>, - #[serde(skip_serializing_if = "Option::is_none")] - pub plugin: Option<PathBuf>, -} - -impl Layout { - pub fn new(layout_path: &Path) -> Self { - let mut layout_file = File::open(&layout_path) - .or_else(|_| File::open(&layout_path.with_extension("yaml"))) - .unwrap_or_else(|_| panic!("cannot find layout {}", &layout_path.display())); - - let mut layout = String::new(); - layout_file - .read_to_string(&mut layout) - .unwrap_or_else(|_| panic!("could not read layout {}", &layout_path.display())); - let layout: Layout = serde_yaml::from_str(&layout) - .unwrap_or_else(|_| panic!("could not parse layout {}", &layout_path.display())); - layout - } - - // It wants to use Path here, but that doesn't compile. - #[allow(clippy::ptr_arg)] - pub fn from_dir(layout: &PathBuf, data_dir: &Path) -> Self { - Self::new(&data_dir.join("layouts/").join(layout)) - } - - pub fn total_terminal_panes(&self) -> usize { - let mut total_panes = 0; - total_panes += self.parts.len(); - for part in self.parts.iter() { - if part.plugin.is_none() { - total_panes += part.total_terminal_panes(); - } - } - total_panes - } - - pub fn position_panes_in_space( - &self, - space: &PositionAndSize, - ) -> Vec<(Layout, PositionAndSize)> { - split_space(space, &self) - } -} diff --git a/src/client/mod.rs b/src/client/mod.rs deleted file mode 100644 index f148a50a..00000000 --- a/src/client/mod.rs +++ /dev/null @@ -1,244 +0,0 @@ -pub mod boundaries; -pub mod layout; -pub mod pane_resizer; -pub mod panes; -pub mod tab; - -use std::env::current_exe; -use std::io::{self, Write}; -use std::path::Path; -use std::process::Command; -use std::sync::mpsc; -use std::thread; - -use crate::cli::CliArgs; -use crate::common::{ - command_is_executing::CommandIsExecuting, - errors::ContextType, - input::config::Config, - input::handler::input_loop, - input::options::Options, - ipc::{ClientToServerMsg, ServerToClientMsg}, - os_input_output::ClientOsApi, - thread_bus::{SenderType, SenderWithContext, SyncChannelWithContext}, - utils::consts::ZELLIJ_IPC_PIPE, -}; - -/// Instructions related to the client-side application and sent from server to client -#[derive(Debug, Clone)] -pub enum ClientInstruction { - Error(String), - Render(Option<String>), - UnblockInputThread, - Exit, - ServerError(String), -} - -impl From<ServerToClientMsg> for ClientInstruction { - fn from(instruction: ServerToClientMsg) -> Self { - match instruction { - ServerToClientMsg::Exit => ClientInstruction::Exit, - ServerToClientMsg::Render(buffer) => ClientInstruction::Render(buffer), - ServerToClientMsg::UnblockInputThread => ClientInstruction::UnblockInputThread, - ServerToClientMsg::ServerError(backtrace) => ClientInstruction::ServerError(backtrace), - } - } -} - -fn spawn_server(socket_path: &Path) -> io::Result<()> { - let status = Command::new(current_exe()?) - .arg("--server") - .arg(socket_path) - .status()?; - if status.success() { - Ok(()) - } else { - let msg = "Process returned non-zero exit code"; - let err_msg = match status.code() { - Some(c) => format!("{}: {}", msg, c), - None => msg.to_string(), - }; - Err(io::Error::new(io::ErrorKind::Other, err_msg)) - } -} - -pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: Config) { - let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}12l\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l"; - let take_snapshot = "\u{1b}[?1049h"; - let bracketed_paste = "\u{1b}[?2004h"; - os_input.unset_raw_mode(0); - let _ = os_input - .get_stdout_writer() - .write(take_snapshot.as_bytes()) - .unwrap(); - let _ = os_input - .get_stdout_writer() - .write(clear_client_terminal_attributes.as_bytes()) - .unwrap(); - std::env::set_var(&"ZELLIJ", "0"); - - #[cfg(not(test))] - spawn_server(&*ZELLIJ_IPC_PIPE).unwrap(); - - let mut command_is_executing = CommandIsExecuting::new(); - - let config_options = Options::from_cli(&config.options, opts.option.clone()); - - let full_screen_ws = os_input.get_terminal_size_using_fd(0); - os_input.connect_to_server(&*ZELLIJ_IPC_PIPE); - os_input.send_to_server(ClientToServerMsg::NewClient( - full_screen_ws, - Box::new(opts), - Box::new(config_options), - )); - os_input.set_raw_mode(0); - let _ = os_input - .get_stdout_writer() - .write(bracketed_paste.as_bytes()) - .unwrap(); - - let (send_client_instructions, receive_client_instructions): SyncChannelWithContext< - ClientInstruction, - > = mpsc::sync_channel(50); - let send_client_instructions = - SenderWithContext::new(SenderType::SyncSender(send_client_instructions)); - - #[cfg(not(test))] - std::panic::set_hook({ - use crate::errors::handle_panic; - let send_client_instructions = send_client_instructions.clone(); - Box::new(move |info| { - handle_panic(info, &send_client_instructions); - }) - }); - - let _stdin_thread = thread::Builder::new() - .name("stdin_handler".to_string()) - .spawn({ - let send_client_instructions = send_client_instructions.clone(); - let command_is_executing = command_is_executing.clone(); - let os_input = os_input.clone(); - move || { - input_loop( - os_input, - config, - command_is_executing, - send_client_instructions, - ) - } - }); - - let _signal_thread = thread::Builder::new() - .name("signal_listener".to_string()) - .spawn({ - let os_input = os_input.clone(); - let send_client_instructions = send_client_instructions.clone(); - move || { - os_input.handle_signals( - Box::new({ - let os_api = os_input.clone(); - move || { - os_api.send_to_server(ClientToServerMsg::TerminalResize( - os_api.get_terminal_size_using_fd(0), - )); - } - }), - Box::new({ - let send_client_instructions = send_client_instructions.clone(); - move || { - send_client_instructions - .send(ClientInstruction::Exit) - .unwrap() - } - }), - ); - } - }) - .unwrap(); - - let router_thread = thread::Builder::new() - .name("router".to_string()) - .spawn({ - let os_input = os_input.clone(); - let mut should_break = false; - move || loop { - let (instruction, err_ctx) = os_input.recv_from_server(); - err_ctx.update_thread_ctx(); - match instruction { - ServerToClientMsg::Exit | ServerToClientMsg::ServerError(_) => { - should_break = true; - } - _ => {} - } - send_client_instructions.send(instruction.into()).unwrap(); - if should_break { - break; - } - } - }) - .unwrap(); - - let handle_error = |backtrace: String| { - os_input.unset_raw_mode(0); - let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1); - let restore_snapshot = "\u{1b}[?1049l"; - let error = format!( - "{}\n{}{}", - goto_start_of_last_line, restore_snapshot, backtrace - ); - let _ = os_input - .get_stdout_writer() - .write(error.as_bytes()) - .unwrap(); - std::process::exit(1); - }; - - loop { - let (client_instruction, mut err_ctx) = receive_client_instructions - .recv() - .expect("failed to receive app instruction on channel"); - - err_ctx.add_call(ContextType::Client((&client_instruction).into())); - match client_instruction { - ClientInstruction::Exit => break, - ClientInstruction::Error(backtrace) => { - let _ = os_input.send_to_server(ClientToServerMsg::ClientExit); - handle_error(backtrace); - } - ClientInstruction::ServerError(backtrace) => { - handle_error(backtrace); - } - ClientInstruction::Render(output) => { - if output.is_none() { - break; - } - let mut stdout = os_input.get_stdout_writer(); - stdout - .write_all(&output.unwrap().as_bytes()) - .expect("cannot write to stdout"); - stdout.flush().expect("could not flush"); - } - ClientInstruction::UnblockInputThread => { - command_is_executing.unblock_input_thread(); - } - } - } - - let _ = os_input.send_to_server(ClientToServerMsg::ClientExit); - router_thread.join().unwrap(); - - // cleanup(); - let reset_style = "\u{1b}[m"; - let show_cursor = "\u{1b}[?25h"; - let restore_snapshot = "\u{1b}[?1049l"; - let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1); - let goodbye_message = format!( - "{}\n{}{}{}Bye from Zellij!\n", - goto_start_of_last_line, restore_snapshot, reset_style, show_cursor - ); - - os_input.unset_raw_mode(0); - let mut stdout = os_input.get_stdout_writer(); - let _ = stdout.write(goodbye_message.as_bytes()).unwrap(); - stdout.flush().unwrap(); -} diff --git a/src/client/pane_resizer.rs b/src/client/pane_resizer.rs deleted file mode 100644 index 73678499..00000000 --- a/src/client/pane_resizer.rs +++ /dev/null @@ -1,532 +0,0 @@ -use crate::os_input_output::ServerOsApi; -use crate::panes::{PaneId, PositionAndSize}; -use crate::tab::Pane; -use std::{ - cmp::Ordering, - collections::{BTreeMap, HashSet}, -}; - -pub struct PaneResizer<'a> { - panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>, - os_api: &'a mut Box<dyn ServerOsApi>, -} - -// TODO: currently there are some functions here duplicated with Tab -// all resizing functions should move here - -impl<'a> PaneResizer<'a> { - pub fn new( - panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>, - os_api: &'a mut Box<dyn ServerOsApi>, - ) -> Self { - PaneResizer { panes, os_api } - } - pub fn resize( - &mut self, - mut current_size: PositionAndSize, - new_size: PositionAndSize, - ) -> Option<(isize, isize)> { - // (column_difference, row_difference) - let mut successfully_resized = false; - let mut column_difference: isize = 0; - let mut row_difference: isize = 0; - match new_size.columns.cmp(¤t_size.columns) { - Ordering::Greater => { - let increase_by = new_size.columns - current_size.columns; - if let Some(panes_to_resize) = find_increasable_vertical_chain( - &self.panes, - increase_by, - current_size.columns, - current_size.rows, - ) { - self.increase_panes_right_and_push_adjacents_right( - panes_to_resize, - increase_by, - ); - column_difference = new_size.columns as isize - current_size.columns as isize; - current_size.columns = - (current_size.columns as isize + column_difference) as usize; - successfully_resized = true; - }; - } - Ordering::Less => { - let reduce_by = current_size.columns - new_size.columns; - if let Some(panes_to_resize) = find_reducible_vertical_chain( - &self.panes, - reduce_by, - current_size.columns, - current_size.rows, - ) { - self.reduce_panes_left_and_pull_adjacents_left(panes_to_resize, reduce_by); - column_difference = new_size.columns as isize - current_size.columns as isize; - current_size.columns = - (current_size.columns as isize + column_difference) as usize; - successfully_resized = true; - }; - } - Ordering::Equal => (), - } - match new_size.rows.cmp(¤t_size.rows) { - Ordering::Greater => { - let increase_by = new_size.rows - current_size.rows; - if let Some(panes_to_resize) = find_increasable_horizontal_chain( - &self.panes, - increase_by, - current_size.columns, - current_size.rows, - ) { - self.increase_panes_down_and_push_down_adjacents(panes_to_resize, increase_by); - row_difference = new_size.rows as isize - current_size.rows as isize; - current_size.rows = (current_size.rows as isize + row_difference) as usize; - successfully_resized = true; - }; - } - Ordering::Less => { - let reduce_by = current_size.rows - new_size.rows; - if let Some(panes_to_resize) = find_reducible_horizontal_chain( - &self.panes, - reduce_by, - current_size.columns, - current_size.rows, - ) { - self.reduce_panes_up_and_pull_adjacents_up(panes_to_resize, reduce_by); - row_difference = new_size.rows as isize - current_size.rows as isize; - current_size.rows = (current_size.rows as isize + row_difference) as usize; - successfully_resized = true; - }; - } - Ordering::Equal => (), - } - if successfully_resized { - Some((column_difference, row_difference)) - } else { - None - } - } - fn reduce_panes_left_and_pull_adjacents_left( - &mut self, - panes_to_reduce: Vec<PaneId>, - reduce_by: usize, - ) { - let mut pulled_panes: HashSet<PaneId> = HashSet::new(); - for pane_id in panes_to_reduce { - let (pane_x, pane_y, pane_columns, pane_rows) = { - let pane = self.panes.get(&pane_id).unwrap(); - (pane.x(), pane.y(), pane.columns(), pane.rows()) - }; - let panes_to_pull = self.panes.values_mut().filter(|p| { - p.x() > pane_x + pane_columns - && (p.y() <= pane_y && p.y() + p.rows() >= pane_y - || p.y() >= pane_y && p.y() + p.rows() <= pane_y + pane_rows) - }); - for pane in panes_to_pull { - if !pulled_panes.contains(&pane.pid()) { - pane.pull_left(reduce_by); - pulled_panes.insert(pane.pid()); - } - } - self.reduce_pane_width_left(&pane_id, reduce_by); - } - } - fn reduce_panes_up_and_pull_adjacents_up( - &mut self, - panes_to_reduce: Vec<PaneId>, - reduce_by: usize, - ) { - let mut pulled_panes: HashSet<PaneId> = HashSet::new(); - for pane_id in panes_to_reduce { - let (pane_x, pane_y, pane_columns, pane_rows) = { - let pane = self.panes.get(&pane_id).unwrap(); - (pane.x(), pane.y(), pane.columns(), pane.rows()) - }; - let panes_to_pull = self.panes.values_mut().filter(|p| { - p.y() > pane_y + pane_rows - && (p.x() <= pane_x && p.x() + p.columns() >= pane_x - || p.x() >= pane_x && p.x() + p.columns() <= pane_x + pane_columns) - }); - for pane in panes_to_pull { - if !pulled_panes.contains(&pane.pid()) { - pane.pull_up(reduce_by); - pulled_panes.insert(pane.pid()); - } - } - self.reduce_pane_height_up(&pane_id, reduce_by); - } - } - fn increase_panes_down_and_push_down_adjacents( - &mut self, - panes_to_increase: Vec<PaneId>, - increase_by: usize, - ) { - let mut pushed_panes: HashSet<PaneId> = HashSet::new(); - for pane_id in panes_to_increase { - let (pane_x, pane_y, pane_columns, pane_rows) = { - let pane = self.panes.get(&pane_id).unwrap(); - (pane.x(), pane.y(), pane.columns(), pane.rows()) - }; - let panes_to_push = self.panes.values_mut().filter(|p| { - p.y() > pane_y + pane_rows - && (p.x() <= pane_x && p.x() + p.columns() >= pane_x - || p.x() >= pane_x && p.x() + p.columns() <= pane_x + pane_columns) - }); - for pane in panes_to_push { - if !pushed_panes.contains(&pane.pid()) { - pane.push_down(increase_by); - pushed_panes.insert(pane.pid()); - } - } - self.increase_pane_height_down(&pane_id, increase_by); - } - } - fn increase_panes_right_and_push_adjacents_right( - &mut self, - panes_to_increase: Vec<PaneId>, - increase_by: usize, - ) { - let mut pushed_panes: HashSet<PaneId> = HashSet::new(); - for pane_id in panes_to_increase { - let (pane_x, pane_y, pane_columns, pane_rows) = { - let pane = self.panes.get(&pane_id).unwrap(); - (pane.x(), pane.y(), pane.columns(), pane.rows()) - }; - let panes_to_push = self.panes.values_mut().filter(|p| { - p.x() > pane_x + pane_columns - && (p.y() <= pane_y && p.y() + p.rows() >= pane_y - || p.y() >= pane_y && p.y() + p.rows() <= pane_y + pane_rows) - }); - for pane in panes_to_push { - if !pushed_panes.contains(&pane.pid()) { - pane.push_right(increase_by); - pushed_panes.insert(pane.pid()); - } - } - self.increase_pane_width_right(&pane_id, increase_by); - } - } - fn reduce_pane_height_up(&mut self, id: &PaneId, count: usize) { - let pane = self.panes.get_mut(id).unwrap(); - pane.reduce_height_up(count); - if let PaneId::Terminal(pid) = id { - self.os_api - .set_terminal_size_using_fd(*pid, pane.columns() as u16, pane.rows() as u16); - } - } - fn increase_pane_height_down(&mut self, id: &PaneId, count: usize) { - let pane = self.panes.get_mut(id).unwrap(); - pane.increase_height_down(count); - if let PaneId::Terminal(pid) = pane.pid() { - self.os_api - .set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16); - } - } - fn increase_pane_width_right(&mut self, id: &PaneId, count: usize) { - let pane = self.panes.get_mut(id).unwrap(); - pane.increase_width_right(count); - if let PaneId::Terminal(pid) = pane.pid() { - self.os_api - .set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16); - } - } - fn reduce_pane_width_left(&mut self, id: &PaneId, count: usize) { - let pane = self.panes.get_mut(id).unwrap(); - pane.reduce_width_left(count); - if let PaneId::Terminal(pid) = pane.pid() { - self.os_api - .set_terminal_size_using_fd(pid, pane.columns() as u16, pane.rows() as u16); - } - } -} - -fn find_next_increasable_horizontal_pane( - panes: &BTreeMap<PaneId, Box<dyn Pane>>, - right_of: &dyn Pane, - increase_by: usize, -) -> Option<PaneId> { - let next_pane_candidates = panes.values().filter( - |p| { - p.x() == right_of.x() + right_of.columns() + 1 && p.horizontally_overlaps_with(right_of) - }, // TODO: the name here is wrong, it should be vertically_overlaps_with - ); - let resizable_candidates = - next_pane_candidates.filter(|p| p.can_increase_height_by(increase_by)); - resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id { - Some(next_pane) => { - let next_pane = panes.get(&next_pane).unwrap(); - if next_pane.y() < p.y() { - next_pane_id - } else { - Some(p.pid()) - } - } - None => Some(p.pid()), - }) -} - -fn find_next_increasable_vertical_pane( - panes: &BTreeMap<PaneId, Box<dyn Pane>>, - below: &dyn Pane, - increase_by: usize, -) -> Option<PaneId> { - let next_pane_candidates = panes.values().filter( - |p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with - ); - let resizable_candidates = - next_pane_candidates.filter(|p| p.can_increase_width_by(increase_by)); - resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id { - Some(next_pane) => { - let next_pane = panes.get(&next_pane).unwrap(); - if next_pane.x() < p.x() { - next_pane_id - } else { - Some(p.pid()) - } - } - None => Some(p.pid()), - }) -} - -fn find_next_reducible_vertical_pane( - panes: &BTreeMap<PaneId, Box<dyn Pane>>, - below: &dyn Pane, - reduce_by: usize, -) -> Option<PaneId> { - let next_pane_candidates = panes.values().filter( - |p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with - ); - let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_width_by(reduce_by)); - resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id { - Some(next_pane) => { - let next_pane = panes.get(&next_pane).unwrap(); - if next_pane.x() < p.x() { - next_pane_id - } else { - Some(p.pid()) - } - } - None => Some(p.pid()), - }) -} - -fn find_next_reducible_horizontal_pane( - panes: &BTreeMap<PaneId, Box<dyn Pane>>, - right_of: &dyn Pane, - reduce_by: usize, -) -> Option<PaneId> { - let next_pane_candidates = panes.values().filter( - |p| { - p.x() == right_of.x() + right_of.columns() + 1 && p.horizontally_overlaps_with(right_of) - }, // TODO: the name here is wrong, it should be vertically_overlaps_with - ); - let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_height_by(reduce_by)); - resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id { - Some(next_pane) => { - let next_pane = panes.get(&next_pane).unwrap(); - if next_pane.y() < p.y() { - next_pane_id - } else { - Some(p.pid()) - } - } - None => Some(p.pid()), - }) -} - -fn find_increasable_horizontal_chain( - panes: &BTreeMap<PaneId, Box<dyn Pane>>, - increase_by: usize, - screen_width: usize, - screen_height: usize, // TODO: this is the previous size (make this clearer) -) -> Option<Vec<PaneId>> { - let mut horizontal_coordinate = 0; - loop { - if horizontal_coordinate == screen_height { - return None; - } - - match panes - .values() - .find(|p| p.x() == 0 && p.y() == horizontal_coordinate) - { - Some(leftmost_pane) => { - if !leftmost_pane.can_increase_height_by(increase_by) { - horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1; - continue; - } - let mut panes_to_resize = vec![]; - let mut current_pane = leftmost_pane; - loop { - panes_to_resize.push(current_pane.pid()); - if current_pane.x() + current_pane.columns() == screen_width { - return Some(panes_to_resize); - } - match find_next_increasable_horizontal_pane( - panes, - current_pane.as_ref(), - increase_by, - ) { - Some(next_pane_id) => { - current_pane = panes.get(&next_pane_id).unwrap(); - } - None => { - horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1; - break; - } - }; - } - } - None => { - return None; - } - } - } -} - -fn find_increasable_vertical_chain( - panes: &BTreeMap<PaneId, Box<dyn Pane>>, - increase_by: usize, - screen_width: usize, - screen_height: usize, // TODO: this is the previous size (make this clearer) -) -> Option<Vec<PaneId>> { - let mut vertical_coordinate = 0; - loop { - if vertical_coordinate == screen_width { - return None; - } - - match panes - .values() - .find(|p| p.y() == 0 && p.x() == vertical_coordinate) - { - Some(topmost_pane) => { - if !topmost_pane.can_increase_width_by(increase_by) { - vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1; - continue; - } - let mut panes_to_resize = vec![]; - let mut current_pane = topmost_pane; - loop { - panes_to_resize.push(current_pane.pid()); - if current_pane.y() + current_pane.rows() == screen_height { - return Some(panes_to_resize); - } - match find_next_increasable_vertical_pane( - panes, - current_pane.as_ref(), - increase_by, - ) { - Some(next_pane_id) => { - current_pane = panes.get(&next_pane_id).unwrap(); - } - None => { - vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1; - break; - } - }; - } - } - None => { - return None; - } - } - } -} - -fn find_reducible_horizontal_chain( - panes: &BTreeMap<PaneId, Box<dyn Pane>>, - reduce_by: usize, - screen_width: usize, - screen_height: usize, // TODO: this is the previous size (make this clearer) -) -> Option<Vec<PaneId>> { - let mut horizontal_coordinate = 0; - loop { - if horizontal_coordinate == screen_height { - return None; - } - - match panes - .values() - .find(|p| p.x() == 0 && p.y() == horizontal_coordinate) - { - Some(leftmost_pane) => { - if !leftmost_pane.can_reduce_height_by(reduce_by) { - horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1; - continue; - } - let mut panes_to_resize = vec![]; - let mut current_pane = leftmost_pane; - loop { - panes_to_resize.push(current_pane.pid()); - if current_pane.x() + current_pane.columns() == screen_width { - return Some(panes_to_resize); - } - match find_next_reducible_horizontal_pane( - panes, - current_pane.as_ref(), - reduce_by, - ) { - Some(next_pane_id) => { - current_pane = panes.get(&next_pane_id).unwrap(); - } - None => { - horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1; - break; - } - }; - } - } - None => { - return None; - } - } - } -} - -fn find_reducible_vertical_chain( - panes: &BTreeMap<PaneId, Box<dyn Pane>>, - increase_by: usize, - screen_width: usize, - screen_height: usize, // TODO: this is the previous size (make this clearer) -) -> Option<Vec<PaneId>> { - let mut vertical_coordinate = 0; - loop { - if vertical_coordinate == screen_width { - return None; - } - - match panes - .values() - .find(|p| p.y() == 0 && p.x() == vertical_coordinate) - { - Some(topmost_pane) => { - if !topmost_pane.can_reduce_width_by(increase_by) { - vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1; - continue; - } - let mut panes_to_resize = vec![]; - let mut current_pane = topmost_pane; - loop { - panes_to_resize.push(current_pane.pid()); - if current_pane.y() + current_pane.rows() == screen_height { - return Some(panes_to_resize); - } - match find_next_reducible_vertical_pane( - panes, - current_pane.as_ref(), - increase_by, - ) { - Some(next_pane_id) => { - current_pane = panes.get(&next_pane_id).unwrap(); - } - None => { - vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1; - break; - } - }; - } - } - None => { - return None; - } - } - } -} diff --git a/src/client/panes/grid.rs b/src/client/panes/grid.rs deleted file mode 100644 index e51b1ed5..00000000 --- a/src/client/panes/grid.rs +++ /dev/null @@ -1,1558 +0,0 @@ -use std::{ - cmp::Ordering, - collections::{BTreeSet, VecDeque}, - fmt::{self, Debug, Formatter}, -}; - -use vte::{Params, Perform}; - -const TABSTOP_WIDTH: usize = 8; // TODO: is this always right? -const SCROLL_BACK: usize = 10_000; - -use crate::utils::consts::VERSION; -use crate::utils::logging::debug_log_to_file; -use crate::utils::shared::version_number; - -use crate::panes::terminal_character::{ - CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset, TerminalCharacter, - EMPTY_TERMINAL_CHARACTER, -}; - -fn get_top_non_canonical_rows(rows: &mut Vec<Row>) -> Vec<Row> { - let mut index_of_last_non_canonical_row = None; - for (i, row) in rows.iter().enumerate() { - if row.is_canonical { - break; - } else { - index_of_last_non_canonical_row = Some(i); - } - } - match index_of_last_non_canonical_row { - Some(index_of_last_non_canonical_row) => { - rows.drain(..=index_of_last_non_canonical_row).collect() - } - None => vec![], - } -} - -fn get_bottom_canonical_row_and_wraps(rows: &mut VecDeque<Row>) -> Vec<Row> { - let mut index_of_last_non_canonical_row = None; - for (i, row) in rows.iter().enumerate().rev() { - index_of_last_non_canonical_row = Some(i); - if row.is_canonical { - break; - } - } - match index_of_last_non_canonical_row { - Some(index_of_last_non_canonical_row) => { - rows.drain(index_of_last_non_canonical_row..).collect() - } - None => vec![], - } -} - -fn transfer_rows_down( - source: &mut VecDeque<Row>, - destination: &mut Vec<Row>, - count: usize, - max_src_width: Option<usize>, - max_dst_width: Option<usize>, -) { - let mut next_lines: Vec<Row> = vec![]; - let mut lines_added_to_destination: isize = 0; - loop { - if lines_added_to_destination as usize == count { - break; - } - if next_lines.is_empty() { - match source.pop_back() { - Some(next_line) => { - let mut top_non_canonical_rows_in_dst = get_top_non_canonical_rows(destination); - lines_added_to_destination -= top_non_canonical_rows_in_dst.len() as isize; - next_lines.push(next_line); - next_lines.append(&mut top_non_canonical_rows_in_dst); - next_lines = match max_dst_width { - Some(max_row_width) => { - Row::from_rows(next_lines).split_to_rows_of_length(max_row_width) - } - None => vec![Row::from_rows(next_lines)], - }; - if next_lines.is_empty() { - // no more lines at source, the line we popped was probably empty - break; - } - } - None => break, // no more rows - } - } - destination.insert(0, next_lines.pop().unwrap()); - lines_added_to_destination += 1; - } - if !next_lines.is_empty() { - match max_src_width { - Some(max_row_width) => { - let excess_rows = Row::from_rows(next_lines).split_to_rows_of_length(max_row_width); - source.extend(excess_rows); - } - None => { - let excess_row = Row::from_rows(next_lines); - bounded_push(source, excess_row); - } - } - } -} - -fn transfer_rows_up( - source: &mut Vec<Row>, - destination: &mut VecDeque<Row>, - count: usize, - max_src_width: Option<usize>, - max_dst_width: Option<usize>, -) { - let mut next_lines: Vec<Row> = vec![]; - for _ in 0..count { - if next_lines.is_empty() { - if !source.is_empty() { - let next_line = source.remove(0); - if !next_line.is_canonical { - let mut bottom_canonical_row_and_wraps_in_dst = - get_bottom_canonical_row_and_wraps(destination); - next_lines.append(&mut bottom_canonical_row_and_wraps_in_dst); - } - next_lines.push(next_line); - next_lines = match max_dst_width { - Some(max_row_width) => { - Row::from_rows(next_lines).split_to_rows_of_length(max_row_width) - } - None => vec![Row::from_rows(next_lines)], - }; - } else { - break; // no more rows - } - } - bounded_push(destination, next_lines.remove(0)); - } - if !next_lines.is_empty() { - match max_src_width { - Some(max_row_width) => { - let excess_rows = Row::from_rows(next_lines).split_to_rows_of_length(max_row_width); - for row in excess_rows { - source.insert(0, row); - } - } - None => { - let excess_row = Row::from_rows(next_lines); - source.insert(0, excess_row); - } - } - } -} - -fn bounded_push(vec: &mut VecDeque<Row>, value: Row) { - if vec.len() >= SCROLL_BACK { - vec.pop_front(); - } - vec.push_back(value) -} - -pub fn create_horizontal_tabstops(columns: usize) -> BTreeSet<usize> { - let mut i = TABSTOP_WIDTH; - let mut horizontal_tabstops = BTreeSet::new(); - loop { - if i > columns { - break; - } - horizontal_tabstops.insert(i); - i += TABSTOP_WIDTH; - } - horizontal_tabstops -} - -#[derive(Clone)] -pub struct Grid { - lines_above: VecDeque<Row>, - viewport: Vec<Row>, - lines_below: Vec<Row>, - horizontal_tabstops: BTreeSet<usize>, - alternative_lines_above_viewport_and_cursor: Option<(VecDeque<Row>, Vec<Row>, Cursor)>, - cursor: Cursor, - saved_cursor_position: Option<Cursor>, - scroll_region: Option<(usize, usize)>, - active_charset: CharsetIndex, - preceding_char: Option<TerminalCharacter>, - pub should_render: bool, - pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "[D") - pub erasure_mode: bool, // ERM - pub insert_mode: bool, - pub disable_linewrap: bool, - pub clear_viewport_before_rendering: bool, - pub width: usize, - pub height: usize, - pub pending_messages_to_pty: Vec<Vec<u8>>, -} - -impl Debug for Grid { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - for (i, row) in self.viewport.iter().enumerate() { - if row.is_canonical { - writeln!(f, "{:02?} (C): {:?}", i, row)?; - } else { - writeln!(f, "{:02?} (W): {:?}", i, row)?; - } - } - Ok(()) - } -} - -impl Grid { - pub fn new(rows: usize, columns: usize) -> Self { - Grid { - lines_above: VecDeque::with_capacity(SCROLL_BACK), - viewport: vec![Row::new().canonical()], - lines_below: vec![], - horizontal_tabstops: create_horizontal_tabstops(columns), - cursor: Cursor::new(0, 0), - saved_cursor_position: None, - scroll_region: None, - preceding_char: None, - width: columns, - height: rows, - should_render: true, - cursor_key_mode: false, - erasure_mode: false, - insert_mode: false, - disable_linewrap: false, - alternative_lines_above_viewport_and_cursor: None, - clear_viewport_before_rendering: false, - active_charset: Default::default(), - pending_messages_to_pty: vec![], - } - } - pub fn contains_widechar(&self) -> bool { - self.viewport.iter().any(|c| c.contains_widechar()) - } - pub fn advance_to_next_tabstop(&mut self, styles: CharacterStyles) { - let mut next_tabstop = None; - for tabstop in self.horizontal_tabstops.iter() { - if *tabstop > self.cursor.x { - next_tabstop = Some(tabstop); - break; - } - } - match next_tabstop { - Some(tabstop) => { - self.cursor.x = *tabstop; - } - None => { - self.cursor.x = self.width.saturating_sub(1); - } - } - let mut empty_character = EMPTY_TERMINAL_CHARACTER; - empty_character.styles = styles; - self.pad_current_line_until(self.cursor.x); - } - pub fn move_to_previous_tabstop(&mut self) { - let mut previous_tabstop = None; - for tabstop in self.horizontal_tabstops.iter() { - if *tabstop >= self.cursor.x { - break; - } - previous_tabstop = Some(tabstop); - } - match previous_tabstop { - Some(tabstop) => { - self.cursor.x = *tabstop; - } - None => { - self.cursor.x = 0; - } - } - } - pub fn cursor_shape(&self) -> CursorShape { - self.cursor.get_shape() - } - fn set_horizontal_tabstop(&mut self) { - self.horizontal_tabstops.insert(self.cursor.x); - } - fn clear_tabstop(&mut self, position: usize) { - self.horizontal_tabstops.remove(&position); - } - fn clear_all_tabstops(&mut self) { - self.horizontal_tabstops.clear(); - } - fn save_cursor_position(&mut self) { - self.saved_cursor_position = Some(self.cursor.clone()); - } - fn restore_cursor_position(&mut self) { - if let Some(saved_cursor_position) = self.saved_cursor_position.as_ref() { - self.cursor = saved_cursor_position.clone(); - } - } - fn configure_charset(&mut self, charset: StandardCharset, index: CharsetIndex) { - self.cursor.charsets[index] = charset; - } - fn set_active_charset(&mut self, index: CharsetIndex) { - self.active_charset = index; - } - fn cursor_canonical_line_index(&self) -> usize { - let mut cursor_canonical_line_index = 0; - let mut canonical_lines_traversed = 0; - for (i, line) in self.viewport.iter().enumerate() { - if line.is_canonical { - cursor_canonical_line_index = canonical_lines_traversed; - canonical_lines_traversed += 1; - } - if i == self.cursor.y { - break; - } - } - cursor_canonical_line_index - } - // TODO: merge these two funtions - fn cursor_index_in_canonical_line(&self) -> usize { - let mut cursor_canonical_line_index = 0; - let mut cursor_index_in_canonical_line = 0; - for (i, line) in self.viewport.iter().enumerate() { - if line.is_canonical { - cursor_canonical_line_index = i; - } - if i == self.cursor.y { - let line_wrap_position_in_line = self.cursor.y - cursor_canonical_line_index; - cursor_index_in_canonical_line = line_wrap_position_in_line + self.cursor.x; - break; - } - } - cursor_index_in_canonical_line - } - fn canonical_line_y_coordinates(&self, canonical_line_index: usize) -> usize { - let mut canonical_lines_traversed = 0; - let mut y_coordinates = 0; - for (i, line) in self.viewport.iter().enumerate() { - if line.is_canonical { - canonical_lines_traversed += 1; - if canonical_lines_traversed == canonical_line_index + 1 { - y_coordinates = i; - break; - } - } - } - y_coordinates - } - pub fn scroll_up_one_line(&mut self) { - if !self.lines_above.is_empty() && self.viewport.len() == self.height { - let line_to_push_down = self.viewport.pop().unwrap(); - self.lines_below.insert(0, line_to_push_down); - let line_to_insert_at_viewport_top = self.lines_above.pop_back().unwrap(); - self.viewport.insert(0, line_to_insert_at_viewport_top); - } - } - pub fn scroll_down_one_line(&mut self) { - if !self.lines_below.is_empty() && self.viewport.len() == self.height { - let mut line_to_push_up = self.viewport.remove(0); - if line_to_push_up.is_canonical { - bounded_push(&mut self.lines_above, line_to_push_up); - } else { - let mut last_line_above = self.lines_above.pop_back().unwrap(); - last_line_above.append(&mut line_to_push_up.columns); - bounded_push(&mut self.lines_above, last_line_above); - } - let line_to_insert_at_viewport_bottom = self.lines_below.remove(0); - self.viewport.push(line_to_insert_at_viewport_bottom); - } - } - pub fn change_size(&mut self, new_rows: usize, new_columns: usize) { - if new_columns != self.width { - let mut cursor_canonical_line_index = self.cursor_canonical_line_index(); - let cursor_index_in_canonical_line = self.cursor_index_in_canonical_line(); - let mut viewport_canonical_lines = vec![]; - for mut row in self.viewport.drain(..) { - if !row.is_canonical - && viewport_canonical_lines.is_empty() - && !self.lines_above.is_empty() - { - let mut first_line_above = self.lines_above.pop_back().unwrap(); - first_line_above.append(&mut row.columns); - viewport_canonical_lines.push(first_line_above); - cursor_canonical_line_index += 1; - } else if row.is_canonical { - viewport_canonical_lines.push(row); - } else { - match viewport_canonical_lines.last_mut() { - Some(last_line) => { - last_line.append(&mut row.columns); - } - None => { - // the state is corrupted somehow - // this is a bug and I'm not yet sure why it happens - // usually it fixes itself and is a result of some race - // TODO: investigate why this happens and solve it - return; - } - } - } - } - let mut new_viewport_rows = vec![]; - for mut canonical_line in viewport_canonical_lines { - let mut canonical_line_parts: Vec<Row> = vec![]; - if canonical_line.columns.is_empty() { - canonical_line_parts.push(Row::new().canonical()); - } - while !canonical_line.columns.is_empty() { - let next_wrap = if canonical_line.len() > new_columns { - canonical_line.columns.drain(..new_columns) - } else { - canonical_line.columns.drain(..) - }; - let row = Row::from_columns(next_wrap.collect()); - // if there are no more parts, this row is canonical as long as it originally - // was canonical (it might not have been for example if it's the first row in - // the viewport, and the actual canonical row is above it in the scrollback) - let row = if canonical_line_parts.is_empty() && canonical_line.is_canonical { - row.canonical() - } else { - row - }; - canonical_line_parts.push(row); - } - new_viewport_rows.append(&mut canonical_line_parts); - } - self.viewport = new_viewport_rows; - - let mut new_cursor_y = self.canonical_line_y_coordinates(cursor_canonical_line_index); - let new_cursor_x = (cursor_index_in_canonical_line / new_columns) - + (cursor_index_in_canonical_line % new_columns); - let current_viewport_row_count = self.viewport.len(); - match current_viewport_row_count.cmp(&self.height) { - Ordering::Less => { - let row_count_to_transfer = self.height - current_viewport_row_count; - transfer_rows_down( - &mut self.lines_above, - &mut self.viewport, - row_count_to_transfer, - None, - Some(new_columns), - ); - let rows_pulled = self.viewport.len() - current_viewport_row_count; - new_cursor_y += rows_pulled; - } - Ordering::Greater => { - let row_count_to_transfer = current_viewport_row_count - self.height; - if row_count_to_transfer > new_cursor_y { - new_cursor_y = 0; - } else { - new_cursor_y -= row_count_to_transfer; - } - transfer_rows_up( - &mut self.viewport, - &mut self.lines_above, - row_count_to_transfer, - Some(new_columns), - None, - ); - } - Ordering::Equal => {} - } - self.cursor.y = new_cursor_y; - self.cursor.x = new_cursor_x; - } - if new_rows != self.height { - let current_viewport_row_count = self.viewport.len(); - match current_viewport_row_count.cmp(&new_rows) { - Ordering::Less => { - let row_count_to_transfer = new_rows - current_viewport_row_count; - transfer_rows_down( - &mut self.lines_above, - &mut self.viewport, - row_count_to_transfer, - None, - Some(new_columns), - ); - let rows_pulled = self.viewport.len() - current_viewport_row_count; - self.cursor.y += rows_pulled; - } - Ordering::Greater => { - let row_count_to_transfer = current_viewport_row_count - new_rows; - if row_count_to_transfer > self.cursor.y { - self.cursor.y = 0; - } else { - self.cursor.y -= row_count_to_transfer; - } - transfer_rows_up( - &mut self.viewport, - &mut self.lines_above, - row_count_to_transfer, - Some(new_columns), - None, - ); - } - Ordering::Equal => {} - } - } - self.height = new_rows; - self.width = new_columns; - if self.scroll_region.is_some() { - self.set_scroll_region_to_viewport_size(); - } - } - pub fn as_character_lines(&self) -> Vec<Vec<TerminalCharacter>> { - let mut lines: Vec<Vec<TerminalCharacter>> = self - .viewport - .iter() - .map(|r| { - let mut line: Vec<TerminalCharacter> = r.columns.iter().copied().collect(); - // pad line - line.resize(self.width, EMPTY_TERMINAL_CHARACTER); - line - }) - .collect(); - let empty_row = vec![EMPTY_TERMINAL_CHARACTER; self.width]; - for _ in lines.len()..self.height { - lines.push(empty_row.clone()); - } - lines - } - pub fn cursor_coordinates(&self) -> Option<(usize, usize)> { - if self.cursor.is_hidden { - None - } else { - Some((self.cursor.x, self.cursor.y)) - } - } - pub fn move_viewport_up(&mut self, count: usize) { - for _ in 0..count { - self.scroll_up_one_line(); - } - } - pub fn move_viewport_down(&mut self, count: usize) { - for _ in 0..count { - self.scroll_down_one_line(); - } - } - pub fn reset_viewport(&mut self) { - let row_count_below = self.lines_below.len(); - for _ in 0..row_count_below { - self.scroll_down_one_line(); - } - } - pub fn rotate_scroll_region_up(&mut self, count: usize) { - if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region { - for _ in 0..count { - let columns = vec![EMPTY_TERMINAL_CHARACTER; self.width]; - if scroll_region_bottom < self.viewport.len() { - self.viewport.remove(scroll_region_bottom); - } - if scroll_region_top < self.viewport.len() { - self.viewport - .insert(scroll_region_top, Row::from_columns(columns).canonical()); - } - } - } - } - pub fn rotate_scroll_region_down(&mut self, count: usize) { - if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region { - for _ in 0..count { - let columns = vec![EMPTY_TERMINAL_CHARACTER; self.width]; - self.viewport.remove(scroll_region_top); - if self.viewport.len() > scroll_region_top { - self.viewport - .insert(scroll_region_bottom, Row::from_columns(columns).canonical()); - } else { - self.viewport.push(Row::from_columns(columns).canonical()); - } - } - } - } - pub fn fill_viewport(&mut self, character: TerminalCharacter) { - self.viewport.clear(); - for _ in 0..self.height { - let columns = vec![character; self.width]; - self.viewport.push(Row::from_columns(columns).canonical()); - } - } - pub fn add_canonical_line(&mut self) { - if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region { - if self.cursor.y == scroll_region_bottom { - // end of scroll region - // when we have a scroll region set and we're at its bottom - // we need to delete its first line, thus shifting all lines in it upwards - // then we add an empty line at its end which will be filled by the application - // controlling the scroll region (presumably filled by whatever comes next in the - // scroll buffer, but that's not something we control) - if scroll_region_top >= self.viewport.len() { - // the state is corrupted - return; - } - self.viewport.remove(scroll_region_top); - let columns = vec![EMPTY_TERMINAL_CHARACTER; self.width]; - if self.viewport.len() >= scroll_region_bottom { - self.viewport - .insert(scroll_region_bottom, Row::from_columns(columns).canonical()); - } else { - self.viewport.push(Row::from_columns(columns).canonical()); - } - return; - } - } - if self.viewport.len() <= self.cursor.y + 1 { - // FIXME: this should add an empty line with the pad_character - // but for some reason this breaks rendering in various situations - // it needs to be investigated and fixed - let new_row = Row::new().canonical(); - self.viewport.push(new_row); - } - if self.cursor.y == self.height - 1 { - let row_count_to_transfer = 1; - transfer_rows_up( - &mut self.viewport, - &mut self.lines_above, - row_count_to_transfer, - Some(self.width), - None, - ); - } else { - self.cursor.y += 1; - } - } - pub fn move_cursor_to_beginning_of_line(&mut self) { - self.cursor.x = 0; - } - pub fn insert_character_at_cursor_position(&mut self, terminal_character: TerminalCharacter) { - match self.viewport.get_mut(self.cursor.y) { - Some(row) => { - row.insert_character_at(terminal_character, self.cursor.x); - if row.len() > self.width { - row.truncate(self.width); - } - } - None => { - // pad lines until cursor if they do not exist - for _ in self.viewport.len()..self.cursor.y { - self.viewport.push(Row::new().canonical()); - } - self.viewport - .push(Row::new().with_character(terminal_character).canonical()); - } - } - } - pub fn add_character_at_cursor_position(&mut self, terminal_character: TerminalCharacter) { - match self.viewport.get_mut(self.cursor.y) { - Some(row) => { - if self.insert_mode { - row.insert_character_at(terminal_character, self.cursor.x); - } else { - row.add_character_at(terminal_character, self.cursor.x); - } - } - None => { - // pad lines until cursor if they do not exist - for _ in self.viewport.len()..self.cursor.y { - self.viewport.push(Row::new().canonical()); - } - self.viewport - .push(Row::new().with_character(terminal_character).canonical()); - } - } - } - pub fn add_character(&mut self, terminal_character: TerminalCharacter) { - // TODO: try to separate adding characters from moving the cursors in this function - if self.cursor.x >= self.width { - if self.disable_linewrap { - return; - } - // line wrap - self.cursor.x = 0; - if self.cursor.y == self.height - 1 { - let row_count_to_transfer = 1; - transfer_rows_up( - &mut self.viewport, - &mut self.lines_above, - row_count_to_transfer, - Some(self.width), - None, - ); - let wrapped_row = Row::new(); - self.viewport.push(wrapped_row); - } else { - self.cursor.y += 1; - if self.viewport.len() <= self.cursor.y { - let line_wrapped_row = Row::new(); - self.viewport.push(line_wrapped_row); - } - } - } - self.add_character_at_cursor_position(terminal_character); - self.move_cursor_forward_until_edge(1); - } - pub fn move_cursor_forward_until_edge(&mut self, count: usize) { - let count_to_move = std::cmp::min(count, self.width - (self.cursor.x)); - self.cursor.x += count_to_move; - } - pub fn replace_characters_in_line_after_cursor(&mut self, replace_with: TerminalCharacter) { - self.viewport - .get_mut(self.cursor.y) - .unwrap() - .truncate(self.cursor.x); - if self.cursor.x < self.width - 1 { - let mut characters_to_append = vec![replace_with; self.width - self.cursor.x]; - self.viewport - .get_mut(self.cursor.y) - .unwrap() - .append(&mut characters_to_append); - } - } - pub fn replace_characters_in_line_before_cursor(&mut self, replace_with: TerminalCharacter) { - let line_part = vec![replace_with; self.cursor.x + 1]; - let row = self.viewport.get_mut(self.cursor.y).unwrap(); - row.replace_beginning_with(line_part); - } - pub fn clear_all_after_cursor(&mut self, replace_with: TerminalCharacter) { - if let Some(cursor_row) = self.viewport.get_mut(self.cursor.y) { - cursor_row.truncate(self.cursor.x); - let replace_with_columns = vec![replace_with; self.width]; - self.replace_characters_in_line_after_cursor(replace_with); - for row in self.viewport.iter_mut().skip(self.cursor.y + 1) { - row.replace_columns(replace_with_columns.clone()); - } - } - } - pub fn clear_all_before_cursor(&mut self, replace_with: TerminalCharacter) { - if self.viewport.get(self.cursor.y).is_some() { - self.replace_characters_in_line_before_cursor(replace_with); - let replace_with_columns = vec![replace_with; self.width]; - for row in self.viewport.iter_mut().take(self.cursor.y) { - row.replace_columns(replace_with_columns.clone()); - } - } - } - pub fn clear_cursor_line(&mut self) { - self.viewport.get_mut(self.cursor.y).unwrap().truncate(0); - } - pub fn clear_all(&mut self, replace_with: TerminalCharacter) { - let replace_with_columns = vec![replace_with; self.width]; - self.replace_characters_in_line_after_cursor(replace_with); - for row in self.viewport.iter_mut() { - row.replace_columns(replace_with_columns.clone()); - } - } - fn pad_current_line_until(&mut self, position: usize) { - let current_row = self.viewport.get_mut(self.cursor.y).unwrap(); - for _ in current_row.len()..position { - current_row.push(EMPTY_TERMINAL_CHARACTER); - } - } - fn pad_lines_until(&mut self, position: usize, pad_character: TerminalCharacter) { - for _ in self.viewport.len()..=position { - let columns = vec![pad_character; self.width]; - self.viewport.push(Row::from_columns(columns).canonical()); - } - } - pub fn move_cursor_to(&mut self, x: usize, y: usize, pad_character: TerminalCharacter) { - match self.scroll_region { - Some((scroll_region_top, scroll_region_bottom)) => { - self.cursor.x = std::cmp::min(self.width - 1, x); - let y_offset = if self.erasure_mode { - scroll_region_top - } else { - 0 - }; - self.cursor.y = std::cmp::min(scroll_region_bottom, y + y_offset); - self.pad_lines_until(self.cursor.y, pad_character); - self.pad_current_line_until(self.cursor.x); - } - None => { - self.cursor.x = std::cmp::min(self.width - 1, x); - self.cursor.y = std::cmp::min(self.height - 1, y); - self.pad_lines_until(self.cursor.y, pad_character); - self.pad_current_line_until(self.cursor.x); - } - } - } - pub fn move_cursor_up(&mut self, count: usize) { - if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region { - if self.cursor.y >= scroll_region_top && self.cursor.y <= scroll_region_bottom { - self.cursor.y = - std::cmp::max(self.cursor.y.saturating_sub(count), scroll_region_top); - return; - } - } - self.cursor.y = if self.cursor.y < count { - 0 - } else { - self.cursor.y - count - }; - } - pub fn move_cursor_up_with_scrolling(&mut self, count: usize) { - let (scroll_region_top, scroll_region_bottom) = - self.scroll_region.unwrap_or((0, self.height - 1)); - for _ in 0..count { - let current_line_index = self.cursor.y; - if current_line_index == scroll_region_top { - // if we're at the top line, we create a new line and remove the last line that - // would otherwise overflow - if scroll_region_bottom < self.viewport.len() { - self.viewport.remove(scroll_region_bottom); - } - self.viewport.insert(current_line_index, Row::new()); // TODO: .canonical() ? - } else if current_line_index > scroll_region_top - && current_line_index <= scroll_region_bottom - { - self.move_cursor_up(count); - } - } - } - pub fn move_cursor_down(&mut self, count: usize, pad_character: TerminalCharacter) { - if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region { - if self.cursor.y >= scroll_region_top && self.cursor.y <= scroll_region_bottom { - self.cursor.y = std::cmp::min(self.cursor.y + count, scroll_region_bottom); - return; - } - } - let lines_to_add = if self.cursor.y + count > self.height - 1 { - (self.cursor.y + count) - (self.height - 1) - } else { - 0 - }; - self.cursor.y = if self.cursor.y + count > self.height - 1 { - self.height - 1 - } else { - self.cursor.y + count - }; - for _ in 0..lines_to_add { - self.add_canonical_line(); - } - self.pad_lines_until(self.cursor.y, pad_character); - } - pub fn move_cursor_back(&mut self, count: usize) { - if self.cursor.x == self.width { - // on the rightmost screen edge, backspace skips one character - self.cursor.x -= 1; - } - if self.cursor.x < count { - self.cursor.x = 0; - } else { - self.cursor.x -= count; - } - } - pub fn hide_cursor(&mut self) { - self.cursor.is_hidden = true; - } - pub fn show_cursor(&mut self) { - self.cursor.is_hidden = false; - } - pub fn set_scroll_region(&mut self, top_line_index: usize, bottom_line_index: Option<usize>) { - let bottom_line_index = bottom_line_index.unwrap_or(self.height); - self.scroll_region = Some((top_line_index, bottom_line_index)); - } - pub fn clear_scroll_region(&mut self) { - self.scroll_region = None; - } - pub fn set_scroll_region_to_viewport_size(&mut self) { - self.scroll_region = Some((0, self.height - 1)); - } - pub fn delete_lines_in_scroll_region( - &mut self, - count: usize, - pad_character: TerminalCharacter, - ) { - if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region { - let current_line_index = self.cursor.y; - if current_line_index >= scroll_region_top && current_line_index <= scroll_region_bottom - { - // when deleting lines inside the scroll region, we must make sure it stays the - // same size (and that other lines below it aren't shifted inside it) - // so we delete the current line(s) and add an empty line at the end of the scroll - // region - for _ in 0..count { - self.viewport.remove(current_line_index); - let columns = vec![pad_character; self.width]; - if self.viewport.len() > scroll_region_bottom { - self.viewport - .insert(scroll_region_bottom, Row::from_columns(columns).canonical()); - } else { - self.viewport.push(Row::from_columns(columns).canonical()); - } - } - } - } - } - pub fn add_empty_lines_in_scroll_region( - &mut self, - count: usize, - pad_character: TerminalCharacter, - ) { - if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region { - let current_line_index = self.cursor.y; - if current_line_index >= scroll_region_top && current_line_index <= scroll_region_bottom - { - // when adding empty lines inside the scroll region, we must make sure it stays the - // same size and that lines don't "leak" outside of it - // so we add an empty line where the cursor currently is, and delete the last line - // of the scroll region - for _ in 0..count { - if scroll_region_bottom < self.viewport.len() { - self.viewport.remove(scroll_region_bottom); - } - let columns = vec![pad_character; self.width]; - self.viewport - .insert(current_line_index, Row::from_columns(columns).canonical()); - } - } - } - } - pub fn move_cursor_to_column(&mut self, column: usize) { - self.cursor.x = column; - self.pad_current_line_until(self.cursor.x); - } - pub fn move_cursor_to_line(&mut self, line: usize, pad_character: TerminalCharacter) { - self.cursor.y = std::cmp::min(self.height - 1, line); - self.pad_lines_until(self.cursor.y, pad_character); - self.pad_current_line_until(self.cursor.x); - } - pub fn replace_with_empty_chars(&mut self, count: usize, empty_char_style: CharacterStyles) { - let mut empty_character = EMPTY_TERMINAL_CHARACTER; - empty_character.styles = empty_char_style; - let pad_until = std::cmp::min(self.width, self.cursor.x + count); - self.pad_current_line_until(pad_until); - let current_row = self.viewport.get_mut(self.cursor.y).unwrap(); - for i in 0..count { - current_row.replace_character_at(empty_character, self.cursor.x + i); - } - } - pub fn erase_characters(&mut self, count: usize, empty_char_style: CharacterStyles) { - let mut empty_character = EMPTY_TERMINAL_CHARACTER; - empty_character.styles = empty_char_style; - let current_row = self.viewport.get_mut(self.cursor.y).unwrap(); - for _ in 0..count { - current_row.delete_character(self.cursor.x); - } - let mut empty_space_to_append = vec![empty_character; count]; - self.viewport - .get_mut(self.cursor.y) - .unwrap() - .append(&mut empty_space_to_append); - } - fn add_newline(&mut self) { - self.add_canonical_line(); - self.mark_for_rerender(); - } - pub fn mark_for_rerender(&mut self) { - self.should_render = true; - } - fn reset_terminal_state(&mut self) { - self.lines_above = VecDeque::with_capacity(SCROLL_BACK); - self.lines_below = vec![]; - self.viewport = vec![Row::new().canonical()]; - self.alternative_lines_above_viewport_and_cursor = None; - self.cursor_key_mode = false; - self.scroll_region = None; - self.clear_viewport_before_rendering = true; - self.cursor = Cursor::new(0, 0); - self.saved_cursor_position = None; - self.active_charset = Default::default(); - self.erasure_mode = false; - self.disable_linewrap = false; - self.cursor.change_shape(CursorShape::Block); - } - fn set_preceding_character(&mut self, terminal_character: TerminalCharacter) { - self.preceding_char = Some(terminal_character); - } -} - -impl Perform for Grid { - fn print(&mut self, c: char) { - let c = self.cursor.charsets[self.active_charset].map(c); - // apparently, building TerminalCharacter like this without a "new" method - // is a little faster - let terminal_character = TerminalCharacter { - character: c, - styles: self.cursor.pending_styles, - }; - self.set_preceding_character(terminal_character); - self.add_character(terminal_character); - } - - fn execute(&mut self, byte: u8) { - match byte { - 8 => { - // backspace - self.move_cursor_back(1); - } - 9 => { - // tab - self.advance_to_next_tabstop(self.cursor.pending_styles); - } - 10 | 11 | 12 => { - // 0a, newline - // 0b, vertical tabulation - // 0c, form feed - self.add_newline(); - } - 13 => { - // 0d, carriage return - self.move_cursor_to_beginning_of_line(); - } - 14 => { - self.set_active_charset(CharsetIndex::G1); - } - 15 => { - self.set_active_charset(CharsetIndex::G0); - } - _ => {} - } - } - - fn hook(&mut self, _params: &Params, _intermediates: &[u8], _ignore: bool, _c: char) { - // TBD - } - - fn put(&mut self, _byte: u8) { - // TBD - } - - fn unhook(&mut self) { - // TBD - } - - fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) { - // TBD - } - - fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], _ignore: bool, c: char) { - let mut params_iter = params.iter(); - let mut next_param_or = |default: u16| { - params_iter - .next() - .map(|param| param[0]) - .filter(|¶m| param != 0) - .unwrap_or(default) as usize - }; - if c == 'm' { - self.cursor - .pending_styles - .add_style_from_ansi_params(&mut params_iter); - } else if c == 'C' || c == 'a' { - // move cursor forward - let move_by = next_param_or(1); - self.move_cursor_forward_until_edge(move_by); - } else if c == 'K' { - // clear line (0 => right, 1 => left, 2 => all) - if let Some(clear_type) = params_iter.next().map(|param| param[0]) { - if clear_type == 0 { - let mut char_to_replace = EMPTY_TERMINAL_CHARACTER; - char_to_replace.styles = self.cursor.pending_styles; - self.replace_characters_in_line_after_cursor(char_to_replace); - } else if clear_type == 1 { - let mut char_to_replace = EMPTY_TERMINAL_CHARACTER; - char_to_replace.styles = self.cursor.pending_styles; - self.replace_characters_in_line_before_cursor(char_to_replace); - } else if clear_type == 2 { - self.clear_cursor_line(); - } - }; - } else if c == 'J' { - // clear all (0 => below, 1 => above, 2 => all, 3 => saved) - let mut char_to_replace = EMPTY_TERMINAL_CHARACTER; - char_to_replace.styles = self.cursor.pending_styles; - - if let Some(clear_type) = params_iter.next().map(|param| param[0]) { - if clear_type == 0 { - self.clear_all_after_cursor(char_to_replace); - } else if clear_type == 1 { - self.clear_all_before_cursor(char_to_replace); - } else if clear_type == 2 { - self.fill_viewport(char_to_replace); - } - }; - } else if c == 'H' || c == 'f' { - // goto row/col - // we subtract 1 from the row/column because these are 1 indexed - let row = next_param_or(1).saturating_sub(1); - let col = next_param_or(1).saturating_sub(1); - let pad_character = EMPTY_TERMINAL_CHARACTER; - self.move_cursor_to(col, row, pad_character); - } else if c == 'A' { - // move cursor up until edge of screen - let move_up_count = next_param_or(1); - self.move_cursor_up(move_up_count as usize); - } else if c == 'B' || c == 'e' { - // move cursor down until edge of screen - let move_down_count = next_param_or(1); - let pad_character = EMPTY_TERMINAL_CHARACTER; - self.move_cursor_down(move_down_count as usize, pad_character); - } else if c == 'D' { - let move_back_count = next_param_or(1); - self.move_cursor_back(move_back_count); - } else if c == 'l' { - let first_intermediate_is_questionmark = match intermediates.get(0) { - Some(b'?') => true, - None => false, - _ => false, - }; - if first_intermediate_is_questionmark { - match params_iter.next().map(|param| param[0]) { - Some(1049) => { - if let Some(( - alternative_lines_above, - alternative_viewport, - alternative_cursor, - )) = self.alternative_lines_above_viewport_and_cursor.as_mut() - { - std::mem::swap(&mut self.lines_above, alternative_lines_above); - std::mem::swap(&mut self.viewport, alternative_viewport); - std::mem::swap(&mut self.cursor, alternative_cursor); - } - self.alternative_lines_above_viewport_and_cursor = None; - self.clear_viewport_before_rendering = true; - self.change_size(self.height, self.width); // the alternative_viewport might have been of a different size... - self.mark_for_rerender(); - } - Some(25) => { - self.hide_cursor(); - self.mark_for_rerender(); - } - Some(1) => { - self.cursor_key_mode = false; - } - Some(3) => { - // DECCOLM - only side effects - self.scroll_region = None; - self.clear_all(EMPTY_TERMINAL_CHARACTER); - self.cursor.x = 0; - self.cursor.y = 0; - } - Some(6) => { - self.erasure_mode = false; - } - Some(7) => { - self.disable_linewrap = true; - } - _ => {} - }; - } else if let Some(4) = params_iter.next().map(|param| param[0]) { - self.insert_mode = false; - } - } else if c == 'h' { - let first_intermediate_is_questionmark = match intermediates.get(0) { - Some(b'?') => true, - None => false, - _ => false, - }; - if first_intermediate_is_questionmark { - match params_iter.next().map(|param| param[0]) { - Some(25) => { - self.show_cursor(); - self.mark_for_rerender(); - } - Some(1049) => { - let current_lines_above = std::mem::replace( - &mut self.lines_above, - VecDeque::with_capacity(SCROLL_BACK), - ); - let current_viewport = - std::mem::replace(&mut self.viewport, vec![Row::new().canonical()]); - let current_cursor = std::mem::replace(&mut self.cursor, Cursor::new(0, 0)); - self.alternative_lines_above_viewport_and_cursor = - Some((current_lines_above, current_viewport, current_cursor)); - self.clear_viewport_before_rendering = true; - } - Some(1) => { - self.cursor_key_mode = true; - } - Some(3) => { - // DECCOLM - only side effects - self.scroll_region = None; - self.clear_all(EMPTY_TERMINAL_CHARACTER); - self.cursor.x = 0; - self.cursor.y = 0; - } - Some(6) => { - self.erasure_mode = true; - } - Some(7) => { - self.disable_linewrap = false; - } - _ => {} - }; - } else if let Some(4) = params_iter.next().map(|param| param[0]) { - self.insert_mode = true; - } - } else if c == 'r' { - if params.len() > 1 { - let top = (next_param_or(1) as usize).saturating_sub(1); - let bottom = params_iter - .next() - .map(|param| param[0] as usize) - .filter(|¶m| param != 0) - .map(|bottom| bottom.saturating_sub(1)); - self.set_scroll_region(top, bottom); - if self.erasure_mode { - self.move_cursor_to_line(top, EMPTY_TERMINAL_CHARACTER); - self.move_cursor_to_beginning_of_line(); - } - } else { - self.clear_scroll_region(); - } - } else if c == 'M' { - // delete lines if currently inside scroll region - let line_count_to_delete = next_param_or(1); - let pad_character = EMPTY_TERMINAL_CHARACTER; - self.delete_lines_in_scroll_region(line_count_to_delete, pad_character); - } else if c == 'L' { - // insert blank lines if inside scroll region - let line_count_to_add = next_param_or(1); - let pad_character = EMPTY_TERMINAL_CHARACTER; - self.add_empty_lines_in_scroll_region(line_count_to_add, pad_character); - } else if c == 'G' || c == '`' { - let column = next_param_or(1).saturating_sub(1); - self.move_cursor_to_column(column); - } else if c == 'g' { - let clear_type = next_param_or(0); - if clear_type == 0 { - self.clear_tabstop(self.cursor.x); - } else if clear_type == 3 { - self.clear_all_tabstops(); - } - } else if c == 'd' { - // goto line - let line = next_param_or(1).saturating_sub(1); - let pad_character = EMPTY_TERMINAL_CHARACTER; - self.move_cursor_to_line(line, pad_character); - } else if c == 'P' { - // erase characters - let count = next_param_or(1); - self.erase_characters(count, self.cursor.pending_styles); - } else if c == 'X' { - // erase characters and replace with empty characters of current style - let count = next_param_or(1); - self.replace_with_empty_chars(count, self.cursor.pending_styles); - } else if c == 'T' { - /* - * 124 54 T SD - * Scroll down, new lines inserted at top of screen - * [4T = Scroll down 4, bring previous lines back into view - */ - let line_count = next_param_or(1); - self.rotate_scroll_region_up(line_count as usize); - } else if c == 'S' { - // move scroll up - let count = next_param_or(1); - self.rotate_scroll_region_down(count); - } else if c == 's' { - self.save_cursor_position(); - } else if c == 'u' { - self.restore_cursor_position(); - } else if c == '@' { - let count = next_param_or(1); - for _ in 0..count { - // TODO: should this be styled? - self.insert_character_at_cursor_position(EMPTY_TERMINAL_CHARACTER); - } - } else if c == 'b' { - if let Some(c) = self.preceding_char { - for _ in 0..next_param_or(1) { - self.add_character(c); - } - } - } else if c == 'E' { - let count = next_param_or(1); - let pad_character = EMPTY_TERMINAL_CHARACTER; - self.move_cursor_down(count, pad_character); - } else if c == 'F' { - let count = next_param_or(1); - self.move_cursor_up(count); - self.move_cursor_to_beginning_of_line(); - } else if c == 'I' { - for _ in 0..next_param_or(1) { - self.advance_to_next_tabstop(self.cursor.pending_styles); - } - } else if c == 'q' { - let first_intermediate_is_space = matches!(intermediates.get(0), Some(b' ')); - if first_intermediate_is_space { - // DECSCUSR (CSI Ps SP q) -- Set Cursor Style. - let cursor_style_id = next_param_or(0); - let shape = match cursor_style_id { - 0 | 2 => Some(CursorShape::Block), - 1 => Some(CursorShape::BlinkingBlock), - 3 => Some(CursorShape::BlinkingUnderline), - 4 => Some(CursorShape::Underline), - 5 => Some(CursorShape::BlinkingBeam), - 6 => Some(CursorShape::Beam), - _ => None, - }; - if let Some(cursor_shape) = shape { - self.cursor.change_shape(cursor_shape); - } - } - } else if c == 'Z' { - for _ in 0..next_param_or(1) { - self.move_to_previous_tabstop(); - } - } else if c == 'c' { - // identify terminal - // https://vt100.net/docs/vt510-rm/DA1.html - match intermediates.get(0) { - None | Some(0) => { - // primary device attributes - let terminal_capabilities = "\u{1b}[?6c"; - self.pending_messages_to_pty - .push(terminal_capabilities.as_bytes().to_vec()); - } - Some(b'>') => { - // secondary device attributes - let version = version_number(VERSION); - let text = format!("\u{1b}[>0;{};1c", version); - self.pending_messages_to_pty.push(text.as_bytes().to_vec()); - } - _ => {} - } - } else if c == 'n' { - // DSR - device status report - // https://vt100.net/docs/vt510-rm/DSR.html - match next_param_or(0) { - 5 => { - // report terminal status - let all_good = "\u{1b}[0n"; - self.pending_messages_to_pty - .push(all_good.as_bytes().to_vec()); - } - 6 => { - // CPR - cursor position report - let position_report = - format!("\x1b[{};{}R", self.cursor.y + 1, self.cursor.x + 1); - self.pending_messages_to_pty - .push(position_report.as_bytes().to_vec()); - } - _ => {} - } - } else if c == 't' { - match next_param_or(1) as usize { - 14 => { - // TODO: report text area size in pixels, currently unimplemented - // to solve this we probably need to query the user's terminal for the cursor - // size and then use it as a multiplier - } - 18 => { - // report text area - let text_area_report = format!("\x1b[8;{};{}t", self.height, self.width); - self.pending_messages_to_pty - .push(text_area_report.as_bytes().to_vec()); - } - 22 => { - // TODO: push title - } - 23 => { - // TODO: pop title - } - _ => {} - } - } else { - let result = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params)); - #[cfg(not(test))] - result.unwrap(); - } - } - - fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, byte: u8) { - match (byte, intermediates.get(0)) { - (b'B', charset_index_symbol) => { - let charset_index: CharsetIndex = match charset_index_symbol { - Some(b'(') => CharsetIndex::G0, - Some(b')') => CharsetIndex::G1, - Some(b'*') => CharsetIndex::G2, - Some(b'+') => CharsetIndex::G3, - _ => { - // invalid, silently do nothing - return; - } - }; - self.configure_charset(StandardCharset::Ascii, charset_index); - } - (b'0', charset_index_symbol) => { - let charset_index: CharsetIndex = match charset_index_symbol { - Some(b'(') => CharsetIndex::G0, - Some(b')') => CharsetIndex::G1, - Some(b'*') => CharsetIndex::G2, - Some(b'+') => CharsetIndex::G3, - _ => { - // invalid, silently do nothing - return; - } - }; - self.configure_charset( - StandardCharset::SpecialCharacterAndLineDrawing, - charset_index, - ); - } - (b'D', None) => { - self.add_newline(); - } - (b'E', None) => { - self.add_newline(); - self.move_cursor_to_beginning_of_line(); - } - (b'M', None) => { - // TODO: if cursor is at the top, it should go down one - self.move_cursor_up_with_scrolling(1); - } - (b'c', None) => { - self.reset_terminal_state(); - } - (b'H', None) => { - self.set_horizontal_tabstop(); - } - (b'7', None) => { - self.save_cursor_position(); - } - (b'Z', None) => { - let terminal_capabilities = "\u{1b}[?6c"; - self.pending_messages_to_pty - .push(terminal_capabilities.as_bytes().to_vec()); - } - (b'8', None) => { - self.restore_cursor_position(); - } - (b'8', Some(b'#')) => { - let mut fill_character = EMPTY_TERMINAL_CHARACTER; - fill_character.character = 'E'; - self.fill_viewport(fill_character); - } - _ => {} - } - } -} - -#[derive(Clone)] -pub struct Row { - pub columns: Vec<TerminalCharacter>, - pub is_canonical: bool, -} - -impl Debug for Row { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - for character in &self.columns { - write!(f, "{:?}", character)?; - } - Ok(()) - } -} - -impl Row { - pub fn new() -> Self { - Row { - columns: vec![], - is_canonical: false, - } - } - pub fn from_columns(columns: Vec<TerminalCharacter>) -> Self { - Row { - columns, - is_canonical: false, - } - } - pub fn from_rows(mut rows: Vec<Row>) -> Self { - if rows.is_empty() { - Row::new() - } else { - let mut first_row = rows.remove(0); - for row in rows.iter_mut() { - first_row.append(&mut row.columns); - } - first_row - } - } - pub fn with_character(mut self, terminal_character: TerminalCharacter) -> Self { - self.columns.push(terminal_character); - self - } - pub fn canonical(mut self) -> Self { - self.is_canonical = true; - self - } - pub fn contains_widechar(&self) -> bool { - self.columns.iter().any(|c| c.is_widechar()) - } - pub fn add_character_at(&mut self, terminal_character: TerminalCharacter, x: usize) { - match self.columns.len().cmp(&x) { - Ordering::Equal => self.columns.push(terminal_character), - Ordering::Less => { - self.columns.resize(x, EMPTY_TERMINAL_CHARACTER); - self.columns.push(terminal_character); - } - Ordering::Greater => { - // this is much more performant than remove/insert - self.columns.push(terminal_character); - self.columns.swap_remove(x); - } - } - } - pub fn insert_character_at(&mut self, terminal_character: TerminalCharacter, x: usize) { - match self.columns.len().cmp(&x) { - Ordering::Equal => self.columns.push(terminal_character), - Ordering::Less => { - self.columns.resize(x, EMPTY_TERMINAL_CHARACTER); - self.columns.push(terminal_character); - } - Ordering::Greater => { - self.columns.insert(x, terminal_character); - } - } - } - pub fn replace_character_at(&mut self, terminal_character: TerminalCharacter, x: usize) { - // this is much more performant than remove/insert - if x < self.columns.len() { - self.columns.push(terminal_character); - self.columns.swap_remove(x); - } - } - pub fn replace_columns(&mut self, columns: Vec<TerminalCharacter>) { - self.columns = columns; - } - pub fn push(&mut self, terminal_character: TerminalCharacter) { - self.columns.push(terminal_character); - } - pub fn truncate(&mut self, x: usize) { - self.columns.truncate(x); - } - pub fn append(&mut self, to_append: &mut Vec<TerminalCharacter>) { - self.columns.append(to_append); - } - pub fn replace_beginning_with(&mut self, mut line_part: Vec<TerminalCharacter>) { - if line_part.len() > self.columns.len() { - self.columns.clear(); - } else { - drop(self.columns.drain(0..line_part.len())); - } - line_part.append(&mut self.columns); - self.columns = line_part; - } - pub fn len(&self) -> usize { - self.columns.len() - } - pub fn delete_character(&mut self, x: usize) { - if x < self.columns.len() { - self.columns.remove(x); - } - } - pub fn split_to_rows_of_length(&mut self, max_row_length: usize) -> Vec<Row> { - let mut parts: Vec<Row> = vec![]; - let mut current_part: Vec<TerminalCharacter> = vec![]; - for character in self.columns.drain(..) { - if current_part.len() == max_row_length { - parts.push(Row::from_columns(current_part)); - current_part = vec![]; - } - current_part.push(character); - } - if !current_part.is_empty() { - parts.push(Row::from_columns(current_part)) - }; - if !parts.is_empty() && self.is_canonical { - parts.get_mut(0).unwrap().is_canonical = true; - } - parts - } -} - -#[cfg(test)] -#[path = "./unit/grid_tests.rs"] -mod grid_tests; diff --git a/src/client/panes/mod.rs b/src/client/panes/mod.rs deleted file mode 100644 index 4e7d6e80..00000000 --- a/src/client/panes/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod grid; -mod plugin_pane; -mod terminal_character; -mod terminal_pane; - -pub use grid::*; -pub use plugin_pane::*; -pub use terminal_character::*; -pub use terminal_pane::*; diff --git a/src/client/panes/plugin_pane.rs b/src/client/panes/plugin_pane.rs deleted file mode 100644 index dde3cd9b..00000000 --- a/src/client/panes/plugin_pane.rs +++ /dev/null @@ -1,227 +0,0 @@ -use std::sync::mpsc::channel; -use std::time::Instant; -use std::unimplemented; - -use crate::common::thread_bus::SenderWithContext; -use crate::panes::{PaneId, PositionAndSize}; -use crate::pty::VteBytes; -use crate::tab::Pane; -use crate::wasm_vm::PluginInstruction; - -pub struct PluginPane { - pub pid: u32, - pub should_render: bool, - pub selectable: bool, - pub invisible_borders: bool, - pub position_and_size: PositionAndSize, - pub position_and_size_override: Option<PositionAndSize>, - pub send_plugin_instructions: SenderWithContext<PluginInstruction>, - pub max_height: Option<usize>, - pub max_width: Option<usize>, - pub active_at: Instant, -} - -impl PluginPane { - pub fn new( - pid: u32, - position_and_size: PositionAndSize, - send_plugin_instructions: SenderWithContext<PluginInstruction>, - ) -> Self { - Self { - pid, - should_render: true, - selectable: true, - invisible_borders: false, - position_and_size, - position_and_size_override: None, - send_plugin_instructions, - max_height: None, - max_width: None, - active_at: Instant::now(), - } - } -} - -impl Pane for PluginPane { - // FIXME: These position and size things should all be moved to default trait implementations, - // with something like a get_pos_and_sz() method underpinning all of them. Alternatively and - // preferably, just use an enum and not a trait object - fn x(&self) -> usize { - self.position_and_size_override - .unwrap_or(self.position_and_size) - .x - } - fn y(&self) -> usize { - self.position_and_size_override - .unwrap_or(self.position_and_size) - .y - } - fn rows(&self) -> usize { - self.position_and_size_override - .unwrap_or(self.position_and_size) - .rows - } - fn columns(&self) -> usize { - self.position_and_size_override - .unwrap_or(self.position_and_size) - .columns - } - fn reset_size_and_position_override(&mut self) { - self.position_and_size_override = None; - self.should_render = true; - } - fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) { - self.position_and_size = *position_and_size; - self.should_render = true; - } - // FIXME: This is obviously a bit outdated and needs the x and y moved into `size` - 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, - ..Default::default() - }; - self.position_and_size_override = Some(position_and_size_override); - self.should_render = true; - } - fn handle_pty_bytes(&mut self, _event: VteBytes) { - unimplemented!() - } - fn cursor_coordinates(&self) -> Option<(usize, usize)> { - None - } - fn adjust_input_to_terminal(&self, _input_bytes: Vec<u8>) -> Vec<u8> { - unimplemented!() // FIXME: Shouldn't need this implmented? - } - - fn position_and_size_override(&self) -> Option<PositionAndSize> { - self.position_and_size_override - } - fn contains_widechar(&self) -> bool { - false - } - fn should_render(&self) -> bool { - self.should_render - } - fn set_should_render(&mut self, should_render: bool) { - self.should_render = should_render; - } - fn selectable(&self) -> bool { - self.selectable - } - fn set_selectable(&mut self, selectable: bool) { - self.selectable = selectable; - } - fn set_invisible_borders(&mut self, invisible_borders: bool) { - self.invisible_borders = invisible_borders; - } - fn set_max_height(&mut self, max_height: usize) { - self.max_height = Some(max_height); - } - fn set_max_width(&mut self, max_width: usize) { - self.max_width = Some(max_width); - } - fn render(&mut self) -> Option<String> { - // if self.should_render { - if true { - // while checking should_render rather than rendering each pane every time - // is more performant, it causes some problems when the pane to the left should be - // rendered and has wide characters (eg. Chinese characters or emoji) - // as a (hopefully) temporary hack, we render all panes until we find a better solution - let (buf_tx, buf_rx) = channel(); - - self.send_plugin_instructions - .send(PluginInstruction::Render( - buf_tx, - self.pid, - self.rows(), - self.columns(), - )) - .unwrap(); - - self.should_render = false; - Some(buf_rx.recv().unwrap()) - } else { - None - } - } - fn pid(&self) -> PaneId { - PaneId::Plugin(self.pid) - } - fn reduce_height_down(&mut self, count: usize) { - self.position_and_size.y += count; - self.position_and_size.rows -= count; - self.should_render = true; - } - fn increase_height_down(&mut self, count: usize) { - self.position_and_size.rows += count; - self.should_render = true; - } - fn increase_height_up(&mut self, count: usize) { - self.position_and_size.y -= count; - self.position_and_size.rows += count; - self.should_render = true; - } - fn reduce_height_up(&mut self, count: usize) { - self.position_and_size.rows -= count; - self.should_render = true; - } - fn reduce_width_right(&mut self, count: usize) { - self.position_and_size.x += count; - self.position_and_size.columns -= count; - self.should_render = true; - } - fn reduce_width_left(&mut self, count: usize) { - self.position_and_size.columns -= count; - self.should_render = true; - } - fn increase_width_left(&mut self, count: usize) { - self.position_and_size.x -= count; - self.position_and_size.columns += count; - self.should_render = true; - } - fn increase_width_right(&mut self, count: usize) { - self.position_and_size.columns += count; - self.should_render = true; - } - 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) { - unimplemented!() - } - fn scroll_down(&mut self, _count: usize) { - unimplemented!() - } - fn clear_scroll(&mut self) { - unimplemented!() - } - fn max_height(&self) -> Option<usize> { - self.max_height - } - fn max_width(&self) -> Option<usize> { - self.max_width - } - fn invisible_borders(&self) -> bool { - self.invisible_borders - } - - fn active_at(&self) -> Instant { - self.active_at - } - - fn set_active_at(&mut self, time: Instant) { - self.active_at = time; - } -} diff --git a/src/client/panes/terminal_character.rs b/src/client/panes/terminal_character.rs deleted file mode 100644 index d2340e2e..00000000 --- a/src/client/panes/terminal_character.rs +++ /dev/null @@ -1,775 +0,0 @@ -use unicode_width::UnicodeWidthChar; - -use crate::utils::logging::debug_log_to_file; -use std::convert::TryFrom; -use std::fmt::{self, Debug, Display, Formatter}; -use std::ops::{Index, IndexMut}; -use vte::ParamsIter; - -pub const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter { - character: ' ', - styles: CharacterStyles { - foreground: Some(AnsiCode::Reset), - background: Some(AnsiCode::Reset), - strike: Some(AnsiCode::Reset), - hidden: Some(AnsiCode::Reset), - reverse: Some(AnsiCode::Reset), - slow_blink: Some(AnsiCode::Reset), - fast_blink: Some(AnsiCode::Reset), - underline: Some(AnsiCode::Reset), - bold: Some(AnsiCode::Reset), - dim: Some(AnsiCode::Reset), - italic: Some(AnsiCode::Reset), - }, -}; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum AnsiCode { - On, - Reset, - NamedColor(NamedColor), - RgbCode((u8, u8, u8)), - ColorIndex(u8), -} - -#[derive(Clone, Copy, Eq, PartialEq, Debug)] -pub enum NamedColor { - Black, - Red, - Green, - Yellow, - Blue, - Magenta, - Cyan, - White, - BrightBlack, - BrightRed, - BrightGreen, - BrightYellow, - BrightBlue, - BrightMagenta, - BrightCyan, - BrightWhite, -} - -impl NamedColor { - fn to_foreground_ansi_code(self) -> String { - match self { - NamedColor::Black => format!("{}", 30), - NamedColor::Red => format!("{}", 31), - NamedColor::Green => format!("{}", 32), - NamedColor::Yellow => format!("{}", 33), - NamedColor::Blue => format!("{}", 34), - NamedColor::Magenta => format!("{}", 35), - NamedColor::Cyan => format!("{}", 36), - NamedColor::White => format!("{}", 37), - NamedColor::BrightBlack => format!("{}", 90), - NamedColor::BrightRed => format!("{}", 91), - NamedColor::BrightGreen => format!("{}", 92), - NamedColor::BrightYellow => format!("{}", 93), - NamedColor::BrightBlue => format!("{}", 94), - NamedColor::BrightMagenta => format!("{}", 95), - NamedColor::BrightCyan => format!("{}", 96), - NamedColor::BrightWhite => format!("{}", 97), - } - } - fn to_background_ansi_code(self) -> String { - match self { - NamedColor::Black => format!("{}", 40), - NamedColor::Red => format!("{}", 41), - NamedColor::Green => format!("{}", 42), - NamedColor::Yellow => format!("{}", 43), - NamedColor::Blue => format!("{}", 44), - NamedColor::Magenta => format!("{}", 45), - NamedColor::Cyan => format!("{}", 46), - NamedColor::White => format!("{}", 47), - NamedColor::BrightBlack => format!("{}", 100), - NamedColor::BrightRed => format!("{}", 101), - NamedColor::BrightGreen => format!("{}", 102), - NamedColor::BrightYellow => format!("{}", 103), - NamedColor::BrightBlue => format!("{}", 104), - NamedColor::BrightMagenta => format!("{}", 105), - NamedColor::BrightCyan => format!("{}", 106), - NamedColor::BrightWhite => format!("{}", 107), - } - } -} - -#[derive(Clone, Copy, Debug)] -pub struct CharacterStyles { - pub foreground: Option<AnsiCode>, - pub background: Option<AnsiCode>, - pub strike: Option<AnsiCode>, - pub hidden: Option<AnsiCode>, - pub reverse: Option<AnsiCode>, - pub slow_blink: Option<AnsiCode>, - pub fast_blink: Option<AnsiCode>, - pub underline: Option<AnsiCode>, - pub bold: Option<AnsiCode>, - pub dim: Option<AnsiCode>, - pub italic: Option<AnsiCode>, -} - -impl CharacterStyles { - pub fn new() -> Self { - CharacterStyles { - foreground: None, - background: None, - strike: None, - hidden: None, - reverse: None, - slow_blink: None, - fast_blink: None, - underline: None, - bold: None, - dim: None, - italic: None, - } - } - pub fn foreground(mut self, foreground_code: Option<AnsiCode>) -> Self { - self.foreground = foreground_code; - self - } - pub fn background(mut self, background_code: Option<AnsiCode>) -> Self { - self.background = background_code; - self - } - pub fn bold(mut self, bold_code: Option<AnsiCode>) -> Self { - self.bold = bold_code; - self - } - pub fn dim(mut self, dim_code: Option<AnsiCode>) -> Self { - self.dim = dim_code; - self - } - pub fn italic(mut self, italic_code: Option<AnsiCode>) -> Self { - self.italic = italic_code; - self - } - pub fn underline(mut self, underline_code: Option<AnsiCode>) -> Self { - self.underline = underline_code; - self - } - pub fn blink_slow(mut self, slow_blink_code: Option<AnsiCode>) -> Self { - self.slow_blink = slow_blink_code; - self - } - pub fn blink_fast(mut self, fast_blink_code: Option<AnsiCode>) -> Self { - self.fast_blink = fast_blink_code; - self - } - pub fn reverse(mut self, reverse_code: Option<AnsiCode>) -> Self { - self.reverse = reverse_code; - self - } - pub fn hidden(mut self, hidden_code: Option<AnsiCode>) -> Self { - self.hidden = hidden_code; - self - } - pub fn strike(mut self, strike_code: Option<AnsiCode>) -> Self { - self.strike = strike_code; - self - } - pub fn clear(&mut self) { - self.foreground = None; - self.background = None; - self.strike = None; - self.hidden = None; - self.reverse = None; - self.slow_blink = None; - self.fast_blink = None; - self.underline = None; - self.bold = None; - self.dim = None; - self.italic = None; - } - pub fn update_and_return_diff( - &mut self, - new_styles: &CharacterStyles, - ) -> Option<CharacterStyles> { - let mut diff: Option<CharacterStyles> = None; - - if new_styles.foreground == Some(AnsiCode::Reset) - && new_styles.background == Some(AnsiCode::Reset) - && new_styles.strike == Some(AnsiCode::Reset) - && new_styles.hidden == Some(AnsiCode::Reset) - && new_styles.reverse == Some(AnsiCode::Reset) - && new_styles.fast_blink == Some(AnsiCode::Reset) - && new_styles.slow_blink == Some(AnsiCode::Reset) - && new_styles.underline == Some(AnsiCode::Reset) - && new_styles.bold == Some(AnsiCode::Reset) - && new_styles.dim == Some(AnsiCode::Reset) - && new_styles.italic == Some(AnsiCode::Reset) - { - self.foreground = Some(AnsiCode::Reset); - self.background = Some(AnsiCode::Reset); - self.strike = Some(AnsiCode::Reset); - self.hidden = Some(AnsiCode::Reset); - self.reverse = Some(AnsiCode::Reset); - self.fast_blink = Some(AnsiCode::Reset); - self.slow_blink = Some(AnsiCode::Reset); - self.underline = Some(AnsiCode::Reset); - self.bold = Some(AnsiCode::Reset); - self.dim = Some(AnsiCode::Reset); - self.italic = Some(AnsiCode::Reset); - return Some(*new_styles); - }; - - if self.foreground != new_styles.foreground { - if let Some(new_diff) = diff.as_mut() { - diff = Some(new_diff.foreground(new_styles.foreground)); - self.foreground = new_styles.foreground; - } else { - diff = Some(CharacterStyles::new().foreground(new_styles.foreground)); - self.foreground = new_styles.foreground; - } - } - if self.background != new_styles.background { - if let Some(new_diff) = diff.as_mut() { - diff = Some(new_diff.background(new_styles.background)); - self.background = new_styles.background; - } else { - diff = Some(CharacterStyles::new().background(new_styles.background)); - self.background = new_styles.background; - } - } - if self.strike != new_styles.strike { - if let Some(new_diff) = diff.as_mut() { - diff = Some(new_diff.strike(new_styles.strike)); - self.strike = new_styles.strike; - } else { - diff = Some(CharacterStyles::new().strike(new_styles.strike)); - self.strike = new_styles.strike; - } - } - if self.hidden != new_styles.hidden { - if let Some(new_diff) = diff.as_mut() { - diff = Some(new_diff.hidden(new_styles.hidden)); - self.hidden = new_styles.hidden; - } else { - diff = Some(CharacterStyles::new().hidden(new_styles.hidden)); - self.hidden = new_styles.hidden; - } - } - if self.reverse != new_styles.reverse { - if let Some(new_diff) = diff.as_mut() { - diff = Some(new_diff.reverse(new_styles.reverse)); - self.reverse = new_styles.reverse; - } else { - diff = Some(CharacterStyles::new().reverse(new_styles.reverse)); - self.reverse = new_styles.reverse; - } - } - if self.slow_blink != new_styles.slow_blink { - if let Some(new_diff) = diff.as_mut() { - diff = Some(new_diff.blink_slow(new_styles.slow_blink)); - self.slow_blink = new_styles.slow_blink; - } else { - diff = Some(CharacterStyles::new().blink_slow(new_styles.slow_blink)); - self.slow_blink = new_styles.slow_blink; - } - } - if self.fast_blink != new_styles.fast_blink { - if let Some(new_diff) = diff.as_mut() { - diff = Some(new_diff.blink_fast(new_styles.fast_blink)); - self.fast_blink = new_styles.fast_blink; - } else { - diff = Some(CharacterStyles::new().blink_fast(new_styles.fast_blink)); - self.fast_blink = new_styles.fast_blink; - } - } - if self.underline != new_styles.underline { - if let Some(new_diff) = diff.as_mut() { - diff = Some(new_diff.underline(new_styles.underline)); - self.underline = new_styles.underline; - } else { - diff = Some(CharacterStyles::new().underline(new_styles.underline)); - self.underline = new_styles.underline; - } - } - if self.bold != new_styles.bold { - if let Some(new_diff) = diff.as_mut() { - diff = Some(new_diff.bold(new_styles.bold)); - self.bold = new_styles.bold; - } else { - diff = Some(CharacterStyles::new().bold(new_styles.bold)); - self.bold = new_styles.bold; - } - } - if self.dim != new_styles.dim { - if let Some(new_diff) = diff.as_mut() { - diff = Some(new_diff.dim(new_styles.dim)); - self.dim = new_styles.dim; - } else { - diff = Some(CharacterStyles::new().dim(new_styles.dim)); - self.dim = new_styles.dim; - } - } - if self.italic != new_styles.italic { - if let Some(new_diff) = diff.as_mut() { - diff = Some(new_diff.italic(new_styles.italic)); - self.italic = new_styles.italic; - } else { - diff = Some(CharacterStyles::new().italic(new_styles.italic)); - self.italic = new_styles.italic; - } - } - diff - } - pub fn reset_all(&mut self) { - self.foreground = Some(AnsiCode::Reset); - self.background = Some(AnsiCode::Reset); - self.bold = Some(AnsiCode::Reset); - self.dim = Some(AnsiCode::Reset); - self.italic = Some(AnsiCode::Reset); - self.underline = Some(AnsiCode::Reset); - self.slow_blink = Some(AnsiCode::Reset); - self.fast_blink = Some(AnsiCode::Reset); - self.reverse = Some(AnsiCode::Reset); - self.hidden = Some(AnsiCode::Reset); - self.strike = Some(AnsiCode::Reset); - } - pub fn add_style_from_ansi_params(&mut self, params: &mut ParamsIter) { - while let Some(param) = params.next() { - match param { - [] | [0] => self.reset_all(), - [1] => *self = self.bold(Some(AnsiCode::On)), - [2] => *self = self.dim(Some(AnsiCode::On)), - [3] => *self = self.italic(Some(AnsiCode::On)), - [4] => *self = self.underline(Some(AnsiCode::On)), - [5] => *self = self.blink_slow(Some(AnsiCode::On)), - [6] => *self = self.blink_fast(Some(AnsiCode::On)), - [7] => *self = self.reverse(Some(AnsiCode::On)), - [8] => *self = self.hidden(Some(AnsiCode::On)), - [9] => *self = self.strike(Some(AnsiCode::On)), - [21] => *self = self.bold(Some(AnsiCode::Reset)), - [22] => { - *self = self.bold(Some(AnsiCode::Reset)); - *self = self.dim(Some(AnsiCode::Reset)); - } - [23] => *self = self.italic(Some(AnsiCode::Reset)), - [24] => *self = self.underline(Some(AnsiCode::Reset)), - [25] => { - *self = self.blink_slow(Some(AnsiCode::Reset)); - *self = self.blink_fast(Some(AnsiCode::Reset)); - } - [27] => *self = self.reverse(Some(AnsiCode::Reset)), - [28] => *self = self.hidden(Some(AnsiCode::Reset)), - [29] => *self = self.strike(Some(AnsiCode::Reset)), - [30] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Black))), - [31] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Red))), - [32] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Green))), - [33] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Yellow))), - [34] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Blue))), - [35] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Magenta))), - [36] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Cyan))), - [37] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::White))), - [38] => { - let mut iter = params.map(|param| param[0]); - if let Some(ansi_code) = parse_sgr_color(&mut iter) { - *self = self.foreground(Some(ansi_code)); - } - } - [38, params @ ..] => { - let rgb_start = if params.len() > 4 { 2 } else { 1 }; - let rgb_iter = params[rgb_start..].iter().copied(); - let mut iter = std::iter::once(params[0]).chain(rgb_iter); - if let Some(ansi_code) = parse_sgr_color(&mut iter) { - *self = self.foreground(Some(ansi_code)); - } - } - [39] => *self = self.foreground(Some(AnsiCode::Reset)), - [40] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Black))), - [41] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Red))), - [42] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Green))), - [43] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Yellow))), - [44] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Blue))), - [45] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Magenta))), - [46] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Cyan))), - [47] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::White))), - [48] => { - let mut iter = params.map(|param| param[0]); - if let Some(ansi_code) = parse_sgr_color(&mut iter) { - *self = self.background(Some(ansi_code)); - } - } - [48, params @ ..] => { - let rgb_start = if params.len() > 4 { 2 } else { 1 }; - let rgb_iter = params[rgb_start..].iter().copied(); - let mut iter = std::iter::once(params[0]).chain(rgb_iter); - if let Some(ansi_code) = parse_sgr_color(&mut iter) { - *self = self.background(Some(ansi_code)); - } - } - [49] => *self = self.background(Some(AnsiCode::Reset)), - [90] => { - *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightBlack))) - } - [91] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightRed))), - [92] => { - *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightGreen))) - } - [93] => { - *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightYellow))) - } - [94] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightBlue))), - [95] => { - *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightMagenta))) - } - [96] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightCyan))), - [97] => { - *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::BrightWhite))) - } - [100] => { - *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightBlack))) - } - [101] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightRed))), - [102] => { - *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightGreen))) - } - [103] => { - *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightYellow))) - } - [104] => { - *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightBlue))) - } - [105] => { - *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightMagenta))) - } - [106] => { - *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightCyan))) - } - [107] => { - *self = self.background(Some(AnsiCode::NamedColor(NamedColor::BrightWhite))) - } - _ => { - let _ = debug_log_to_file(format!("unhandled csi m code {:?}", param)); - return; - } - } - } - } -} - -impl Display for CharacterStyles { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if self.foreground == Some(AnsiCode::Reset) - && self.background == Some(AnsiCode::Reset) - && self.strike == Some(AnsiCode::Reset) - && self.hidden == Some(AnsiCode::Reset) - && self.reverse == Some(AnsiCode::Reset) - && self.fast_blink == Some(AnsiCode::Reset) - && self.slow_blink == Some(AnsiCode::Reset) - && self.underline == Some(AnsiCode::Reset) - && self.bold == Some(AnsiCode::Reset) - && self.dim == Some(AnsiCode::Reset) - && self.italic == Some(AnsiCode::Reset) - { - write!(f, "\u{1b}[m")?; // reset all - return Ok(()); - } - if let Some(ansi_code) = self.foreground { - match ansi_code { - AnsiCode::RgbCode((r, g, b)) => { - write!(f, "\u{1b}[38;2;{};{};{}m", r, g, b)?; - } - AnsiCode::ColorIndex(color_index) => { - write!(f, "\u{1b}[38;5;{}m", color_index)?; - } - AnsiCode::Reset => { - write!(f, "\u{1b}[39m")?; - } - AnsiCode::NamedColor(named_color) => { - write!(f, "\u{1b}[{}m", named_color.to_foreground_ansi_code())?; - } - _ => {} - } - }; - if let Some(ansi_code) = self.background { - match ansi_code { - AnsiCode::RgbCode((r, g, b)) => { - write!(f, "\u{1b}[48;2;{};{};{}m", r, g, b)?; - } - AnsiCode::ColorIndex(color_index) => { - write!(f, "\u{1b}[48;5;{}m", color_index)?; - } - AnsiCode::Reset => { - write!(f, "\u{1b}[49m")?; - } - AnsiCode::NamedColor(named_color) => { - write!(f, "\u{1b}[{}m", named_color.to_background_ansi_code())?; - } - _ => {} - } - } - if let Some(ansi_code) = self.strike { - match ansi_code { - AnsiCode::On => { - write!(f, "\u{1b}[9m")?; - } - AnsiCode::Reset => { - write!(f, "\u{1b}[29m")?; - } - _ => {} - } - } - if let Some(ansi_code) = self.hidden { - match ansi_code { - AnsiCode::On => { - write!(f, "\u{1b}[8m")?; - } - AnsiCode::Reset => { - write!(f, "\u{1b}[28m")?; - } - _ => {} - } - } - if let Some(ansi_code) = self.reverse { - match ansi_code { - AnsiCode::On => { - write!(f, "\u{1b}[7m")?; - } - AnsiCode::Reset => { - write!(f, "\u{1b}[27m")?; - } - _ => {} - } - } - if let Some(ansi_code) = self.fast_blink { - match ansi_code { - AnsiCode::On => { - write!(f, "\u{1b}[6m")?; - } - AnsiCode::Reset => { - write!(f, "\u{1b}[25m")?; - } - _ => {} - } - } - if let Some(ansi_code) = self.slow_blink { - match ansi_code { - AnsiCode::On => { - write!(f, "\u{1b}[5m")?; - } - AnsiCode::Reset => { - write!(f, "\u{1b}[25m")?; - } - _ => {} - } - } - if let Some(ansi_code) = self.bold { - match ansi_code { - AnsiCode::On => { - write!(f, "\u{1b}[1m")?; - } - AnsiCode::Reset => { - write!(f, "\u{1b}[22m\u{1b}[24m")?; - // TODO: this cancels bold + underline, if this behaviour is indeed correct, we - // need to properly handle it in the struct methods etc like dim - } - _ => {} - } - } - // notice the order is important here, bold must be before underline - // because the bold reset also resets underline, and would override it - // otherwise - if let Some(ansi_code) = self.underline { - match ansi_code { - AnsiCode::On => { - write!(f, "\u{1b}[4m")?; - } - AnsiCode::Reset => { - write!(f, "\u{1b}[24m")?; - } - _ => {} - } - } - if let Some(ansi_code) = self.dim { - match ansi_code { - AnsiCode::On => { - write!(f, "\u{1b}[2m")?; - } - AnsiCode::Reset => { - if let Some(AnsiCode::Reset) = self.bold { - // we only reset dim if both dim and bold should be reset - write!(f, "\u{1b}[22m")?; - } - } - _ => {} - } - } - if let Some(ansi_code) = self.italic { - match ansi_code { - AnsiCode::On => { - write!(f, "\u{1b}[3m")?; - } - AnsiCode::Reset => { - write!(f, "\u{1b}[23m")?; - } - _ => {} - } - }; - Ok(()) - } -} - -#[derive(Clone, Copy, Debug)] -pub enum CharsetIndex { - G0, - G1, - G2, - G3, -} - -impl Default for CharsetIndex { - fn default() -> Self { - CharsetIndex::G0 - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum StandardCharset { - Ascii, - SpecialCharacterAndLineDrawing, -} - -impl Default for StandardCharset { - fn default() -> Self { - StandardCharset::Ascii - } -} - -impl StandardCharset { - /// Switch/Map character to the active charset. Ascii is the common case and - /// for that we want to do as little as possible. - #[inline] - pub fn map(self, c: char) -> char { - match self { - StandardCharset::Ascii => c, - StandardCharset::SpecialCharacterAndLineDrawing => match c { - '`' => '◆', - 'a' => '▒', - 'b' => '␉', - 'c' => '␌', - 'd' => '␍', - 'e' => '␊', - 'f' => '°', - 'g' => '±', - 'h' => '', - 'i' => '␋', - 'j' => '┘', - 'k' => '┐', - 'l' => '┌', - 'm' => '└', - 'n' => '┼', - 'o' => '⎺', - 'p' => '⎻', - 'q' => '─', - 'r' => '⎼', - 's' => '⎽', - 't' => '├', - 'u' => '┤', - 'v' => '┴', - 'w' => '┬', - 'x' => '│', - 'y' => '≤', - 'z' => '≥', - '{' => 'π', - '|' => '≠', - '}' => '£', - '~' => '·', - _ => c, - }, - } - } -} - -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] -pub struct Charsets([StandardCharset; 4]); - -impl Index<CharsetIndex> for Charsets { - type Output = StandardCharset; - - fn index(&self, index: CharsetIndex) -> &StandardCharset { - &self.0[index as usize] - } -} - -impl IndexMut<CharsetIndex> for Charsets { - fn index_mut(&mut self, index: CharsetIndex) -> &mut StandardCharset { - &mut self.0[index as usize] - } -} - -#[derive(Clone, Copy, Debug)] -pub enum CursorShape { - Block, - BlinkingBlock, - Underline, - BlinkingUnderline, - Beam, - BlinkingBeam, -} - -#[derive(Clone, Debug)] -pub struct Cursor { - pub x: usize, - pub y: usize, - pub is_hidden: bool, - pub pending_styles: CharacterStyles, - pub charsets: Charsets, - shape: CursorShape, -} - -impl Cursor { - pub fn new(x: usize, y: usize) -> Self { - Cursor { - x, - y, - is_hidden: false, - pending_styles: CharacterStyles::new(), - charsets: Default::default(), - shape: CursorShape::Block, - } - } - pub fn change_shape(&mut self, shape: CursorShape) { - self.shape = shape; - } - pub fn get_shape(&self) -> CursorShape { - self.shape - } -} - -#[derive(Clone, Copy)] -pub struct TerminalCharacter { - pub character: char, - pub styles: CharacterStyles, -} - -impl ::std::fmt::Debug for TerminalCharacter { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.character) - } -} - -impl TerminalCharacter { - pub fn width(&self) -> usize { - self.character.width().unwrap_or(0) - } - - pub fn is_widechar(&self) -> bool { - self.width() > 1 - } -} - -fn parse_sgr_color(params: &mut dyn Iterator<Item = u16>) -> Option<AnsiCode> { - match params.next() { - Some(2) => Some(AnsiCode::RgbCode(( - u8::try_from(params.next()?).ok()?, - u8::try_from(params.next()?).ok()?, - u8::try_from(params.next()?).ok()?, - ))), - Some(5) => Some(AnsiCode::ColorIndex(u8::try_from(params.next()?).ok()?)), - _ => None, - } -} diff --git a/src/client/panes/terminal_pane.rs b/src/client/panes/terminal_pane.rs deleted file mode 100644 index 5c4f55c8..00000000 --- a/src/client/panes/terminal_pane.rs +++ /dev/null @@ -1,362 +0,0 @@ -use std::fmt::Debug; -use std::os::unix::io::RawFd; -use std::time::Instant; - -use nix::pty::Winsize; -use serde::{Deserialize, Serialize}; - -use crate::panes::grid::Grid; -use crate::panes::terminal_character::{ - CharacterStyles, CursorShape, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, -}; -use crate::pty::VteBytes; -use crate::tab::Pane; - -#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug, Serialize, Deserialize)] -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, Serialize, Deserialize)] -pub struct PositionAndSize { - pub x: usize, - pub y: usize, - pub rows: usize, - pub columns: usize, - pub max_rows: Option<usize>, - pub max_columns: Option<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>, - pub max_width: Option<usize>, - pub active_at: Instant, - 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, - ..Default::default() - }; - 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); - } - self.set_should_render(true); - } - 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 contains_widechar(&self) -> bool { - self.grid.contains_widechar() - } - 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_max_width(&mut self, max_width: usize) { - self.max_width = Some(max_width); - } - fn set_invisible_borders(&mut self, _invisible_borders: bool) { - unimplemented!(); - } - fn max_height(&self) -> Option<usize> { - self.max_height - } - fn max_width(&self) -> Option<usize> { - self.max_width - } - fn render(&mut self) -> Option<String> { - if self.should_render() { - 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.set_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.set_should_render(true); - } - fn scroll_down(&mut self, count: usize) { - self.grid.move_viewport_down(count); - self.set_should_render(true); - } - fn clear_scroll(&mut self) { - self.grid.reset_viewport(); - self.set_should_render(true); - } - - fn active_at(&self) -> Instant { - self.active_at - } - - fn set_active_at(&mut self, time: Instant) { - self.active_at = time; - } - fn cursor_shape_csi(&self) -> String { - match self.grid.cursor_shape() { - CursorShape::Block => "\u{1b}[0 q".to_string(), - CursorShape::BlinkingBlock => "\u{1b}[1 q".to_string(), - CursorShape::Underline => "\u{1b}[4 q".to_string(), - CursorShape::BlinkingUnderline => "\u{1b}[3 q".to_string(), - CursorShape::Beam => "\u{1b}[6 q".to_string(), - CursorShape::BlinkingBeam => "\u{1b}[5 q".to_string(), - } - } - fn drain_messages_to_pty(&mut self) -> Vec<Vec<u8>> { - self.grid.pending_messages_to_pty.drain(..).collect() - } -} - -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, - max_width: None, - vte_parser: vte::Parser::new(), - active_at: Instant::now(), - } - } - 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); - self.set_should_render(true); - } - 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() - } -} diff --git a/src/client/panes/unit/grid_tests.rs b/src/client/panes/unit/grid_tests.rs deleted file mode 100644 index 8517a975..00000000 --- a/src/client/panes/unit/grid_tests.rs +++ /dev/null @@ -1,397 +0,0 @@ -use super::super::Grid; -use ::insta::assert_snapshot; - -fn read_fixture(fixture_name: &str) -> Vec<u8> { - let mut path_to_file = std::path::PathBuf::new(); - path_to_file.push("src"); - path_to_file.push("tests"); - path_to_file.push("fixtures"); - path_to_file.push(fixture_name); - let content = std::fs::read(path_to_file) - .unwrap_or_else(|_| panic!("could not read fixture {:?}", &fixture_name)); - content -} - -#[test] -fn vttest1_0() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest1-0"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest1_1() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest1-1"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest1_2() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest1-2"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest1_3() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest1-3"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest1_4() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest1-4"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest1_5() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest1-5"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest2_0() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest2-0"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest2_1() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest2-1"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest2_2() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest2-2"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest2_3() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest2-3"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest2_4() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest2-4"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest2_5() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest2-5"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest2_6() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest2-6"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest2_7() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest2-7"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest2_8() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest2-8"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest2_9() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest2-9"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest2_10() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest2-10"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest2_11() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest2-11"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest2_12() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest2-12"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest2_13() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest2-13"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest2_14() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest2-14"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest3_0() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(41, 110); - let fixture_name = "vttest3-0"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest8_0() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); - let fixture_name = "vttest8-0"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest8_1() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); - let fixture_name = "vttest8-1"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest8_2() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); - let fixture_name = "vttest8-2"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest8_3() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); - let fixture_name = "vttest8-3"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest8_4() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); - let fixture_name = "vttest8-4"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn vttest8_5() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); - let fixture_name = "vttest8-5"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn csi_b() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); - let fixture_name = "csi-b"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn csi_capital_i() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); - let fixture_name = "csi-capital-i"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn csi_capital_z() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); - let fixture_name = "csi-capital-z"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid)); -} - -#[test] -fn terminal_reports() { - let mut vte_parser = vte::Parser::new(); - let mut grid = Grid::new(51, 97); - let fixture_name = "terminal_reports"; - let content = read_fixture(fixture_name); - for byte in content { - vte_parser.advance(&mut grid, byte); - } - assert_snapshot!(format!("{:?}", grid.pending_messages_to_pty)); -} diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_b.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_b.snap deleted file mode 100644 index 1d0c34e5..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_b.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): ffffff -01 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_i.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_i.snap deleted file mode 100644 index 11f7b8f8..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_i.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): foo -01 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_z.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_z.snap deleted file mode 100644 index 04f492b8..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__csi_capital_z.snap +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): 12345678foo234567890 -01 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__terminal_reports.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__terminal_reports.snap deleted file mode 100644 index b2dca273..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__terminal_reports.snap +++ /dev/null @@ -1,6 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid . pending_messages_to_pty)" - ---- -[[27, 91, 63, 54, 99], [27, 91, 56, 59, 53, 49, 59, 57, 55, 116], [27, 91, 63, 54, 99], [27, 91, 48, 110], [27, 91, 49, 59, 49, 82]] diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_0.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_0.snap deleted file mode 100644 index 8641098c..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_0.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/grid.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): ******************************************************************************** -01 (C): *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++* -02 (C): *+ +* -03 (C): *+ +* -04 (C): *+ +* -05 (C): *+ +* -06 (C): *+ +* -07 (C): *+ +* -08 (C): *+ EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +* -09 (C): *+ E E +* -10 (C): *+ E The screen should be cleared, and have an unbroken bor- E +* -11 (C): *+ E der of *'s and +'s around the edge, and exactly in the E +* -12 (C): *+ E middle there should be a frame of E's around this text E +* -13 (C): *+ E with one (1) free position around it. Push <RETURN> E +* -14 (C): *+ E E +* -15 (C): *+ EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +* -16 (C): *+ +* -17 (C): *+ +* -18 (C): *+ +* -19 (C): *+ +* -20 (C): *+ +* -21 (C): *+ +* -22 (C): *+ ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -23 (C): ******************************************************************************** -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_1.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_1.snap deleted file mode 100644 index 09663484..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_1.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/grid.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): ************************************************************************************************************** -01 (C): *++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++* -02 (C): *+ +* -03 (C): *+ +* -04 (C): *+ +* -05 (C): *+ +* -06 (C): *+ +* -07 (C): *+ +* -08 (C): *+ EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +* -09 (C): *+ E E +* -10 (C): *+ E The screen should be cleared, and have an unbroken bor- E +* -11 (C): *+ E der of *'s and +'s around the edge, and exactly in the E +* -12 (C): *+ E middle there should be a frame of E's around this text E +* -13 (C): *+ E with one (1) free position around it. Push <RETURN> E +* -14 (C): *+ E E +* -15 (C): *+ EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +* -16 (C): *+ +* -17 (C): *+ +* -18 (C): *+ +* -19 (C): *+ +* -20 (C): *+ +* -21 (C): *+ +* -22 (C): ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * -23 (C): ************************************************************************************************************** -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_2.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_2.snap deleted file mode 100644 index 05d80b1d..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_2.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/grid.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): Test of autowrap, mixing control and print characters. -01 (C): The left/right margins should have letters in order: -02 (C): I iJ -03 (C): j -04 (C): K K k -05 (C): L l -06 (C): M mN -07 (C): n -08 (C): O O o -09 (C): P p -10 (C): Q qR -11 (C): r -12 (C): S S s -13 (C): T t -14 (C): U uV -15 (C): v -16 (C): W W w -17 (C): X x -18 (C): Y yZ -19 (C): z -20 (C): -21 (C): Push <RETURN> -22 (C): -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_3.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_3.snap deleted file mode 100644 index 64c3e7b9..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_3.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/grid.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): Test of autowrap, mixing control and print characters. -01 (C): The left/right margins should have letters in order: -02 (C): I i -03 (C): J j -04 (C): K k -05 (C): L l -06 (C): M m -07 (C): N n -08 (C): O o -09 (C): P p -10 (C): Q q -11 (C): R r -12 (C): S s -13 (C): T t -14 (C): U u -15 (C): V v -16 (C): W w -17 (C): X x -18 (C): Y y -19 (C): Z z -20 (C): -21 (C): Push <RETURN> -22 (C): -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_4.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_4.snap deleted file mode 100644 index dcb417ae..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_4.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/grid.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): Test of cursor-control characters inside ESC sequences. -01 (C): Below should be four identical lines: -02 (C): -03 (C): A B C D E F G H I -04 (C): A B C D E F G H I -05 (C): A B C D E F G H I -06 (C): A B C D E F G H I -07 (C): -08 (C): Push <RETURN> -09 (C): -10 (C): -11 (C): -12 (C): -13 (C): -14 (C): -15 (C): -16 (C): -17 (C): -18 (C): -19 (C): -20 (C): -21 (C): -22 (C): -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_5.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_5.snap deleted file mode 100644 index d044691c..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest1_5.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/grid.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): Test of leading zeros in ESC sequences. -01 (C): Two lines below you should see the sentence "This is a correct sentence". -02 (C): -03 (C): This is a correct sentence -04 (C): -05 (C): -06 (C): -07 (C): -08 (C): -09 (C): -10 (C): -11 (C): -12 (C): -13 (C): -14 (C): -15 (C): -16 (C): -17 (C): -18 (C): -19 (C): Push <RETURN> -20 (C): -21 (C): -22 (C): -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_0.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_0.snap deleted file mode 100644 index f5bd11a5..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_0.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): ************************************************************************************************************** -01 (C): ************************************************** -02 (C): ************************************************************************************************************** -03 (C): -04 (C): This should be three identical lines of *'s completely filling -05 (C): the top of the screen without any empty lines between. -06 (C): (Test of WRAP AROUND mode setting.) -07 (C): Push <RETURN> -08 (C): -09 (C): -10 (C): -11 (C): -12 (C): -13 (C): -14 (C): -15 (C): -16 (C): -17 (C): -18 (C): -19 (C): -20 (C): -21 (C): -22 (C): -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_1.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_1.snap deleted file mode 100644 index 148b8732..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_1.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): * * * * * * * * * * * * * -01 (C): * * * * * * * * * * * * * -02 (C): -03 (C): Test of TAB setting/resetting. These two lines -04 (C): should look the same. Push <RETURN> -05 (C): -06 (C): -07 (C): -08 (C): -09 (C): -10 (C): -11 (C): -12 (C): -13 (C): -14 (C): -15 (C): -16 (C): -17 (C): -18 (C): -19 (C): -20 (C): -21 (C): -22 (C): -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_10.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_10.snap deleted file mode 100644 index 7cbb8545..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_10.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): -01 (C): -02 (C): -03 (C): -04 (C): -05 (C): -06 (C): -07 (C): -08 (C): -09 (C): -10 (C): -11 (C): -12 (C): -13 (C): -14 (C): -15 (C): -16 (C): -17 (C): -18 (C): -19 (C): -20 (C): -21 (C): -22 (C): This line should be the one above the bottom of the screen. Push <RETURN> -23 (C): Origin mode test. This line should be at the bottom of the screen. -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_11.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_11.snap deleted file mode 100644 index 1201290b..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_11.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): This line should be at the top of the screen. Push <RETURN> -01 (C): -02 (C): -03 (C): -04 (C): -05 (C): -06 (C): -07 (C): -08 (C): -09 (C): -10 (C): -11 (C): -12 (C): -13 (C): -14 (C): -15 (C): -16 (C): -17 (C): -18 (C): -19 (C): -20 (C): -21 (C): -22 (C): -23 (C): Origin mode test. This line should be at the bottom of the screen. -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_12.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_12.snap deleted file mode 100644 index fc15da63..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_12.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): Graphic rendition test pattern: -01 (C): -02 (C): -03 (C): vanilla bold -04 (C): -05 (C): underline bold underline -06 (C): -07 (C): blink bold blink -08 (C): -09 (C): underline blink bold underline blink -10 (C): -11 (C): negative bold negative -12 (C): -13 (C): underline negative bold underline negative -14 (C): -15 (C): blink negative bold blink negative -16 (C): -17 (C): underline blink negative bold underline blink negative -18 (C): -19 (C): -20 (C): -21 (C): -22 (C): Dark background. Push <RETURN> -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_13.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_13.snap deleted file mode 100644 index ee8e30fb..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_13.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): Graphic rendition test pattern: -01 (C): -02 (C): -03 (C): vanilla bold -04 (C): -05 (C): underline bold underline -06 (C): -07 (C): blink bold blink -08 (C): -09 (C): underline blink bold underline blink -10 (C): -11 (C): negative bold negative -12 (C): -13 (C): underline negative bold underline negative -14 (C): -15 (C): blink negative bold blink negative -16 (C): -17 (C): underline blink negative bold underline blink negative -18 (C): -19 (C): -20 (C): -21 (C): -22 (C): Light background. Push <RETURN> -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_14.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_14.snap deleted file mode 100644 index 2bdf1956..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_14.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): AAAAA -01 (C): AAAAA -02 (C): AAAAA -03 (C): AAAAA -04 (C): -05 (C): -06 (C): -07 (C): normal bold underscored blinking reversed -08 (C): -09 (C): stars: ********** ********** ********** ********** ********** -10 (C): -11 (C): line: ────────── ────────── ────────── ────────── ────────── -12 (C): -13 (C): x'es: xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx -14 (C): -15 (C): diamonds: ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ ◆◆◆◆◆◆◆◆◆◆ -16 (C): -17 (C): -18 (C): -19 (C): -20 (C): Test of the SAVE/RESTORE CURSOR feature. There should -21 (C): be ten characters of each flavour, and a rectangle -22 (C): of 5 x 4 A's filling the top left of the screen. -23 (C): Push <RETURN> -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_2.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_2.snap deleted file mode 100644 index 91a9a2e6..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_2.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 -01 (C): 123456789012345678901 -02 (C): This is 132 column mode, light background. -03 (C): This is 132 column mode, light background. -04 (C): This is 132 column mode, light background. -05 (C): This is 132 column mode, light background. -06 (C): This is 132 column mode, light background. -07 (C): This is 132 column mode, light background. -08 (C): This is 132 column mode, light background. -09 (C): This is 132 column mode, light background. -10 (C): This is 132 column mode, light background. -11 (C): This is 132 column mode, light background. -12 (C): This is 132 column mode, light background. -13 (C): This is 132 column mode, light background. -14 (C): This is 132 column mode, light background. -15 (C): This is 132 column mode, light background. -16 (C): This is 132 column mode, light background. -17 (C): This is 132 column mode, light background. -18 (C): This is 132 column mode, light background. -19 (C): This is 132 column mode, light background.Push <RETURN> -20 (C): -21 (C): -22 (C): -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_3.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_3.snap deleted file mode 100644 index c3881efb..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_3.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): 1234567890123456789012345678901234567890123456789012345678901234567890123456789 -01 (C): -02 (C): This is 80 column mode, light background. -03 (C): This is 80 column mode, light background. -04 (C): This is 80 column mode, light background. -05 (C): This is 80 column mode, light background. -06 (C): This is 80 column mode, light background. -07 (C): This is 80 column mode, light background. -08 (C): This is 80 column mode, light background. -09 (C): This is 80 column mode, light background. -10 (C): This is 80 column mode, light background. -11 (C): This is 80 column mode, light background. -12 (C): This is 80 column mode, light background. -13 (C): This is 80 column mode, light background. -14 (C): This is 80 column mode, light background. -15 (C): This is 80 column mode, light background. -16 (C): This is 80 column mode, light background. -17 (C): This is 80 column mode, light background. -18 (C): This is 80 column mode, light background. -19 (C): This is 80 column mode, light background.Push <RETURN> -20 (C): -21 (C): -22 (C): -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_4.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_4.snap deleted file mode 100644 index 089f3d7d..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_4.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 -01 (C): 123456789012345678901 -02 (C): This is 132 column mode, dark background. -03 (C): This is 132 column mode, dark background. -04 (C): This is 132 column mode, dark background. -05 (C): This is 132 column mode, dark background. -06 (C): This is 132 column mode, dark background. -07 (C): This is 132 column mode, dark background. -08 (C): This is 132 column mode, dark background. -09 (C): This is 132 column mode, dark background. -10 (C): This is 132 column mode, dark background. -11 (C): This is 132 column mode, dark background. -12 (C): This is 132 column mode, dark background. -13 (C): This is 132 column mode, dark background. -14 (C): This is 132 column mode, dark background. -15 (C): This is 132 column mode, dark background. -16 (C): This is 132 column mode, dark background. -17 (C): This is 132 column mode, dark background. -18 (C): This is 132 column mode, dark background. -19 (C): This is 132 column mode, dark background.Push <RETURN> -20 (C): -21 (C): -22 (C): -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_5.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_5.snap deleted file mode 100644 index 6cf729c1..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_5.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): 1234567890123456789012345678901234567890123456789012345678901234567890123456789 -01 (C): -02 (C): This is 80 column mode, dark background. -03 (C): This is 80 column mode, dark background. -04 (C): This is 80 column mode, dark background. -05 (C): This is 80 column mode, dark background. -06 (C): This is 80 column mode, dark background. -07 (C): This is 80 column mode, dark background. -08 (C): This is 80 column mode, dark background. -09 (C): This is 80 column mode, dark background. -10 (C): This is 80 column mode, dark background. -11 (C): This is 80 column mode, dark background. -12 (C): This is 80 column mode, dark background. -13 (C): This is 80 column mode, dark background. -14 (C): This is 80 column mode, dark background. -15 (C): This is 80 column mode, dark background. -16 (C): This is 80 column mode, dark background. -17 (C): This is 80 column mode, dark background. -18 (C): This is 80 column mode, dark background. -19 (C): This is 80 column mode, dark background.Push <RETURN> -20 (C): -21 (C): -22 (C): -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_6.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_6.snap deleted file mode 100644 index 3c6a1c77..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_6.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): -01 (C): -02 (C): -03 (C): -04 (C): -05 (C): -06 (C): -07 (C): -08 (C): -09 (C): -10 (C): -11 (W): Push <RETURN> -12 (W): Soft scroll down region [12..13] size 2 Line 29 -13 (C): -14 (C): -15 (C): -16 (C): -17 (C): -18 (C): -19 (C): -20 (C): -21 (C): -22 (C): -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_7.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_7.snap deleted file mode 100644 index 0c4c5cac..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_7.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (W): Push <RETURN> -01 (W): Soft scroll down region [1..24] size 24 Line 29 -02 (W): Soft scroll down region [1..24] size 24 Line 28 -03 (W): Soft scroll down region [1..24] size 24 Line 27 -04 (W): Soft scroll down region [1..24] size 24 Line 26 -05 (W): Soft scroll down region [1..24] size 24 Line 25 -06 (W): Soft scroll down region [1..24] size 24 Line 24 -07 (W): Soft scroll down region [1..24] size 24 Line 23 -08 (W): Soft scroll down region [1..24] size 24 Line 22 -09 (W): Soft scroll down region [1..24] size 24 Line 21 -10 (W): Soft scroll down region [1..24] size 24 Line 20 -11 (W): Soft scroll down region [1..24] size 24 Line 19 -12 (W): Soft scroll down region [1..24] size 24 Line 18 -13 (W): Soft scroll down region [1..24] size 24 Line 17 -14 (W): Soft scroll down region [1..24] size 24 Line 16 -15 (W): Soft scroll down region [1..24] size 24 Line 15 -16 (W): Soft scroll down region [1..24] size 24 Line 14 -17 (W): Soft scroll down region [1..24] size 24 Line 13 -18 (W): Soft scroll down region [1..24] size 24 Line 12 -19 (W): Soft scroll down region [1..24] size 24 Line 11 -20 (W): Soft scroll down region [1..24] size 24 Line 10 -21 (W): Soft scroll down region [1..24] size 24 Line 9 -22 (W): Soft scroll down region [1..24] size 24 Line 8 -23 (W): Soft scroll down region [1..24] size 24 Line 7 -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_8.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_8.snap deleted file mode 100644 index 9e9ac891..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_8.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): -01 (C): -02 (C): -03 (C): -04 (C): -05 (C): -06 (C): -07 (C): -08 (C): -09 (C): -10 (C): -11 (W): Push <RETURN> -12 (W): Jump scroll down region [12..13] size 2 Line 29 -13 (C): -14 (C): -15 (C): -16 (C): -17 (C): -18 (C): -19 (C): -20 (C): -21 (C): -22 (C): -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_9.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_9.snap deleted file mode 100644 index 852203c8..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest2_9.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (W): Push <RETURN> -01 (W): Jump scroll down region [1..24] size 24 Line 29 -02 (W): Jump scroll down region [1..24] size 24 Line 28 -03 (W): Jump scroll down region [1..24] size 24 Line 27 -04 (W): Jump scroll down region [1..24] size 24 Line 26 -05 (W): Jump scroll down region [1..24] size 24 Line 25 -06 (W): Jump scroll down region [1..24] size 24 Line 24 -07 (W): Jump scroll down region [1..24] size 24 Line 23 -08 (W): Jump scroll down region [1..24] size 24 Line 22 -09 (W): Jump scroll down region [1..24] size 24 Line 21 -10 (W): Jump scroll down region [1..24] size 24 Line 20 -11 (W): Jump scroll down region [1..24] size 24 Line 19 -12 (W): Jump scroll down region [1..24] size 24 Line 18 -13 (W): Jump scroll down region [1..24] size 24 Line 17 -14 (W): Jump scroll down region [1..24] size 24 Line 16 -15 (W): Jump scroll down region [1..24] size 24 Line 15 -16 (W): Jump scroll down region [1..24] size 24 Line 14 -17 (W): Jump scroll down region [1..24] size 24 Line 13 -18 (W): Jump scroll down region [1..24] size 24 Line 12 -19 (W): Jump scroll down region [1..24] size 24 Line 11 -20 (W): Jump scroll down region [1..24] size 24 Line 10 -21 (W): Jump scroll down region [1..24] size 24 Line 9 -22 (W): Jump scroll down region [1..24] size 24 Line 8 -23 (W): Jump scroll down region [1..24] size 24 Line 7 -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest3_0.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest3_0.snap deleted file mode 100644 index 6902cf25..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest3_0.snap +++ /dev/null @@ -1,47 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): Selected as G0 (with SI) Selected as G1 (with SO) -01 (C): -02 (C): Character set B (US ASCII) -03 (C): !"#$%&'()*+,-./0123456789:;<=>? !"#$%&'()*+,-./0123456789:;<=>? -04 (C): @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ -05 (C): `abcdefghijklmnopqrstuvwxyz{|}~ `abcdefghijklmnopqrstuvwxyz{|}~ -06 (C): Character set A (British) -07 (C): !"#$%&'()*+,-./0123456789:;<=>? !"#$%&'()*+,-./0123456789:;<=>? -08 (C): @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ -09 (C): `abcdefghijklmnopqrstuvwxyz{|}~ `abcdefghijklmnopqrstuvwxyz{|}~ -10 (C): Character set 0 (DEC Special graphics and line drawing) -11 (C): !"#$%&'()*+,-./0123456789:;<=>? !"#$%&'()*+,-./0123456789:;<=>? -12 (C): @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ -13 (C): ◆▒␉␌␍␊°±␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£· ◆▒␉␌␍␊°±␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£· -14 (C): Character set 1 (DEC Alternate character ROM standard characters) -15 (C): !"#$%&'()*+,-./0123456789:;<=>? !"#$%&'()*+,-./0123456789:;<=>? -16 (C): @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ -17 (C): `abcdefghijklmnopqrstuvwxyz{|}~ `abcdefghijklmnopqrstuvwxyz{|}~ -18 (C): Character set 2 (DEC Alternate character ROM special graphics) -19 (C): !"#$%&'()*+,-./0123456789:;<=>? !"#$%&'()*+,-./0123456789:;<=>? -20 (C): @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ -21 (C): `abcdefghijklmnopqrstuvwxyz{|}~ `abcdefghijklmnopqrstuvwxyz{|}~ -22 (C): -23 (C): These are the installed character sets. Push <RETURN> -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_0.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_0.snap deleted file mode 100644 index 18cdd01b..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_0.snap +++ /dev/null @@ -1,57 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -01 (C): Top line: A's, bottom line: X's, this line, nothing more. Push <RETURN> -02 (C): -03 (C): -04 (C): -05 (C): -06 (C): -07 (C): -08 (C): -09 (C): -10 (C): -11 (C): -12 (C): -13 (C): -14 (C): -15 (C): -16 (C): -17 (C): -18 (C): -19 (C): -20 (C): -21 (C): -22 (C): -23 (C): XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): -41 (C): -42 (C): -43 (C): -44 (C): -45 (C): -46 (C): -47 (C): -48 (C): -49 (C): -50 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_1.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_1.snap deleted file mode 100644 index 7618e82b..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_1.snap +++ /dev/null @@ -1,57 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): A******************************************************************************BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -01 (C): -02 (C): -03 (C): Test of 'Insert Mode'. The top line should be 'A*** ... ***B'. Push <RETURN> -04 (C): -05 (C): -06 (C): -07 (C): -08 (C): -09 (C): -10 (C): -11 (C): -12 (C): -13 (C): -14 (C): -15 (C): -16 (C): -17 (C): -18 (C): -19 (C): -20 (C): -21 (C): -22 (C): -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): -41 (C): -42 (C): -43 (C): -44 (C): -45 (C): -46 (C): -47 (C): -48 (C): -49 (C): -50 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_2.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_2.snap deleted file mode 100644 index 0873ec23..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_2.snap +++ /dev/null @@ -1,57 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -01 (C): -02 (C): -03 (C): Test of 'Delete Character'. The top line should be 'AB'. Push <RETURN> -04 (C): -05 (C): -06 (C): -07 (C): -08 (C): -09 (C): -10 (C): -11 (C): -12 (C): -13 (C): -14 (C): -15 (C): -16 (C): -17 (C): -18 (C): -19 (C): -20 (C): -21 (C): -22 (C): -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): -41 (C): -42 (C): -43 (C): -44 (C): -45 (C): -46 (C): -47 (C): -48 (C): -49 (C): -50 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_3.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_3.snap deleted file mode 100644 index 049f5083..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_3.snap +++ /dev/null @@ -1,57 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -01 (C): BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB -02 (C): CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC -03 (C): The right column should be staggered DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD -04 (C): by one. Push <RETURN>EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE -05 (C): FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -06 (C): GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG -07 (C): HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -08 (C): IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII -09 (C): JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ -10 (C): KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK -11 (C): LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL -12 (C): MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM -13 (C): NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN -14 (C): OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO -15 (C): PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP -16 (C): QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ -17 (C): RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR -18 (C): SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -19 (C): TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT -20 (C): UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU -21 (C): VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV -22 (C): WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW -23 (C): XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): -41 (C): -42 (C): -43 (C): -44 (C): -45 (C): -46 (C): -47 (C): -48 (C): -49 (C): -50 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_4.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_4.snap deleted file mode 100644 index e7eb978f..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_4.snap +++ /dev/null @@ -1,57 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -01 (C): BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB -02 (C): CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC -03 (C): The right column should be staggered -04 (C): by one. Push <RETURN>EEEEEEEEEEEEE -05 (C): FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -06 (C): GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG -07 (C): HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -08 (C): IIIIIIIIIIIIIIIIIIIIIIIIIIIIIII -09 (C): JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ -10 (C): KKKKKKKKKKKKKKKKKKKKKKKKKKKKK -11 (C): LLLLLLLLLLLLLLLLLLLLLLLLLLLL -12 (C): MMMMMMMMMMMMMMMMMMMMMMMMMMM -13 (C): NNNNNNNNNNNNNNNNNNNNNNNNNN -14 (C): OOOOOOOOOOOOOOOOOOOOOOOOO -15 (C): PPPPPPPPPPPPPPPPPPPPPPPP -16 (C): QQQQQQQQQQQQQQQQQQQQQQQ -17 (C): RRRRRRRRRRRRRRRRRRRRRR -18 (C): SSSSSSSSSSSSSSSSSSSSS -19 (C): TTTTTTTTTTTTTTTTTTTT -20 (C): UUUUUUUUUUUUUUUUUUU -21 (C): VVVVVVVVVVVVVVVVVV -22 (C): WWWWWWWWWWWWWWWWW -23 (C): XXXXXXXXXXXXXXXX -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): -41 (C): -42 (C): -43 (C): -44 (C): -45 (C): -46 (C): -47 (C): -48 (C): -49 (C): -50 (C): - diff --git a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_5.snap b/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_5.snap deleted file mode 100644 index 9b54cbc8..00000000 --- a/src/client/panes/unit/snapshots/zellij__client__panes__grid__grid_tests__vttest8_5.snap +++ /dev/null @@ -1,57 +0,0 @@ ---- -source: src/client/panes/./unit/grid_tests.rs -expression: "format!(\"{:?}\", grid)" - ---- -00 (C): If your terminal has the ANSI 'Insert Character' function -01 (C): (the VT102 does not), then you should see a line like this -02 (C): A B C D E F G H I J K L M N O P Q R S T U V W X Y Z -03 (C): below: -04 (C): -05 (C): A B C D E F G H I J K L M N O P Q R S T U V W X Y Z -06 (C): -07 (C): -08 (C): -09 (C): Push <RETURN> -10 (C): -11 (C): -12 (C): -13 (C): -14 (C): -15 (C): -16 (C): -17 (C): -18 (C): -19 (C): -20 (C): -21 (C): -22 (C): -23 (C): -24 (C): -25 (C): -26 (C): -27 (C): -28 (C): -29 (C): -30 (C): -31 (C): -32 (C): -33 (C): -34 (C): -35 (C): -36 (C): -37 (C): -38 (C): -39 (C): -40 (C): -41 (C): -42 (C): -43 (C): -44 (C): -45 (C): -46 (C): -47 (C): -48 (C): -49 (C): -50 (C): - diff --git a/src/client/tab.rs b/src/client/tab.rs deleted file mode 100644 index 29a41530..00000000 --- a/src/client/tab.rs +++ /dev/null @@ -1,2274 +0,0 @@ -//! `Tab`s holds multiple panes. It tracks their coordinates (x/y) and size, -//! as well as how they should be resized - -use crate::client::pane_resizer::PaneResizer; -use crate::common::input::handler::parse_keys; -use crate::common::thread_bus::ThreadSenders; -use crate::layout::Layout; -use crate::os_input_output::ServerOsApi; -use crate::panes::{PaneId, PositionAndSize, TerminalPane}; -use crate::pty::{PtyInstruction, VteBytes}; -use crate::server::ServerInstruction; -use crate::utils::shared::adjust_to_size; -use crate::wasm_vm::PluginInstruction; -use crate::{boundaries::Boundaries, panes::PluginPane}; -use serde::{Deserialize, Serialize}; -use std::os::unix::io::RawFd; -use std::sync::mpsc::channel; -use std::time::Instant; -use std::{ - cmp::Reverse, - collections::{BTreeMap, HashSet}, -}; -use zellij_tile::data::{Event, InputMode, ModeInfo, Palette}; - -const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this - -// MIN_TERMINAL_HEIGHT here must be larger than the height of any of the status bars -// this is a dirty hack until we implement fixed panes -const MIN_TERMINAL_HEIGHT: usize = 3; -const MIN_TERMINAL_WIDTH: usize = 4; - -type BorderAndPaneIds = (usize, Vec<PaneId>); - -fn split_vertically_with_gap(rect: &PositionAndSize) -> (PositionAndSize, PositionAndSize) { - let width_of_each_half = (rect.columns - 1) / 2; - let mut first_rect = *rect; - let mut second_rect = *rect; - if rect.columns % 2 == 0 { - first_rect.columns = width_of_each_half + 1; - } else { - first_rect.columns = width_of_each_half; - } - second_rect.x = first_rect.x + first_rect.columns + 1; - second_rect.columns = width_of_each_half; - (first_rect, second_rect) -} - -fn split_horizontally_with_gap(rect: &PositionAndSize) -> (PositionAndSize, PositionAndSize) { - let height_of_each_half = (rect.rows - 1) / 2; - let mut first_rect = *rect; - let mut second_rect = *rect; - if rect.rows % 2 == 0 { - first_rect.rows = height_of_each_half + 1; - } else { - first_rect.rows = height_of_each_half; - } - second_rect.y = first_rect.y + first_rect.rows + 1; - second_rect.rows = height_of_each_half; - (first_rect, second_rect) -} - -pub struct Tab { - pub index: usize, - pub position: usize, - pub name: String, - panes: BTreeMap<PaneId, Box<dyn Pane>>, - panes_to_hide: HashSet<PaneId>, - active_terminal: Option<PaneId>, - max_panes: Option<usize>, - full_screen_ws: PositionAndSize, - fullscreen_is_active: bool, - os_api: Box<dyn ServerOsApi>, - pub senders: ThreadSenders, - synchronize_is_active: bool, - should_clear_display_before_rendering: bool, - pub mode_info: ModeInfo, - pub input_mode: InputMode, - pub colors: Palette, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct TabData { - /* subset of fields to publish to plugins */ - pub position: usize, - pub name: String, - pub active: bool, - pub mode_info: ModeInfo, - pub input_mode: InputMode, - pub colors: Palette, -} - -// FIXME: Use a struct that has a pane_type enum, to reduce all of the duplication -pub trait Pane { - fn x(&self) -> usize; - fn y(&self) -> usize; - fn rows(&self) -> usize; - fn columns(&self) -> usize; - fn reset_size_and_position_override(&mut self); - fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize); - fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize); - fn handle_pty_bytes(&mut self, bytes: VteBytes); - fn cursor_coordinates(&self) -> Option<(usize, usize)>; - fn adjust_input_to_terminal(&self, input_bytes: Vec<u8>) -> Vec<u8>; - - fn position_and_size_override(&self) -> Option<PositionAndSize>; - fn should_render(&self) -> bool; - fn set_should_render(&mut self, should_render: bool); - // FIXME: this method is used to trigger a force render to hide widechar problem - // it should be removed when we can handle widechars - fn contains_widechar(&self) -> bool; - fn selectable(&self) -> bool; - fn set_selectable(&mut self, selectable: bool); - fn set_invisible_borders(&mut self, invisible_borders: bool); - fn set_max_height(&mut self, max_height: usize); - fn set_max_width(&mut self, max_width: usize); - fn render(&mut self) -> Option<String>; - fn pid(&self) -> PaneId; - fn reduce_height_down(&mut self, count: usize); - fn increase_height_down(&mut self, count: usize); - fn increase_height_up(&mut self, count: usize); - fn reduce_height_up(&mut self, count: usize); - fn increase_width_right(&mut self, count: usize); - fn reduce_width_right(&mut self, count: usize); - fn reduce_width_left(&mut self, count: usize); - fn increase_width_left(&mut self, count: usize); - fn push_down(&mut self, count: usize); - fn push_right(&mut self, count: usize); - fn pull_left(&mut self, count: usize); - fn pull_up(&mut self, count: usize); - fn scroll_up(&mut self, count: usize); - fn scroll_down(&mut self, count: usize); - fn clear_scroll(&mut self); - fn active_at(&self) -> Instant; - fn set_active_at(&mut self, instant: Instant); - fn cursor_shape_csi(&self) -> String { - "\u{1b}[0 q".to_string() // default to non blinking block - } - - fn right_boundary_x_coords(&self) -> usize { - self.x() + self.columns() - } - fn bottom_boundary_y_coords(&self) -> usize { - self.y() + self.rows() - } - fn is_directly_right_of(&self, other: &dyn Pane) -> bool { - self.x() == other.x() + other.columns() + 1 - } - fn is_directly_left_of(&self, other: &dyn Pane) -> bool { - self.x() + self.columns() + 1 == other.x() - } - fn is_directly_below(&self, other: &dyn Pane) -> bool { - self.y() == other.y() + other.rows() + 1 - } - fn is_directly_above(&self, other: &dyn Pane) -> bool { - self.y() + self.rows() + 1 == other.y() - } - fn horizontally_overlaps_with(&self, other: &dyn Pane) -> bool { - (self.y() >= other.y() && self.y() <= (other.y() + other.rows())) - || ((self.y() + self.rows()) <= (other.y() + other.rows()) - && (self.y() + self.rows()) > other.y()) - || (self.y() <= other.y() && (self.y() + self.rows() >= (other.y() + other.rows()))) - || (other.y() <= self.y() && (other.y() + other.rows() >= (self.y() + self.rows()))) - } - fn get_horizontal_overlap_with(&self, other: &dyn Pane) -> usize { - std::cmp::min(self.y() + self.rows(), other.y() + other.rows()) - - std::cmp::max(self.y(), other.y()) - } - fn vertically_overlaps_with(&self, other: &dyn Pane) -> bool { - (self.x() >= other.x() && self.x() <= (other.x() + other.columns())) - || ((self.x() + self.columns()) <= (other.x() + other.columns()) - && (self.x() + self.columns()) > other.x()) - || (self.x() <= other.x() - && (self.x() + self.columns() >= (other.x() + other.columns()))) - || (other.x() <= self.x() - && (other.x() + other.columns() >= (self.x() + self.columns()))) - } - fn get_vertical_overlap_with(&self, other: &dyn Pane) -> usize { - std::cmp::min(self.x() + self.columns(), other.x() + other.columns()) - - std::cmp::max(self.x(), other.x()) - } - fn position_and_size(&self) -> PositionAndSize { - PositionAndSize { - x: self.x(), - y: self.y(), - columns: self.columns(), - rows: self.rows(), - ..Default::default() - } - } - fn can_increase_height_by(&self, increase_by: usize) -> bool { - self.max_height() - .map(|max_height| self.rows() + increase_by <= max_height) - .unwrap_or(true) - } - fn can_increase_width_by(&self, increase_by: usize) -> bool { - self.max_width() - .map(|max_width| self.columns() + increase_by <= max_width) - .unwrap_or(true) - } - fn can_reduce_height_by(&self, reduce_by: usize) -> bool { - self.rows() > reduce_by && self.rows() - reduce_by >= self.min_height() - } - fn can_reduce_width_by(&self, reduce_by: usize) -> bool { - self.columns() > reduce_by && self.columns() - reduce_by >= self.min_width() - } - fn min_width(&self) -> usize { - MIN_TERMINAL_WIDTH - } - fn min_height(&self) -> usize { - MIN_TERMINAL_HEIGHT - } - fn max_width(&self) -> Option<usize> { - None - } - fn max_height(&self) -> Option<usize> { - None - } - fn invisible_borders(&self) -> bool { - false - } - fn drain_messages_to_pty(&mut self) -> Vec<Vec<u8>> { - // TODO: this is only relevant to terminal panes - // we should probably refactor away from this trait at some point - vec![] - } -} - -impl Tab { - // FIXME: Still too many arguments for clippy to be happy... - #[allow(clippy::too_many_arguments)] - pub fn new( - index: usize, - position: usize, - name: String, - full_screen_ws: &PositionAndSize, - mut os_api: Box<dyn ServerOsApi>, - senders: ThreadSenders, - max_panes: Option<usize>, - pane_id: Option<PaneId>, - mode_info: ModeInfo, - input_mode: InputMode, - colors: Palette, - ) -> Self { - let panes = if let Some(PaneId::Terminal(pid)) = pane_id { - let new_terminal = TerminalPane::new(pid, *full_screen_ws); - os_api.set_terminal_size_using_fd( - new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, - ); - let mut panes: BTreeMap<PaneId, Box<dyn Pane>> = BTreeMap::new(); - panes.insert(PaneId::Terminal(pid), Box::new(new_terminal)); - panes - } else { - BTreeMap::new() - }; - Tab { - index, - position, - panes, - name, - max_panes, - panes_to_hide: HashSet::new(), - active_terminal: pane_id, - full_screen_ws: *full_screen_ws, - fullscreen_is_active: false, - synchronize_is_active: false, - os_api, - senders, - should_clear_display_before_rendering: false, - mode_info, - input_mode, - colors, - } - } - - pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec<RawFd>) { - // TODO: this should be an attribute on Screen instead of full_screen_ws - let free_space = PositionAndSize { - x: 0, - y: 0, - rows: self.full_screen_ws.rows, - columns: self.full_screen_ws.columns, - ..Default::default() - }; - self.panes_to_hide.clear(); - let positions_in_layout = layout.position_panes_in_space(&free_space); - let mut positions_and_size = positions_in_layout.iter(); - for (pane_kind, terminal_pane) in self.panes.iter_mut() { - // for now the layout only supports terminal panes - if let PaneId::Terminal(pid) = pane_kind { - match positions_and_size.next() { - Some((_, position_and_size)) => { - terminal_pane.reset_size_and_position_override(); - if let Some(max_rows) = position_and_size.max_rows { - terminal_pane.set_max_height(max_rows); - } - if let Some(max_columns) = position_and_size.max_columns { - terminal_pane.set_max_width(max_columns); - } - terminal_pane.change_pos_and_size(&position_and_size); - self.os_api.set_terminal_size_using_fd( - *pid, - position_and_size.columns as u16, - position_and_size.rows as u16, - ); - } - None => { - // we filled the entire layout, no room for this pane - // TODO: handle active terminal - self.panes_to_hide.insert(PaneId::Terminal(*pid)); - } - } - } - } - let mut new_pids = new_pids.iter(); - for (layout, position_and_size) in positions_and_size { - // Just a regular terminal - if let Some(plugin) = &layout.plugin { - let (pid_tx, pid_rx) = channel(); - self.senders - .send_to_plugin(PluginInstruction::Load(pid_tx, plugin.clone())) - .unwrap(); - let pid = pid_rx.recv().unwrap(); - let mut new_plugin = PluginPane::new( - pid, - *position_and_size, - self.senders.to_plugin.as_ref().unwrap().clone(), - ); - if let Some(max_rows) = position_and_size.max_rows { - new_plugin.set_max_height(max_rows); - } - if let Some(max_columns) = position_and_size.max_columns { - new_plugin.set_max_width(max_columns); - } - self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin)); - // Send an initial mode update to the newly loaded plugin only! - self.senders - .send_to_plugin(PluginInstruction::Update( - Some(pid), - Event::ModeUpdate(self.mode_info.clone()), - )) - .unwrap(); - } else { - // there are still panes left to fill, use the pids we received in this method - let pid = new_pids.next().unwrap(); // if this crashes it means we got less pids than there are panes in this layout - let new_terminal = TerminalPane::new(*pid, *position_and_size); - self.os_api.set_terminal_size_using_fd( - new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, - ); - self.panes - .insert(PaneId::Terminal(*pid), Box::new(new_terminal)); - } - } - for unused_pid in new_pids { - // this is a bit of a hack and happens because we don't have any central location that - // can query the screen as to how many panes it needs to create a layout - // fixing this will require a bit of an architecture change - self.senders - .send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid))) - .unwrap(); - } - self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next(); - self.render(); - } - pub fn new_pane(&mut self, pid: PaneId) { - self.close_down_to_max_terminals(); - if self.fullscreen_is_active { - self.toggle_active_pane_fullscreen(); - } - if !self.has_panes() { - if let PaneId::Terminal(term_pid) = pid { - let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws); - self.os_api.set_terminal_size_using_fd( - new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, - ); - self.panes.insert(pid, Box::new(new_terminal)); - self.active_terminal = Some(pid); - } - } else { - // TODO: check minimum size of active terminal - - let (_largest_terminal_size, terminal_id_to_split) = self.get_panes().fold( - (0, None), - |(current_largest_terminal_size, current_terminal_id_to_split), - id_and_terminal_to_check| { - let (id_of_terminal_to_check, terminal_to_check) = id_and_terminal_to_check; - let terminal_size = (terminal_to_check.rows() * CURSOR_HEIGHT_WIDTH_RATIO) - * terminal_to_check.columns(); - let terminal_can_be_split = terminal_to_check.columns() >= MIN_TERMINAL_WIDTH - && terminal_to_check.rows() >= MIN_TERMINAL_HEIGHT - && ((terminal_to_check.columns() > terminal_to_check.min_width() * 2) - || (terminal_to_check.rows() > terminal_to_check.min_height() * 2)); - if terminal_can_be_split && terminal_size > current_largest_terminal_size { - (terminal_size, Some(*id_of_terminal_to_check)) - } else { - (current_largest_terminal_size, current_terminal_id_to_split) - } - }, - ); - if terminal_id_to_split.is_none() { - self.senders - .send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty - .unwrap(); - return; // likely no terminal large enough to split - } - let terminal_id_to_split = terminal_id_to_split.unwrap(); - let terminal_to_split = self.panes.get_mut(&terminal_id_to_split).unwrap(); - let terminal_ws = PositionAndSize { - rows: terminal_to_split.rows(), - columns: terminal_to_split.columns(), - x: terminal_to_split.x(), - y: terminal_to_split.y(), - ..Default::default() - }; - if terminal_to_split.rows() * CURSOR_HEIGHT_WIDTH_RATIO > terminal_to_split.columns() - && terminal_to_split.rows() > terminal_to_split.min_height() * 2 - { - if let PaneId::Terminal(term_pid) = pid { - let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws); - let new_terminal = TerminalPane::new(term_pid, bottom_winsize); - self.os_api.set_terminal_size_using_fd( - new_terminal.pid, - bottom_winsize.columns as u16, - bottom_winsize.rows as u16, - ); - terminal_to_split.change_pos_and_size(&top_winsize); - self.panes.insert(pid, Box::new(new_terminal)); - if let PaneId::Terminal(terminal_id_to_split) = terminal_id_to_split { - self.os_api.set_terminal_size_using_fd( - terminal_id_to_split, - top_winsize.columns as u16, - top_winsize.rows as u16, - ); - } - self.active_terminal = Some(pid); - } - } else if terminal_to_split.columns() > terminal_to_split.min_width() * 2 { - if let PaneId::Terminal(term_pid) = pid { - let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws); - let new_terminal = TerminalPane::new(term_pid, right_winsize); - self.os_api.set_terminal_size_using_fd( - new_terminal.pid, - right_winsize.columns as u16, - right_winsize.rows as u16, - ); - terminal_to_split.change_pos_and_size(&left_winsize); - self.panes.insert(pid, Box::new(new_terminal)); - if let PaneId::Terminal(terminal_id_to_split) = terminal_id_to_split { - self.os_api.set_terminal_size_using_fd( - terminal_id_to_split, - left_winsize.columns as u16, - left_winsize.rows as u16, - ); - } - } - } - self.active_terminal = Some(pid); - self.render(); - } - } - pub fn horizontal_split(&mut self, pid: PaneId) { - self.close_down_to_max_terminals(); - if self.fullscreen_is_active { - self.toggle_active_pane_fullscreen(); - } - if !self.has_panes() { - if let PaneId::Terminal(term_pid) = pid { - let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws); - self.os_api.set_terminal_size_using_fd( - new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, - ); - self.panes.insert(pid, Box::new(new_terminal)); - self.active_terminal = Some(pid); - } - } else if let PaneId::Terminal(term_pid) = pid { - // TODO: check minimum size of active terminal - let active_pane_id = &self.get_active_pane_id().unwrap(); - let active_pane = self.panes.get_mut(active_pane_id).unwrap(); - if active_pane.rows() < MIN_TERMINAL_HEIGHT * 2 + 1 { - self.senders - .send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty - .unwrap(); - return; - } - let terminal_ws = PositionAndSize { - x: active_pane.x(), - y: active_pane.y(), - rows: active_pane.rows(), - columns: active_pane.columns(), - ..Default::default() - }; - let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws); - - active_pane.change_pos_and_size(&top_winsize); - - let new_terminal = TerminalPane::new(term_pid, bottom_winsize); - self.os_api.set_terminal_size_using_fd( - new_terminal.pid, - bottom_winsize.columns as u16, - bottom_winsize.rows as u16, - ); - self.panes.insert(pid, Box::new(new_terminal)); - - if let PaneId::Terminal(active_terminal_pid) = active_pane_id { - self.os_api.set_terminal_size_using_fd( - *active_terminal_pid, - top_winsize.columns as u16, - top_winsize.rows as u16, - ); - } - - self.active_terminal = Some(pid); - self.render(); - } - } - pub fn vertical_split(&mut self, pid: PaneId) { - self.close_down_to_max_terminals(); - if self.fullscreen_is_active { - self.toggle_active_pane_fullscreen(); - } - if !self.has_panes() { - if let PaneId::Terminal(term_pid) = pid { - let new_terminal = TerminalPane::new(term_pid, self.full_screen_ws); - self.os_api.set_terminal_size_using_fd( - new_terminal.pid, - new_terminal.columns() as u16, - new_terminal.rows() as u16, - ); - self.panes.insert(pid, Box::new(new_terminal)); - self.active_terminal = Some(pid); - } - } else if let PaneId::Terminal(term_pid) = pid { - // TODO: check minimum size of active terminal - let active_pane_id = &self.get_active_pane_id().unwrap(); - let active_pane = self.panes.get_mut(active_pane_id).unwrap(); - if active_pane.columns() < MIN_TERMINAL_WIDTH * 2 + 1 { - self.senders - .send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty - .unwrap(); - return; - } - let terminal_ws = PositionAndSize { - x: active_pane.x(), - y: active_pane.y(), - rows: active_pane.rows(), - columns: active_pane.columns(), - ..Default::default() - }; - let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws); - - active_pane.change_pos_and_size(&left_winsize); - - let new_terminal = TerminalPane::new(term_pid, right_winsize); - self.os_api.set_terminal_size_using_fd( - new_terminal.pid, - right_winsize.columns as u16, - right_winsize.rows as u16, - ); - self.panes.insert(pid, Box::new(new_terminal)); - - if let PaneId::Terminal(active_terminal_pid) = active_pane_id { - self.os_api.set_terminal_size_using_fd( - *active_terminal_pid, - left_winsize.columns as u16, - left_winsize.rows as u16, - ); - } - - self.active_terminal = Some(pid); - self.render(); - } - } - pub fn get_active_pane(&self) -> Option<&dyn Pane> { - // FIXME: Could use Option::map() here - match self.get_active_pane_id() { - Some(active_pane) => self.panes.get(&active_pane).map(Box::as_ref), - None => None, - } - } - fn get_active_pane_id(&self) -> Option<PaneId> { - self.active_terminal - } - fn get_active_terminal_id(&self) -> Option<RawFd> { - // FIXME: Is there a better way to do this? - if let Some(PaneId::Terminal(pid)) = self.active_terminal { - Some(pid) - } else { - None - } - } - pub fn has_terminal_pid(&self, pid: RawFd) -> bool { - self.panes.contains_key(&PaneId::Terminal(pid)) - } - pub fn handle_pty_bytes(&mut self, pid: RawFd, bytes: VteBytes) { - // if we don't have the terminal in self.terminals it's probably because - // of a race condition where the terminal was created in pty but has not - // yet been created in Screen. These events are currently not buffered, so - // if you're debugging seemingly randomly missing stdout data, this is - // the reason - if let Some(terminal_output) = self.panes.get_mut(&PaneId::Terminal(pid)) { - terminal_output.handle_pty_bytes(bytes); - let messages_to_pty = terminal_output.drain_messages_to_pty(); - for message in messages_to_pty { - self.write_to_pane_id(message, PaneId::Terminal(pid)); - } - } - } - pub fn write_to_terminals_on_current_tab(&mut self, input_bytes: Vec<u8>) { - let pane_ids = self.get_pane_ids(); - pane_ids.iter().for_each(|&pane_id| { - self.write_to_pane_id(input_bytes.clone(), pane_id); - }); - } - pub fn write_to_active_terminal(&mut self, input_bytes: Vec<u8>) { - self.write_to_pane_id(input_bytes, self.get_active_pane_id().unwrap()); - } - pub fn write_to_pane_id(&mut self, input_bytes: Vec<u8>, pane_id: PaneId) { - match pane_id { - PaneId::Terminal(active_terminal_id) => { - let active_terminal = self.panes.get(&pane_id).unwrap(); - let mut adjusted_input = active_terminal.adjust_input_to_terminal(input_bytes); - self.os_api - .write_to_tty_stdin(active_terminal_id, &mut adjusted_input) - .expect("failed to write to terminal"); - self.os_api - .tcdrain(active_terminal_id) - .expect("failed to drain terminal"); - } - PaneId::Plugin(pid) => { - for key in parse_keys(&input_bytes) { - self.senders - .send_to_plugin(PluginInstruction::Update(Some(pid), Event::KeyPress(key))) - .unwrap() - } - } - } - } - pub fn get_active_terminal_cursor_position(&self) -> Option<(usize, usize)> { - // (x, y) - let active_terminal = &self.get_active_pane()?; - active_terminal - .cursor_coordinates() - .map(|(x_in_terminal, y_in_terminal)| { - let x = active_terminal.x() + x_in_terminal; - let y = active_terminal.y() + y_in_terminal; - (x, y) - }) - } - pub fn toggle_active_pane_fullscreen(&mut self) { - if let Some(active_pane_id) = self.get_active_pane_id() { - if self.fullscreen_is_active { - for terminal_id in self.panes_to_hide.iter() { - self.panes - .get_mut(terminal_id) - .unwrap() - .set_should_render(true); - } - self.panes_to_hide.clear(); - let active_terminal = self.panes.get_mut(&active_pane_id).unwrap(); - active_terminal.reset_size_and_position_override(); - } else { - let panes = self.get_panes(); - let pane_ids_to_hide = - panes.filter_map( - |(&id, _)| { - if id != active_pane_id { - Some(id) - } else { - None - } - }, - ); - self.panes_to_hide = pane_ids_to_hide.collect(); - if self.panes_to_hide.is_empty() { - // nothing to do, pane is already as fullscreen as it can be, let's bail - return; - } else { - let active_terminal = self.panes.get_mut(&active_pane_id).unwrap(); - active_terminal.override_size_and_position( - self.full_screen_ws.x, - self.full_screen_ws.y, - &self.full_screen_ws, - ); - } - } - let active_terminal = self.panes.get(&active_pane_id).unwrap(); - if let PaneId::Terminal(active_pid) = active_pane_id { - self.os_api.set_terminal_size_using_fd( - active_pid, - active_terminal.columns() as u16, - active_terminal.rows() as u16, - ); - } - self.render(); - self.toggle_fullscreen_is_active(); - } - } - pub fn toggle_fullscreen_is_active(&mut self) { - self.fullscreen_is_active = !self.fullscreen_is_active; - } - pub fn set_force_render(&mut self) { - for pane in self.panes.values_mut() { - pane.set_should_render(true); - } - } - pub fn is_sync_panes_active(&self) -> bool { - self.synchronize_is_active - } - pub fn toggle_sync_panes_is_active(&mut self) { - self.synchronize_is_active = !self.synchronize_is_active; - } - pub fn panes_contain_widechar(&self) -> bool { - self.panes.iter().any(|(_, p)| p.contains_widechar()) - } - pub fn render(&mut self) { - if self.active_terminal.is_none() { - // we might not have an active terminal if we closed the last pane - // in that case, we should not render as the app is exiting - return; - } - // if any pane contain widechar, all pane in the same row will messup. We should render them every time - // FIXME: remove this when we can handle widechars correctly - if self.panes_contain_widechar() { - self.set_force_render() - } - let mut output = String::new(); - let mut boundaries = Boundaries::new( - self.full_screen_ws.columns as u16, - self.full_screen_ws.rows as u16, - ); - let hide_cursor = "\u{1b}[?25l"; - output.push_str(hide_cursor); - if self.should_clear_display_before_rendering { - let clear_display = "\u{1b}[2J"; - output.push_str(clear_display); - self.should_clear_display_before_rendering = false; - } - for (kind, pane) in self.panes.iter_mut() { - if !self.panes_to_hide.contains(&pane.pid()) { - match self.active_terminal.unwrap() == pane.pid() { - true => { - pane.set_active_at(Instant::now()); - boundaries.add_rect(pane.as_ref(), self.mode_info.mode, Some(self.colors)) - } - false => boundaries.add_rect(pane.as_ref(), self.mode_info.mode, None), - } - if let Some(vte_output) = pane.render() { - let vte_output = if let PaneId::Terminal(_) = kind { - vte_output - } else { - adjust_to_size(&vte_output, pane.rows(), pane.columns()) - }; - // FIXME: Use Termion for cursor and style clearing? - output.push_str(&format!( - "\u{1b}[{};{}H\u{1b}[m{}", - pane.y() + 1, - pane.x() + 1, - vte_output - )); - } - } - } - - // TODO: only render (and calculate) boundaries if there was a resize - output.push_str(&boundaries.vte_output()); - - match self.get_active_terminal_cursor_position() { - Some((cursor_position_x, cursor_position_y)) => { - let show_cursor = "\u{1b}[?25h"; - let change_cursor_shape = self.get_active_pane().unwrap().cursor_shape_csi(); - let goto_cursor_position = &format!( - "\u{1b}[{};{}H\u{1b}[m{}", - cursor_position_y + 1, - cursor_position_x + 1, - change_cursor_shape - ); // goto row/col - output.push_str(show_cursor); - output.push_str(goto_cursor_position); - } - None => { - let hide_cursor = "\u{1b}[?25l"; - output.push_str(hide_cursor); - } - } - - self.senders - .send_to_server(ServerInstruction::Render(Some(output))) - .unwrap(); - } - fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> { - self.panes.iter() - } - // FIXME: This is some shameful duplication... - fn get_selectable_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> { - self.panes.iter().filter(|(_, p)| p.selectable()) - } - fn has_panes(&self) -> bool { - let mut all_terminals = self.get_panes(); - all_terminals.next().is_some() - } - fn has_selectable_panes(&self) -> bool { - let mut all_terminals = self.get_selectable_panes(); - all_terminals.next().is_some() - } - fn next_active_pane(&self, panes: Vec<PaneId>) -> Option<PaneId> { - panes - .into_iter() - .rev() - .find(|pid| self.panes.get(pid).unwrap().selectable()) - } - fn pane_ids_directly_left_of(&self, id: &PaneId) -> Option<Vec<PaneId>> { - let mut ids = vec![]; - let terminal_to_check = self.panes.get(id).unwrap(); - if terminal_to_check.x() == 0 { - return None; - } - for (&pid, terminal) in self.get_panes() { - if terminal.x() + terminal.columns() == terminal_to_check.x() - 1 { - ids.push(pid); - } - } - if ids.is_empty() { - None - } else { - Some(ids) - } - } - fn pane_ids_directly_right_of(&self, id: &PaneId) -> Option<Vec<PaneId>> { - let mut ids = vec![]; - let terminal_to_check = self.panes.get(id).unwrap(); - for (&pid, terminal) in self.get_panes() { - if terminal.x() == terminal_to_check.x() + terminal_to_check.columns() + 1 { - ids.push(pid); - } - } - if ids.is_empty() { - None - } else { - Some(ids) - } - } - fn pane_ids_directly_below(&self, id: &PaneId) -> Option<Vec<PaneId>> { - let mut ids = vec![]; - let terminal_to_check = self.panes.get(id).unwrap(); - for (&pid, terminal) in self.get_panes() { - if terminal.y() == terminal_to_check.y() + terminal_to_check.rows() + 1 { - ids.push(pid); - } - } - if ids.is_empty() { - None - } else { - Some(ids) - } - } - fn pane_ids_directly_above(&self, id: &PaneId) -> Option<Vec<PaneId>> { - let mut ids = vec![]; - let terminal_to_check = self.panes.get(id).unwrap(); - for (&pid, terminal) in self.get_panes() { - if terminal.y() + terminal.rows() + 1 == terminal_to_check.y() { - ids.push(pid); - } - } - if ids.is_empty() { - None - } else { - Some(ids) - } - } - fn panes_top_aligned_with_pane(&self, pane: &dyn Pane) -> Vec<&dyn Pane> { - self.panes - .keys() - .map(|t_id| self.panes.get(&t_id).unwrap().as_ref()) - .filter(|terminal| terminal.pid() != pane.pid() && terminal.y() == pane.y()) - .collect() - } - fn panes_bottom_aligned_with_pane(&self, pane: &dyn Pane) -> Vec<&dyn Pane> { - self.panes - .keys() - .map(|t_id| self.panes.get(&t_id).unwrap().as_ref()) - .filter(|terminal| { - terminal.pid() != pane.pid() - && terminal.y() + terminal.rows() == pane.y() + pane.rows() - }) - .collect() - } - fn panes_right_aligned_with_pane(&self, pane: &dyn Pane) -> Vec<&dyn Pane> { - self.panes - .keys() - .map(|t_id| self.panes.get(&t_id).unwrap().as_ref()) - .filter(|terminal| { - terminal.pid() != pane.pid() - && terminal.x() + terminal.columns() == pane.x() + pane.columns() - }) - .collect() - } - fn panes_left_aligned_with_pane(&self, pane: &dyn Pane) -> Vec<&dyn Pane> { - self.panes - .keys() - .map(|t_id| self.panes.get(&t_id).unwrap().as_ref()) - .filter(|terminal| terminal.pid() != pane.pid() && terminal.x() == pane.x()) - .collect() - } - fn right_aligned_contiguous_panes_above( - &self, - id: &PaneId, - terminal_borders_to_the_right: &HashSet<usize>, - ) -> BorderAndPaneIds { - let mut terminals = vec![]; - let terminal_to_check = self - .panes - .get(id) - .expect("terminal id does not exist") - .as_ref(); - let mut right_aligned_terminals = self.panes_right_aligned_with_pane(terminal_to_check); - // terminals that are next to each other up to current - right_aligned_terminals.sort_by_key(|a| Reverse(a.y())); - for terminal in right_aligned_terminals { - let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check); - if terminal.y() + terminal.rows() + 1 == terminal_to_check.y() { - terminals.push(terminal); - } - } - // top-most border aligned with a pane border to the right - let mut top_resize_border = 0; - for terminal in &terminals { - let bottom_terminal_boundary = terminal.y() + terminal.rows(); - if terminal_borders_to_the_right - .get(&(bottom_terminal_boundary + 1)) - .is_some() - && top_resize_border < bottom_terminal_boundary - { - top_resize_border = bottom_terminal_boundary + 1; - } - } - terminals.retain(|terminal| terminal.y() >= top_resize_border); - // if there are no adjacent panes to resize, we use the border of the main pane we're - // resizing - let top_resize_border = if terminals.is_empty() { - terminal_to_check.y() - } else { - top_resize_border - }; - let terminal_ids: Vec<PaneId> = terminals.iter().map(|t| t.pid()).collect(); - (top_resize_border, terminal_ids) - } - fn right_aligned_contiguous_panes_below( - &self, - id: &PaneId, - terminal_borders_to_the_right: &HashSet<usize>, - ) -> BorderAndPaneIds { - let mut terminals = vec![]; - let terminal_to_check = self - .panes - .get(id) - .expect("terminal id does not exist") - .as_ref(); - let mut right_aligned_terminals = self.panes_right_aligned_with_pane(terminal_to_check); - // terminals that are next to each other up to current - right_aligned_terminals.sort_by_key(|a| a.y()); - for terminal in right_aligned_terminals { - let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check); - if terminal.y() == terminal_to_check.y() + terminal_to_check.rows() + 1 { - terminals.push(terminal); - } - } - // bottom-most border aligned with a pane border to the right - let mut bottom_resize_border = self.full_screen_ws.rows; - for terminal in &terminals { - let top_terminal_boundary = terminal.y(); - if terminal_borders_to_the_right - .get(&(top_terminal_boundary)) - .is_some() - && top_terminal_boundary < bottom_resize_border - { - bottom_resize_border = top_terminal_boundary; - } - } - terminals.retain(|terminal| terminal.y() + terminal.rows() <= bottom_resize_border); - // if there are no adjacent panes to resize, we use the border of the main pane we're - // resizing - let bottom_resize_border = if terminals.is_empty() { - terminal_to_check.y() + terminal_to_check.rows() - } else { - bottom_resize_border - }; - let terminal_ids: Vec<PaneId> = terminals.iter().map(|t| t.pid()).collect(); - (bottom_resize_border, terminal_ids) - } - fn left_aligned_contiguous_panes_above( - &self, - id: &PaneId, - terminal_borders_to_the_left: &HashSet<usize>, - ) -> BorderAndPaneIds { - let mut terminals = vec![]; - let terminal_to_check = self - .panes - .get(id) - .expect("terminal id does not exist") - .as_ref(); - let mut left_aligned_terminals = self.panes_left_aligned_with_pane(terminal_to_check); - // terminals that are next to each other up to current - left_aligned_terminals.sort_by_key(|a| Reverse(a.y())); - for terminal in left_aligned_terminals { - let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check); - if terminal.y() + terminal.rows() + 1 == terminal_to_check.y() { - terminals.push(terminal); - } - } - // top-most border aligned with a pane border to the right - let mut top_resize_border = 0; - for terminal in &terminals { - let bottom_terminal_boundary = terminal.y() + terminal.rows(); - if terminal_borders_to_the_left - .get(&(bottom_terminal_boundary + 1)) - .is_some() - && top_resize_border < bottom_terminal_boundary - { - top_resize_border = bottom_terminal_boundary + 1; - } - } - terminals.retain(|terminal| terminal.y() >= top_resize_border); - // if there are no adjacent panes to resize, we use the border of the main pane we're - // resizing - let top_resize_border = if terminals.is_empty() { - terminal_to_check.y() - } else { - top_resize_border - }; - let terminal_ids: Vec<PaneId> = terminals.iter().map(|t| t.pid()).collect(); - (top_resize_border, terminal_ids) - } - fn left_aligned_contiguous_panes_below( - &self, - id: &PaneId, - terminal_borders_to_the_left: &HashSet<usize>, - ) -> BorderAndPaneIds { - let mut terminals = vec![]; - let terminal_to_check = self - .panes - .get(id) - .expect("terminal id does not exist") - .as_ref(); - let mut left_aligned_terminals = self.panes_left_aligned_with_pane(terminal_to_check); - // terminals that are next to each other up to current - left_aligned_terminals.sort_by_key(|a| a.y()); - for terminal in left_aligned_terminals { - let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check); - if terminal.y() == terminal_to_check.y() + terminal_to_check.rows() + 1 { - terminals.push(terminal); - } - } - // bottom-most border aligned with a pane border to the left - let mut bottom_resize_border = self.full_screen_ws.rows; - for terminal in &terminals { - let top_terminal_boundary = terminal.y(); - if terminal_borders_to_the_left - .get(&(top_terminal_boundary)) - .is_some() - && top_terminal_boundary < bottom_resize_border - { - bottom_resize_border = top_terminal_boundary; - } - } - terminals.retain(|terminal| { - // terminal.y() + terminal.rows() < bottom_resize_border - terminal.y() + terminal.rows() <= bottom_resize_border - }); - // if there are no adjacent panes to resize, we use the border of the main pane we're - // resizing - let bottom_resize_border = if terminals.is_empty() { - terminal_to_check.y() + terminal_to_check.rows() - } else { - bottom_resize_border - }; - let terminal_ids: Vec<PaneId> = terminals.iter().map(|t| t.pid()).collect(); - (bottom_resize_border, terminal_ids) - } - fn top_aligned_contiguous_panes_to_the_left( - &self, - id: &PaneId, - terminal_borders_above: &HashSet<usize>, - ) -> BorderAndPaneIds { - let mut terminals = vec![]; - let terminal_to_check = self - .panes - .get(id) - .expect("terminal id does not exist") - .as_ref(); - let mut top_aligned_terminals = self.panes_top_aligned_with_pane(terminal_to_check); - // terminals that are next to each other up to current - top_aligned_terminals.sort_by_key(|a| Reverse(a.x())); - for terminal in top_aligned_terminals { - let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check); - if terminal.x() + terminal.columns() + 1 == terminal_to_check.x() { - terminals.push(terminal); - } - } - // leftmost border aligned with a pane border above - let mut left_resize_border = 0; - for terminal in &terminals { - let right_terminal_boundary = terminal.x() + terminal.columns(); - if terminal_borders_above - .get(&(right_terminal_boundary + 1)) - .is_some() - && left_resize_border < right_terminal_boundary - { - left_resize_border = right_terminal_boundary + 1; - } - } - terminals.retain(|terminal| terminal.x() >= left_resize_border); - // if there are no adjacent panes to resize, we use the border of the main pane we're - // resizing - let left_resize_border = if terminals.is_empty() { - terminal_to_check.x() - } else { - left_resize_border - }; - let terminal_ids: Vec<PaneId> = terminals.iter().map(|t| t.pid()).collect(); - (left_resize_border, terminal_ids) - } - fn top_aligned_contiguous_panes_to_the_right( - &self, - id: &PaneId, - terminal_borders_above: &HashSet<usize>, - ) -> BorderAndPaneIds { - let mut terminals = vec![]; - let terminal_to_check = self.panes.get(id).unwrap().as_ref(); - let mut top_aligned_terminals = self.panes_top_aligned_with_pane(terminal_to_check); - // terminals that are next to each other up to current - top_aligned_terminals.sort_by_key(|a| a.x()); - for terminal in top_aligned_terminals { - let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check); - if terminal.x() == terminal_to_check.x() + terminal_to_check.columns() + 1 { - terminals.push(terminal); - } - } - // rightmost border aligned with a pane border above - let mut right_resize_border = self.full_screen_ws.columns; - for terminal in &terminals { - let left_terminal_boundary = terminal.x(); - if terminal_borders_above - .get(&left_terminal_boundary) - .is_some() - && right_resize_border > left_terminal_boundary - { - right_resize_border = left_terminal_boundary; - } - } - terminals.retain(|terminal| terminal.x() + terminal.columns() <= right_resize_border); - // if there are no adjacent panes to resize, we use the border of the main pane we're - // resizing - let right_resize_border = if terminals.is_empty() { - terminal_to_check.x() + terminal_to_check.columns() - } else { - right_resize_border - }; - let terminal_ids: Vec<PaneId> = terminals.iter().map(|t| t.pid()).collect(); - (right_resize_border, terminal_ids) - } - fn bottom_aligned_contiguous_panes_to_the_left( - &self, - id: &PaneId, - terminal_borders_below: &HashSet<usize>, - ) -> BorderAndPaneIds { - let mut terminals = vec![]; - let terminal_to_check = self.panes.get(id).unwrap().as_ref(); - let mut bottom_aligned_terminals = self.panes_bottom_aligned_with_pane(terminal_to_check); - bottom_aligned_terminals.sort_by_key(|a| Reverse(a.x())); - // terminals that are next to each other up to current - for terminal in bottom_aligned_terminals { - let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check); - if terminal.x() + terminal.columns() + 1 == terminal_to_check.x() { - terminals.push(terminal); - } - } - // leftmost border aligned with a pane border above - let mut left_resize_border = 0; - for terminal in &terminals { - let right_terminal_boundary = terminal.x() + terminal.columns(); - if terminal_borders_below - .get(&(right_terminal_boundary + 1)) - .is_some() - && left_resize_border < right_terminal_boundary - { - left_resize_border = right_terminal_boundary + 1; - } - } - terminals.retain(|terminal| terminal.x() >= left_resize_border); - // if there are no adjacent panes to resize, we use the border of the main pane we're - // resizing - let left_resize_border = if terminals.is_empty() { - terminal_to_check.x() - } else { - left_resize_border - }; - let terminal_ids: Vec<PaneId> = terminals.iter().map(|t| t.pid()).collect(); - (left_resize_border, terminal_ids) - } - fn bottom_aligned_contiguous_panes_to_the_right( - &self, - id: &PaneId, - terminal_borders_below: &HashSet<usize>, - ) -> BorderAndPaneIds { - let mut terminals = vec![]; - let terminal_to_check = self.panes.get(id).unwrap().as_ref(); - let mut bottom_aligned_terminals = self.panes_bottom_aligned_with_pane(terminal_to_check); - bottom_aligned_terminals.sort_by_key(|a| a.x()); - // terminals that are next to each other up to current - for terminal in bottom_aligned_terminals { - let terminal_to_check = terminals.last().unwrap_or(&terminal_to_check); - if terminal.x() == terminal_to_check.x() + terminal_to_check.columns() + 1 { - terminals.push(terminal); - } - } - // leftmost border aligned with a pane border above - let mut right_resize_border = self.full_screen_ws.columns; - for terminal in &terminals { - let left_terminal_boundary = terminal.x(); - if terminal_borders_below - .get(&left_terminal_boundary) - .is_some() - && right_resize_border > left_terminal_boundary - { - right_resize_border = left_terminal_boundary; - } - } - terminals.retain(|terminal| terminal.x() + terminal.columns() <= right_resize_border); - let right_resize_border = if terminals.is_empty() { - terminal_to_check.x() + terminal_to_check.columns() - } else { - right_resize_border - }; - let terminal_ids: Vec<PaneId> = terminals.iter().map(|t| t.pid()).collect(); - (right_resize_border, terminal_ids) - } - fn reduce_pane_height_down(&mut self, id: &PaneId, count: usize) { - let terminal = self.panes.get_mut(id).unwrap(); - terminal.reduce_height_down(count); - if let PaneId::Terminal(pid) = id { - self.os_api.set_terminal_size_using_fd( - *pid, - terminal.columns() as u16, - terminal.rows() as u16, - ); - } - } - fn reduce_pane_height_up(&mut self, id: &PaneId, count: usize) { - let terminal = self.panes.get_mut(id).unwrap(); - terminal.reduce_height_up(count); - if let PaneId::Terminal(pid) = id { - self.os_api.set_terminal_size_using_fd( - *pid, - terminal.columns() as u16, - terminal.rows() as u16, - ); - } - } - fn increase_pane_height_down(&mut self, id: &PaneId, count: usize) { - let terminal = self.panes.get_mut(id).unwrap(); - terminal.increase_height_down(count); - if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.set_terminal_size_using_fd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ); - } - } - fn increase_pane_height_up(&mut self, id: &PaneId, count: usize) { - let terminal = self.panes.get_mut(id).unwrap(); - terminal.increase_height_up(count); - if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.set_terminal_size_using_fd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ); - } - } - fn increase_pane_width_right(&mut self, id: &PaneId, count: usize) { - let terminal = self.panes.get_mut(id).unwrap(); - terminal.increase_width_right(count); - if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.set_terminal_size_using_fd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ); - } - } - fn increase_pane_width_left(&mut self, id: &PaneId, count: usize) { - let terminal = self.panes.get_mut(id).unwrap(); - terminal.increase_width_left(count); - if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.set_terminal_size_using_fd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ); - } - } - fn reduce_pane_width_right(&mut self, id: &PaneId, count: usize) { - let terminal = self.panes.get_mut(id).unwrap(); - terminal.reduce_width_right(count); - if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.set_terminal_size_using_fd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ); - } - } - fn reduce_pane_width_left(&mut self, id: &PaneId, count: usize) { - let terminal = self.panes.get_mut(id).unwrap(); - terminal.reduce_width_left(count); - if let PaneId::Terminal(pid) = terminal.pid() { - self.os_api.set_terminal_size_using_fd( - pid, - terminal.columns() as u16, - terminal.rows() as u16, - ); - } - } - fn pane_is_between_vertical_borders( - &self, - id: &PaneId, - left_border_x: usize, - right_border_x: usize, - ) -> bool { - let terminal = self - .panes - .get(id) - .expect("could not find terminal to check between borders"); - terminal.x() >= left_border_x && terminal.x() + terminal.columns() <= right_border_x - } - fn pane_is_between_horizontal_borders( - &self, - id: &PaneId, - top_border_y: usize, - bottom_border_y: usize, - ) -> bool { - let terminal = self - .panes - .get(id) - .expect("could not find terminal to check between borders"); - terminal.y() >= top_border_y && terminal.y() + terminal.rows() <= bottom_border_y - } - fn reduce_pane_and_surroundings_up(&mut self, id: &PaneId, count: usize) { - let mut terminals_below = self - .pane_ids_directly_below(&id) - .expect("can't reduce pane size up if there are no terminals below"); - let terminal_borders_below: HashSet<usize> = terminals_below - .iter() - .map(|t| self.panes.get(t).unwrap().x()) - .collect(); - let (left_resize_border, terminals_to_the_left) = - self.bottom_aligned_contiguous_panes_to_the_left(&id, &terminal_borders_below); - let (right_resize_border, terminals_to_the_right) = - self.bottom_aligned_contiguous_panes_to_the_right(&id, &terminal_borders_below); - terminals_below.retain(|t| { - self.pane_is_between_vertical_borders(t, left_resize_border, right_resize_border) - }); - self.reduce_pane_height_up(&id, count); - for terminal_id in terminals_below { - self.increase_pane_height_up(&terminal_id, count); - } - for terminal_id in terminals_to_the_left - .iter() - .chain(terminals_to_the_right.iter()) - { - self.reduce_pane_height_up(&terminal_id, count); - } - } - fn reduce_pane_and_surroundings_down(&mut self, id: &PaneId, count: usize) { - let mut terminals_above = self - .pane_ids_directly_above(&id) - .expect("can't reduce pane size down if there are no terminals above"); - let terminal_borders_above: HashSet<usize> = terminals_above - .iter() - .map(|t| self.panes.get(t).unwrap().x()) - .collect(); - let (left_resize_border, terminals_to_the_left) = - self.top_aligned_contiguous_panes_to_the_left(&id, &terminal_borders_above); - let (right_resize_border, terminals_to_the_right) = - self.top_aligned_contiguous_panes_to_the_right(&id, &terminal_borders_above); - terminals_above.retain(|t| { - self.pane_is_between_vertical_borders(t, left_resize_border, right_resize_border) - }); - self.reduce_pane_height_down(&id, count); - for terminal_id in terminals_above { - self.increase_pane_height_down(&terminal_id, count); - } - for terminal_id in terminals_to_the_left - .iter() - .chain(terminals_to_the_right.iter()) - { - self.reduce_pane_height_down(&terminal_id, count); - } - } - fn reduce_pane_and_surroundings_right(&mut self, id: &PaneId, count: usize) { - let mut terminals_to_the_left = self - .pane_ids_directly_left_of(&id) - .expect("can't reduce pane size right if there are no terminals to the left"); - let terminal_borders_to_the_left: HashSet<usize> = terminals_to_the_left - .iter() - .map(|t| self.panes.get(t).unwrap().y()) - .collect(); - let (top_resize_border, terminals_above) = - self.left_aligned_contiguous_panes_above(&id, &terminal_borders_to_the_left); - let (bottom_resize_border, terminals_below) = - self.left_aligned_contiguous_panes_below(&id, &terminal_borders_to_the_left); - terminals_to_the_left.retain(|t| { - self.pane_is_between_horizontal_borders(t, top_resize_border, bottom_resize_border) - }); - self.reduce_pane_width_right(&id, count); - for terminal_id in terminals_to_the_left { - self.increase_pane_width_right(&terminal_id, count); - } - for terminal_id in terminals_above.iter().chain(terminals_below.iter()) { - self.reduce_pane_width_right(&terminal_id, count); - } - } - fn reduce_pane_and_surroundings_left(&mut self, id: &PaneId, count: usize) { - let mut terminals_to_the_right = self - .pane_ids_directly_right_of(&id) - .expect("can't reduce pane size left if there are no terminals to the right"); - let terminal_borders_to_the_right: HashSet<usize> = terminals_to_the_right - .iter() - .map(|t| self.panes.get(t).unwrap().y()) - .collect(); - let (top_resize_border, terminals_above) = - self.right_aligned_contiguous_panes_above(&id, &terminal_borders_to_the_right); - let (bottom_resize_border, terminals_below) = - self.right_aligned_contiguous_panes_below(&id, &terminal_borders_to_the_right); - terminals_to_the_right.retain(|t| { - self.pane_is_between_horizontal_borders(t, top_resize_border, bottom_resize_border) - }); - self.reduce_pane_width_left(&id, count); - for terminal_id in terminals_to_the_right { - self.increase_pane_width_left(&terminal_id, count); - } - for terminal_id in terminals_above.iter().chain(terminals_below.iter()) { - self.reduce_pane_width_left(&terminal_id, count); - } - } - fn increase_pane_and_surroundings_up(&mut self, id: &PaneId, count: usize) { - let mut terminals_above = self - .pane_ids_directly_above(&id) - .expect("can't increase pane size up if there are no terminals above"); - let terminal_borders_above: HashSet<usize> = terminals_above - .iter() - .map(|t| self.panes.get(t).unwrap().x()) - .collect(); - let (left_resize_border, terminals_to_the_left) = - self.top_aligned_contiguous_panes_to_the_left(&id, &terminal_borders_above); - let (right_resize_border, terminals_to_the_right) = - self.top_aligned_contiguous_panes_to_the_right(&id, &terminal_borders_above); - terminals_above.retain(|t| { - self.pane_is_between_vertical_borders(t, left_resize_border, right_resize_border) - }); - self.increase_pane_height_up(&id, count); - for terminal_id in terminals_above { - self.reduce_pane_height_up(&terminal_id, count); - } - for terminal_id in terminals_to_the_left - .iter() - .chain(terminals_to_the_right.iter()) - { - self.increase_pane_height_up(&terminal_id, count); - } - } - fn increase_pane_and_surroundings_down(&mut self, id: &PaneId, count: usize) { - let mut terminals_below = self - .pane_ids_directly_below(&id) - .expect("can't increase pane size down if there are no terminals below"); - let terminal_borders_below: HashSet<usize> = terminals_below - .iter() - .map(|t| self.panes.get(t).unwrap().x()) - .collect(); - let (left_resize_border, terminals_to_the_left) = - self.bottom_aligned_contiguous_panes_to_the_left(&id, &terminal_borders_below); - let (right_resize_border, terminals_to_the_right) = - self.bottom_aligned_contiguous_panes_to_the_right(&id, &terminal_borders_below); - terminals_below.retain(|t| { - self.pane_is_between_vertical_borders(t, left_resize_border, right_resize_border) - }); - self.increase_pane_height_down(&id, count); - for terminal_id in terminals_below { - self.reduce_pane_height_down(&terminal_id, count); - } - for terminal_id in terminals_to_the_left - .iter() - .chain(terminals_to_the_right.iter()) - { - self.increase_pane_height_down(&terminal_id, count); - } - } - fn increase_pane_and_surroundings_right(&mut self, id: &PaneId, count: usize) { - let mut terminals_to_the_right = self - .pane_ids_directly_right_of(&id) - .expect("can't increase pane size right if there are no terminals to the right"); - let terminal_borders_to_the_right: HashSet<usize> = terminals_to_the_right - .iter() - .map(|t| self.panes.get(t).unwrap().y()) - .collect(); - let (top_resize_border, terminals_above) = - self.right_aligned_contiguous_panes_above(&id, &terminal_borders_to_the_right); - let (bottom_resize_border, terminals_below) = - self.right_aligned_contiguous_panes_below(&id, &terminal_borders_to_the_right); - terminals_to_the_right.retain(|t| { - self.pane_is_between_horizontal_borders(t, top_resize_border, bottom_resize_border) - }); - self.increase_pane_width_right(&id, count); - for terminal_id in terminals_to_the_right { - self.reduce_pane_width_right(&terminal_id, count); - } - for terminal_id in terminals_above.iter().chain(terminals_below.iter()) { - self.increase_pane_width_right(&terminal_id, count); - } - } - fn increase_pane_and_surroundings_left(&mut self, id: &PaneId, count: usize) { - let mut terminals_to_the_left = self - .pane_ids_directly_left_of(&id) - .expect("can't increase pane size right if there are no terminals to the right"); - let terminal_borders_to_the_left: HashSet<usize> = terminals_to_the_left - .iter() - .map(|t| self.panes.get(t).unwrap().y()) - .collect(); - let (top_resize_border, terminals_above) = - self.left_aligned_contiguous_panes_above(&id, &terminal_borders_to_the_left); - let (bottom_resize_border, terminals_below) = - self.left_aligned_contiguous_panes_below(&id, &terminal_borders_to_the_left); - terminals_to_the_left.retain(|t| { - self.pane_is_between_horizontal_borders(t, top_resize_border, bottom_resize_border) - }); - self.increase_pane_width_left(&id, count); - for terminal_id in terminals_to_the_left { - self.reduce_pane_width_left(&terminal_id, count); - } - for terminal_id in terminals_above.iter().chain(terminals_below.iter()) { - self.increase_pane_width_left(&terminal_id, count); - } - } - fn can_increase_pane_and_surroundings_right( - &self, - pane_id: &PaneId, - increase_by: usize, - ) -> bool { - let pane = self.panes.get(pane_id).unwrap(); - let can_increase_pane_size = pane - .max_width() - .map(|max_width| pane.columns() + increase_by <= max_width) - .unwrap_or(true); // no max width, increase to your heart's content - if !can_increase_pane_size { - return false; - } - let mut new_pos_and_size_for_pane = pane.position_and_size(); - new_pos_and_size_for_pane.columns += increase_by; - - if let Some(panes_to_the_right) = self.pane_ids_directly_right_of(&pane_id) { - return panes_to_the_right.iter().all(|id| { - let p = self.panes.get(id).unwrap(); - p.columns() > increase_by && p.columns() - increase_by >= p.min_width() - }); - } else { - false - } - } - fn can_increase_pane_and_surroundings_left( - &self, - pane_id: &PaneId, - increase_by: usize, - ) -> bool { - let pane = self.panes.get(pane_id).unwrap(); - let can_increase_pane_size = pane - .max_width() - .map(|max_width| pane.columns() + increase_by <= max_width) - .unwrap_or(true); // no max width, increase to your heart's content - if !can_increase_pane_size { - return false; - } - let mut new_pos_and_size_for_pane = pane.position_and_size(); - new_pos_and_size_for_pane.x = new_pos_and_size_for_pane.x.saturating_sub(increase_by); - - if let Some(panes_to_the_left) = self.pane_ids_directly_left_of(&pane_id) { - return panes_to_the_left.iter().all(|id| { - let p = self.panes.get(id).unwrap(); - p.columns() > increase_by && p.columns() - increase_by >= p.min_width() - }); - } else { - false - } - } - fn can_increase_pane_and_surroundings_down( - &self, - pane_id: &PaneId, - increase_by: usize, - ) -> bool { - let pane = self.panes.get(pane_id).unwrap(); - let can_increase_pane_size = pane - .max_height() - .map(|max_height| pane.rows() + increase_by <= max_height) - .unwrap_or(true); // no max width, increase to your heart's content - if !can_increase_pane_size { - return false; - } - let mut new_pos_and_size_for_pane = pane.position_and_size(); - new_pos_and_size_for_pane.rows += increase_by; - - if let Some(panes_below) = self.pane_ids_directly_below(&pane_id) { - return panes_below.iter().all(|id| { - let p = self.panes.get(id).unwrap(); - p.rows() > increase_by && p.rows() - increase_by >= p.min_height() - }); - } else { - false - } - } - fn can_increase_pane_and_surroundings_up(&self, pane_id: &PaneId, increase_by: usize) -> bool { - let pane = self.panes.get(pane_id).unwrap(); - let can_increase_pane_size = pane - .max_height() - .map(|max_height| pane.rows() + increase_by <= max_height) - .unwrap_or(true); // no max width, increase to your heart's content - if !can_increase_pane_size { - return false; - } - let mut new_pos_and_size_for_pane = pane.position_and_size(); - new_pos_and_size_for_pane.y = new_pos_and_size_for_pane.y.saturating_sub(increase_by); - - if let Some(panes_above) = self.pane_ids_directly_above(&pane_id) { - return panes_above.iter().all(|id| { - let p = self.panes.get(id).unwrap(); - p.rows() > increase_by && p.rows() - increase_by >= p.min_height() - }); - } else { - false - } - } - fn can_reduce_pane_and_surroundings_right(&self, pane_id: &PaneId, reduce_by: usize) -> bool { - let pane = self.panes.get(pane_id).unwrap(); - let pane_columns = pane.columns(); - let can_reduce_pane_size = - pane_columns > reduce_by && pane_columns - reduce_by >= pane.min_width(); - if !can_reduce_pane_size { - return false; - } - if let Some(panes_to_the_left) = self.pane_ids_directly_left_of(&pane_id) { - return panes_to_the_left.iter().all(|id| { - let p = self.panes.get(id).unwrap(); - p.max_width() - .map(|max_width| p.columns() + reduce_by <= max_width) - .unwrap_or(true) // no max width, increase to your heart's content - }); - } else { - false - } - } - fn can_reduce_pane_and_surroundings_left(&self, pane_id: &PaneId, reduce_by: usize) -> bool { - let pane = self.panes.get(pane_id).unwrap(); - let pane_columns = pane.columns(); - let can_reduce_pane_size = - pane_columns > reduce_by && pane_columns - reduce_by >= pane.min_width(); - if !can_reduce_pane_size { - return false; - } - if let Some(panes_to_the_right) = self.pane_ids_directly_right_of(&pane_id) { - return panes_to_the_right.iter().all(|id| { - let p = self.panes.get(id).unwrap(); - p.max_width() - .map(|max_width| p.columns() + reduce_by <= max_width) - .unwrap_or(true) // no max width, increase to your heart's content - }); - } else { - false - } - } - fn can_reduce_pane_and_surroundings_down(&self, pane_id: &PaneId, reduce_by: usize) -> bool { - let pane = self.panes.get(pane_id).unwrap(); - let pane_rows = pane.rows(); - let can_reduce_pane_size = - pane_rows > reduce_by && pane_rows - reduce_by >= pane.min_height(); - if !can_reduce_pane_size { - return false; - } - if let Some(panes_above) = self.pane_ids_directly_above(&pane_id) { - return panes_above.iter().all(|id| { - let p = self.panes.get(id).unwrap(); - p.max_height() - .map(|max_height| p.rows() + reduce_by <= max_height) - .unwrap_or(true) // no max height, increase to your heart's content - }); - } else { - false - } - } - fn can_reduce_pane_and_surroundings_up(&self, pane_id: &PaneId, reduce_by: usize) -> bool { - let pane = self.panes.get(pane_id).unwrap(); - let pane_rows = pane.rows(); - let can_reduce_pane_size = - pane_rows > reduce_by && pane_rows - reduce_by >= pane.min_height(); - if !can_reduce_pane_size { - return false; - } - if let Some(panes_below) = self.pane_ids_directly_below(&pane_id) { - return panes_below.iter().all(|id| { - let p = self.panes.get(id).unwrap(); - p.max_height() - .map(|max_height| p.rows() + reduce_by <= max_height) - .unwrap_or(true) // no max height, increase to your heart's content - }); - } else { - false - } - } - pub fn resize_whole_tab(&mut self, new_screen_size: PositionAndSize) { - if self.fullscreen_is_active { - // this is not ideal, we can improve this - self.toggle_active_pane_fullscreen(); - } - if let Some((column_difference, row_difference)) = - PaneResizer::new(&mut self.panes, &mut self.os_api) - .resize(self.full_screen_ws, new_screen_size) - { - self.should_clear_display_before_rendering = true; - self.full_screen_ws.columns = - (self.full_screen_ws.columns as isize + column_difference) as usize; - self.full_screen_ws.rows = - (self.full_screen_ws.rows as isize + row_difference) as usize; - }; - } - pub fn resize_left(&mut self) { - // TODO: find out by how much we actually reduced and only reduce by that much - let count = 10; - if let Some(active_pane_id) = self.get_active_pane_id() { - if self.can_increase_pane_and_surroundings_left(&active_pane_id, count) { - self.increase_pane_and_surroundings_left(&active_pane_id, count); - } else if self.can_reduce_pane_and_surroundings_left(&active_pane_id, count) { - self.reduce_pane_and_surroundings_left(&active_pane_id, count); - } - } - self.render(); - } - pub fn resize_right(&mut self) { - // TODO: find out by how much we actually reduced and only reduce by that much - let count = 10; - if let Some(active_pane_id) = self.get_active_pane_id() { - if self.can_increase_pane_and_surroundings_right(&active_pane_id, count) { - self.increase_pane_and_surroundings_right(&active_pane_id, count); - } else if self.can_reduce_pane_and_surroundings_right(&active_pane_id, count) { - self.reduce_pane_and_surroundings_right(&active_pane_id, count); - } - } - self.render(); - } - pub fn resize_down(&mut self) { - // TODO: find out by how much we actually reduced and only reduce by that much - let count = 2; - if let Some(active_pane_id) = self.get_active_pane_id() { - if self.can_increase_pane_and_surroundings_down(&active_pane_id, count) { - self.increase_pane_and_surroundings_down(&active_pane_id, count); - } else if self.can_reduce_pane_and_surroundings_down(&active_pane_id, count) { - self.reduce_pane_and_surroundings_down(&active_pane_id, count); - } - } - self.render(); - } - pub fn resize_up(&mut self) { - // TODO: find out by how much we actually reduced and only reduce by that much - let count = 2; - if let Some(active_pane_id) = self.get_active_pane_id() { - if self.can_increase_pane_and_surroundings_up(&active_pane_id, count) { - self.increase_pane_and_surroundings_up(&active_pane_id, count); - } else if self.can_reduce_pane_and_surroundings_up(&active_pane_id, count) { - self.reduce_pane_and_surroundings_up(&active_pane_id, count); - } - } - self.render(); - } - pub fn move_focus(&mut self) { - if !self.has_selectable_panes() { - return; - } - if self.fullscreen_is_active { - return; - } - let active_terminal_id = self.get_active_pane_id().unwrap(); - let terminal_ids: Vec<PaneId> = self.get_selectable_panes().map(|(&pid, _)| pid).collect(); // TODO: better, no allocations - let first_terminal = terminal_ids.get(0).unwrap(); - let active_terminal_id_position = terminal_ids - .iter() - .position(|id| id == &active_terminal_id) - .unwrap(); - if let Some(next_terminal) = terminal_ids.get(active_terminal_id_position + 1) { - self.active_terminal = Some(*next_terminal); - } else { - self.active_terminal = Some(*first_terminal); - } - self.render(); - } - pub fn focus_next_pane(&mut self) { - if !self.has_selectable_panes() { - return; - } - if self.fullscreen_is_active { - return; - } - let active_pane_id = self.get_active_pane_id().unwrap(); - let mut panes: Vec<(&PaneId, &Box<dyn Pane>)> = self.get_selectable_panes().collect(); - panes.sort_by(|(_a_id, a_pane), (_b_id, b_pane)| { - if a_pane.y() == b_pane.y() { - a_pane.x().cmp(&b_pane.x()) - } else { - a_pane.y().cmp(&b_pane.y()) - } - }); - let first_pane = panes.get(0).unwrap(); - let active_pane_position = panes - .iter() - .position(|(id, _)| *id == &active_pane_id) // TODO: better - .unwrap(); - if let Some(next_pane) = panes.get(active_pane_position + 1) { - self.active_terminal = Some(*next_pane.0); - } else { - self.active_terminal = Some(*first_pane.0); - } - self.render(); - } - pub fn focus_previous_pane(&mut self) { - if !self.has_selectable_panes() { - return; - } - if self.fullscreen_is_active { - return; - } - let active_pane_id = self.get_active_pane_id().unwrap(); - let mut panes: Vec<(&PaneId, &Box<dyn Pane>)> = self.get_selectable_panes().collect(); - panes.sort_by(|(_a_id, a_pane), (_b_id, b_pane)| { - if a_pane.y() == b_pane.y() { - a_pane.x().cmp(&b_pane.x()) - } else { - a_pane.y().cmp(&b_pane.y()) - } - }); - let last_pane = panes.last().unwrap(); - let active_pane_position = panes - .iter() - .position(|(id, _)| *id == &active_pane_id) // TODO: better - .unwrap(); - if active_pane_position == 0 { - self.active_terminal = Some(*last_pane.0); - } else { - self.active_terminal = Some(*panes.get(active_pane_position - 1).unwrap().0); - } - self.render(); - } - // returns a boolean that indicates whether the focus moved - pub fn move_focus_left(&mut self) -> bool { - if !self.has_selectable_panes() { - return false; - } - if self.fullscreen_is_active { - return false; - } - let active_terminal = self.get_active_pane(); - if let Some(active) = active_terminal { - let terminals = self.get_selectable_panes(); - let next_index = terminals - .enumerate() - .filter(|(_, (_, c))| { - c.is_directly_left_of(active) && c.horizontally_overlaps_with(active) - }) - .max_by_key(|(_, (_, c))| c.active_at()) - .map(|(_, (pid, _))| pid); - match next_index { - Some(&p) => { - self.active_terminal = Some(p); - self.render(); - return true; - } - None => { - self.active_terminal = Some(active.pid()); - } - } - } else { - self.active_terminal = Some(active_terminal.unwrap().pid()); - } - false - } - pub fn move_focus_down(&mut self) { - if !self.has_selectable_panes() { - return; - } - if self.fullscreen_is_active { - return; - } - let active_terminal = self.get_active_pane(); - if let Some(active) = active_terminal { - let terminals = self.get_selectable_panes(); - let next_index = terminals - .enumerate() - .filter(|(_, (_, c))| { - c.is_directly_below(active) && c.vertically_overlaps_with(active) - }) - .max_by_key(|(_, (_, c))| c.active_at()) - .map(|(_, (pid, _))| pid); - match next_index { - Some(&p) => { - self.active_terminal = Some(p); - } - None => { - self.active_terminal = Some(active.pid()); - } - } - } else { - self.active_terminal = Some(active_terminal.unwrap().pid()); - } - self.render(); - } - pub fn move_focus_up(&mut self) { - if !self.has_selectable_panes() { - return; - } - if self.fullscreen_is_active { - return; - } - let active_terminal = self.get_active_pane(); - if let Some(active) = active_terminal { - let terminals = self.get_selectable_panes(); - let next_index = terminals - .enumerate() - .filter(|(_, (_, c))| { - c.is_directly_above(active) && c.vertically_overlaps_with(active) - }) - .max_by_key(|(_, (_, c))| c.active_at()) - .map(|(_, (pid, _))| pid); - match next_index { - Some(&p) => { - self.active_terminal = Some(p); - } - None => { - self.active_terminal = Some(active.pid()); - } - } - } else { - self.active_terminal = Some(active_terminal.unwrap().pid()); - } - self.render(); - } - // returns a boolean that indicates whether the focus moved - pub fn move_focus_right(&mut self) -> bool { - if !self.has_selectable_panes() { - return false; - } - if self.fullscreen_is_active { - return false; - } - let active_terminal = self.get_active_pane(); - if let Some(active) = active_terminal { - let terminals = self.get_selectable_panes(); - let next_index = terminals - .enumerate() - .filter(|(_, (_, c))| { - c.is_directly_right_of(active) && c.horizontally_overlaps_with(active) - }) - .max_by_key(|(_, (_, c))| c.active_at()) - .map(|(_, (pid, _))| pid); - match next_index { - Some(&p) => { - self.active_terminal = Some(p); - self.render(); - return true; - } - None => { - self.active_terminal = Some(active.pid()); - } - } - } else { - self.active_terminal = Some(active_terminal.unwrap().pid()); - } - false - } - fn horizontal_borders(&self, terminals: &[PaneId]) -> HashSet<usize> { - terminals.iter().fold(HashSet::new(), |mut borders, t| { - let terminal = self.panes.get(t).unwrap(); - borders.insert(terminal.y()); - borders.insert(terminal.y() + terminal.rows() + 1); // 1 for the border width - borders - }) - } - fn vertical_borders(&self, terminals: &[PaneId]) -> HashSet<usize> { - terminals.iter().fold(HashSet::new(), |mut borders, t| { - let terminal = self.panes.get(t).unwrap(); - borders.insert(terminal.x()); - borders.insert(terminal.x() + terminal.columns() + 1); // 1 for the border width - borders - }) - } - fn panes_to_the_left_between_aligning_borders(&self, id: PaneId) -> Option<Vec<PaneId>> { - if let Some(terminal) = self.panes.get(&id) { - let upper_close_border = terminal.y(); - let lower_close_border = terminal.y() + terminal.rows() + 1; - - if let Some(mut terminals_to_the_left) = self.pane_ids_directly_left_of(&id) { - let terminal_borders_to_the_left = self.horizontal_borders(&terminals_to_the_left); - if terminal_borders_to_the_left.contains(&upper_close_border) - && terminal_borders_to_the_left.contains(&lower_close_border) - { - terminals_to_the_left.retain(|t| { - self.pane_is_between_horizontal_borders( - t, - upper_close_border, - lower_close_border, - ) - }); - return Some(terminals_to_the_left); - } - } - } - None - } - fn panes_to_the_right_between_aligning_borders(&self, id: PaneId) -> Option<Vec<PaneId>> { - if let Some(terminal) = self.panes.get(&id) { - let upper_close_border = terminal.y(); - let lower_close_border = terminal.y() + terminal.rows() + 1; - - if let Some(mut terminals_to_the_right) = self.pane_ids_directly_right_of(&id) { - let terminal_borders_to_the_right = - self.horizontal_borders(&terminals_to_the_right); - if terminal_borders_to_the_right.contains(&upper_close_border) - && terminal_borders_to_the_right.contains(&lower_close_border) - { - terminals_to_the_right.retain(|t| { - self.pane_is_between_horizontal_borders( - t, - upper_close_border, - lower_close_border, - ) - }); - return Some(terminals_to_the_right); - } - } - } - None - } - fn panes_above_between_aligning_borders(&self, id: PaneId) -> Option<Vec<PaneId>> { - if let Some(terminal) = self.panes.get(&id) { - let left_close_border = terminal.x(); - let right_close_border = terminal.x() + terminal.columns() + 1; - - if let Some(mut terminals_above) = self.pane_ids_directly_above(&id) { - let terminal_borders_above = self.vertical_borders(&terminals_above); - if terminal_borders_above.contains(&left_close_border) - && terminal_borders_above.contains(&right_close_border) - { - terminals_above.retain(|t| { - self.pane_is_between_vertical_borders( - t, - left_close_border, - right_close_border, - ) - }); - return Some(terminals_above); - } - } - } - None - } - fn panes_below_between_aligning_borders(&self, id: PaneId) -> Option<Vec<PaneId>> { - if let Some(terminal) = self.panes.get(&id) { - let left_close_border = terminal.x(); - let right_close_border = terminal.x() + terminal.columns() + 1; - - if let Some(mut terminals_below) = self.pane_ids_directly_below(&id) { - let terminal_borders_below = self.vertical_borders(&terminals_below); - if terminal_borders_below.contains(&left_close_border) - && terminal_borders_below.contains(&right_close_border) - { - terminals_below.retain(|t| { - self.pane_is_between_vertical_borders( - t, - left_close_border, - right_close_border, - ) - }); - return Some(terminals_below); - } - } - } - None - } - fn close_down_to_max_terminals(&mut self) { - if let Some(max_panes) = self.max_panes { - let terminals = self.get_pane_ids(); - for &pid in terminals.iter().skip(max_panes - 1) { - self.senders - .send_to_pty(PtyInstruction::ClosePane(pid)) - .unwrap(); - self.close_pane_without_rerender(pid); - } - } - } - pub fn get_pane_ids(&self) -> Vec<PaneId> { - self.get_panes().map(|(&pid, _)| pid).collect() - } - pub fn set_pane_selectable(&mut self, id: PaneId, selectable: bool) { - if let Some(pane) = self.panes.get_mut(&id) { - pane.set_selectable(selectable); - if self.get_active_pane_id() == Some(id) && !selectable { - self.active_terminal = self.next_active_pane(self.get_pane_ids()) - } - } - } - pub fn set_pane_invisible_borders(&mut self, id: PaneId, invisible_borders: bool) { - if let Some(pane) = self.panes.get_mut(&id) { - pane.set_invisible_borders(invisible_borders); - } - } - pub fn set_pane_max_height(&mut self, id: PaneId, max_height: usize) { - if let Some(pane) = self.panes.get_mut(&id) { - pane.set_max_height(max_height); - } - } - pub fn close_pane(&mut self, id: PaneId) { - if self.panes.get(&id).is_some() { - self.close_pane_without_rerender(id); - } - } - pub fn close_pane_without_rerender(&mut self, id: PaneId) { - if self.fullscreen_is_active { - self.toggle_active_pane_fullscreen(); - } - if let Some(pane_to_close) = self.panes.get(&id) { - let pane_to_close_width = pane_to_close.columns(); - let pane_to_close_height = pane_to_close.rows(); - if let Some(panes) = self.panes_to_the_left_between_aligning_borders(id) { - if panes.iter().all(|p| { - let pane = self.panes.get(p).unwrap(); - pane.can_increase_width_by(pane_to_close_width + 1) - }) { - for pane_id in panes.iter() { - self.increase_pane_width_right(&pane_id, pane_to_close_width + 1); - // 1 for the border - } - self.panes.remove(&id); - if self.active_terminal == Some(id) { - self.active_terminal = self.next_active_pane(panes); - } - return; - } - } - if let Some(panes) = self.panes_to_the_right_between_aligning_borders(id) { - if panes.iter().all(|p| { - let pane = self.panes.get(p).unwrap(); - pane.can_increase_width_by(pane_to_close_width + 1) - }) { - for pane_id in panes.iter() { - self.increase_pane_width_left(&pane_id, pane_to_close_width + 1); - // 1 for the border - } - self.panes.remove(&id); - if self.active_terminal == Some(id) { - self.active_terminal = self.next_active_pane(panes); - } - return; - } - } - if let Some(panes) = self.panes_above_between_aligning_borders(id) { - if panes.iter().all(|p| { - let pane = self.panes.get(p).unwrap(); - pane.can_increase_height_by(pane_to_close_height + 1) - }) { - for pane_id in panes.iter() { - self.increase_pane_height_down(&pane_id, pane_to_close_height + 1); - // 1 for the border - } - self.panes.remove(&id); - if self.active_terminal == Some(id) { - self.active_terminal = self.next_active_pane(panes); - } - return; - } - } - if let Some(panes) = self.panes_below_between_aligning_borders(id) { - if panes.iter().all(|p| { - let pane = self.panes.get(p).unwrap(); - pane.can_increase_height_by(pane_to_close_height + 1) - }) { - for pane_id in panes.iter() { - self.increase_pane_height_up(&pane_id, pane_to_close_height + 1); - // 1 for the border - } - self.panes.remove(&id); - if self.active_terminal == Some(id) { - self.active_terminal = self.next_active_pane(panes); - } - return; - } - } - // if we reached here, this is either the last pane or there's some sort of - // configuration error (eg. we're trying to close a pane surrounded by fixed panes) - self.panes.remove(&id); - } - } - pub fn close_focused_pane(&mut self) { - if let Some(active_pane_id) = self.get_active_pane_id() { - self.close_pane(active_pane_id); - self.senders - .send_to_pty(PtyInstruction::ClosePane(active_pane_id)) - .unwrap(); - } - } - pub fn scroll_active_terminal_up(&mut self) { - if let Some(active_terminal_id) = self.get_active_terminal_id() { - let active_terminal = self - .panes - .get_mut(&PaneId::Terminal(active_terminal_id)) - .unwrap(); - active_terminal.scroll_up(1); - self.render(); - } - } - pub fn scroll_active_terminal_down(&mut self) { - if let Some(active_terminal_id) = self.get_active_terminal_id() { - let active_terminal = self - .panes - .get_mut(&PaneId::Terminal(active_terminal_id)) - .unwrap(); - active_terminal.scroll_down(1); - self.render(); - } - } - pub fn scroll_active_terminal_up_page(&mut self) { - if let Some(active_terminal_id) = self.get_active_terminal_id() { - let active_terminal = self - .panes - .get_mut(&PaneId::Terminal(active_terminal_id)) - .unwrap(); - // prevent overflow when row == 0 - let scroll_columns = active_terminal.rows().max(1) - 1; - active_terminal.scroll_up(scroll_columns); - self.render(); - } - } - pub fn scroll_active_terminal_down_page(&mut self) { - if let Some(active_terminal_id) = self.get_active_terminal_id() { - let active_terminal = self - .panes - .get_mut(&PaneId::Terminal(active_terminal_id)) - .unwrap(); - // prevent overflow when row == 0 - let scroll_columns = active_terminal.rows().max(1) - 1; - active_terminal.scroll_down(scroll_columns); - self.render(); - } - } - pub fn clear_active_terminal_scroll(&mut self) { - if let Some(active_terminal_id) = self.get_active_terminal_id() { - let active_terminal = self - .panes - .get_mut(&PaneId::Terminal(active_terminal_id)) - .unwrap(); - active_terminal.clear_scroll(); - } - } -} diff --git a/src/common/command_is_executing.rs b/src/common/command_is_executing.rs deleted file mode 100644 index ad032557..00000000 --- a/src/common/command_is_executing.rs +++ /dev/null @@ -1,33 +0,0 @@ -#![allow(clippy::mutex_atomic)] -use std::sync::{Arc, Condvar, Mutex}; - -#[derive(Clone)] -pub struct CommandIsExecuting { - input_thread: Arc<(Mutex<bool>, Condvar)>, -} - -impl CommandIsExecuting { - pub fn new() -> Self { - CommandIsExecuting { - input_thread: Arc::new((Mutex::new(false), Condvar::new())), - } - } - pub fn blocking_input_thread(&mut self) { - let (lock, _cvar) = &*self.input_thread; - let mut input_thread = lock.lock().unwrap(); - *input_thread = true; - } - pub fn unblock_input_thread(&mut self) { - let (lock, cvar) = &*self.input_thread; - let mut input_thread = lock.lock().unwrap(); - *input_thread = false; - cvar.notify_all(); - } - pub fn wait_until_input_thread_is_unblocked(&self) { - let (lock, cvar) = &*self.input_thread; - let mut input_thread = lock.lock().unwrap(); - while *input_thread { - input_thread = cvar.wait(input_thread).unwrap(); - } - } -} diff --git a/src/common/errors.rs b/src/common/errors.rs deleted file mode 100644 index 262dfd6b..00000000 --- a/src/common/errors.rs +++ /dev/null @@ -1,393 +0,0 @@ -//! Error context system based on a thread-local representation of the call stack, itself based on -//! the instructions that are sent between threads. - -use serde::{Deserialize, Serialize}; - -use std::fmt::{Display, Error, Formatter}; - -use crate::client::ClientInstruction; -use crate::common::thread_bus::{ASYNCOPENCALLS, OPENCALLS}; -use crate::pty::PtyInstruction; -use crate::screen::ScreenInstruction; -use crate::server::ServerInstruction; - -/// The maximum amount of calls an [`ErrorContext`] will keep track -/// of in its stack representation. This is a per-thread maximum. -const MAX_THREAD_CALL_STACK: usize = 6; - -#[cfg(not(test))] -use super::thread_bus::SenderWithContext; -#[cfg(not(test))] -use std::panic::PanicInfo; - -pub trait ErrorInstruction { - fn error(err: String) -> Self; -} - -impl ErrorInstruction for ClientInstruction { - fn error(err: String) -> Self { - ClientInstruction::Error(err) - } -} - -impl ErrorInstruction for ServerInstruction { - fn error(err: String) -> Self { - ServerInstruction::Error(err) - } -} - -/// Custom panic handler/hook. Prints the [`ErrorContext`]. -#[cfg(not(test))] -pub fn handle_panic<T>(info: &PanicInfo<'_>, sender: &SenderWithContext<T>) -where - T: ErrorInstruction + Clone, -{ - use backtrace::Backtrace; - use std::{process, thread}; - let backtrace = Backtrace::new(); - let thread = thread::current(); - let thread = thread.name().unwrap_or("unnamed"); - - let msg = match info.payload().downcast_ref::<&'static str>() { - Some(s) => Some(*s), - None => info.payload().downcast_ref::<String>().map(|s| &**s), - }; - - let err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); - - let backtrace = match (info.location(), msg) { - (Some(location), Some(msg)) => format!( - "{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked at '{}': {}:{}\n\u{1b}[0;0m{:?}", - err_ctx, - thread, - msg, - location.file(), - location.line(), - backtrace, - ), - (Some(location), None) => format!( - "{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked: {}:{}\n\u{1b}[0;0m{:?}", - err_ctx, - thread, - location.file(), - location.line(), - backtrace - ), - (None, Some(msg)) => format!( - "{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked at '{}'\n\u{1b}[0;0m{:?}", - err_ctx, thread, msg, backtrace - ), - (None, None) => format!( - "{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked\n\u{1b}[0;0m{:?}", - err_ctx, thread, backtrace - ), - }; - - if thread == "main" { - println!("{}", backtrace); - process::exit(1); - } else { - let _ = sender.send(T::error(backtrace)); - } -} - -pub fn get_current_ctx() -> ErrorContext { - ASYNCOPENCALLS - .try_with(|ctx| *ctx.borrow()) - .unwrap_or_else(|_| OPENCALLS.with(|ctx| *ctx.borrow())) -} - -/// A representation of the call stack. -#[derive(Clone, Copy, Serialize, Deserialize)] -pub struct ErrorContext { - calls: [ContextType; MAX_THREAD_CALL_STACK], -} - -impl ErrorContext { - /// Returns a new, blank [`ErrorContext`] containing only [`Empty`](ContextType::Empty) - /// calls. - pub fn new() -> Self { - Self { - calls: [ContextType::Empty; MAX_THREAD_CALL_STACK], - } - } - - /// Adds a call to this [`ErrorContext`]'s call stack representation. - pub fn add_call(&mut self, call: ContextType) { - for ctx in self.calls.iter_mut() { - if *ctx == ContextType::Empty { - *ctx = call; - break; - } - } - self.update_thread_ctx() - } - - /// Updates the thread local [`ErrorContext`]. - pub fn update_thread_ctx(&self) { - ASYNCOPENCALLS - .try_with(|ctx| *ctx.borrow_mut() = *self) - .unwrap_or_else(|_| OPENCALLS.with(|ctx| *ctx.borrow_mut() = *self)); - } -} - -impl Default for ErrorContext { - fn default() -> Self { - Self::new() - } -} - -impl Display for ErrorContext { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - writeln!(f, "Originating Thread(s):")?; - for (index, ctx) in self.calls.iter().enumerate() { - if *ctx == ContextType::Empty { - break; - } - writeln!(f, "\u{1b}[0;0m{}. {}", index + 1, ctx)?; - } - Ok(()) - } -} - -/// Different types of calls that form an [`ErrorContext`] call stack. -/// -/// Complex variants store a variant of a related enum, whose variants can be built from -/// the corresponding Zellij MSPC instruction enum variants ([`ScreenInstruction`], -/// [`PtyInstruction`], [`ClientInstruction`], etc). -#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] -pub enum ContextType { - /// A screen-related call. - Screen(ScreenContext), - /// A PTY-related call. - Pty(PtyContext), - /// A plugin-related call. - Plugin(PluginContext), - /// An app-related call. - Client(ClientContext), - /// A server-related call. - IPCServer(ServerContext), - StdinHandler, - AsyncTask, - /// An empty, placeholder call. This should be thought of as representing no call at all. - /// A call stack representation filled with these is the representation of an empty call stack. - Empty, -} - -// TODO use the `colored` crate for color formatting -impl Display for ContextType { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - let purple = "\u{1b}[1;35m"; - let green = "\u{1b}[0;32m"; - match *self { - ContextType::Screen(c) => write!(f, "{}screen_thread: {}{:?}", purple, green, c), - ContextType::Pty(c) => write!(f, "{}pty_thread: {}{:?}", purple, green, c), - ContextType::Plugin(c) => write!(f, "{}plugin_thread: {}{:?}", purple, green, c), - ContextType::Client(c) => write!(f, "{}main_thread: {}{:?}", purple, green, c), - ContextType::IPCServer(c) => write!(f, "{}ipc_server: {}{:?}", purple, green, c), - ContextType::StdinHandler => { - write!(f, "{}stdin_handler_thread: {}AcceptInput", purple, green) - } - ContextType::AsyncTask => { - write!(f, "{}stream_terminal_bytes: {}AsyncTask", purple, green) - } - ContextType::Empty => write!(f, ""), - } - } -} - -// FIXME: Just deriving EnumDiscriminants from strum will remove the need for any of this!!! -/// Stack call representations corresponding to the different types of [`ScreenInstruction`]s. -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum ScreenContext { - HandlePtyBytes, - Render, - NewPane, - HorizontalSplit, - VerticalSplit, - WriteCharacter, - ResizeLeft, - ResizeRight, - ResizeDown, - ResizeUp, - SwitchFocus, - FocusNextPane, - FocusPreviousPane, - MoveFocusLeft, - MoveFocusLeftOrPreviousTab, - MoveFocusDown, - MoveFocusUp, - MoveFocusRight, - MoveFocusRightOrNextTab, - Exit, - ScrollUp, - ScrollDown, - PageScrollUp, - PageScrollDown, - ClearScroll, - CloseFocusedPane, - ToggleActiveSyncTab, - ToggleActiveTerminalFullscreen, - SetSelectable, - SetInvisibleBorders, - SetMaxHeight, - ClosePane, - ApplyLayout, - NewTab, - SwitchTabNext, - SwitchTabPrev, - CloseTab, - GoToTab, - UpdateTabName, - TerminalResize, - ChangeMode, -} - -// FIXME: Just deriving EnumDiscriminants from strum will remove the need for any of this!!! -impl From<&ScreenInstruction> for ScreenContext { - fn from(screen_instruction: &ScreenInstruction) -> Self { - match *screen_instruction { - ScreenInstruction::PtyBytes(..) => ScreenContext::HandlePtyBytes, - ScreenInstruction::Render => ScreenContext::Render, - ScreenInstruction::NewPane(_) => ScreenContext::NewPane, - ScreenInstruction::HorizontalSplit(_) => ScreenContext::HorizontalSplit, - ScreenInstruction::VerticalSplit(_) => ScreenContext::VerticalSplit, - ScreenInstruction::WriteCharacter(_) => ScreenContext::WriteCharacter, - ScreenInstruction::ResizeLeft => ScreenContext::ResizeLeft, - ScreenInstruction::ResizeRight => ScreenContext::ResizeRight, - ScreenInstruction::ResizeDown => ScreenContext::ResizeDown, - ScreenInstruction::ResizeUp => ScreenContext::ResizeUp, - ScreenInstruction::SwitchFocus => ScreenContext::SwitchFocus, - ScreenInstruction::FocusNextPane => ScreenContext::FocusNextPane, - ScreenInstruction::FocusPreviousPane => ScreenContext::FocusPreviousPane, - ScreenInstruction::MoveFocusLeft => ScreenContext::MoveFocusLeft, - ScreenInstruction::MoveFocusLeftOrPreviousTab => { - ScreenContext::MoveFocusLeftOrPreviousTab - } - ScreenInstruction::MoveFocusDown => ScreenContext::MoveFocusDown, - ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp, - ScreenInstruction::MoveFocusRight => ScreenContext::MoveFocusRight, - ScreenInstruction::MoveFocusRightOrNextTab => ScreenContext::MoveFocusRightOrNextTab, - ScreenInstruction::Exit => ScreenContext::Exit, - ScreenInstruction::ScrollUp => ScreenContext::ScrollUp, - ScreenInstruction::ScrollDown => ScreenContext::ScrollDown, - ScreenInstruction::PageScrollUp => ScreenContext::PageScrollUp, - ScreenInstruction::PageScrollDown => ScreenContext::PageScrollDown, - ScreenInstruction::ClearScroll => ScreenContext::ClearScroll, - ScreenInstruction::CloseFocusedPane => ScreenContext::CloseFocusedPane, - ScreenInstruction::ToggleActiveTerminalFullscreen => { - ScreenContext::ToggleActiveTerminalFullscreen - } - ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable, - ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders, - ScreenInstruction::SetMaxHeight(..) => ScreenContext::SetMaxHeight, - ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane, - ScreenInstruction::ApplyLayout(..) => ScreenContext::ApplyLayout, - ScreenInstruction::NewTab(_) => ScreenContext::NewTab, - ScreenInstruction::SwitchTabNext => ScreenContext::SwitchTabNext, - ScreenInstruction::SwitchTabPrev => ScreenContext::SwitchTabPrev, - ScreenInstruction::CloseTab => ScreenContext::CloseTab, - ScreenInstruction::GoToTab(_) => ScreenContext::GoToTab, - ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName, - ScreenInstruction::TerminalResize(_) => ScreenContext::TerminalResize, - ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode, - ScreenInstruction::ToggleActiveSyncTab => ScreenContext::ToggleActiveSyncTab, - } - } -} - -/// Stack call representations corresponding to the different types of [`PtyInstruction`]s. -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum PtyContext { - SpawnTerminal, - SpawnTerminalVertically, - SpawnTerminalHorizontally, - NewTab, - ClosePane, - CloseTab, - Exit, -} - -impl From<&PtyInstruction> for PtyContext { - fn from(pty_instruction: &PtyInstruction) -> Self { - match *pty_instruction { - PtyInstruction::SpawnTerminal(_) => PtyContext::SpawnTerminal, - PtyInstruction::SpawnTerminalVertically(_) => PtyContext::SpawnTerminalVertically, - PtyInstruction::SpawnTerminalHorizontally(_) => PtyContext::SpawnTerminalHorizontally, - PtyInstruction::ClosePane(_) => PtyContext::ClosePane, - PtyInstruction::CloseTab(_) => PtyContext::CloseTab, - PtyInstruction::NewTab => PtyContext::NewTab, - PtyInstruction::Exit => PtyContext::Exit, - } - } -} - -// FIXME: This whole pattern *needs* a macro eventually, it's soul-crushing to write - -use crate::wasm_vm::PluginInstruction; - -/// Stack call representations corresponding to the different types of [`PluginInstruction`]s. -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum PluginContext { - Load, - Update, - Render, - Unload, - Exit, -} - -impl From<&PluginInstruction> for PluginContext { - fn from(plugin_instruction: &PluginInstruction) -> Self { - match *plugin_instruction { - PluginInstruction::Load(..) => PluginContext::Load, - PluginInstruction::Update(..) => PluginContext::Update, - PluginInstruction::Render(..) => PluginContext::Render, - PluginInstruction::Unload(_) => PluginContext::Unload, - PluginInstruction::Exit => PluginContext::Exit, - } - } -} - -/// Stack call representations corresponding to the different types of [`ClientInstruction`]s. -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum ClientContext { - Exit, - Error, - UnblockInputThread, - Render, - ServerError, -} - -impl From<&ClientInstruction> for ClientContext { - fn from(client_instruction: &ClientInstruction) -> Self { - match *client_instruction { - ClientInstruction::Exit => ClientContext::Exit, - ClientInstruction::Error(_) => ClientContext::Error, - ClientInstruction::ServerError(_) => ClientContext::ServerError, - ClientInstruction::Render(_) => ClientContext::Render, - ClientInstruction::UnblockInputThread => ClientContext::UnblockInputThread, - } - } -} - -/// Stack call representations corresponding to the different types of [`ServerInstruction`]s. -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub enum ServerContext { - NewClient, - Render, - UnblockInputThread, - ClientExit, - Error, -} - -impl From<&ServerInstruction> for ServerContext { - fn from(server_instruction: &ServerInstruction) -> Self { - match *server_instruction { - ServerInstruction::NewClient(..) => ServerContext::NewClient, - ServerInstruction::Render(_) => ServerContext::Render, - ServerInstruction::UnblockInputThread => ServerContext::UnblockInputThread, - ServerInstruction::ClientExit => ServerContext::ClientExit, - ServerInstruction::Error(_) => ServerContext::Error, - } - } -} diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs deleted file mode 100644 index b806aa3f..00000000 --- a/src/common/input/actions.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! Definition of the actions that can be bound to keys. - -use serde::{Deserialize, Serialize}; -use zellij_tile::data::InputMode; - -/// The four directions (left, right, up, down). -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub enum Direction { - Left, - Right, - Up, - Down, -} - -/// Actions that can be bound to keys. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub enum Action { - /// Quit Zellij. - Quit, - /// Write to the terminal. - Write(Vec<u8>), - /// Switch to the specified input mode. - SwitchToMode(InputMode), - /// Resize focus pane in specified direction. - Resize(Direction), - /// Switch focus to next pane in specified direction. - FocusNextPane, - FocusPreviousPane, - /// Move the focus pane in specified direction. - SwitchFocus, - MoveFocus(Direction), - /// Tries to move the focus pane in specified direction. - /// If there is no pane in the direction, move to previous/next Tab. - MoveFocusOrTab(Direction), - /// Scroll up in focus pane. - ScrollUp, - /// Scroll down in focus pane. - ScrollDown, - /// Scroll up one page in focus pane. - PageScrollUp, - /// Scroll down one page in focus pane. - PageScrollDown, - /// Toggle between fullscreen focus pane and normal layout. - ToggleFocusFullscreen, - /// Toggle between sending text commands to all panes on the current tab and normal mode. - ToggleActiveSyncTab, - /// Open a new pane in the specified direction (relative to focus). - /// If no direction is specified, will try to use the biggest available space. - NewPane(Option<Direction>), - /// Close the focus pane. - CloseFocus, - /// Create a new tab. - NewTab, - /// Do nothing. - NoOp, - /// Go to the next tab. - GoToNextTab, - /// Go to the previous tab. - GoToPreviousTab, - /// Close the current tab. - CloseTab, - GoToTab(u32), - TabNameInput(Vec<u8>), -} diff --git a/src/common/input/config.rs b/src/common/input/config.rs deleted file mode 100644 index 3dffe71c..00000000 --- a/src/common/input/config.rs +++ /dev/null @@ -1,218 +0,0 @@ -//! Deserializes configuration options. -use std::error; -use std::fmt::{self, Display}; -use std::fs::File; -use std::io::{self, Read}; -use std::path::{Path, PathBuf}; - -use super::keybinds::{Keybinds, KeybindsFromYaml}; -use super::options::Options; -use crate::cli::{CliArgs, ConfigCli}; -use crate::common::setup; - -use serde::{Deserialize, Serialize}; -use std::convert::TryFrom; - -const DEFAULT_CONFIG_FILE_NAME: &str = "config.yaml"; - -type ConfigResult = Result<Config, ConfigError>; - -/// Intermediate deserialization config struct -#[derive(Debug, Deserialize)] -pub struct ConfigFromYaml { - #[serde(flatten)] - pub options: Option<Options>, - pub keybinds: Option<KeybindsFromYaml>, -} - -/// Main configuration. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct Config { - pub keybinds: Keybinds, - pub options: Options, -} - -#[derive(Debug)] -pub enum ConfigError { - // Deserialization error - Serde(serde_yaml::Error), - // Io error - Io(io::Error), - // Io error with path context - IoPath(io::Error, PathBuf), - // Internal Deserialization Error - FromUtf8(std::string::FromUtf8Error), -} - -impl Default for Config { - fn default() -> Self { - let keybinds = Keybinds::default(); - let options = Options::default(); - Config { keybinds, options } - } -} - -impl TryFrom<&CliArgs> for Config { - type Error = ConfigError; - - fn try_from(opts: &CliArgs) -> ConfigResult { - if let Some(ref path) = opts.config { - return Config::new(&path); - } - - if let Some(ConfigCli::Setup(setup)) = opts.option.clone() { - if setup.clean { - return Config::from_default_assets(); - } - } - - let config_dir = opts - .config_dir - .clone() - .or_else(setup::find_default_config_dir); - - if let Some(ref config) = config_dir { - let path = config.join(DEFAULT_CONFIG_FILE_NAME); - if path.exists() { - Config::new(&path) - } else { - Config::from_default_assets() - } - } else { - Config::from_default_assets() - } - } -} - -impl Config { - /// Uses defaults, but lets config override them. - pub fn from_yaml(yaml_config: &str) -> ConfigResult { - let config_from_yaml: ConfigFromYaml = serde_yaml::from_str(&yaml_config)?; - let keybinds = Keybinds::get_default_keybinds_with_config(config_from_yaml.keybinds); - let options = Options::from_yaml(config_from_yaml.options); - Ok(Config { keybinds, options }) - } - - /// Deserializes from given path. - pub fn new(path: &Path) -> ConfigResult { - match File::open(path) { - Ok(mut file) => { - let mut yaml_config = String::new(); - file.read_to_string(&mut yaml_config) - .map_err(|e| ConfigError::IoPath(e, path.to_path_buf()))?; - Ok(Config::from_yaml(&yaml_config)?) - } - Err(e) => Err(ConfigError::IoPath(e, path.into())), - } - } - - /// Gets default configuration from assets - // TODO Deserialize the Configuration from bytes &[u8], - // once serde-yaml supports zero-copy - pub fn from_default_assets() -> ConfigResult { - Self::from_yaml(String::from_utf8(setup::DEFAULT_CONFIG.to_vec())?.as_str()) - } -} - -impl Display for ConfigError { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - match self { - ConfigError::Io(ref err) => write!(formatter, "IoError: {}", err), - ConfigError::IoPath(ref err, ref path) => { - write!(formatter, "IoError: {}, File: {}", err, path.display(),) - } - ConfigError::Serde(ref err) => write!(formatter, "Deserialization error: {}", err), - ConfigError::FromUtf8(ref err) => write!(formatter, "FromUtf8Error: {}", err), - } - } -} - -impl std::error::Error for ConfigError { - fn cause(&self) -> Option<&dyn error::Error> { - match *self { - ConfigError::Io(ref err) => Some(err), - ConfigError::IoPath(ref err, _) => Some(err), - ConfigError::Serde(ref err) => Some(err), - ConfigError::FromUtf8(ref err) => Some(err), - } - } -} - -impl From<io::Error> for ConfigError { - fn from(err: io::Error) -> ConfigError { - ConfigError::Io(err) - } -} - -impl From<serde_yaml::Error> for ConfigError { - fn from(err: serde_yaml::Error) -> ConfigError { - ConfigError::Serde(err) - } -} - -impl From<std::string::FromUtf8Error> for ConfigError { - fn from(err: std::string::FromUtf8Error) -> ConfigError { - ConfigError::FromUtf8(err) - } -} - -// The unit test location. -#[cfg(test)] -mod config_test { - use std::io::Write; - - use tempfile::tempdir; - - use super::*; - - #[test] - fn try_from_cli_args_with_config() { - let arbitrary_config = PathBuf::from("nonexistent.yaml"); - let mut opts = CliArgs::default(); - opts.config = Some(arbitrary_config); - println!("OPTS= {:?}", opts); - let result = Config::try_from(&opts); - assert!(result.is_err()); - } - - #[test] - fn try_from_cli_args_with_option_clean() { - use crate::common::setup::Setup; - let mut opts = CliArgs::default(); - opts.option = Some(ConfigCli::Setup(Setup { - clean: true, - ..Setup::default() - })); - let result = Config::try_from(&opts); - assert!(result.is_ok()); - } - - #[test] - fn try_from_cli_args_with_config_dir() { - let mut opts = CliArgs::default(); - let tmp = tempdir().unwrap(); - File::create(tmp.path().join(DEFAULT_CONFIG_FILE_NAME)) - .unwrap() - .write_all(b"keybinds: invalid\n") - .unwrap(); - opts.config_dir = Some(tmp.path().to_path_buf()); - let result = Config::try_from(&opts); - assert!(result.is_err()); - } - - #[test] - fn try_from_cli_args_with_config_dir_without_config() { - let mut opts = CliArgs::default(); - let tmp = tempdir().unwrap(); - opts.config_dir = Some(tmp.path().to_path_buf()); - let result = Config::try_from(&opts); - assert_eq!(result.unwrap(), Config::default()); - } - - #[test] - fn try_from_cli_args_default() { - let opts = CliArgs::default(); - let result = Config::try_from(&opts); - assert_eq!(result.unwrap(), Config::default()); - } -} diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs deleted file mode 100644 index 4ac2cf83..00000000 --- a/src/common/input/handler.rs +++ /dev/null @@ -1,268 +0,0 @@ -//! Main input logic. - -use super::actions::Action; -use super::keybinds::Keybinds; -use crate::client::ClientInstruction; -use crate::common::input::config::Config; -use crate::common::ipc::ClientToServerMsg; -use crate::common::thread_bus::{SenderWithContext, OPENCALLS}; -use crate::errors::ContextType; -use crate::os_input_output::ClientOsApi; -use crate::CommandIsExecuting; - -use termion::input::{TermRead, TermReadEventsAndRaw}; -use zellij_tile::data::{InputMode, Key, ModeInfo, Palette, PluginCapabilities}; - -/// Handles the dispatching of [`Action`]s according to the current -/// [`InputMode`], and keep tracks of the current [`InputMode`]. -struct InputHandler { - /// The current input mode - mode: InputMode, - os_input: Box<dyn ClientOsApi>, - config: Config, - command_is_executing: CommandIsExecuting, - send_client_instructions: SenderWithContext<ClientInstruction>, - should_exit: bool, - pasting: bool, -} - -impl InputHandler { - /// Returns a new [`InputHandler`] with the attributes specified as arguments. - fn new( - os_input: Box<dyn ClientOsApi>, - command_is_executing: CommandIsExecuting, - config: Config, - send_client_instructions: SenderWithContext<ClientInstruction>, - ) -> Self { - InputHandler { - mode: InputMode::Normal, - os_input, - config, - command_is_executing, - send_client_instructions, - should_exit: false, - pasting: false, - } - } - - /// Main input event loop. Interprets the terminal [`Event`](termion::event::Event)s - /// as [`Action`]s according to the current [`InputMode`], and dispatches those actions. - fn handle_input(&mut self) { - let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); - err_ctx.add_call(ContextType::StdinHandler); - let alt_left_bracket = vec![27, 91]; - let bracketed_paste_start = vec![27, 91, 50, 48, 48, 126]; // \u{1b}[200~ - let bracketed_paste_end = vec![27, 91, 50, 48, 49, 126]; // \u{1b}[201 - loop { - if self.should_exit { - break; - } - let stdin_buffer = self.os_input.read_from_stdin(); - for key_result in stdin_buffer.events_and_raw() { - match key_result { - Ok((event, raw_bytes)) => match event { - termion::event::Event::Key(key) => { - let key = cast_termion_key(key); - self.handle_key(&key, raw_bytes); - } - termion::event::Event::Unsupported(unsupported_key) => { - // we have to do this because of a bug in termion - // this should be a key event and not an unsupported event - if unsupported_key == alt_left_bracket { - let key = Key::Alt('['); - self.handle_key(&key, raw_bytes); - } else if unsupported_key == bracketed_paste_start { - self.pasting = true; - } else if unsupported_key == bracketed_paste_end { - self.pasting = false; - } else { - // this is a hack because termion doesn't recognize certain keys - // in this case we just forward it to the terminal - self.handle_unknown_key(raw_bytes); - } - } - termion::event::Event::Mouse(_) => { - // Mouse events aren't implemented yet, - // use a NoOp untill then. - } - }, - Err(err) => panic!("Encountered read error: {:?}", err), - } - } - } - } - fn handle_unknown_key(&mut self, raw_bytes: Vec<u8>) { - if self.mode == InputMode::Normal || self.mode == InputMode::Locked { - let action = Action::Write(raw_bytes); - self.dispatch_action(action); - } - } - fn handle_key(&mut self, key: &Key, raw_bytes: Vec<u8>) { - let keybinds = &self.config.keybinds; - if self.pasting { - // we're inside a paste block, if we're in a mode that allows sending text to the - // terminal, send all text directly without interpreting it - // otherwise, just discard the input - if self.mode == InputMode::Normal || self.mode == InputMode::Locked { - let action = Action::Write(raw_bytes); - self.dispatch_action(action); - } - } else { - for action in Keybinds::key_to_actions(&key, raw_bytes, &self.mode, keybinds) { - let should_exit = self.dispatch_action(action); - if should_exit { - self.should_exit = true; - } - } - } - } - - /// Dispatches an [`Action`]. - /// - /// This function's body dictates what each [`Action`] actually does when - /// dispatched. - /// - /// # Return value - /// Currently, this function returns a boolean that indicates whether - /// [`Self::handle_input()`] should break after this action is dispatched. - /// This is a temporary measure that is only necessary due to the way that the - /// framework works, and shouldn't be necessary anymore once the test framework - /// is revised. See [issue#183](https://github.com/zellij-org/zellij/issues/183). - fn dispatch_action(&mut self, action: Action) -> bool { - let mut should_break = false; - - match action { - Action::Quit => { - self.exit(); - should_break = true; - } - Action::SwitchToMode(mode) => { - self.mode = mode; - self.os_input - .send_to_server(ClientToServerMsg::Action(action)); - } - Action::CloseFocus - | Action::NewPane(_) - | Action::NewTab - | Action::GoToNextTab - | Action::GoToPreviousTab - | Action::CloseTab - | Action::GoToTab(_) - | Action::MoveFocusOrTab(_) => { - self.command_is_executing.blocking_input_thread(); - self.os_input - .send_to_server(ClientToServerMsg::Action(action)); - self.command_is_executing - .wait_until_input_thread_is_unblocked(); - } - _ => self - .os_input - .send_to_server(ClientToServerMsg::Action(action)), - } - - should_break - } - - /// Routine to be called when the input handler exits (at the moment this is the - /// same as quitting Zellij). - fn exit(&mut self) { - self.send_client_instructions - .send(ClientInstruction::Exit) - .unwrap(); - } -} - -/// Creates a [`Help`] struct indicating the current [`InputMode`] and its keybinds -/// (as pairs of [`String`]s). -// TODO this should probably be automatically generated in some way -pub fn get_mode_info( - mode: InputMode, - palette: Palette, - capabilities: PluginCapabilities, -) -> ModeInfo { - let mut keybinds: Vec<(String, String)> = vec![]; - match mode { - InputMode::Normal | InputMode::Locked => {} - InputMode::Resize => { - keybinds.push(("←↓↑→".to_string(), "Resize".to_string())); - } - InputMode::Pane => { - keybinds.push(("←↓↑→".to_string(), "Move focus".to_string())); - keybinds.push(("p".to_string(), "Next".to_string())); - keybinds.push(("n".to_string(), "New".to_string())); - keybinds.push(("d".to_string(), "Down split".to_string())); - keybinds.push(("r".to_string(), "Right split".to_string())); - keybinds.push(("x".to_string(), "Close".to_string())); - keybinds.push(("f".to_string(), "Fullscreen".to_string())); - } - InputMode::Tab => { - keybinds.push(("←↓↑→".to_string(), "Move focus".to_string())); - keybinds.push(("n".to_string(), "New".to_string())); - keybinds.push(("x".to_string(), "Close".to_string())); - keybinds.push(("r".to_string(), "Rename".to_string())); - keybinds.push(("s".to_string(), "Sync".to_string())); - } - InputMode::Scroll => { - keybinds.push(("↓↑".to_string(), "Scroll".to_string())); - keybinds.push(("PgUp/PgDn".to_string(), "Scroll Page".to_string())); - } - InputMode::RenameTab => { - keybinds.push(("Enter".to_string(), "when done".to_string())); - } - } - ModeInfo { - mode, - keybinds, - palette, - capabilities, - } -} - -/// Entry point to the module. Instantiates an [`InputHandler`] and starts -/// its [`InputHandler::handle_input()`] loop. -pub fn input_loop( - os_input: Box<dyn ClientOsApi>, - config: Config, - command_is_executing: CommandIsExecuting, - send_client_instructions: SenderWithContext<ClientInstruction>, -) { - let _handler = InputHandler::new( - os_input, - command_is_executing, - config, - send_client_instructions, - ) - .handle_input(); -} - -pub fn parse_keys(input_bytes: &[u8]) -> Vec<Key> { - input_bytes.keys().flatten().map(cast_termion_key).collect() -} - -// FIXME: This is an absolutely cursed function that should be destroyed as soon -// as an alternative that doesn't touch zellij-tile can be developed... -fn cast_termion_key(event: termion::event::Key) -> Key { - match event { - termion::event::Key::Backspace => Key::Backspace, - termion::event::Key::Left => Key::Left, - termion::event::Key::Right => Key::Right, - termion::event::Key::Up => Key::Up, - termion::event::Key::Down => Key::Down, - termion::event::Key::Home => Key::Home, - termion::event::Key::End => Key::End, - termion::event::Key::PageUp => Key::PageUp, - termion::event::Key::PageDown => Key::PageDown, - termion::event::Key::BackTab => Key::BackTab, - termion::event::Key::Delete => Key::Delete, - termion::event::Key::Insert => Key::Insert, - termion::event::Key::F(n) => Key::F(n), - termion::event::Key::Char(c) => Key::Char(c), - termion::event::Key::Alt(c) => Key::Alt(c), - termion::event::Key::Ctrl(c) => Key::Ctrl(c), - termion::event::Key::Null => Key::Null, - termion::event::Key::Esc => Key::Esc, - _ => { - unimplemented!("Encountered an unknown key!") - } - } -} diff --git a/src/common/input/keybinds.rs b/src/common/input/keybinds.rs deleted file mode 100644 index 8bef47fd..00000000 --- a/src/common/input/keybinds.rs +++ /dev/null @@ -1,301 +0,0 @@ -//! Mapping of inputs to sequences of actions. -use std::collections::HashMap; - -use super::actions::Action; -use super::config; - -use serde::{Deserialize, Serialize}; -use strum::IntoEnumIterator; -use zellij_tile::data::*; - -/// Used in the config struct -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct Keybinds(HashMap<InputMode, ModeKeybinds>); -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct ModeKeybinds(HashMap<Key, Vec<Action>>); - -/// Intermediate struct used for deserialisation -/// Used in the config file. -#[derive(Clone, Debug, PartialEq, Deserialize)] -pub struct KeybindsFromYaml { - #[serde(flatten)] - keybinds: HashMap<InputMode, Vec<KeyActionUnbind>>, - #[serde(default)] - unbind: Unbind, -} - -/// Intermediate enum used for deserialisation -#[derive(Clone, Debug, PartialEq, Deserialize)] -#[serde(untagged)] -enum KeyActionUnbind { - KeyAction(KeyActionFromYaml), - Unbind(UnbindFromYaml), -} - -/// Intermediate struct used for deserialisation -#[derive(Clone, Debug, PartialEq, Deserialize)] -struct KeyActionUnbindFromYaml { - keybinds: Vec<KeyActionFromYaml>, - unbind: Unbind, -} - -/// Intermediate struct used for deserialisation -#[derive(Clone, Debug, PartialEq, Deserialize)] -pub struct KeyActionFromYaml { - action: Vec<Action>, - key: Vec<Key>, -} - -/// Intermediate struct used for deserialisation -#[derive(Clone, Debug, PartialEq, Deserialize)] -struct UnbindFromYaml { - unbind: Unbind, -} - -/// List of keys, for which to disable their respective default actions -/// `All` is a catch all, and will disable the default actions for all keys. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize)] -#[serde(untagged)] -enum Unbind { - // This is the correct order, don't rearrange! - // Suspected Bug in the untagged macro. - // 1. Keys - Keys(Vec<Key>), - // 2. All - All(bool), -} - -impl Default for Keybinds { - // Use once per codepath - // TODO investigate why - fn default() -> Keybinds { - Self::from_default_assets() - } -} - -impl Keybinds { - pub fn new() -> Keybinds { - Keybinds(HashMap::<InputMode, ModeKeybinds>::new()) - } - - fn from_default_assets() -> Keybinds { - config::Config::from_default_assets() - .expect("Keybinds from default assets Error!") - .keybinds - } - - /// Entrypoint from the config module - pub fn get_default_keybinds_with_config(from_yaml: Option<KeybindsFromYaml>) -> Keybinds { - let default_keybinds = match from_yaml.clone() { - Some(keybinds) => match keybinds.unbind { - Unbind::All(true) => Keybinds::new(), - Unbind::All(false) | Unbind::Keys(_) => Keybinds::unbind(keybinds), - }, - None => Keybinds::default(), - }; - - if let Some(keybinds) = from_yaml { - default_keybinds.merge_keybinds(Keybinds::from(keybinds)) - } else { - default_keybinds - } - } - - /// Unbinds the default keybindings in relation to their mode - fn unbind(from_yaml: KeybindsFromYaml) -> Keybinds { - let mut keybind_config = Self::new(); - let mut unbind_config: HashMap<InputMode, Unbind> = HashMap::new(); - let keybinds_from_yaml = from_yaml.keybinds; - - for mode in InputMode::iter() { - if let Some(keybinds) = keybinds_from_yaml.get(&mode) { - for keybind in keybinds.iter() { - match keybind { - KeyActionUnbind::Unbind(unbind) => { - unbind_config.insert(mode, unbind.unbind.clone()); - } - KeyActionUnbind::KeyAction(key_action_from_yaml) => { - keybind_config - .0 - .insert(mode, ModeKeybinds::from(key_action_from_yaml.clone())); - } - } - } - } - } - - let mut default = Self::default().unbind_mode(unbind_config); - - // Toplevel Unbinds - if let Unbind::Keys(_) = from_yaml.unbind { - let mut unbind_config: HashMap<InputMode, Unbind> = HashMap::new(); - for mode in InputMode::iter() { - unbind_config.insert(mode, from_yaml.unbind.clone()); - } - default = default.unbind_mode(unbind_config); - }; - - default.merge_keybinds(keybind_config) - } - - /// Unbind [`Key`] bindings respective to their mode - fn unbind_mode(&self, unbind: HashMap<InputMode, Unbind>) -> Keybinds { - let mut keybinds = Keybinds::new(); - - for mode in InputMode::iter() { - if let Some(unbind) = unbind.get(&mode) { - match unbind { - Unbind::All(true) => {} - Unbind::Keys(keys) => { - if let Some(defaults) = self.0.get(&mode) { - keybinds - .0 - .insert(mode, defaults.clone().unbind_keys(keys.to_vec())); - } - } - Unbind::All(false) => { - if let Some(defaults) = self.0.get(&mode) { - keybinds.0.insert(mode, defaults.clone()); - } - } - } - } else if let Some(defaults) = self.0.get(&mode) { - keybinds.0.insert(mode, defaults.clone()); - } - } - keybinds - } - - /// Merges two Keybinds structs into one Keybinds struct - /// `other` overrides the ModeKeybinds of `self`. - fn merge_keybinds(&self, other: Keybinds) -> Keybinds { - let mut keybinds = Keybinds::new(); - - for mode in InputMode::iter() { - let mut mode_keybinds = ModeKeybinds::new(); - if let Some(keybind) = self.0.get(&mode) { - mode_keybinds.0.extend(keybind.0.clone()); - }; - if let Some(keybind) = other.0.get(&mode) { - mode_keybinds.0.extend(keybind.0.clone()); - } - if !mode_keybinds.0.is_empty() { - keybinds.0.insert(mode, mode_keybinds); - } - } - keybinds - } - - /// Converts a [`Key`] terminal event to a sequence of [`Action`]s according to the current - /// [`InputMode`] and [`Keybinds`]. - pub fn key_to_actions( - key: &Key, - input: Vec<u8>, - mode: &InputMode, - keybinds: &Keybinds, - ) -> Vec<Action> { - let mode_keybind_or_action = |action: Action| { - keybinds - .0 - .get(mode) - .unwrap_or_else(|| unreachable!("Unrecognized mode: {:?}", mode)) - .0 - .get(key) - .cloned() - .unwrap_or_else(|| vec![action]) - }; - match *mode { - InputMode::Normal | InputMode::Locked => mode_keybind_or_action(Action::Write(input)), - InputMode::RenameTab => mode_keybind_or_action(Action::TabNameInput(input)), - _ => mode_keybind_or_action(Action::NoOp), - } - } -} - -impl ModeKeybinds { - fn new() -> ModeKeybinds { - ModeKeybinds(HashMap::<Key, Vec<Action>>::new()) - } - - /// Merges `self` with `other`, if keys are the same, `other` overwrites. - fn merge(self, other: ModeKeybinds) -> ModeKeybinds { - let mut merged = self; - merged.0.extend(other.0); - merged - } - - /// Remove [`Key`]'s from [`ModeKeybinds`] - fn unbind_keys(self, unbind: Vec<Key>) -> Self { - let mut keymap = self; - for key in unbind { - keymap.0.remove(&key); - } - keymap - } -} - -impl From<KeybindsFromYaml> for Keybinds { - fn from(keybinds_from_yaml: KeybindsFromYaml) -> Keybinds { - let mut keybinds = Keybinds::new(); - - for mode in InputMode::iter() { - let mut mode_keybinds = ModeKeybinds::new(); - for key_action in keybinds_from_yaml.keybinds.get(&mode).iter() { - for keybind in key_action.iter() { - mode_keybinds = mode_keybinds.merge(ModeKeybinds::from(keybind.clone())); - } - } - keybinds.0.insert(mode, mode_keybinds); - } - keybinds - } -} - -/// For each [`Key`] assigned to [`Action`]s, -/// map the [`Action`]s to the [`Key`] -impl From<KeyActionFromYaml> for ModeKeybinds { - fn from(key_action: KeyActionFromYaml) -> ModeKeybinds { - let actions = key_action.action; - - ModeKeybinds( - key_action - .key - .into_iter() - .map(|k| (k, actions.clone())) - .collect::<HashMap<Key, Vec<Action>>>(), - ) - } -} - -impl From<KeyActionUnbind> for ModeKeybinds { - fn from(key_action_unbind: KeyActionUnbind) -> ModeKeybinds { - match key_action_unbind { - KeyActionUnbind::KeyAction(key_action) => ModeKeybinds::from(key_action), - KeyActionUnbind::Unbind(_) => ModeKeybinds::new(), - } - } -} - -impl From<Vec<KeyActionFromYaml>> for ModeKeybinds { - fn from(key_action_from_yaml: Vec<KeyActionFromYaml>) -> ModeKeybinds { - let mut mode_keybinds = ModeKeybinds::new(); - - for keybind in key_action_from_yaml { - for key in keybind.key { - mode_keybinds.0.insert(key, keybind.action.clone()); - } - } - mode_keybinds - } -} - -impl Default for Unbind { - fn default() -> Unbind { - Unbind::All(false) - } -} - -// The unit test location. -#[cfg(test)] -#[path = "./unit/keybinds_test.rs"] -mod keybinds_test; diff --git a/src/common/input/mod.rs b/src/common/input/mod.rs deleted file mode 100644 index f30ccf23..00000000 --- a/src/common/input/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! The way terminal input is handled. - -pub mod actions; -pub mod config; -pub mod handler; -pub mod keybinds; -pub mod options; diff --git a/src/common/input/options.rs b/src/common/input/options.rs deleted file mode 100644 index 33625b65..00000000 --- a/src/common/input/options.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Handles cli and configuration options -use crate::cli::ConfigCli; -use serde::{Deserialize, Serialize}; -use structopt::StructOpt; - -#[derive(Clone, Default, Debug, PartialEq, Deserialize, Serialize, StructOpt)] -/// Options that can be set either through the config file, -/// or cli flags -pub struct Options { - /// Allow plugins to use a more simplified layout - /// that is compatible with more fonts - #[structopt(long)] - pub simplified_ui: bool, -} - -impl Options { - pub fn from_yaml(from_yaml: Option<Options>) -> Options { - if let Some(opts) = from_yaml { - opts - } else { - Options::default() - } - } - - /// Merges two [`Options`] structs, a `Some` in `other` - /// will supercede a `Some` in `self` - // TODO: Maybe a good candidate for a macro? - pub fn merge(&self, other: Options) -> Options { - let simplified_ui = if other.simplified_ui { - true - } else { - self.simplified_ui - }; - - Options { simplified_ui } - } - - pub fn from_cli(&self, other: Option<ConfigCli>) -> Options { - if let Some(ConfigCli::Options(options)) = other { - Options::merge(&self, options) - } else { - self.to_owned() - } - } -} diff --git a/src/common/input/unit/keybinds_test.rs b/src/common/input/unit/keybinds_test.rs deleted file mode 100644 index 4767fc15..00000000 --- a/src/common/input/unit/keybinds_test.rs +++ /dev/null @@ -1,801 +0,0 @@ -use super::super::actions::*; -use super::super::keybinds::*; -use zellij_tile::data::Key; - -#[test] -fn merge_keybinds_merges_different_keys() { - let mut mode_keybinds_self = ModeKeybinds::new(); - mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]); - let mut mode_keybinds_other = ModeKeybinds::new(); - mode_keybinds_other - .0 - .insert(Key::Backspace, vec![Action::NoOp]); - - let mut mode_keybinds_expected = ModeKeybinds::new(); - mode_keybinds_expected - .0 - .insert(Key::F(1), vec![Action::NoOp]); - mode_keybinds_expected - .0 - .insert(Key::Backspace, vec![Action::NoOp]); - - let mode_keybinds_merged = mode_keybinds_self.merge(mode_keybinds_other); - - assert_eq!(mode_keybinds_expected, mode_keybinds_merged); -} - -#[test] -fn merge_mode_keybinds_overwrites_same_keys() { - let mut mode_keybinds_self = ModeKeybinds::new(); - mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]); - let mut mode_keybinds_other = ModeKeybinds::new(); - mode_keybinds_other - .0 - .insert(Key::F(1), vec![Action::GoToTab(1)]); - - let mut mode_keybinds_expected = ModeKeybinds::new(); - mode_keybinds_expected - .0 - .insert(Key::F(1), vec![Action::GoToTab(1)]); - - let mode_keybinds_merged = mode_keybinds_self.merge(mode_keybinds_other); - - assert_eq!(mode_keybinds_expected, mode_keybinds_merged); -} - -#[test] -fn merge_keybinds_merges() { - let mut mode_keybinds_self = ModeKeybinds::new(); - mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]); - let mut mode_keybinds_other = ModeKeybinds::new(); - mode_keybinds_other - .0 - .insert(Key::Backspace, vec![Action::NoOp]); - let mut keybinds_self = Keybinds::new(); - keybinds_self - .0 - .insert(InputMode::Normal, mode_keybinds_self.clone()); - let mut keybinds_other = Keybinds::new(); - keybinds_other - .0 - .insert(InputMode::Resize, mode_keybinds_other.clone()); - let mut keybinds_expected = Keybinds::new(); - keybinds_expected - .0 - .insert(InputMode::Normal, mode_keybinds_self); - keybinds_expected - .0 - .insert(InputMode::Resize, mode_keybinds_other); - - assert_eq!( - keybinds_expected, - keybinds_self.merge_keybinds(keybinds_other) - ) -} - -#[test] -fn merge_keybinds_overwrites_same_keys() { - let mut mode_keybinds_self = ModeKeybinds::new(); - mode_keybinds_self.0.insert(Key::F(1), vec![Action::NoOp]); - mode_keybinds_self.0.insert(Key::F(2), vec![Action::NoOp]); - mode_keybinds_self.0.insert(Key::F(3), vec![Action::NoOp]); - let mut mode_keybinds_other = ModeKeybinds::new(); - mode_keybinds_other - .0 - .insert(Key::F(1), vec![Action::GoToTab(1)]); - mode_keybinds_other - .0 - .insert(Key::F(2), vec![Action::GoToTab(2)]); - mode_keybinds_other - .0 - .insert(Key::F(3), vec![Action::GoToTab(3)]); - let mut keybinds_self = Keybinds::new(); - keybinds_self - .0 - .insert(InputMode::Normal, mode_keybinds_self.clone()); - let mut keybinds_other = Keybinds::new(); - keybinds_other - .0 - .insert(InputMode::Normal, mode_keybinds_other.clone()); - let mut keybinds_expected = Keybinds::new(); - keybinds_expected - .0 - .insert(InputMode::Normal, mode_keybinds_other); - - assert_eq!( - keybinds_expected, - keybinds_self.merge_keybinds(keybinds_other) - ) -} - -#[test] -fn from_keyaction_from_yaml_to_mode_keybindings() { - let actions = vec![Action::NoOp, Action::GoToTab(1)]; - let keyaction = KeyActionFromYaml { - action: actions.clone(), - key: vec![Key::F(1), Key::Backspace, Key::Char('t')], - }; - - let mut expected = ModeKeybinds::new(); - expected.0.insert(Key::F(1), actions.clone()); - expected.0.insert(Key::Backspace, actions.clone()); - expected.0.insert(Key::Char('t'), actions); - - assert_eq!(expected, ModeKeybinds::from(keyaction)); -} - -#[test] -fn toplevel_unbind_unbinds_all() { - let from_yaml = KeybindsFromYaml { - unbind: Unbind::All(true), - keybinds: HashMap::new(), - }; - - let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml)); - - assert_eq!(keybinds_from_yaml, Keybinds::new()); -} - -#[test] -fn no_unbind_unbinds_none() { - let from_yaml = KeybindsFromYaml { - unbind: Unbind::All(false), - keybinds: HashMap::new(), - }; - - let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml)); - - assert_eq!(keybinds_from_yaml, Keybinds::default()); -} - -#[test] -fn last_keybind_is_taken() { - let actions_1 = vec![Action::NoOp, Action::NewTab]; - let keyaction_1 = KeyActionFromYaml { - action: actions_1.clone(), - key: vec![Key::F(1), Key::Backspace, Key::Char('t')], - }; - let actions_2 = vec![Action::GoToTab(1)]; - let keyaction_2 = KeyActionFromYaml { - action: actions_2.clone(), - key: vec![Key::F(1), Key::Backspace, Key::Char('t')], - }; - - let mut expected = ModeKeybinds::new(); - expected.0.insert(Key::F(1), actions_2.clone()); - expected.0.insert(Key::Backspace, actions_2.clone()); - expected.0.insert(Key::Char('t'), actions_2); - - assert_eq!(expected, ModeKeybinds::from(vec![keyaction_1, keyaction_2])); -} - -#[test] -fn last_keybind_overwrites() { - let actions_1 = vec![Action::NoOp, Action::NewTab]; - let keyaction_1 = KeyActionFromYaml { - action: actions_1.clone(), - key: vec![Key::F(1), Key::Backspace, Key::Char('t')], - }; - let actions_2 = vec![Action::GoToTab(1)]; - let keyaction_2 = KeyActionFromYaml { - action: actions_2.clone(), - key: vec![Key::F(1), Key::Char('t')], - }; - - let mut expected = ModeKeybinds::new(); - expected.0.insert(Key::F(1), actions_2.clone()); - expected.0.insert(Key::Backspace, actions_1.clone()); - expected.0.insert(Key::Char('t'), actions_2); - - assert_eq!(expected, ModeKeybinds::from(vec![keyaction_1, keyaction_2])); -} - -#[test] -fn unbind_single_mode() { - let unbind = Unbind::All(true); - let unbind_from_yaml = UnbindFromYaml { unbind }; - let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)]; - - let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - keys.insert(InputMode::Normal, key_action_unbinds); - - let keybinds_from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::All(false), - }; - - let keybinds = Keybinds::unbind(keybinds_from_yaml); - let result = keybinds.0.get(&InputMode::Normal); - assert!(result.is_none()); -} - -#[test] -fn unbind_multiple_modes() { - let unbind = Unbind::All(true); - let unbind_from_yaml = UnbindFromYaml { unbind }; - let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)]; - - let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - keys.insert(InputMode::Normal, key_action_unbinds.clone()); - keys.insert(InputMode::Pane, key_action_unbinds); - - let keybinds_from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::All(false), - }; - - let keybinds = Keybinds::unbind(keybinds_from_yaml); - let normal = keybinds.0.get(&InputMode::Normal); - let pane = keybinds.0.get(&InputMode::Pane); - assert!(normal.is_none()); - assert!(pane.is_none()); -} - -#[test] -fn unbind_single_keybind_single_mode() { - let unbind = Unbind::Keys(vec![Key::Alt('n')]); - let unbind_from_yaml = UnbindFromYaml { unbind }; - let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)]; - - let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - keys.insert(InputMode::Normal, key_action_unbinds); - - let keybinds_from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::All(false), - }; - - let keybinds = Keybinds::unbind(keybinds_from_yaml); - let mode_keybinds = keybinds.0.get(&InputMode::Normal); - let result = mode_keybinds - .expect("Mode shouldn't be empty") - .0 - .get(&Key::Alt('n')); - assert!(result.is_none()); -} - -#[test] -fn unbind_single_keybind_multiple_modes() { - let unbind_n = Unbind::Keys(vec![Key::Alt('n')]); - let unbind_h = Unbind::Keys(vec![Key::Alt('h')]); - let unbind_from_yaml_n = UnbindFromYaml { unbind: unbind_n }; - let unbind_from_yaml_h = UnbindFromYaml { unbind: unbind_h }; - let key_action_unbinds_n = vec![KeyActionUnbind::Unbind(unbind_from_yaml_n)]; - let key_action_unbinds_h = vec![KeyActionUnbind::Unbind(unbind_from_yaml_h)]; - - let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - keys.insert(InputMode::Normal, key_action_unbinds_n); - keys.insert(InputMode::Pane, key_action_unbinds_h); - - let keybinds_from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::All(false), - }; - - let keybinds = Keybinds::unbind(keybinds_from_yaml); - let normal = keybinds.0.get(&InputMode::Normal); - let pane = keybinds.0.get(&InputMode::Pane); - let result_normal = normal - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_pane = pane.expect("Mode shouldn't be empty").0.get(&Key::Alt('h')); - assert!(result_normal.is_none()); - assert!(result_pane.is_none()); -} - -#[test] -fn unbind_multiple_keybinds_single_mode() { - let unbind = Unbind::Keys(vec![Key::Alt('n'), Key::Ctrl('p')]); - let unbind_from_yaml = UnbindFromYaml { unbind }; - let key_action_unbinds = vec![KeyActionUnbind::Unbind(unbind_from_yaml)]; - - let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - keys.insert(InputMode::Normal, key_action_unbinds); - - let keybinds_from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::All(false), - }; - - let keybinds = Keybinds::unbind(keybinds_from_yaml); - let mode_keybinds = keybinds.0.get(&InputMode::Normal); - let result_n = mode_keybinds - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_p = mode_keybinds - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Ctrl('p')); - assert!(result_n.is_none()); - assert!(result_p.is_none()); -} - -#[test] -fn unbind_multiple_keybinds_multiple_modes() { - let unbind_normal = Unbind::Keys(vec![Key::Alt('n'), Key::Ctrl('p')]); - let unbind_resize = Unbind::Keys(vec![Key::Char('h'), Key::Ctrl('r')]); - let unbind_from_yaml_normal = UnbindFromYaml { - unbind: unbind_normal, - }; - let unbind_from_yaml_resize = UnbindFromYaml { - unbind: unbind_resize, - }; - let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml_normal)]; - let key_action_unbinds_resize = vec![KeyActionUnbind::Unbind(unbind_from_yaml_resize)]; - - let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - keys.insert(InputMode::Normal, key_action_unbinds_normal); - keys.insert(InputMode::Resize, key_action_unbinds_resize); - - let keybinds_from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::All(false), - }; - - let keybinds = Keybinds::unbind(keybinds_from_yaml); - let mode_keybinds_normal = keybinds.0.get(&InputMode::Normal); - let mode_keybinds_resize = keybinds.0.get(&InputMode::Resize); - let result_normal_1 = mode_keybinds_normal - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_normal_2 = mode_keybinds_normal - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Ctrl('p')); - let result_resize_1 = mode_keybinds_resize - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Char('h')); - let result_resize_2 = mode_keybinds_resize - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Ctrl('r')); - assert!(result_normal_1.is_none()); - assert!(result_resize_1.is_none()); - assert!(result_normal_2.is_none()); - assert!(result_resize_2.is_none()); -} - -#[test] -fn unbind_multiple_keybinds_all_modes() { - let unbind = Unbind::Keys(vec![Key::Alt('n'), Key::Alt('h')]); - let keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - let keybinds_from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind, - }; - - let keybinds = Keybinds::unbind(keybinds_from_yaml); - let mode_keybinds_normal = keybinds.0.get(&InputMode::Normal); - let mode_keybinds_resize = keybinds.0.get(&InputMode::Resize); - let result_normal_1 = mode_keybinds_normal - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_normal_2 = mode_keybinds_normal - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Ctrl('h')); - let result_resize_1 = mode_keybinds_resize - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Char('n')); - let result_resize_2 = mode_keybinds_resize - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Ctrl('h')); - assert!(result_normal_1.is_none()); - assert!(result_resize_1.is_none()); - assert!(result_normal_2.is_none()); - assert!(result_resize_2.is_none()); -} - -#[test] -fn unbind_all_toplevel_single_key_single_mode() { - let unbind = Unbind::Keys(vec![Key::Alt('h')]); - let unbind_from_yaml = UnbindFromYaml { unbind }; - let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml)]; - let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - keys.insert(InputMode::Normal, key_action_unbinds_normal); - let from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::All(true), - }; - - let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml)); - assert_eq!(keybinds_from_yaml, Keybinds::new()); -} - -#[test] -fn unbind_all_toplevel_single_key_multiple_modes() { - let unbind_n = Unbind::Keys(vec![Key::Alt('n')]); - let unbind_h = Unbind::Keys(vec![Key::Alt('h')]); - let unbind_from_yaml_n = UnbindFromYaml { unbind: unbind_n }; - let unbind_from_yaml_h = UnbindFromYaml { unbind: unbind_h }; - let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml_n)]; - let key_action_unbinds_pane = vec![KeyActionUnbind::Unbind(unbind_from_yaml_h)]; - let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - keys.insert(InputMode::Normal, key_action_unbinds_normal); - keys.insert(InputMode::Pane, key_action_unbinds_pane); - let from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::All(true), - }; - - let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml)); - assert_eq!(keybinds_from_yaml, Keybinds::new()); -} - -#[test] -fn unbind_all_toplevel_multiple_key_multiple_modes() { - let unbind_n = Unbind::Keys(vec![Key::Alt('n'), Key::Ctrl('p')]); - let unbind_h = Unbind::Keys(vec![Key::Alt('h'), Key::Ctrl('t')]); - let unbind_from_yaml_n = UnbindFromYaml { unbind: unbind_n }; - let unbind_from_yaml_h = UnbindFromYaml { unbind: unbind_h }; - let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml_n)]; - let key_action_unbinds_pane = vec![KeyActionUnbind::Unbind(unbind_from_yaml_h)]; - let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - keys.insert(InputMode::Normal, key_action_unbinds_normal); - keys.insert(InputMode::Pane, key_action_unbinds_pane); - let from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::All(true), - }; - - let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml)); - assert_eq!(keybinds_from_yaml, Keybinds::new()); -} - -#[test] -fn unbind_all_toplevel_all_key_multiple_modes() { - let unbind = Unbind::All(true); - let unbind_from_yaml = UnbindFromYaml { unbind }; - let key_action_unbinds_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml.clone())]; - let key_action_unbinds_pane = vec![KeyActionUnbind::Unbind(unbind_from_yaml)]; - let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - keys.insert(InputMode::Normal, key_action_unbinds_normal); - keys.insert(InputMode::Pane, key_action_unbinds_pane); - let from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::All(true), - }; - - let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml)); - assert_eq!(keybinds_from_yaml, Keybinds::new()); -} - -#[test] -fn unbind_single_keybind_all_modes() { - let keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - let from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::Keys(vec![Key::Alt('n')]), - }; - - let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml)); - - let result_normal = keybinds_from_yaml - .0 - .get(&InputMode::Normal) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_pane = keybinds_from_yaml - .0 - .get(&InputMode::Pane) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_resize = keybinds_from_yaml - .0 - .get(&InputMode::Resize) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_tab = keybinds_from_yaml - .0 - .get(&InputMode::Tab) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - - assert!(result_normal.is_none()); - assert!(result_pane.is_none()); - assert!(result_resize.is_none()); - assert!(result_tab.is_none()); -} - -#[test] -fn unbind_single_toplevel_single_key_single_mode_identical() { - let unbind = Unbind::Keys(vec![Key::Alt('n')]); - let unbind_from_yaml = UnbindFromYaml { unbind }; - let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)]; - let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - keys.insert(InputMode::Normal, key_action_unbind); - let from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::Keys(vec![Key::Alt('n')]), - }; - - let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml)); - - let result_normal = keybinds_from_yaml - .0 - .get(&InputMode::Normal) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_pane = keybinds_from_yaml - .0 - .get(&InputMode::Pane) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_resize = keybinds_from_yaml - .0 - .get(&InputMode::Resize) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_tab = keybinds_from_yaml - .0 - .get(&InputMode::Tab) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - - assert!(result_normal.is_none()); - assert!(result_pane.is_none()); - assert!(result_resize.is_none()); - assert!(result_tab.is_none()); -} - -#[test] -fn unbind_single_toplevel_single_key_single_mode_differing() { - let unbind = Unbind::Keys(vec![Key::Alt('l')]); - let unbind_from_yaml = UnbindFromYaml { unbind }; - let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)]; - let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - keys.insert(InputMode::Normal, key_action_unbind); - let from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::Keys(vec![Key::Alt('n')]), - }; - - let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml)); - - let result_normal_n = keybinds_from_yaml - .0 - .get(&InputMode::Normal) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_normal_l = keybinds_from_yaml - .0 - .get(&InputMode::Normal) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('l')); - let result_resize_n = keybinds_from_yaml - .0 - .get(&InputMode::Resize) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_resize_l = keybinds_from_yaml - .0 - .get(&InputMode::Resize) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('l')); - - assert!(result_normal_n.is_none()); - assert!(result_normal_l.is_none()); - assert!(result_resize_n.is_none()); - assert!(result_resize_l.is_some()); -} - -#[test] -fn unbind_single_toplevel_single_key_multiple_modes() { - let unbind = Unbind::Keys(vec![Key::Alt('l')]); - let unbind_from_yaml = UnbindFromYaml { unbind }; - let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)]; - let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - keys.insert(InputMode::Normal, key_action_unbind.clone()); - keys.insert(InputMode::Pane, key_action_unbind); - let from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::Keys(vec![Key::Alt('n')]), - }; - - let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml)); - - let result_normal_n = keybinds_from_yaml - .0 - .get(&InputMode::Normal) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_normal_l = keybinds_from_yaml - .0 - .get(&InputMode::Normal) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('l')); - let result_pane_n = keybinds_from_yaml - .0 - .get(&InputMode::Pane) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_pane_l = keybinds_from_yaml - .0 - .get(&InputMode::Pane) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('l')); - - assert!(result_normal_n.is_none()); - assert!(result_normal_l.is_none()); - assert!(result_pane_n.is_none()); - assert!(result_pane_l.is_none()); -} - -#[test] -fn unbind_single_toplevel_multiple_keys_single_mode() { - let unbind = Unbind::Keys(vec![ - Key::Alt('l'), - Key::Alt('h'), - Key::Alt('j'), - Key::Alt('k'), - ]); - let unbind_from_yaml = UnbindFromYaml { unbind }; - let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)]; - let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - keys.insert(InputMode::Normal, key_action_unbind.clone()); - keys.insert(InputMode::Pane, key_action_unbind); - let from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::Keys(vec![Key::Alt('n')]), - }; - - let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml)); - - let result_normal_n = keybinds_from_yaml - .0 - .get(&InputMode::Normal) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_normal_l = keybinds_from_yaml - .0 - .get(&InputMode::Normal) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('l')); - let result_normal_k = keybinds_from_yaml - .0 - .get(&InputMode::Pane) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('k')); - let result_normal_h = keybinds_from_yaml - .0 - .get(&InputMode::Pane) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('h')); - - assert!(result_normal_n.is_none()); - assert!(result_normal_l.is_none()); - assert!(result_normal_h.is_none()); - assert!(result_normal_k.is_none()); -} - -#[test] -fn unbind_single_toplevel_multiple_keys_multiple_modes() { - let unbind_normal = Unbind::Keys(vec![Key::Alt('l'), Key::Ctrl('p')]); - let unbind_from_yaml_normal = UnbindFromYaml { - unbind: unbind_normal, - }; - let key_action_unbind_normal = vec![KeyActionUnbind::Unbind(unbind_from_yaml_normal)]; - let unbind = Unbind::Keys(vec![Key::Alt('l'), Key::Alt('k')]); - let unbind_from_yaml = UnbindFromYaml { unbind }; - let key_action_unbind = vec![KeyActionUnbind::Unbind(unbind_from_yaml)]; - let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - keys.insert(InputMode::Normal, key_action_unbind_normal); - keys.insert(InputMode::Pane, key_action_unbind); - let from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::Keys(vec![Key::Alt('n')]), - }; - - let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml)); - - let result_normal_n = keybinds_from_yaml - .0 - .get(&InputMode::Normal) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_normal_p = keybinds_from_yaml - .0 - .get(&InputMode::Normal) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Ctrl('p')); - let result_normal_l = keybinds_from_yaml - .0 - .get(&InputMode::Normal) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('l')); - let result_pane_p = keybinds_from_yaml - .0 - .get(&InputMode::Pane) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Ctrl('p')); - let result_pane_n = keybinds_from_yaml - .0 - .get(&InputMode::Pane) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('n')); - let result_pane_l = keybinds_from_yaml - .0 - .get(&InputMode::Pane) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Alt('l')); - - assert!(result_normal_n.is_none()); - assert!(result_normal_l.is_none()); - assert!(result_normal_p.is_none()); - assert!(result_pane_n.is_none()); - assert!(result_pane_p.is_some()); - assert!(result_pane_l.is_none()); -} - -#[test] -fn uppercase_and_lowercase_are_distinct() { - let key_action_n = KeyActionFromYaml { - key: vec![Key::Char('n')], - action: vec![Action::NewTab], - }; - let key_action_large_n = KeyActionFromYaml { - key: vec![Key::Char('N')], - action: vec![Action::NewPane(None)], - }; - - let key_action_unbind = vec![ - KeyActionUnbind::KeyAction(key_action_n), - KeyActionUnbind::KeyAction(key_action_large_n), - ]; - let mut keys = HashMap::<InputMode, Vec<KeyActionUnbind>>::new(); - keys.insert(InputMode::Normal, key_action_unbind); - let from_yaml = KeybindsFromYaml { - keybinds: keys, - unbind: Unbind::All(false), - }; - - let keybinds_from_yaml = Keybinds::get_default_keybinds_with_config(Some(from_yaml)); - let result_n = keybinds_from_yaml - .0 - .get(&InputMode::Normal) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Char('n')); - let result_large_n = keybinds_from_yaml - .0 - .get(&InputMode::Normal) - .expect("ModeKeybinds shouldn't be empty") - .0 - .get(&Key::Char('N')); - - assert!(result_n.is_some()); - assert!(result_large_n.is_some()); -} diff --git a/src/common/ipc.rs b/src/common/ipc.rs deleted file mode 100644 index af6dd859..00000000 --- a/src/common/ipc.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! IPC stuff for starting to split things into a client and server model. - -use crate::cli::CliArgs; -use crate::common::{ - errors::{get_current_ctx, ErrorContext}, - input::{actions::Action, options::Options}, -}; -use crate::panes::PositionAndSize; -use interprocess::local_socket::LocalSocketStream; -use nix::unistd::dup; -use serde::{Deserialize, Serialize}; -use std::io::{self, Write}; -use std::marker::PhantomData; -use std::os::unix::io::{AsRawFd, FromRawFd}; - -type SessionId = u64; - -#[derive(PartialEq, Eq, Serialize, Deserialize, Hash)] -pub struct Session { - // Unique ID for this session - id: SessionId, - // Identifier for the underlying IPC primitive (socket, pipe) - conn_name: String, - // User configured alias for the session - alias: String, -} - -// How do we want to connect to a session? -#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum ClientType { - Reader, - Writer, -} - -// Types of messages sent from the client to the server -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum ClientToServerMsg { - /*// List which sessions are available - ListSessions, - // Create a new session - CreateSession, - // Attach to a running session - AttachToSession(SessionId, ClientType), - // Force detach - DetachSession(SessionId), - // Disconnect from the session we're connected to - DisconnectFromSession,*/ - ClientExit, - TerminalResize(PositionAndSize), - NewClient(PositionAndSize, Box<CliArgs>, Box<Options>), - Action(Action), -} - -// Types of messages sent from the server to the client -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum ServerToClientMsg { - /*// Info about a particular session - SessionInfo(Session), - // A list of sessions - SessionList(HashSet<Session>),*/ - Render(Option<String>), - UnblockInputThread, - Exit, - ServerError(String), -} - -/// Sends messages on a stream socket, along with an [`ErrorContext`]. -pub struct IpcSenderWithContext<T: Serialize> { - sender: io::BufWriter<LocalSocketStream>, - _phantom: PhantomData<T>, -} - -impl<T: Serialize> IpcSenderWithContext<T> { - /// Returns a sender to the given [LocalSocketStream](interprocess::local_socket::LocalSocketStream). - pub fn new(sender: LocalSocketStream) -> Self { - Self { - sender: io::BufWriter::new(sender), - _phantom: PhantomData, - } - } - - /// Sends an event, along with the current [`ErrorContext`], on this [`IpcSenderWithContext`]'s socket. - pub fn send(&mut self, msg: T) { - let err_ctx = get_current_ctx(); - bincode::serialize_into(&mut self.sender, &(msg, err_ctx)).unwrap(); - self.sender.flush().unwrap(); - } - - /// Returns an [`IpcReceiverWithContext`] with the same socket as this sender. - pub fn get_receiver<F>(&self) -> IpcReceiverWithContext<F> - where - F: for<'de> Deserialize<'de> + Serialize, - { - let sock_fd = self.sender.get_ref().as_raw_fd(); - let dup_sock = dup(sock_fd).unwrap(); - let socket = unsafe { LocalSocketStream::from_raw_fd(dup_sock) }; - IpcReceiverWithContext::new(socket) - } -} - -/// Receives messages on a stream socket, along with an [`ErrorContext`]. -pub struct IpcReceiverWithContext<T> { - receiver: io::BufReader<LocalSocketStream>, - _phantom: PhantomData<T>, -} - -impl<T> IpcReceiverWithContext<T> -where - T: for<'de> Deserialize<'de> + Serialize, -{ - /// Returns a receiver to the given [LocalSocketStream](interprocess::local_socket::LocalSocketStream). - pub fn new(receiver: LocalSocketStream) -> Self { - Self { - receiver: io::BufReader::new(receiver), - _phantom: PhantomData, - } - } - - /// Receives an event, along with the current [`ErrorContext`], on this [`IpcReceiverWithContext`]'s socket. - pub fn recv(&mut self) -> (T, ErrorContext) { - bincode::deserialize_from(&mut self.receiver).unwrap() - } - - /// Returns an [`IpcSenderWithContext`] with the same socket as this receiver. - pub fn get_sender<F: Serialize>(&self) -> IpcSenderWithContext<F> { - let sock_fd = self.receiver.get_ref().as_raw_fd(); - let dup_sock = dup(sock_fd).unwrap(); - let socket = unsafe { LocalSocketStream::from_raw_fd(dup_sock) }; - IpcSenderWithContext::new(socket) - } -} diff --git a/src/common/mod.rs b/src/common/mod.rs deleted file mode 100644 index c91e1e05..00000000 --- a/src/common/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub mod command_is_executing; -pub mod errors; -pub mod input; -pub mod ipc; -pub mod os_input_output; -pub mod pty; -pub mod screen; -pub mod setup; -pub mod thread_bus; -pub mod utils; -pub mod wasm_vm; - -use crate::panes::PaneId; -use crate::server::ServerInstruction; diff --git a/src/common/os_input_output.rs b/src/common/os_input_output.rs deleted file mode 100644 index adde6fdf..00000000 --- a/src/common/os_input_output.rs +++ /dev/null @@ -1,414 +0,0 @@ -use crate::common::ipc::{ - ClientToServerMsg, IpcReceiverWithContext, IpcSenderWithContext, ServerToClientMsg, -}; -use crate::errors::ErrorContext; -use crate::panes::PositionAndSize; -use crate::utils::shared::default_palette; -use interprocess::local_socket::LocalSocketStream; -use nix::fcntl::{fcntl, FcntlArg, OFlag}; -use nix::pty::{forkpty, Winsize}; -use nix::sys::signal::{kill, Signal}; -use nix::sys::termios; -use nix::sys::wait::waitpid; -use nix::unistd::{self, ForkResult, Pid}; -use signal_hook::{consts::signal::*, iterator::Signals}; -use std::io::prelude::*; -use std::os::unix::{fs::PermissionsExt, io::RawFd}; -use std::path::{Path, PathBuf}; -use std::process::{Child, Command}; -use std::sync::{Arc, Mutex}; -use std::{env, fs, io}; -use zellij_tile::data::Palette; - -const UNIX_PERMISSIONS: u32 = 0o700; - -pub fn set_permissions(path: &Path) -> io::Result<()> { - let mut permissions = fs::metadata(path)?.permissions(); - permissions.set_mode(UNIX_PERMISSIONS); - fs::set_permissions(path, permissions) -} - -fn into_raw_mode(pid: RawFd) { - let mut tio = termios::tcgetattr(pid).expect("could not get terminal attribute"); - termios::cfmakeraw(&mut tio); - match termios::tcsetattr(pid, termios::SetArg::TCSANOW, &tio) { - Ok(_) => {} - Err(e) => panic!("error {:?}", e), - }; -} - -fn unset_raw_mode(pid: RawFd, orig_termios: termios::Termios) { - match termios::tcsetattr(pid, termios::SetArg::TCSANOW, &orig_termios) { - Ok(_) => {} - Err(e) => panic!("error {:?}", e), - }; -} - -pub fn get_terminal_size_using_fd(fd: RawFd) -> PositionAndSize { - // TODO: do this with the nix ioctl - use libc::ioctl; - use libc::TIOCGWINSZ; - - let mut winsize = Winsize { - ws_row: 0, - ws_col: 0, - ws_xpixel: 0, - ws_ypixel: 0, - }; - - unsafe { ioctl(fd, TIOCGWINSZ, &mut winsize) }; - PositionAndSize::from(winsize) -} - -pub fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) { - // TODO: do this with the nix ioctl - use libc::ioctl; - use libc::TIOCSWINSZ; - - let winsize = Winsize { - ws_col: columns, - ws_row: rows, - ws_xpixel: 0, - ws_ypixel: 0, - }; - unsafe { ioctl(fd, TIOCSWINSZ, &winsize) }; -} - -/// Handle some signals for the child process. This will loop until the child -/// process exits. -fn handle_command_exit(mut child: Child) { - // register the SIGINT signal (TODO handle more signals) - let mut signals = ::signal_hook::iterator::Signals::new(&[SIGINT]).unwrap(); - 'handle_exit: loop { - // test whether the child process has exited - match child.try_wait() { - Ok(Some(_status)) => { - // if the child process has exited, break outside of the loop - // and exit this function - // TODO: handle errors? - break 'handle_exit; - } - Ok(None) => { - ::std::thread::sleep(::std::time::Duration::from_millis(100)); - } - Err(e) => panic!("error attempting to wait: {}", e), - } - - for signal in signals.pending() { - if let SIGINT = signal { - child.kill().unwrap(); - child.wait().unwrap(); - break 'handle_exit; - } - } - } -} - -/// Spawns a new terminal from the parent terminal with [`termios`](termios::Termios) -/// `orig_termios`. -/// -/// If a `file_to_open` is given, the text editor specified by environment variable `EDITOR` -/// (or `VISUAL`, if `EDITOR` is not set) will be started in the new terminal, with the given -/// file open. If no file is given, the shell specified by environment variable `SHELL` will -/// be started in the new terminal. -/// -/// # Panics -/// -/// This function will panic if both the `EDITOR` and `VISUAL` environment variables are not -/// set. -// FIXME this should probably be split into different functions, or at least have less levels -// of indentation in some way -fn spawn_terminal(file_to_open: Option<PathBuf>, orig_termios: termios::Termios) -> (RawFd, RawFd) { - let (pid_primary, pid_secondary): (RawFd, RawFd) = { - match forkpty(None, Some(&orig_termios)) { - Ok(fork_pty_res) => { - let pid_primary = fork_pty_res.master; - let pid_secondary = match fork_pty_res.fork_result { - ForkResult::Parent { child } => { - // fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::empty())).expect("could not fcntl"); - fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) - .expect("could not fcntl"); - child - } - ForkResult::Child => match file_to_open { - Some(file_to_open) => { - if env::var("EDITOR").is_err() && env::var("VISUAL").is_err() { - panic!("Can't edit files if an editor is not defined. To fix: define the EDITOR or VISUAL environment variables with the path to your editor (eg. /usr/bin/vim)"); - } - let editor = - env::var("EDITOR").unwrap_or_else(|_| env::var("VISUAL").unwrap()); - - let child = Command::new(editor) - .args(&[file_to_open]) - .spawn() - .expect("failed to spawn"); - handle_command_exit(child); - ::std::process::exit(0); - } - None => { - let child = Command::new(env::var("SHELL").unwrap()) - .spawn() - .expect("failed to spawn"); - handle_command_exit(child); - ::std::process::exit(0); - } - }, - }; - (pid_primary, pid_secondary.as_raw()) - } - Err(e) => { - panic!("failed to fork {:?}", e); - } - } - }; - (pid_primary, pid_secondary) -} - -#[derive(Clone)] -pub struct ServerOsInputOutput { - orig_termios: Arc<Mutex<termios::Termios>>, - receive_instructions_from_client: Option<Arc<Mutex<IpcReceiverWithContext<ClientToServerMsg>>>>, - send_instructions_to_client: Arc<Mutex<Option<IpcSenderWithContext<ServerToClientMsg>>>>, -} - -/// The `ServerOsApi` trait represents an abstract interface to the features of an operating system that -/// Zellij server requires. -pub trait ServerOsApi: Send + Sync { - /// Sets the size of the terminal associated to file descriptor `fd`. - fn set_terminal_size_using_fd(&mut self, fd: RawFd, cols: u16, rows: u16); - /// Spawn a new terminal, with an optional file to open in a terminal program. - fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> (RawFd, RawFd); - /// Read bytes from the standard output of the virtual terminal referred to by `fd`. - fn read_from_tty_stdout(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>; - /// Write bytes to the standard input of the virtual terminal referred to by `fd`. - fn write_to_tty_stdin(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>; - /// Wait until all output written to the object referred to by `fd` has been transmitted. - fn tcdrain(&mut self, fd: RawFd) -> Result<(), nix::Error>; - /// Terminate the process with process ID `pid`. - // FIXME `RawFd` is semantically the wrong type here. It should either be a raw libc::pid_t, - // or a nix::unistd::Pid. See `man kill.3`, nix::sys::signal::kill (both take an argument - // called `pid` and of type `pid_t`, and not `fd`) - fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error>; - /// Returns a [`Box`] pointer to this [`ServerOsApi`] struct. - fn box_clone(&self) -> Box<dyn ServerOsApi>; - /// Receives a message on server-side IPC channel - fn recv_from_client(&self) -> (ClientToServerMsg, ErrorContext); - /// Sends a message to client - fn send_to_client(&self, msg: ServerToClientMsg); - /// Adds a sender to client - fn add_client_sender(&mut self); - /// Update the receiver socket for the client - fn update_receiver(&mut self, stream: LocalSocketStream); - fn load_palette(&self) -> Palette; -} - -impl ServerOsApi for ServerOsInputOutput { - fn set_terminal_size_using_fd(&mut self, fd: RawFd, cols: u16, rows: u16) { - set_terminal_size_using_fd(fd, cols, rows); - } - fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> (RawFd, RawFd) { - let orig_termios = self.orig_termios.lock().unwrap(); - spawn_terminal(file_to_open, orig_termios.clone()) - } - fn read_from_tty_stdout(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> { - unistd::read(fd, buf) - } - fn write_to_tty_stdin(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> { - unistd::write(fd, buf) - } - fn tcdrain(&mut self, fd: RawFd) -> Result<(), nix::Error> { - termios::tcdrain(fd) - } - fn box_clone(&self) -> Box<dyn ServerOsApi> { - Box::new((*self).clone()) - } - fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error> { - // TODO: - // Ideally, we should be using SIGINT rather than SIGKILL here, but there are cases in which - // the terminal we're trying to kill hangs on SIGINT and so all the app gets stuck - // that's why we're sending SIGKILL here - // A better solution would be to send SIGINT here and not wait for it, and then have - // a background thread do the waitpid stuff and send SIGKILL if the process is stuck - kill(Pid::from_raw(pid), Some(Signal::SIGKILL)).unwrap(); - waitpid(Pid::from_raw(pid), None).unwrap(); - Ok(()) - } - fn recv_from_client(&self) -> (ClientToServerMsg, ErrorContext) { - self.receive_instructions_from_client - .as_ref() - .unwrap() - .lock() - .unwrap() - .recv() - } - fn send_to_client(&self, msg: ServerToClientMsg) { - self.send_instructions_to_client - .lock() - .unwrap() - .as_mut() - .unwrap() - .send(msg); - } - fn add_client_sender(&mut self) { - assert!(self.send_instructions_to_client.lock().unwrap().is_none()); - let sender = self - .receive_instructions_from_client - .as_ref() - .unwrap() - .lock() - .unwrap() - .get_sender(); - *self.send_instructions_to_client.lock().unwrap() = Some(sender); - } - fn update_receiver(&mut self, stream: LocalSocketStream) { - self.receive_instructions_from_client = - Some(Arc::new(Mutex::new(IpcReceiverWithContext::new(stream)))); - } - fn load_palette(&self) -> Palette { - default_palette() - } -} - -impl Clone for Box<dyn ServerOsApi> { - fn clone(&self) -> Box<dyn ServerOsApi> { - self.box_clone() - } -} - -pub fn get_server_os_input() -> ServerOsInputOutput { - let current_termios = termios::tcgetattr(0).unwrap(); - let orig_termios = Arc::new(Mutex::new(current_termios)); - ServerOsInputOutput { - orig_termios, - receive_instructions_from_client: None, - send_instructions_to_client: Arc::new(Mutex::new(None)), - } -} - -#[derive(Clone)] -pub struct ClientOsInputOutput { - orig_termios: Arc<Mutex<termios::Termios>>, - send_instructions_to_server: Arc<Mutex<Option<IpcSenderWithContext<ClientToServerMsg>>>>, - receive_instructions_from_server: Arc<Mutex<Option<IpcReceiverWithContext<ServerToClientMsg>>>>, -} - -/// The `ClientOsApi` trait represents an abstract interface to the features of an operating system that -/// Zellij client requires. -pub trait ClientOsApi: Send + Sync { - /// Returns the size of the terminal associated to file descriptor `fd`. - fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize; - /// Set the terminal associated to file descriptor `fd` to - /// [raw mode](https://en.wikipedia.org/wiki/Terminal_mode). - fn set_raw_mode(&mut self, fd: RawFd); - /// Set the terminal associated to file descriptor `fd` to - /// [cooked mode](https://en.wikipedia.org/wiki/Terminal_mode). - fn unset_raw_mode(&self, fd: RawFd); - /// Returns the writer that allows writing to standard output. - fn get_stdout_writer(&self) -> Box<dyn io::Write>; - /// Returns the raw contents of standard input. - fn read_from_stdin(&self) -> Vec<u8>; - /// Returns a [`Box`] pointer to this [`ClientOsApi`] struct. - fn box_clone(&self) -> Box<dyn ClientOsApi>; - /// Sends a message to the server. - fn send_to_server(&self, msg: ClientToServerMsg); - /// Receives a message on client-side IPC channel - // This should be called from the client-side router thread only. - fn recv_from_server(&self) -> (ServerToClientMsg, ErrorContext); - fn handle_signals(&self, sigwinch_cb: Box<dyn Fn()>, quit_cb: Box<dyn Fn()>); - /// Establish a connection with the server socket. - fn connect_to_server(&self, path: &Path); -} - -impl ClientOsApi for ClientOsInputOutput { - fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize { - get_terminal_size_using_fd(fd) - } - fn set_raw_mode(&mut self, fd: RawFd) { - into_raw_mode(fd); - } - fn unset_raw_mode(&self, fd: RawFd) { - let orig_termios = self.orig_termios.lock().unwrap(); - unset_raw_mode(fd, orig_termios.clone()); - } - fn box_clone(&self) -> Box<dyn ClientOsApi> { - Box::new((*self).clone()) - } - fn read_from_stdin(&self) -> Vec<u8> { - let stdin = std::io::stdin(); - let mut stdin = stdin.lock(); - let buffer = stdin.fill_buf().unwrap(); - let length = buffer.len(); - let read_bytes = Vec::from(buffer); - stdin.consume(length); - read_bytes - } - fn get_stdout_writer(&self) -> Box<dyn io::Write> { - let stdout = ::std::io::stdout(); - Box::new(stdout) - } - fn send_to_server(&self, msg: ClientToServerMsg) { - self.send_instructions_to_server - .lock() - .unwrap() - .as_mut() - .unwrap() - .send(msg); - } - fn recv_from_server(&self) -> (ServerToClientMsg, ErrorContext) { - self.receive_instructions_from_server - .lock() - .unwrap() - .as_mut() - .unwrap() - .recv() - } - fn handle_signals(&self, sigwinch_cb: Box<dyn Fn()>, quit_cb: Box<dyn Fn()>) { - let mut signals = Signals::new(&[SIGWINCH, SIGTERM, SIGINT, SIGQUIT, SIGHUP]).unwrap(); - for signal in signals.forever() { - match signal { - SIGWINCH => { - sigwinch_cb(); - } - SIGTERM | SIGINT | SIGQUIT | SIGHUP => { - quit_cb(); - break; - } - _ => unreachable!(), - } - } - } - fn connect_to_server(&self, path: &Path) { - let socket; - loop { - match LocalSocketStream::connect(path) { - Ok(sock) => { - socket = sock; - break; - } - Err(_) => { - std::thread::sleep(std::time::Duration::from_millis(50)); - } - } - } - let sender = IpcSenderWithContext::new(socket); - let receiver = sender.get_receiver(); - *self.send_instructions_to_server.lock().unwrap() = Some(sender); - *self.receive_instructions_from_server.lock().unwrap() = Some(receiver); - } -} - -impl Clone for Box<dyn ClientOsApi> { - fn clone(&self) -> Box<dyn ClientOsApi> { - self.box_clone() - } -} - -pub fn get_client_os_input() -> ClientOsInputOutput { - let current_termios = termios::tcgetattr(0).unwrap(); - let orig_termios = Arc::new(Mutex::new(current_termios)); - ClientOsInputOutput { - orig_termios, - send_instructions_to_server: Arc::new(Mutex::new(None)), - receive_instructions_from_server: Arc::new(Mutex::new(None)), - } -} diff --git a/src/common/pty.rs b/src/common/pty.rs deleted file mode 100644 index 5f5442b5..00000000 --- a/src/common/pty.rs +++ /dev/null @@ -1,293 +0,0 @@ -use async_std::stream::*; -use async_std::task; -use async_std::task::*; -use std::collections::HashMap; -use std::os::unix::io::RawFd; -use std::path::PathBuf; -use std::pin::*; -use std::time::{Duration, Instant}; - -use crate::client::panes::PaneId; -use crate::common::errors::{get_current_ctx, ContextType}; -use crate::common::screen::ScreenInstruction; -use crate::common::thread_bus::{Bus, ThreadSenders}; -use crate::layout::Layout; -use crate::os_input_output::ServerOsApi; -use crate::server::ServerInstruction; -use crate::utils::logging::debug_to_file; -use crate::wasm_vm::PluginInstruction; - -pub struct ReadFromPid { - pid: RawFd, - os_input: Box<dyn ServerOsApi>, -} - -impl ReadFromPid { - pub fn new(pid: &RawFd, os_input: Box<dyn ServerOsApi>) -> ReadFromPid { - ReadFromPid { - pid: *pid, - os_input, - } - } -} - -impl Stream for ReadFromPid { - type Item = Vec<u8>; - fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { - let mut read_buffer = [0; 65535]; - let pid = self.pid; - let read_result = &self.os_input.read_from_tty_stdout(pid, &mut read_buffer); - match read_result { - Ok(res) => { - if *res == 0 { - // indicates end of file - Poll::Ready(None) - } else { - let res = Some(read_buffer[..*res].to_vec()); - Poll::Ready(res) - } - } - Err(e) => { - match e { - nix::Error::Sys(errno) => { - if *errno == nix::errno::Errno::EAGAIN { - Poll::Ready(Some(vec![])) // TODO: better with timeout waker somehow - } else { - Poll::Ready(None) - } - } - _ => Poll::Ready(None), - } - } - } - } -} - -pub type VteBytes = Vec<u8>; - -/// Instructions related to PTYs (pseudoterminals). -#[derive(Clone, Debug)] -pub enum PtyInstruction { - SpawnTerminal(Option<PathBuf>), - SpawnTerminalVertically(Option<PathBuf>), - SpawnTerminalHorizontally(Option<PathBuf>), - NewTab, - ClosePane(PaneId), - CloseTab(Vec<PaneId>), - Exit, -} - -pub struct Pty { - pub bus: Bus<PtyInstruction>, - pub id_to_child_pid: HashMap<RawFd, RawFd>, - debug_to_file: bool, - task_handles: HashMap<RawFd, JoinHandle<()>>, -} - -pub fn pty_thread_main(mut pty: Pty, maybe_layout: Option<Layout>) { - loop { - let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel"); - err_ctx.add_call(ContextType::Pty((&event).into())); - match event { - PtyInstruction::SpawnTerminal(file_to_open) => { - let pid = pty.spawn_terminal(file_to_open); - pty.bus - .senders - .send_to_screen(ScreenInstruction::NewPane(PaneId::Terminal(pid))) - .unwrap(); - } - PtyInstruction::SpawnTerminalVertically(file_to_open) => { - let pid = pty.spawn_terminal(file_to_open); - pty.bus - .senders - .send_to_screen(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid))) - .unwrap(); - } - PtyInstruction::SpawnTerminalHorizontally(file_to_open) => { - let pid = pty.spawn_terminal(file_to_open); - pty.bus - .senders - .send_to_screen(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid))) - .unwrap(); - } - PtyInstruction::NewTab => { - if let Some(layout) = maybe_layout.clone() { - pty.spawn_terminals_for_layout(layout); - } else { - let pid = pty.spawn_terminal(None); - pty.bus - .senders - .send_to_screen(ScreenInstruction::NewTab(pid)) - .unwrap(); - } - } - PtyInstruction::ClosePane(id) => { - pty.close_pane(id); - pty.bus - .senders - .send_to_server(ServerInstruction::UnblockInputThread) - .unwrap(); - } - PtyInstruction::CloseTab(ids) => { - pty.close_tab(ids); - pty.bus - .senders - .send_to_server(ServerInstruction::UnblockInputThread) - .unwrap(); - } - PtyInstruction::Exit => break, - } - } -} - -fn stream_terminal_bytes( - pid: RawFd, - senders: ThreadSenders, - os_input: Box<dyn ServerOsApi>, - debug: bool, -) -> JoinHandle<()> { - let mut err_ctx = get_current_ctx(); - task::spawn({ - async move { - err_ctx.add_call(ContextType::AsyncTask); - let mut terminal_bytes = ReadFromPid::new(&pid, os_input); - - let mut last_byte_receive_time: Option<Instant> = None; - let mut pending_render = false; - let max_render_pause = Duration::from_millis(30); - - while let Some(bytes) = terminal_bytes.next().await { - let bytes_is_empty = bytes.is_empty(); - if debug { - for byte in bytes.iter() { - debug_to_file(*byte, pid).unwrap(); - } - } - if !bytes_is_empty { - let _ = senders.send_to_screen(ScreenInstruction::PtyBytes(pid, bytes)); - // for UX reasons, if we got something on the wire, we only send the render notice if: - // 1. there aren't any more bytes on the wire afterwards - // 2. a certain period (currently 30ms) has elapsed since the last render - // (otherwise if we get a large amount of data, the display would hang - // until it's done) - // 3. the stream has ended, and so we render 1 last time - match last_byte_receive_time.as_mut() { - Some(receive_time) => { - if receive_time.elapsed() > max_render_pause { - pending_render = false; - let _ = senders.send_to_screen(ScreenInstruction::Render); - last_byte_receive_time = Some(Instant::now()); - } else { - pending_render = true; - } - } - None => { - last_byte_receive_time = Some(Instant::now()); - pending_render = true; - } - }; - } else { - if pending_render { - pending_render = false; - let _ = senders.send_to_screen(ScreenInstruction::Render); - } - last_byte_receive_time = None; - task::sleep(::std::time::Duration::from_millis(10)).await; - } - } - senders.send_to_screen(ScreenInstruction::Render).unwrap(); - #[cfg(not(test))] - // this is a little hacky, and is because the tests end the file as soon as - // we read everything, rather than hanging until there is new data - // a better solution would be to fix the test fakes, but this will do for now - senders - .send_to_screen(ScreenInstruction::ClosePane(PaneId::Terminal(pid))) - .unwrap(); - } - }) -} - -impl Pty { - pub fn new(bus: Bus<PtyInstruction>, debug_to_file: bool) -> Self { - Pty { - bus, - id_to_child_pid: HashMap::new(), - debug_to_file, - task_handles: HashMap::new(), - } - } - pub fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> RawFd { - let (pid_primary, pid_secondary): (RawFd, RawFd) = self - .bus - .os_input - .as_mut() - .unwrap() - .spawn_terminal(file_to_open); - let task_handle = stream_terminal_bytes( - pid_primary, - self.bus.senders.clone(), - self.bus.os_input.as_ref().unwrap().clone(), - self.debug_to_file, - ); - self.task_handles.insert(pid_primary, task_handle); - self.id_to_child_pid.insert(pid_primary, pid_secondary); - pid_primary - } - pub fn spawn_terminals_for_layout(&mut self, layout: Layout) { - let total_panes = layout.total_terminal_panes(); - let mut new_pane_pids = vec![]; - for _ in 0..total_panes { - let (pid_primary, pid_secondary): (RawFd, RawFd) = - self.bus.os_input.as_mut().unwrap().spawn_terminal(None); - self.id_to_child_pid.insert(pid_primary, pid_secondary); - new_pane_pids.push(pid_primary); - } - self.bus - .senders - .send_to_screen(ScreenInstruction::ApplyLayout( - layout, - new_pane_pids.clone(), - )) - .unwrap(); - for id in new_pane_pids { - let task_handle = stream_terminal_bytes( - id, - self.bus.senders.clone(), - self.bus.os_input.as_ref().unwrap().clone(), - self.debug_to_file, - ); - self.task_handles.insert(id, task_handle); - } - } - pub fn close_pane(&mut self, id: PaneId) { - match id { - PaneId::Terminal(id) => { - let child_pid = self.id_to_child_pid.remove(&id).unwrap(); - let handle = self.task_handles.remove(&id).unwrap(); - self.bus.os_input.as_mut().unwrap().kill(child_pid).unwrap(); - task::block_on(async { - handle.cancel().await; - }); - } - PaneId::Plugin(pid) => drop( - self.bus - .senders - .send_to_plugin(PluginInstruction::Unload(pid)), - ), - } - } - pub fn close_tab(&mut self, ids: Vec<PaneId>) { - ids.iter().for_each(|&id| { - self.close_pane(id); - }); - } -} - -impl Drop for Pty { - fn drop(&mut self) { - let child_ids: Vec<RawFd> = self.id_to_child_pid.keys().copied().collect(); - for id in child_ids { - self.close_pane(PaneId::Terminal(id)); - } - } -} diff --git a/src/common/screen.rs b/src/common/screen.rs deleted file mode 100644 index d2030243..00000000 --- a/src/common/screen.rs +++ /dev/null @@ -1,598 +0,0 @@ -//! Things related to [`Screen`]s. - -use std::collections::BTreeMap; -use std::os::unix::io::RawFd; -use std::str; - -use crate::common::input::options::Options; -use crate::common::pty::{PtyInstruction, VteBytes}; -use crate::common::thread_bus::Bus; -use crate::errors::ContextType; -use crate::layout::Layout; -use crate::panes::PaneId; -use crate::panes::PositionAndSize; -use crate::server::ServerInstruction; -use crate::tab::Tab; -use crate::wasm_vm::PluginInstruction; - -use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PluginCapabilities, TabInfo}; - -/// Instructions that can be sent to the [`Screen`]. -#[derive(Debug, Clone)] -pub enum ScreenInstruction { - PtyBytes(RawFd, VteBytes), - Render, - NewPane(PaneId), - HorizontalSplit(PaneId), - VerticalSplit(PaneId), - WriteCharacter(Vec<u8>), - ResizeLeft, - ResizeRight, - ResizeDown, - ResizeUp, - SwitchFocus, - FocusNextPane, - FocusPreviousPane, - MoveFocusLeft, - MoveFocusLeftOrPreviousTab, - MoveFocusDown, - MoveFocusUp, - MoveFocusRight, - MoveFocusRightOrNextTab, - Exit, - ScrollUp, - ScrollDown, - PageScrollUp, - PageScrollDown, - ClearScroll, - CloseFocusedPane, - ToggleActiveTerminalFullscreen, - SetSelectable(PaneId, bool), - SetMaxHeight(PaneId, usize), - SetInvisibleBorders(PaneId, bool), - ClosePane(PaneId), - ApplyLayout(Layout, Vec<RawFd>), - NewTab(RawFd), - SwitchTabNext, - SwitchTabPrev, - ToggleActiveSyncTab, - CloseTab, - GoToTab(u32), - UpdateTabName(Vec<u8>), - TerminalResize(PositionAndSize), - ChangeMode(ModeInfo), -} - -/// A [`Screen`] holds multiple [`Tab`]s, each one holding multiple [`panes`](crate::client::panes). -/// It only directly controls which tab is active, delegating the rest to the individual `Tab`. -pub struct Screen { - /// A Bus for sending and receiving messages with the other threads. - pub bus: Bus<ScreenInstruction>, - /// An optional maximal amount of panes allowed per [`Tab`] in this [`Screen`] instance. - max_panes: Option<usize>, - /// A map between this [`Screen`]'s tabs and their ID/key. - tabs: BTreeMap<usize, Tab>, - /// The full size of this [`Screen`]. - full_screen_ws: PositionAndSize, - /// The index of this [`Screen`]'s active [`Tab`]. - active_tab_index: Option<usize>, - mode_info: ModeInfo, - input_mode: InputMode, - colors: Palette, -} - -impl Screen { - /// Creates and returns a new [`Screen`]. - pub fn new( - bus: Bus<ScreenInstruction>, - full_screen_ws: &PositionAndSize, - max_panes: Option<usize>, - mode_info: ModeInfo, - input_mode: InputMode, - colors: Palette, - ) -> Self { - Screen { - bus, - max_panes, - full_screen_ws: *full_screen_ws, - active_tab_index: None, - tabs: BTreeMap::new(), - mode_info, - input_mode, - colors, - } - } - - /// Creates a new [`Tab`] in this [`Screen`], containing a single - /// [pane](crate::client::panes) with PTY file descriptor `pane_id`. - pub fn new_tab(&mut self, pane_id: RawFd) { - let tab_index = self.get_new_tab_index(); - let position = self.tabs.len(); - let tab = Tab::new( - tab_index, - position, - String::new(), - &self.full_screen_ws, - self.bus.os_input.as_ref().unwrap().clone(), - self.bus.senders.clone(), - self.max_panes, - Some(PaneId::Terminal(pane_id)), - self.mode_info.clone(), - self.input_mode, - self.colors, - ); - self.active_tab_index = Some(tab_index); - self.tabs.insert(tab_index, tab); - self.update_tabs(); - self.render(); - } - - /// Returns the index where a new [`Tab`] should be created in this [`Screen`]. - /// Currently, this is right after the last currently existing tab, or `0` if - /// no tabs exist in this screen yet. - fn get_new_tab_index(&self) -> usize { - if let Some(index) = self.tabs.keys().last() { - *index + 1 - } else { - 0 - } - } - - /// Sets this [`Screen`]'s active [`Tab`] to the next tab. - pub fn switch_tab_next(&mut self) { - let active_tab_pos = self.get_active_tab().unwrap().position; - let new_tab_pos = (active_tab_pos + 1) % self.tabs.len(); - - for tab in self.tabs.values_mut() { - if tab.position == new_tab_pos { - tab.set_force_render(); - self.active_tab_index = Some(tab.index); - break; - } - } - self.update_tabs(); - self.render(); - } - - /// Sets this [`Screen`]'s active [`Tab`] to the previous tab. - pub fn switch_tab_prev(&mut self) { - let active_tab_pos = self.get_active_tab().unwrap().position; - let new_tab_pos = if active_tab_pos == 0 { - self.tabs.len() - 1 - } else { - active_tab_pos - 1 - }; - for tab in self.tabs.values_mut() { - if tab.position == new_tab_pos { - tab.set_force_render(); - self.active_tab_index = Some(tab.index); - break; - } - } - self.update_tabs(); - self.render(); - } - - pub fn go_to_tab(&mut self, mut tab_index: usize) { - tab_index -= 1; - let active_tab_index = self.get_active_tab().unwrap().index; - if let Some(t) = self.tabs.values_mut().find(|t| t.position == tab_index) { - if t.index != active_tab_index { - t.set_force_render(); - self.active_tab_index = Some(t.index); - self.update_tabs(); - self.render(); - } - } - } - - /// Closes this [`Screen`]'s active [`Tab`], exiting the application if it happens - /// to be the last tab. - pub fn close_tab(&mut self) { - let active_tab_index = self.active_tab_index.unwrap(); - if self.tabs.len() > 1 { - self.switch_tab_prev(); - } - let active_tab = self.tabs.remove(&active_tab_index).unwrap(); - let pane_ids = active_tab.get_pane_ids(); - // below we don't check the result of sending the CloseTab instruction to the pty thread - // because this might be happening when the app is closing, at which point the pty thread - // has already closed and this would result in an error - self.bus - .senders - .send_to_pty(PtyInstruction::CloseTab(pane_ids)) - .unwrap(); - if self.tabs.is_empty() { - self.active_tab_index = None; - self.bus - .senders - .send_to_server(ServerInstruction::Render(None)) - .unwrap(); - } else { - for t in self.tabs.values_mut() { - if t.position > active_tab.position { - t.position -= 1; - } - } - self.update_tabs(); - } - } - - pub fn resize_to_screen(&mut self, new_screen_size: PositionAndSize) { - self.full_screen_ws = new_screen_size; - for (_, tab) in self.tabs.iter_mut() { - tab.resize_whole_tab(new_screen_size); - } - let _ = self.get_active_tab_mut().map(|t| t.set_force_render()); - self.render(); - } - - /// Renders this [`Screen`], which amounts to rendering its active [`Tab`]. - pub fn render(&mut self) { - if let Some(active_tab) = self.get_active_tab_mut() { - if active_tab.get_active_pane().is_some() { - active_tab.render(); - } else { - self.close_tab(); - } - }; - } - - /// Returns a mutable reference to this [`Screen`]'s tabs. - pub fn get_tabs_mut(&mut self) -> &mut BTreeMap<usize, Tab> { - &mut self.tabs - } - - /// Returns an immutable reference to this [`Screen`]'s active [`Tab`]. - pub fn get_active_tab(&self) -> Option<&Tab> { - match self.active_tab_index { - Some(tab) => self.tabs.get(&tab), - None => None, - } - } - - /// Returns a mutable reference to this [`Screen`]'s active [`Tab`]. - pub fn get_active_tab_mut(&mut self) -> Option<&mut Tab> { - match self.active_tab_index { - Some(tab) => self.get_tabs_mut().get_mut(&tab), - None => None, - } - } - - /// Creates a new [`Tab`] in this [`Screen`], applying the specified [`Layout`] - /// and switching to it. - pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec<RawFd>) { - let tab_index = self.get_new_tab_index(); - let position = self.tabs.len(); - let mut tab = Tab::new( - tab_index, - position, - String::new(), - &self.full_screen_ws, - self.bus.os_input.as_ref().unwrap().clone(), - self.bus.senders.clone(), - self.max_panes, - None, - self.mode_info.clone(), - self.input_mode, - self.colors, - ); - tab.apply_layout(layout, new_pids); - self.active_tab_index = Some(tab_index); - self.tabs.insert(tab_index, tab); - self.update_tabs(); - } - - pub fn update_tabs(&self) { - let mut tab_data = vec![]; - let active_tab_index = self.active_tab_index.unwrap(); - for tab in self.tabs.values() { - tab_data.push(TabInfo { - position: tab.position, - name: tab.name.clone(), - active: active_tab_index == tab.index, - is_sync_panes_active: tab.is_sync_panes_active(), - }); - } - self.bus - .senders - .send_to_plugin(PluginInstruction::Update(None, Event::TabUpdate(tab_data))) - .unwrap(); - } - - pub fn update_active_tab_name(&mut self, buf: Vec<u8>) { - let s = str::from_utf8(&buf).unwrap(); - let active_tab = self.get_active_tab_mut().unwrap(); - match s { - "\0" => { - active_tab.name = String::new(); - } - "\u{007F}" | "\u{0008}" => { - //delete and backspace keys - active_tab.name.pop(); - } - c => { - active_tab.name.push_str(c); - } - } - self.update_tabs(); - } - pub fn change_mode(&mut self, mode_info: ModeInfo) { - self.mode_info = mode_info; - for tab in self.tabs.values_mut() { - tab.mode_info = self.mode_info.clone(); - } - } -} - -// The box is here in order to make the -// NewClient enum smaller -#[allow(clippy::boxed_local)] -pub fn screen_thread_main( - bus: Bus<ScreenInstruction>, - max_panes: Option<usize>, - full_screen_ws: PositionAndSize, - config_options: Box<Options>, -) { - let colors = bus.os_input.as_ref().unwrap().load_palette(); - let capabilities = config_options.simplified_ui; - - let mut screen = Screen::new( - bus, - &full_screen_ws, - max_panes, - ModeInfo { - palette: colors, - capabilities: PluginCapabilities { - arrow_fonts: capabilities, - }, - ..ModeInfo::default() - }, - InputMode::Normal, - colors, - ); - loop { - let (event, mut err_ctx) = screen - .bus - .recv() - .expect("failed to receive event on channel"); - err_ctx.add_call(ContextType::Screen((&event).into())); - match event { - ScreenInstruction::PtyBytes(pid, vte_bytes) => { - let active_tab = screen.get_active_tab_mut().unwrap(); - if active_tab.has_terminal_pid(pid) { - // it's most likely that this event is directed at the active tab - // look there first - active_tab.handle_pty_bytes(pid, vte_bytes); - } else { - // if this event wasn't directed at the active tab, start looking - // in other tabs - let all_tabs = screen.get_tabs_mut(); - for tab in all_tabs.values_mut() { - if tab.has_terminal_pid(pid) { - tab.handle_pty_bytes(pid, vte_bytes); - break; - } - } - } - } - ScreenInstruction::Render => { - screen.render(); - } - ScreenInstruction::NewPane(pid) => { - screen.get_active_tab_mut().unwrap().new_pane(pid); - screen - .bus - .senders - .send_to_server(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::HorizontalSplit(pid) => { - screen.get_active_tab_mut().unwrap().horizontal_split(pid); - screen - .bus - .senders - .send_to_server(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::VerticalSplit(pid) => { - screen.get_active_tab_mut().unwrap().vertical_split(pid); - screen - .bus - .senders - .send_to_server(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::WriteCharacter(bytes) => { - let active_tab = screen.get_active_tab_mut().unwrap(); - match active_tab.is_sync_panes_active() { - true => active_tab.write_to_terminals_on_current_tab(bytes), - false => active_tab.write_to_active_terminal(bytes), - } - } - ScreenInstruction::ResizeLeft => { - screen.get_active_tab_mut().unwrap().resize_left(); - } - ScreenInstruction::ResizeRight => { - screen.get_active_tab_mut().unwrap().resize_right(); - } - ScreenInstruction::ResizeDown => { - screen.get_active_tab_mut().unwrap().resize_down(); - } - ScreenInstruction::ResizeUp => { - screen.get_active_tab_mut().unwrap().resize_up(); - } - ScreenInstruction::SwitchFocus => { - screen.get_active_tab_mut().unwrap().move_focus(); - } - ScreenInstruction::FocusNextPane => { - screen.get_active_tab_mut().unwrap().focus_next_pane(); - } - ScreenInstruction::FocusPreviousPane => { - screen.get_active_tab_mut().unwrap().focus_previous_pane(); - } - ScreenInstruction::MoveFocusLeft => { - screen.get_active_tab_mut().unwrap().move_focus_left(); - } - ScreenInstruction::MoveFocusLeftOrPreviousTab => { - if !screen.get_active_tab_mut().unwrap().move_focus_left() { - screen.switch_tab_prev(); - } - screen - .bus - .senders - .send_to_server(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::MoveFocusDown => { - screen.get_active_tab_mut().unwrap().move_focus_down(); - } - ScreenInstruction::MoveFocusRight => { - screen.get_active_tab_mut().unwrap().move_focus_right(); - } - ScreenInstruction::MoveFocusRightOrNextTab => { - if !screen.get_active_tab_mut().unwrap().move_focus_right() { - screen.switch_tab_next(); - } - screen - .bus - .senders - .send_to_server(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::MoveFocusUp => { - screen.get_active_tab_mut().unwrap().move_focus_up(); - } - ScreenInstruction::ScrollUp => { - screen - .get_active_tab_mut() - .unwrap() - .scroll_active_terminal_up(); - } - ScreenInstruction::ScrollDown => { - screen - .get_active_tab_mut() - .unwrap() - .scroll_active_terminal_down(); - } - ScreenInstruction::PageScrollUp => { - screen - .get_active_tab_mut() - .unwrap() - .scroll_active_terminal_up_page(); - } - ScreenInstruction::PageScrollDown => { - screen - .get_active_tab_mut() - .unwrap() - .scroll_active_terminal_down_page(); - } - ScreenInstruction::ClearScroll => { - screen - .get_active_tab_mut() - .unwrap() - .clear_active_terminal_scroll(); - } - ScreenInstruction::CloseFocusedPane => { - screen.get_active_tab_mut().unwrap().close_focused_pane(); - screen.render(); - } - ScreenInstruction::SetSelectable(id, selectable) => { - screen - .get_active_tab_mut() - .unwrap() - .set_pane_selectable(id, selectable); - } - ScreenInstruction::SetMaxHeight(id, max_height) => { - screen - .get_active_tab_mut() - .unwrap() - .set_pane_max_height(id, max_height); - } - ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => { - screen - .get_active_tab_mut() - .unwrap() - .set_pane_invisible_borders(id, invisible_borders); - screen.render(); - } - ScreenInstruction::ClosePane(id) => { - screen.get_active_tab_mut().unwrap().close_pane(id); - screen.render(); - } - ScreenInstruction::ToggleActiveTerminalFullscreen => { - screen - .get_active_tab_mut() - .unwrap() - .toggle_active_pane_fullscreen(); - } - ScreenInstruction::NewTab(pane_id) => { - screen.new_tab(pane_id); - screen - .bus - .senders - .send_to_server(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::SwitchTabNext => { - screen.switch_tab_next(); - screen - .bus - .senders - .send_to_server(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::SwitchTabPrev => { - screen.switch_tab_prev(); - screen - .bus - .senders - .send_to_server(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::CloseTab => { - screen.close_tab(); - screen - .bus - .senders - .send_to_server(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::ApplyLayout(layout, new_pane_pids) => { - screen.apply_layout(layout, new_pane_pids); - screen - .bus - .senders - .send_to_server(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::GoToTab(tab_index) => { - screen.go_to_tab(tab_index as usize); - screen - .bus - .senders - .send_to_server(ServerInstruction::UnblockInputThread) - .unwrap(); - } - ScreenInstruction::UpdateTabName(c) => { - screen.update_active_tab_name(c); - } - ScreenInstruction::TerminalResize(new_size) => { - screen.resize_to_screen(new_size); - } - ScreenInstruction::ChangeMode(mode_info) => { - screen.change_mode(mode_info); - } - ScreenInstruction::ToggleActiveSyncTab => { - screen - .get_active_tab_mut() - .unwrap() - .toggle_sync_panes_is_active(); - screen.update_tabs(); - } - ScreenInstruction::Exit => { - break; - } - } - } -} diff --git a/src/common/setup.rs b/src/common/setup.rs deleted file mode 100644 index 2e076776..00000000 --- a/src/common/setup.rs +++ /dev/null @@ -1,240 +0,0 @@ -use crate::cli::CliArgs; -use crate::common::utils::consts::{ - FEATURES, SYSTEM_DEFAULT_CONFIG_DIR, SYSTEM_DEFAULT_DATA_DIR_PREFIX, VERSION, ZELLIJ_PROJ_DIR, -}; -use crate::os_input_output::set_permissions; -use directories_next::BaseDirs; -use serde::{Deserialize, Serialize}; -use std::io::Write; -use std::{fs, path::Path, path::PathBuf}; -use structopt::StructOpt; - -const CONFIG_LOCATION: &str = ".config/zellij"; -const CONFIG_NAME: &str = "config.yaml"; -static ARROW_SEPARATOR: &str = ""; - -#[macro_export] -macro_rules! asset_map { - ($($src:literal => $dst:literal),+ $(,)?) => { - { - let mut assets = std::collections::HashMap::new(); - $( - assets.insert($dst, include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $src)).to_vec()); - )+ - assets - } - } -} - -pub mod install { - use super::*; - - pub fn populate_data_dir(data_dir: &Path) { - // First run installation of default plugins & layouts - let mut assets = asset_map! { - "assets/layouts/default.yaml" => "layouts/default.yaml", - "assets/layouts/strider.yaml" => "layouts/strider.yaml", - }; - assets.extend(asset_map! { - "assets/plugins/status-bar.wasm" => "plugins/status-bar.wasm", - "assets/plugins/tab-bar.wasm" => "plugins/tab-bar.wasm", - "assets/plugins/strider.wasm" => "plugins/strider.wasm", - }); - assets.insert("VERSION", VERSION.as_bytes().to_vec()); - - let last_version = fs::read_to_string(data_dir.join("VERSION")).unwrap_or_default(); - let out_of_date = VERSION != last_version; - - for (path, bytes) in assets { - let path = data_dir.join(path); - let parent_path = path.parent().unwrap(); - fs::create_dir_all(parent_path).unwrap(); - set_permissions(parent_path).unwrap(); - if out_of_date || !path.exists() { - fs::write(path, bytes).expect("Failed to install default assets!"); - } - } - } -} - -#[cfg(not(test))] -/// Goes through a predefined list and checks for an already -/// existing config directory, returns the first match -pub fn find_default_config_dir() -> Option<PathBuf> { - default_config_dirs() - .into_iter() - .filter(|p| p.is_some()) - .find(|p| p.clone().unwrap().exists()) - .flatten() -} - -#[cfg(test)] -pub fn find_default_config_dir() -> Option<PathBuf> { - None -} - -/// Order in which config directories are checked -fn default_config_dirs() -> Vec<Option<PathBuf>> { - vec![ - home_config_dir(), - Some(xdg_config_dir()), - Some(Path::new(SYSTEM_DEFAULT_CONFIG_DIR).to_path_buf()), - ] -} - -/// Looks for an existing dir, uses that, else returns a -/// dir matching the config spec. -pub fn get_default_data_dir() -> PathBuf { - vec![ - xdg_data_dir(), - Path::new(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij"), - ] - .into_iter() - .find(|p| p.exists()) - .unwrap_or_else(xdg_data_dir) -} - -pub fn xdg_config_dir() -> PathBuf { - ZELLIJ_PROJ_DIR.config_dir().to_owned() -} - -pub fn xdg_data_dir() -> PathBuf { - ZELLIJ_PROJ_DIR.data_dir().to_owned() -} - -pub fn home_config_dir() -> Option<PathBuf> { - if let Some(user_dirs) = BaseDirs::new() { - let config_dir = user_dirs.home_dir().join(CONFIG_LOCATION); - Some(config_dir) - } else { - None - } -} - -pub fn dump_asset(asset: &[u8]) -> std::io::Result<()> { - std::io::stdout().write_all(&asset)?; - Ok(()) -} - -pub const DEFAULT_CONFIG: &[u8] = include_bytes!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/", - "assets/config/default.yaml" -)); - -pub fn dump_default_config() -> std::io::Result<()> { - dump_asset(DEFAULT_CONFIG) -} - -#[derive(Debug, Default, Clone, StructOpt, Serialize, Deserialize)] -pub struct Setup { - /// Dump the default configuration file to stdout - #[structopt(long)] - pub dump_config: bool, - /// Disables loading of configuration file at default location, - /// loads the defaults that zellij ships with - #[structopt(long)] - pub clean: bool, - /// Checks the configuration of zellij and displays - /// currently used directories - #[structopt(long)] - pub check: bool, - - #[structopt(long)] - pub generate_completion: Option<String>, -} - -impl Setup { - /// Entrypoint from main - pub fn from_cli(&self, opts: CliArgs) -> std::io::Result<()> { - if self.dump_config { - dump_default_config()?; - } - - if self.check { - Setup::check_defaults_config(opts)?; - } - - if let Some(shell) = &self.generate_completion { - Self::generate_completion(shell.into()); - } - - Ok(()) - } - - pub fn check_defaults_config(opts: CliArgs) -> std::io::Result<()> { - let data_dir = opts.data_dir.unwrap_or_else(get_default_data_dir); - let config_dir = opts.config_dir.or_else(find_default_config_dir); - let plugin_dir = data_dir.join("plugins"); - let layout_dir = data_dir.join("layouts"); - let system_data_dir = PathBuf::from(SYSTEM_DEFAULT_DATA_DIR_PREFIX).join("share/zellij"); - let config_file = opts - .config - .or_else(|| config_dir.clone().map(|p| p.join(CONFIG_NAME))); - - let mut message = String::new(); - - message.push_str(&format!("[Version]: {:?}\n", VERSION)); - if let Some(config_dir) = config_dir { - message.push_str(&format!("[CONFIG DIR]: {:?}\n", config_dir)); - } else { - message.push_str(&"[CONFIG DIR]: Not Found\n"); - let mut default_config_dirs = default_config_dirs() - .iter() - .filter_map(|p| p.clone()) - .collect::<Vec<PathBuf>>(); - default_config_dirs.dedup(); - message.push_str( - &" On your system zellij looks in the following config directories by default:\n", - ); - for dir in default_config_dirs { - message.push_str(&format!(" {:?}\n", dir)); - } - } - if let Some(config_file) = config_file { - use crate::common::input::config::Config; - message.push_str(&format!("[CONFIG FILE]: {:?}\n", config_file)); - match Config::new(&config_file) { - Ok(_) => message.push_str(&"[CONFIG FILE]: Well defined.\n"), - Err(e) => message.push_str(&format!("[CONFIG ERROR]: {}\n", e)), - } - } else { - message.push_str(&"[CONFIG FILE]: Not Found\n"); - message.push_str(&format!( - " By default zellij looks for a file called [{}] in the configuration directory\n", - CONFIG_NAME - )); - } - message.push_str(&format!("[DATA DIR]: {:?}\n", data_dir)); - message.push_str(&format!("[PLUGIN DIR]: {:?}\n", plugin_dir)); - message.push_str(&format!("[LAYOUT DIR]: {:?}\n", layout_dir)); - message.push_str(&format!("[SYSTEM DATA DIR]: {:?}\n", system_data_dir)); - - message.push_str(&format!("[ARROW SEPARATOR]: {}\n", ARROW_SEPARATOR)); - message.push_str(&" Is the [ARROW_SEPARATOR] displayed correctly?\n"); - message.push_str(&" If not you may want to either start zellij with a compatible mode 'zellij options --simple-ui'\n"); - message.push_str(&" Or check the font that is in use:\n https://zellij.dev/documentation/compatibility.html#the-status-bar-fonts-dont-render-correctly\n"); - - message.push_str(&format!("[FEATURES]: {:?}\n", FEATURES)); - message.push_str(&"[DOCUMENTATION]: zellij.dev/documentation/\n"); - - std::io::stdout().write_all(message.as_bytes())?; - - Ok(()) - } - fn generate_completion(shell: String) { - let shell = match shell.as_ref() { - "bash" => structopt::clap::Shell::Bash, - "fish" => structopt::clap::Shell::Fish, - "zsh" => structopt::clap::Shell::Zsh, - "powerShell" => structopt::clap::Shell::PowerShell, - "elvish" => structopt::clap::Shell::Elvish, - other => { - eprintln!("Unsupported shell: {}", other); - std::process::exit(1); - } - }; - let mut out = std::io::stdout(); - CliArgs::clap().gen_completions_to("zellij", shell, &mut out); - } -} diff --git a/src/common/thread_bus.rs b/src/common/thread_bus.rs deleted file mode 100644 index 8827d068..00000000 --- a/src/common/thread_bus.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! Definitions and helpers for sending and receiving messages between threads. - -use async_std::task_local; -use std::cell::RefCell; -use std::sync::mpsc; - -use crate::common::pty::PtyInstruction; -use crate::common::ServerInstruction; -use crate::errors::{get_current_ctx, ErrorContext}; -use crate::os_input_output::ServerOsApi; -use crate::screen::ScreenInstruction; -use crate::wasm_vm::PluginInstruction; - -/// An [MPSC](mpsc) asynchronous channel with added error context. -pub type ChannelWithContext<T> = ( - mpsc::Sender<(T, ErrorContext)>, - mpsc::Receiver<(T, ErrorContext)>, -); -/// An [MPSC](mpsc) synchronous channel with added error context. -pub type SyncChannelWithContext<T> = ( - mpsc::SyncSender<(T, ErrorContext)>, - mpsc::Receiver<(T, ErrorContext)>, -); - -/// Wrappers around the two standard [MPSC](mpsc) sender types, [`mpsc::Sender`] and [`mpsc::SyncSender`], with an additional [`ErrorContext`]. -#[derive(Clone)] -pub enum SenderType<T: Clone> { - /// A wrapper around an [`mpsc::Sender`], adding an [`ErrorContext`]. - Sender(mpsc::Sender<(T, ErrorContext)>), - /// A wrapper around an [`mpsc::SyncSender`], adding an [`ErrorContext`]. - SyncSender(mpsc::SyncSender<(T, ErrorContext)>), -} - -/// Sends messages on an [MPSC](std::sync::mpsc) channel, along with an [`ErrorContext`], -/// synchronously or asynchronously depending on the underlying [`SenderType`]. -#[derive(Clone)] -pub struct SenderWithContext<T: Clone> { - sender: SenderType<T>, -} - -impl<T: Clone> SenderWithContext<T> { - pub fn new(sender: SenderType<T>) -> Self { - Self { sender } - } - - /// Sends an event, along with the current [`ErrorContext`], on this - /// [`SenderWithContext`]'s channel. - pub fn send(&self, event: T) -> Result<(), mpsc::SendError<(T, ErrorContext)>> { - let err_ctx = get_current_ctx(); - match self.sender { - SenderType::Sender(ref s) => s.send((event, err_ctx)), - SenderType::SyncSender(ref s) => s.send((event, err_ctx)), - } - } -} - -unsafe impl<T: Clone> Send for SenderWithContext<T> {} -unsafe impl<T: Clone> Sync for SenderWithContext<T> {} - -thread_local!( - /// A key to some thread local storage (TLS) that holds a representation of the thread's call - /// stack in the form of an [`ErrorContext`]. - pub static OPENCALLS: RefCell<ErrorContext> = RefCell::default() -); - -task_local! { - /// A key to some task local storage that holds a representation of the task's call - /// stack in the form of an [`ErrorContext`]. - pub static ASYNCOPENCALLS: RefCell<ErrorContext> = RefCell::default() -} - -/// A container for senders to the different threads in zellij on the server side -#[derive(Clone)] -pub struct ThreadSenders { - pub to_screen: Option<SenderWithContext<ScreenInstruction>>, - pub to_pty: Option<SenderWithContext<PtyInstruction>>, - pub to_plugin: Option<SenderWithContext<PluginInstruction>>, - pub to_server: Option<SenderWithContext<ServerInstruction>>, -} - -impl ThreadSenders { - pub fn send_to_screen( - &self, - instruction: ScreenInstruction, - ) -> Result<(), mpsc::SendError<(ScreenInstruction, ErrorContext)>> { - self.to_screen.as_ref().unwrap().send(instruction) - } - - pub fn send_to_pty( - &self, - instruction: PtyInstruction, - ) -> Result<(), mpsc::SendError<(PtyInstruction, ErrorContext)>> { - self.to_pty.as_ref().unwrap().send(instruction) - } - - pub fn send_to_plugin( - &self, - instruction: PluginInstruction, - ) -> Result<(), mpsc::SendError<(PluginInstruction, ErrorContext)>> { - self.to_plugin.as_ref().unwrap().send(instruction) - } - - pub fn send_to_server( - &self, - instruction: ServerInstruction, - ) -> Result<(), mpsc::SendError<(ServerInstruction, ErrorContext)>> { - self.to_server.as_ref().unwrap().send(instruction) - } -} - -/// A container for a receiver, OS input and the senders to a given thread -pub struct Bus<T> { - pub receiver: mpsc::Receiver<(T, ErrorContext)>, - pub senders: ThreadSenders, - pub os_input: Option<Box<dyn ServerOsApi>>, -} - -impl<T> Bus<T> { - pub fn new( - receiver: mpsc::Receiver<(T, ErrorContext)>, - to_screen: Option<&SenderWithContext<ScreenInstruction>>, - to_pty: Option<&SenderWithContext<PtyInstruction>>, - to_plugin: Option<&SenderWithContext<PluginInstruction>>, - to_server: Option<&SenderWithContext<ServerInstruction>>, - os_input: Option<Box<dyn ServerOsApi>>, - ) -> Self { - Bus { - receiver, - senders: ThreadSenders { - to_screen: to_screen.cloned(), - to_pty: to_pty.cloned(), - to_plugin: to_plugin.cloned(), - to_server: to_server.cloned(), - }, - os_input: os_input.clone(), - } - } - - pub fn recv(&self) -> Result<(T, ErrorContext), mpsc::RecvError> { - self.receiver.recv() - } -} diff --git a/src/common/utils/consts.rs b/src/common/utils/consts.rs deleted file mode 100644 index 42843eb8..00000000 --- a/src/common/utils/consts.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! Zellij program-wide constants. - -use crate::os_input_output::set_permissions; -use directories_next::ProjectDirs; -use lazy_static::lazy_static; -use nix::unistd::Uid; -use std::path::PathBuf; -use std::{env, fs}; - -pub const ZELLIJ_CONFIG_FILE_ENV: &str = "ZELLIJ_CONFIG_FILE"; -pub const ZELLIJ_CONFIG_DIR_ENV: &str = "ZELLIJ_CONFIG_DIR"; -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); - -pub const SYSTEM_DEFAULT_CONFIG_DIR: &str = "/etc/zellij"; -pub const SYSTEM_DEFAULT_DATA_DIR_PREFIX: &str = system_default_data_dir(); - -const fn system_default_data_dir() -> &'static str { - if let Some(data_dir) = std::option_env!("PREFIX") { - data_dir - } else { - &"/usr" - } -} - -lazy_static! { - static ref UID: Uid = Uid::current(); - pub static ref SESSION_NAME: String = names::Generator::default().next().unwrap(); - pub static ref ZELLIJ_PROJ_DIR: ProjectDirs = - ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap(); - pub static ref ZELLIJ_IPC_PIPE: PathBuf = { - let mut ipc_dir = env::var("ZELLIJ_SOCKET_DIR").map_or_else( - |_| { - ZELLIJ_PROJ_DIR - .runtime_dir() - .map_or_else(|| ZELLIJ_TMP_DIR.clone(), |p| p.to_owned()) - }, - PathBuf::from, - ); - ipc_dir.push(VERSION); - fs::create_dir_all(&ipc_dir).unwrap(); - set_permissions(&ipc_dir).unwrap(); - ipc_dir.push(&*SESSION_NAME); - ipc_dir - }; - pub static ref ZELLIJ_TMP_DIR: PathBuf = - PathBuf::from("/tmp/zellij-".to_string() + &format!("{}", *UID)); - pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = ZELLIJ_TMP_DIR.join("zellij-log"); - pub static ref ZELLIJ_TMP_LOG_FILE: PathBuf = ZELLIJ_TMP_LOG_DIR.join("log.txt"); -} - -pub const FEATURES: &[&str] = &[ - #[cfg(feature = "disable_automatic_asset_installation")] - "disable_automatic_asset_installation", -]; diff --git a/src/common/utils/logging.rs b/src/common/utils/logging.rs deleted file mode 100644 index 0dd382d6..00000000 --- a/src/common/utils/logging.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! Zellij logging utility functions. - -use std::{ - fs, - io::{self, prelude::*}, - os::unix::io::RawFd, - path::{Path, PathBuf}, -}; - -use crate::os_input_output::set_permissions; -use crate::utils::consts::{ZELLIJ_TMP_LOG_DIR, ZELLIJ_TMP_LOG_FILE}; - -pub fn atomic_create_file(file_name: &Path) -> io::Result<()> { - let _ = fs::OpenOptions::new() - .append(true) - .create(true) - .open(file_name)?; - set_permissions(file_name) -} - -pub fn atomic_create_dir(dir_name: &Path) -> io::Result<()> { - let result = if let Err(e) = fs::create_dir(dir_name) { - if e.kind() == std::io::ErrorKind::AlreadyExists { - Ok(()) - } else { - Err(e) - } - } else { - Ok(()) - }; - if result.is_ok() { - set_permissions(dir_name)?; - } - result -} - -pub fn debug_log_to_file(mut message: String) -> io::Result<()> { - message.push('\n'); - debug_log_to_file_without_newline(message) -} - -pub fn debug_log_to_file_without_newline(message: String) -> io::Result<()> { - atomic_create_file(&*ZELLIJ_TMP_LOG_FILE)?; - let mut file = fs::OpenOptions::new() - .append(true) - .open(&*ZELLIJ_TMP_LOG_FILE)?; - file.write_all(message.as_bytes()) -} - -pub fn _debug_log_to_file_pid_3(message: String, pid: RawFd) -> io::Result<()> { - if pid == 3 { - debug_log_to_file(message) - } else { - Ok(()) - } -} - -pub fn _delete_log_file() -> io::Result<()> { - if fs::metadata(&*ZELLIJ_TMP_LOG_FILE).is_ok() { - fs::remove_file(&*ZELLIJ_TMP_LOG_FILE) - } else { - Ok(()) - } -} - -pub fn _delete_log_dir() -> io::Result<()> { - if fs::metadata(&*ZELLIJ_TMP_LOG_DIR).is_ok() { - fs::remove_dir_all(&*ZELLIJ_TMP_LOG_DIR) - } else { - Ok(()) - } -} - -pub fn debug_to_file(message: u8, pid: RawFd) -> io::Result<()> { - let mut path = PathBuf::new(); - path.push(&*ZELLIJ_TMP_LOG_DIR); - path.push(format!("zellij-{}.log", pid.to_string())); - - let mut file = fs::OpenOptions::new() - .append(true) - .create(true) - .open(&path)?; - set_permissions(&path)?; - file.write_all(&[message]) -} diff --git a/src/common/utils/mod.rs b/src/common/utils/mod.rs deleted file mode 100644 index 1cb4df27..00000000 --- a/src/common/utils/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Zellij utilities. - -pub mod consts; -pub mod logging; -pub mod shared; diff --git a/src/common/utils/shared.rs b/src/common/utils/shared.rs deleted file mode 100644 index 26a2f90a..00000000 --- a/src/common/utils/shared.rs +++ /dev/null @@ -1,108 +0,0 @@ -//! Some general utility functions. - -use std::{iter, str::from_utf8}; - -use strip_ansi_escapes::strip; - -use colors_transform::{Color, Rgb}; -use zellij_tile::data::{Palette, PaletteColor, PaletteSource, Theme}; - -fn ansi_len(s: &str) -> usize { - from_utf8(&strip(s.as_bytes()).unwrap()) - .unwrap() - .chars() - .count() -} - -pub fn adjust_to_size(s: &str, rows: usize, columns: usize) -> String { - s.lines() - .map(|l| { - let actual_len = ansi_len(l); - if actual_len > columns { - let mut line = String::from(l); - line.truncate(columns); - line - } else { - [l, &str::repeat(" ", columns - ansi_len(l))].concat() - } - }) - .chain(iter::repeat(str::repeat(" ", columns))) - .take(rows) - .collect::<Vec<_>>() - .join("\n\r") -} - -// Colors -pub mod colors { - pub const WHITE: u8 = 255; - pub const GREEN: u8 = 154; - pub const GRAY: u8 = 238; - pub const BRIGHT_GRAY: u8 = 245; - pub const RED: u8 = 88; - pub const ORANGE: u8 = 166; - pub const BLACK: u8 = 16; -} - -pub fn _hex_to_rgb(hex: &str) -> (u8, u8, u8) { - let rgb = Rgb::from_hex_str(hex) - .expect("The passed argument must be a valid hex color") - .as_tuple(); - (rgb.0 as u8, rgb.1 as u8, rgb.2 as u8) -} - -pub fn default_palette() -> Palette { - Palette { - source: PaletteSource::Default, - theme: Theme::Dark, - fg: PaletteColor::EightBit(colors::BRIGHT_GRAY), - bg: PaletteColor::EightBit(colors::GRAY), - black: PaletteColor::EightBit(colors::BLACK), - red: PaletteColor::EightBit(colors::RED), - green: PaletteColor::EightBit(colors::GREEN), - yellow: PaletteColor::EightBit(colors::GRAY), - blue: PaletteColor::EightBit(colors::GRAY), - magenta: PaletteColor::EightBit(colors::GRAY), - cyan: PaletteColor::EightBit(colors::GRAY), - white: PaletteColor::EightBit(colors::WHITE), - orange: PaletteColor::EightBit(colors::ORANGE), - } -} - -// Dark magic -pub fn _detect_theme(bg: PaletteColor) -> Theme { - match bg { - PaletteColor::Rgb((r, g, b)) => { - // HSP, P stands for perceived brightness - let hsp: f64 = (0.299 * (r as f64 * r as f64) - + 0.587 * (g as f64 * g as f64) - + 0.114 * (b as f64 * b as f64)) - .sqrt(); - match hsp > 127.5 { - true => Theme::Light, - false => Theme::Dark, - } - } - _ => Theme::Dark, - } -} - -// (this was shamelessly copied from alacritty) -// -// This returns the current terminal version as a unique number based on the -// semver version. The different versions are padded to ensure that a higher semver version will -// always report a higher version number. -pub fn version_number(mut version: &str) -> usize { - if let Some(separator) = version.rfind('-') { - version = &version[..separator]; - } - - let mut version_number = 0; - - let semver_versions = version.split('.'); - for (i, semver_version) in semver_versions.rev().enumerate() { - let semver_number = semver_version.parse::<usize>().unwrap_or(0); - version_number += usize::pow(100, i as u32) * semver_number; - } - - version_number -} diff --git a/src/common/wasm_vm.rs b/src/common/wasm_vm.rs deleted file mode 100644 index 51eb4ed8..00000000 --- a/src/common/wasm_vm.rs +++ /dev/null @@ -1,267 +0,0 @@ -use std::collections::{HashMap, HashSet}; -use std::fs; -use std::path::PathBuf; -use std::process; -use std::str::FromStr; -use std::sync::{mpsc::Sender, Arc, Mutex}; -use std::thread; -use std::time::{Duration, Instant}; - -use serde::{de::DeserializeOwned, Serialize}; -use wasmer::{ - imports, ChainableNamedResolver, Function, ImportObject, Instance, Module, Store, Value, - WasmerEnv, -}; -use wasmer_wasi::{Pipe, WasiEnv, WasiState}; -use zellij_tile::data::{Event, EventType, PluginIds}; - -use crate::common::errors::ContextType; -use crate::common::pty::PtyInstruction; -use crate::common::screen::ScreenInstruction; -use crate::common::thread_bus::{Bus, ThreadSenders}; -use crate::common::PaneId; - -#[derive(Clone, Debug)] -pub enum PluginInstruction { - Load(Sender<u32>, PathBuf), - Update(Option<u32>, Event), // Focused plugin / broadcast, event data - Render(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols - Unload(u32), - Exit, -} - -#[derive(WasmerEnv, Clone)] -pub struct PluginEnv { - pub plugin_id: u32, - pub senders: ThreadSenders, - pub wasi_env: WasiEnv, - pub subscriptions: Arc<Mutex<HashSet<EventType>>>, -} - -// Thread main -------------------------------------------------------------------------------------------------------- -pub fn wasm_thread_main(bus: Bus<PluginInstruction>, store: Store, data_dir: PathBuf) { - let mut plugin_id = 0; - let mut plugin_map = HashMap::new(); - loop { - let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel"); - err_ctx.add_call(ContextType::Plugin((&event).into())); - match event { - PluginInstruction::Load(pid_tx, path) => { - let plugin_dir = data_dir.join("plugins/"); - let wasm_bytes = fs::read(&path) - .or_else(|_| fs::read(&path.with_extension("wasm"))) - .or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm"))) - .unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display())); - - // FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that - let module = Module::new(&store, &wasm_bytes).unwrap(); - - let output = Pipe::new(); - let input = Pipe::new(); - let mut wasi_env = WasiState::new("Zellij") - .env("CLICOLOR_FORCE", "1") - .preopen(|p| { - p.directory(".") // FIXME: Change this to a more meaningful dir - .alias(".") - .read(true) - .write(true) - .create(true) - }) - .unwrap() - .stdin(Box::new(input)) - .stdout(Box::new(output)) - .finalize() - .unwrap(); - - let wasi = wasi_env.import_object(&module).unwrap(); - - let plugin_env = PluginEnv { - plugin_id, - senders: bus.senders.clone(), - wasi_env, - subscriptions: Arc::new(Mutex::new(HashSet::new())), - }; - - let zellij = zellij_exports(&store, &plugin_env); - let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap(); - - let start = instance.exports.get_function("_start").unwrap(); - - // This eventually calls the `.load()` method - start.call(&[]).unwrap(); - - plugin_map.insert(plugin_id, (instance, plugin_env)); - pid_tx.send(plugin_id).unwrap(); - plugin_id += 1; - } - PluginInstruction::Update(pid, event) => { - for (&i, (instance, plugin_env)) in &plugin_map { - let subs = plugin_env.subscriptions.lock().unwrap(); - // FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType? - let event_type = EventType::from_str(&event.to_string()).unwrap(); - if (pid.is_none() || pid == Some(i)) && subs.contains(&event_type) { - let update = instance.exports.get_function("update").unwrap(); - wasi_write_object(&plugin_env.wasi_env, &event); - update.call(&[]).unwrap(); - } - } - drop(bus.senders.send_to_screen(ScreenInstruction::Render)); - } - PluginInstruction::Render(buf_tx, pid, rows, cols) => { - let (instance, plugin_env) = plugin_map.get(&pid).unwrap(); - - let render = instance.exports.get_function("render").unwrap(); - - render - .call(&[Value::I32(rows as i32), Value::I32(cols as i32)]) - .unwrap(); - - buf_tx.send(wasi_read_string(&plugin_env.wasi_env)).unwrap(); - } - PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)), - PluginInstruction::Exit => break, - } - } -} - -// Plugin API --------------------------------------------------------------------------------------------------------- - -pub fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObject { - macro_rules! zellij_export { - ($($host_function:ident),+ $(,)?) => { - imports! { - "zellij" => { - $(stringify!($host_function) => - Function::new_native_with_env(store, plugin_env.clone(), $host_function),)+ - } - } - } - } - - zellij_export! { - host_subscribe, - host_unsubscribe, - host_set_invisible_borders, - host_set_max_height, - host_set_selectable, - host_get_plugin_ids, - host_open_file, - host_set_timeout, - } -} - -fn host_subscribe(plugin_env: &PluginEnv) { - let mut subscriptions = plugin_env.subscriptions.lock().unwrap(); - let new: HashSet<EventType> = wasi_read_object(&plugin_env.wasi_env); - subscriptions.extend(new); -} - -fn host_unsubscribe(plugin_env: &PluginEnv) { - let mut subscriptions = plugin_env.subscriptions.lock().unwrap(); - let old: HashSet<EventType> = wasi_read_object(&plugin_env.wasi_env); - subscriptions.retain(|k| !old.contains(k)); -} - -fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) { - let selectable = selectable != 0; - plugin_env - .senders - .send_to_screen(ScreenInstruction::SetSelectable( - PaneId::Plugin(plugin_env.plugin_id), - selectable, - )) - .unwrap() -} - -fn host_set_max_height(plugin_env: &PluginEnv, max_height: i32) { - let max_height = max_height as usize; - plugin_env - .senders - .send_to_screen(ScreenInstruction::SetMaxHeight( - PaneId::Plugin(plugin_env.plugin_id), - max_height, - )) - .unwrap() -} - -fn host_set_invisible_borders(plugin_env: &PluginEnv, invisible_borders: i32) { - let invisible_borders = invisible_borders != 0; - plugin_env - .senders - .send_to_screen(ScreenInstruction::SetInvisibleBorders( - PaneId::Plugin(plugin_env.plugin_id), - invisible_borders, - )) - .unwrap() -} - -fn host_get_plugin_ids(plugin_env: &PluginEnv) { - let ids = PluginIds { - plugin_id: plugin_env.plugin_id, - zellij_pid: process::id(), - }; - wasi_write_object(&plugin_env.wasi_env, &ids); -} - -fn host_open_file(plugin_env: &PluginEnv) { - let path: PathBuf = wasi_read_object(&plugin_env.wasi_env); - plugin_env - .senders - .send_to_pty(PtyInstruction::SpawnTerminal(Some(path))) - .unwrap(); -} - -fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) { - // There is a fancy, high-performance way to do this with zero additional threads: - // If the plugin thread keeps a BinaryHeap of timer structs, it can manage multiple and easily `.peek()` at the - // next time to trigger in O(1) time. Once the wake-up time is known, the `wasm` thread can use `recv_timeout()` - // to wait for an event with the timeout set to be the time of the next wake up. If events come in in the meantime, - // they are handled, but if the timeout triggers, we replace the event from `recv()` with an - // `Update(pid, TimerEvent)` and pop the timer from the Heap (or reschedule it). No additional threads for as many - // timers as we'd like. - // - // But that's a lot of code, and this is a few lines: - let send_plugin_instructions = plugin_env.senders.to_plugin.clone(); - let update_target = Some(plugin_env.plugin_id); - thread::spawn(move || { - let start_time = Instant::now(); - thread::sleep(Duration::from_secs_f64(secs)); - // FIXME: The way that elapsed time is being calculated here is not exact; it doesn't take into account the - // time it takes an event to actually reach the plugin after it's sent to the `wasm` thread. - let elapsed_time = Instant::now().duration_since(start_time).as_secs_f64(); - - send_plugin_instructions - .unwrap() - .send(PluginInstruction::Update( - update_target, - Event::Timer(elapsed_time), - )) - .unwrap(); - }); -} - -// Helper Functions --------------------------------------------------------------------------------------------------- - -// FIXME: Unwrap city -pub fn wasi_read_string(wasi_env: &WasiEnv) -> String { - let mut state = wasi_env.state(); - let wasi_file = state.fs.stdout_mut().unwrap().as_mut().unwrap(); - let mut buf = String::new(); - wasi_file.read_to_string(&mut buf).unwrap(); - buf -} - -pub fn wasi_write_string(wasi_env: &WasiEnv, buf: &str) { - let mut state = wasi_env.state(); - let wasi_file = state.fs.stdin_mut().unwrap().as_mut().unwrap(); - writeln!(wasi_file, "{}\r", buf).unwrap(); -} - -pub fn wasi_write_object(wasi_env: &WasiEnv, object: &impl Serialize) { - wasi_write_string(wasi_env, &serde_json::to_string(&object).unwrap()); -} - -pub fn wasi_read_object<T: DeserializeOwned>(wasi_env: &WasiEnv) -> T { - let json = wasi_read_string(wasi_env); - serde_json::from_str(&json).unwrap() -} diff --git a/src/main.rs b/src/main.rs index 49e42ffb..1e1d4764 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,31 +1,22 @@ -mod cli; -mod client; -mod common; -mod server; #[cfg(test)] mod tests; -use client::{boundaries, layout, panes, start_client, tab}; -use common::{ - command_is_executing, errors, os_input_output, pty, screen, setup::Setup, utils, wasm_vm, -}; -use server::start_server; +use std::convert::TryFrom; use structopt::StructOpt; - -use crate::cli::CliArgs; -use crate::command_is_executing::CommandIsExecuting; -use crate::common::input::config::Config; -use crate::os_input_output::{get_client_os_input, get_server_os_input}; -use crate::utils::{ +use zellij_client::{os_input_output::get_client_os_input, start_client}; +use zellij_server::{os_input_output::get_server_os_input, start_server}; +use zellij_utils::{ + cli::{CliArgs, ConfigCli}, consts::{ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR}, + input::config::Config, logging::*, + setup::Setup, }; -use std::convert::TryFrom; pub fn main() { let opts = CliArgs::from_args(); - if let Some(crate::cli::ConfigCli::Setup(setup)) = opts.option.clone() { + if let Some(ConfigCli::Setup(setup)) = opts.option.clone() { Setup::from_cli(&setup, opts).expect("Failed to print to stdout"); std::process::exit(0); } else { @@ -39,10 +30,22 @@ pub fn main() { atomic_create_dir(&*ZELLIJ_TMP_DIR).unwrap(); atomic_create_dir(&*ZELLIJ_TMP_LOG_DIR).unwrap(); if let Some(path) = opts.server { - let os_input = get_server_os_input(); + let os_input = match get_server_os_input() { + Ok(server_os_input) => server_os_input, + Err(e) => { + eprintln!("failed to open terminal:\n{}", e); + std::process::exit(1); + } + }; start_server(Box::new(os_input), path); } else { - let os_input = get_client_os_input(); + let os_input = match get_client_os_input() { + Ok(os_input) => os_input, + Err(e) => { + eprintln!("failed to open terminal:\n{}", e); + std::process::exit(1); + } + }; start_client(Box::new(os_input), opts, config); } } diff --git a/src/server/mod.rs b/src/server/mod.rs deleted file mode 100644 index 12cc2229..00000000 --- a/src/server/mod.rs +++ /dev/null @@ -1,291 +0,0 @@ -pub mod route; - -use std::sync::{Arc, RwLock}; -use std::thread; -use std::{path::PathBuf, sync::mpsc}; -use wasmer::Store; -use zellij_tile::data::PluginCapabilities; - -use crate::cli::CliArgs; -use crate::common::thread_bus::{Bus, ThreadSenders}; -use crate::common::{ - errors::ContextType, - input::options::Options, - ipc::{ClientToServerMsg, ServerToClientMsg}, - os_input_output::ServerOsApi, - pty::{pty_thread_main, Pty, PtyInstruction}, - screen::{screen_thread_main, ScreenInstruction}, - setup::{get_default_data_dir, install::populate_data_dir}, - thread_bus::{ChannelWithContext, SenderType, SenderWithContext, SyncChannelWithContext}, - wasm_vm::{wasm_thread_main, PluginInstruction}, -}; -use crate::layout::Layout; -use crate::panes::PositionAndSize; -use route::route_thread_main; - -/// Instructions related to server-side application including the -/// ones sent by client to server -#[derive(Debug, Clone)] -pub enum ServerInstruction { - NewClient(PositionAndSize, Box<CliArgs>, Box<Options>), - Render(Option<String>), - UnblockInputThread, - ClientExit, - Error(String), -} - -impl From<ClientToServerMsg> for ServerInstruction { - fn from(instruction: ClientToServerMsg) -> Self { - match instruction { - ClientToServerMsg::ClientExit => ServerInstruction::ClientExit, - ClientToServerMsg::NewClient(pos, opts, options) => { - ServerInstruction::NewClient(pos, opts, options) - } - _ => unreachable!(), - } - } -} - -pub struct SessionMetaData { - pub senders: ThreadSenders, - pub capabilities: PluginCapabilities, - screen_thread: Option<thread::JoinHandle<()>>, - pty_thread: Option<thread::JoinHandle<()>>, - wasm_thread: Option<thread::JoinHandle<()>>, -} - -impl Drop for SessionMetaData { - fn drop(&mut self) { - let _ = self.senders.send_to_pty(PtyInstruction::Exit); - let _ = self.senders.send_to_screen(ScreenInstruction::Exit); - let _ = self.senders.send_to_plugin(PluginInstruction::Exit); - let _ = self.screen_thread.take().unwrap().join(); - let _ = self.pty_thread.take().unwrap().join(); - let _ = self.wasm_thread.take().unwrap().join(); - } -} - -pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) { - #[cfg(not(test))] - daemonize::Daemonize::new() - .working_directory(std::env::var("HOME").unwrap()) - .umask(0o077) - .start() - .expect("could not daemonize the server process"); - - std::env::set_var(&"ZELLIJ", "0"); - - let (to_server, server_receiver): SyncChannelWithContext<ServerInstruction> = - mpsc::sync_channel(50); - let to_server = SenderWithContext::new(SenderType::SyncSender(to_server)); - let sessions: Arc<RwLock<Option<SessionMetaData>>> = Arc::new(RwLock::new(None)); - - #[cfg(not(test))] - std::panic::set_hook({ - use crate::errors::handle_panic; - let to_server = to_server.clone(); - Box::new(move |info| { - handle_panic(info, &to_server); - }) - }); - - #[cfg(test)] - thread::Builder::new() - .name("server_router".to_string()) - .spawn({ - let sessions = sessions.clone(); - let os_input = os_input.clone(); - let to_server = to_server.clone(); - - move || route_thread_main(sessions, os_input, to_server) - }) - .unwrap(); - #[cfg(not(test))] - let _ = thread::Builder::new() - .name("server_listener".to_string()) - .spawn({ - use crate::common::os_input_output::set_permissions; - use interprocess::local_socket::LocalSocketListener; - - let os_input = os_input.clone(); - let sessions = sessions.clone(); - let to_server = to_server.clone(); - let socket_path = socket_path.clone(); - move || { - drop(std::fs::remove_file(&socket_path)); - let listener = LocalSocketListener::bind(&*socket_path).unwrap(); - set_permissions(&socket_path).unwrap(); - for stream in listener.incoming() { - match stream { - Ok(stream) => { - let mut os_input = os_input.clone(); - os_input.update_receiver(stream); - let sessions = sessions.clone(); - let to_server = to_server.clone(); - thread::Builder::new() - .name("server_router".to_string()) - .spawn({ - let sessions = sessions.clone(); - let os_input = os_input.clone(); - let to_server = to_server.clone(); - - move || route_thread_main(sessions, os_input, to_server) - }) - .unwrap(); - } - Err(err) => { - panic!("err {:?}", err); - } - } - } - } - }); - - loop { - let (instruction, mut err_ctx) = server_receiver.recv().unwrap(); - err_ctx.add_call(ContextType::IPCServer((&instruction).into())); - match instruction { - ServerInstruction::NewClient(full_screen_ws, opts, config_options) => { - let session_data = init_session( - os_input.clone(), - opts, - config_options, - to_server.clone(), - full_screen_ws, - ); - *sessions.write().unwrap() = Some(session_data); - sessions - .read() - .unwrap() - .as_ref() - .unwrap() - .senders - .send_to_pty(PtyInstruction::NewTab) - .unwrap(); - } - ServerInstruction::UnblockInputThread => { - os_input.send_to_client(ServerToClientMsg::UnblockInputThread); - } - ServerInstruction::ClientExit => { - *sessions.write().unwrap() = None; - os_input.send_to_client(ServerToClientMsg::Exit); - break; - } - ServerInstruction::Render(output) => { - os_input.send_to_client(ServerToClientMsg::Render(output)) - } - ServerInstruction::Error(backtrace) => { - os_input.send_to_client(ServerToClientMsg::ServerError(backtrace)); - break; - } - } - } - #[cfg(not(test))] - drop(std::fs::remove_file(&socket_path)); -} - -fn init_session( - os_input: Box<dyn ServerOsApi>, - opts: Box<CliArgs>, - config_options: Box<Options>, - to_server: SenderWithContext<ServerInstruction>, - full_screen_ws: PositionAndSize, -) -> SessionMetaData { - let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = mpsc::channel(); - let to_screen = SenderWithContext::new(SenderType::Sender(to_screen)); - - let (to_plugin, plugin_receiver): ChannelWithContext<PluginInstruction> = mpsc::channel(); - let to_plugin = SenderWithContext::new(SenderType::Sender(to_plugin)); - let (to_pty, pty_receiver): ChannelWithContext<PtyInstruction> = mpsc::channel(); - let to_pty = SenderWithContext::new(SenderType::Sender(to_pty)); - - // Determine and initialize the data directory - let data_dir = opts.data_dir.unwrap_or_else(get_default_data_dir); - - #[cfg(not(disable_automatic_asset_installation))] - populate_data_dir(&data_dir); - - let capabilities = PluginCapabilities { - arrow_fonts: config_options.simplified_ui, - }; - - // Don't use default layouts in tests, but do everywhere else - #[cfg(not(test))] - let default_layout = Some(PathBuf::from("default")); - #[cfg(test)] - let default_layout = None; - let layout_path = opts.layout_path; - let maybe_layout = opts - .layout - .as_ref() - .map(|p| Layout::from_dir(&p, &data_dir)) - .or_else(|| layout_path.map(|p| Layout::new(&p))) - .or_else(|| default_layout.map(|p| Layout::from_dir(&p, &data_dir))); - - let pty_thread = thread::Builder::new() - .name("pty".to_string()) - .spawn({ - let pty = Pty::new( - Bus::new( - pty_receiver, - Some(&to_screen), - None, - Some(&to_plugin), - Some(&to_server), - Some(os_input.clone()), - ), - opts.debug, - ); - - move || pty_thread_main(pty, maybe_layout) - }) - .unwrap(); - - let screen_thread = thread::Builder::new() - .name("screen".to_string()) - .spawn({ - let screen_bus = Bus::new( - screen_receiver, - None, - Some(&to_pty), - Some(&to_plugin), - Some(&to_server), - Some(os_input.clone()), - ); - let max_panes = opts.max_panes; - - move || { - screen_thread_main(screen_bus, max_panes, full_screen_ws, config_options); - } - }) - .unwrap(); - - let wasm_thread = thread::Builder::new() - .name("wasm".to_string()) - .spawn({ - let plugin_bus = Bus::new( - plugin_receiver, - Some(&to_screen), - Some(&to_pty), - None, - None, - None, - ); - let store = Store::default(); - - move || wasm_thread_main(plugin_bus, store, data_dir) - }) - .unwrap(); - SessionMetaData { - senders: ThreadSenders { - to_screen: Some(to_screen), - to_pty: Some(to_pty), - to_plugin: Some(to_plugin), - to_server: None, - }, - capabilities, - screen_thread: Some(screen_thread), - pty_thread: Some(pty_thread), - wasm_thread: Some(wasm_thread), - } -} diff --git a/src/server/route.rs b/src/server/route.rs deleted file mode 100644 index c435a0e6..00000000 --- a/src/server/route.rs +++ /dev/null @@ -1,215 +0,0 @@ -use std::sync::{Arc, RwLock}; - -use zellij_tile::data::Event; - -use crate::common::input::actions::{Action, Direction}; -use crate::common::input::handler::get_mode_info; -use crate::common::ipc::ClientToServerMsg; -use crate::common::os_input_output::ServerOsApi; -use crate::common::pty::PtyInstruction; -use crate::common::screen::ScreenInstruction; -use crate::common::thread_bus::SenderWithContext; -use crate::common::wasm_vm::PluginInstruction; -use crate::server::{ServerInstruction, SessionMetaData}; - -fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn ServerOsApi) { - match action { - Action::Write(val) => { - session - .senders - .send_to_screen(ScreenInstruction::ClearScroll) - .unwrap(); - session - .senders - .send_to_screen(ScreenInstruction::WriteCharacter(val)) - .unwrap(); - } - Action::SwitchToMode(mode) => { - let palette = os_input.load_palette(); - session - .senders - .send_to_plugin(PluginInstruction::Update( - None, - Event::ModeUpdate(get_mode_info(mode, palette, session.capabilities)), - )) - .unwrap(); - session - .senders - .send_to_screen(ScreenInstruction::ChangeMode(get_mode_info( - mode, - palette, - session.capabilities, - ))) - .unwrap(); - session - .senders - .send_to_screen(ScreenInstruction::Render) - .unwrap(); - } - Action::Resize(direction) => { - let screen_instr = match direction { - Direction::Left => ScreenInstruction::ResizeLeft, - Direction::Right => ScreenInstruction::ResizeRight, - Direction::Up => ScreenInstruction::ResizeUp, - Direction::Down => ScreenInstruction::ResizeDown, - }; - session.senders.send_to_screen(screen_instr).unwrap(); - } - Action::SwitchFocus => { - session - .senders - .send_to_screen(ScreenInstruction::SwitchFocus) - .unwrap(); - } - Action::FocusNextPane => { - session - .senders - .send_to_screen(ScreenInstruction::FocusNextPane) - .unwrap(); - } - Action::FocusPreviousPane => { - session - .senders - .send_to_screen(ScreenInstruction::FocusPreviousPane) - .unwrap(); - } - Action::MoveFocus(direction) => { - let screen_instr = match direction { - Direction::Left => ScreenInstruction::MoveFocusLeft, - Direction::Right => ScreenInstruction::MoveFocusRight, - Direction::Up => ScreenInstruction::MoveFocusUp, - Direction::Down => ScreenInstruction::MoveFocusDown, - }; - session.senders.send_to_screen(screen_instr).unwrap(); - } - Action::MoveFocusOrTab(direction) => { - let screen_instr = match direction { - Direction::Left => ScreenInstruction::MoveFocusLeftOrPreviousTab, - Direction::Right => ScreenInstruction::MoveFocusRightOrNextTab, - _ => unreachable!(), - }; - session.senders.send_to_screen(screen_instr).unwrap(); - } - Action::ScrollUp => { - session - .senders - .send_to_screen(ScreenInstruction::ScrollUp) - .unwrap(); - } - Action::ScrollDown => { - session - .senders - .send_to_screen(ScreenInstruction::ScrollDown) - .unwrap(); - } - Action::PageScrollUp => { - session - .senders - .send_to_screen(ScreenInstruction::PageScrollUp) - .unwrap(); - } - Action::PageScrollDown => { - session - .senders - .send_to_screen(ScreenInstruction::PageScrollDown) - .unwrap(); - } - Action::ToggleFocusFullscreen => { - session - .senders - .send_to_screen(ScreenInstruction::ToggleActiveTerminalFullscreen) - .unwrap(); - } - Action::NewPane(direction) => { - let pty_instr = match direction { - Some(Direction::Left) => PtyInstruction::SpawnTerminalVertically(None), - Some(Direction::Right) => PtyInstruction::SpawnTerminalVertically(None), - Some(Direction::Up) => PtyInstruction::SpawnTerminalHorizontally(None), - Some(Direction::Down) => PtyInstruction::SpawnTerminalHorizontally(None), - // No direction specified - try to put it in the biggest available spot - None => PtyInstruction::SpawnTerminal(None), - }; - session.senders.send_to_pty(pty_instr).unwrap(); - } - Action::CloseFocus => { - session - .senders - .send_to_screen(ScreenInstruction::CloseFocusedPane) - .unwrap(); - } - Action::NewTab => { - session.senders.send_to_pty(PtyInstruction::NewTab).unwrap(); - } - Action::GoToNextTab => { - session - .senders - .send_to_screen(ScreenInstruction::SwitchTabNext) - .unwrap(); - } - Action::GoToPreviousTab => { - session - .senders - .send_to_screen(ScreenInstruction::SwitchTabPrev) - .unwrap(); - } - Action::ToggleActiveSyncTab => { - session - .senders - .send_to_screen(ScreenInstruction::ToggleActiveSyncTab) - .unwrap(); - } - Action::CloseTab => { - session - .senders - .send_to_screen(ScreenInstruction::CloseTab) - .unwrap(); - } - Action::GoToTab(i) => { - session - .senders - .send_to_screen(ScreenInstruction::GoToTab(i)) - .unwrap(); - } - Action::TabNameInput(c) => { - session - .senders - .send_to_screen(ScreenInstruction::UpdateTabName(c)) - .unwrap(); - } - Action::NoOp => {} - Action::Quit => panic!("Received unexpected action"), - } -} - -pub fn route_thread_main( - sessions: Arc<RwLock<Option<SessionMetaData>>>, - mut os_input: Box<dyn ServerOsApi>, - to_server: SenderWithContext<ServerInstruction>, -) { - loop { - let (instruction, err_ctx) = os_input.recv_from_client(); - err_ctx.update_thread_ctx(); - let rlocked_sessions = sessions.read().unwrap(); - match instruction { - ClientToServerMsg::ClientExit => { - to_server.send(instruction.into()).unwrap(); - break; - } - ClientToServerMsg::Action(action) => { - route_action(action, rlocked_sessions.as_ref().unwrap(), &*os_input); - } - ClientToServerMsg::TerminalResize(new_size) => { - rlocked_sessions - .as_ref() - .unwrap() - .senders - .send_to_screen(ScreenInstruction::TerminalResize(new_size)) - .unwrap(); - } - ClientToServerMsg::NewClient(..) => { - os_input.add_client_sender(); - to_server.send(instruction.into()).unwrap(); - } - } - } -} diff --git a/src/tests/fakes.rs b/src/tests/fakes.rs index 98ec694a..09cc2ba3 100644 --- a/src/tests/fakes.rs +++ b/src/tests/fakes.rs @@ -1,4 +1,5 @@ -use crate::panes::PositionAndSize; +use crate::tests::possible_tty_inputs::{get_possible_tty_inputs, Bytes}; +use crate::tests::utils::commands::{QUIT, SLEEP}; use interprocess::local_socket::LocalSocketStream; use std::collections::{HashMap, VecDeque}; use std::io::Write; @@ -6,15 +7,16 @@ use std::os::unix::io::RawFd; use std::path::PathBuf; use std::sync::{mpsc, Arc, Condvar, Mutex}; use std::time::{Duration, Instant}; - -use crate::common::ipc::{ClientToServerMsg, ServerToClientMsg}; -use crate::common::thread_bus::{ChannelWithContext, SenderType, SenderWithContext}; -use crate::errors::ErrorContext; -use crate::os_input_output::{ClientOsApi, ServerOsApi}; -use crate::tests::possible_tty_inputs::{get_possible_tty_inputs, Bytes}; -use crate::tests::utils::commands::{QUIT, SLEEP}; -use crate::utils::shared::default_palette; +use zellij_client::os_input_output::ClientOsApi; +use zellij_server::os_input_output::ServerOsApi; use zellij_tile::data::Palette; +use zellij_utils::{ + channels::{ChannelWithContext, SenderType, SenderWithContext}, + errors::ErrorContext, + ipc::{ClientToServerMsg, ServerToClientMsg}, + pane_size::PositionAndSize, + shared::default_palette, +}; const MIN_TIME_BETWEEN_SNAPSHOTS: Duration = Duration::from_millis(150); @@ -217,6 +219,9 @@ impl ClientOsApi for FakeInputOutput { } } fn connect_to_server(&self, _path: &std::path::Path) {} + fn load_palette(&self) -> Palette { + default_palette() + } } impl ServerOsApi for FakeInputOutput { diff --git a/src/tests/integration/basic.rs b/src/tests/integration/basic.rs index bb581eb1..640b1c55 100644 --- a/src/tests/integration/basic.rs +++ b/src/tests/integration/basic.rs @@ -1,7 +1,6 @@ -use crate::panes::PositionAndSize; use ::insta::assert_snapshot; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::config::Config; use crate::tests::fakes::FakeInputOutput; use crate::tests::start; use crate::tests::utils::commands::{ @@ -12,6 +11,7 @@ use crate::tests::utils::commands::{ }; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::CliArgs; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(fake_win_size.clone()) diff --git a/src/tests/integration/close_pane.rs b/src/tests/integration/close_pane.rs index cfb912ee..b5ba7f19 100644 --- a/src/tests/integration/close_pane.rs +++ b/src/tests/integration/close_pane.rs @@ -1,17 +1,17 @@ -use crate::panes::PositionAndSize; use ::insta::assert_snapshot; +use zellij_utils::pane_size::PositionAndSize; use crate::tests::fakes::FakeInputOutput; use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::CliArgs; -use crate::common::input::config::Config; use crate::tests::utils::commands::{ CLOSE_PANE_IN_PANE_MODE, ESC, MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_DOWN_IN_RESIZE_MODE, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(fake_win_size.clone()) diff --git a/src/tests/integration/compatibility.rs b/src/tests/integration/compatibility.rs index 06222a3a..3b28a970 100644 --- a/src/tests/integration/compatibility.rs +++ b/src/tests/integration/compatibility.rs @@ -1,15 +1,15 @@ use ::insta::assert_snapshot; use ::std::collections::HashMap; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; use crate::tests::possible_tty_inputs::Bytes; use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::config::Config; use crate::tests::utils::commands::QUIT; +use zellij_utils::input::config::Config; /* * These tests are general compatibility tests for non-trivial scenarios running in the terminal. diff --git a/src/tests/integration/layouts.rs b/src/tests/integration/layouts.rs index fa46e9f7..e6986924 100644 --- a/src/tests/integration/layouts.rs +++ b/src/tests/integration/layouts.rs @@ -1,13 +1,13 @@ use insta::assert_snapshot; use std::path::PathBuf; -use crate::common::input::config::Config; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; use crate::tests::start; use crate::tests::utils::commands::QUIT; use crate::tests::utils::get_output_frame_snapshots; use crate::CliArgs; +use zellij_utils::input::config::Config; +use zellij_utils::pane_size::PositionAndSize; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(fake_win_size.clone()) diff --git a/src/tests/integration/move_focus_down.rs b/src/tests/integration/move_focus_down.rs index b1032bd0..2f18ab62 100644 --- a/src/tests/integration/move_focus_down.rs +++ b/src/tests/integration/move_focus_down.rs @@ -1,16 +1,16 @@ use ::insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::config::Config; use crate::tests::utils::commands::{ MOVE_FOCUS_DOWN_IN_PANE_MODE, MOVE_FOCUS_UP_IN_PANE_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) diff --git a/src/tests/integration/move_focus_left.rs b/src/tests/integration/move_focus_left.rs index 4842f342..a9fca49e 100644 --- a/src/tests/integration/move_focus_left.rs +++ b/src/tests/integration/move_focus_left.rs @@ -1,17 +1,17 @@ use ::insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::config::Config; use crate::tests::utils::commands::{ ENTER, MOVE_FOCUS_LEFT_IN_NORMAL_MODE, MOVE_FOCUS_LEFT_IN_PANE_MODE, MOVE_FOCUS_RIGHT_IN_PANE_MODE, NEW_TAB_IN_TAB_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, TAB_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) diff --git a/src/tests/integration/move_focus_right.rs b/src/tests/integration/move_focus_right.rs index 6ff6e21a..c85d5b96 100644 --- a/src/tests/integration/move_focus_right.rs +++ b/src/tests/integration/move_focus_right.rs @@ -1,17 +1,17 @@ use ::insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::config::Config; use crate::tests::utils::commands::{ ENTER, MOVE_FOCUS_LEFT_IN_PANE_MODE, MOVE_FOCUS_RIGHT_IN_NORMAL_MODE, MOVE_FOCUS_RIGHT_IN_PANE_MODE, NEW_TAB_IN_TAB_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, TAB_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) diff --git a/src/tests/integration/move_focus_up.rs b/src/tests/integration/move_focus_up.rs index 068ded52..d161ff8a 100644 --- a/src/tests/integration/move_focus_up.rs +++ b/src/tests/integration/move_focus_up.rs @@ -1,16 +1,16 @@ use ::insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::config::Config; use crate::tests::utils::commands::{ MOVE_FOCUS_DOWN_IN_PANE_MODE, MOVE_FOCUS_UP_IN_PANE_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) diff --git a/src/tests/integration/resize_down.rs b/src/tests/integration/resize_down.rs index cc4b9ec4..4ef45a06 100644 --- a/src/tests/integration/resize_down.rs +++ b/src/tests/integration/resize_down.rs @@ -1,17 +1,17 @@ use insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::config::Config; use crate::tests::utils::commands::{ MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_DOWN_IN_RESIZE_MODE, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) diff --git a/src/tests/integration/resize_left.rs b/src/tests/integration/resize_left.rs index c5ba5427..1b820501 100644 --- a/src/tests/integration/resize_left.rs +++ b/src/tests/integration/resize_left.rs @@ -1,16 +1,16 @@ use ::insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::config::Config; use crate::tests::utils::commands::{ MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) diff --git a/src/tests/integration/resize_right.rs b/src/tests/integration/resize_right.rs index 43409ec1..44601f2c 100644 --- a/src/tests/integration/resize_right.rs +++ b/src/tests/integration/resize_right.rs @@ -1,16 +1,16 @@ use ::insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::config::Config; use crate::tests::utils::commands::{ MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_MODE, RESIZE_RIGHT_IN_RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) diff --git a/src/tests/integration/resize_up.rs b/src/tests/integration/resize_up.rs index 200fcc6e..9aca8d5a 100644 --- a/src/tests/integration/resize_up.rs +++ b/src/tests/integration/resize_up.rs @@ -1,16 +1,16 @@ use ::insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::config::Config; use crate::tests::utils::commands::{ MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, RESIZE_LEFT_IN_RESIZE_MODE, RESIZE_MODE, RESIZE_UP_IN_RESIZE_MODE, SLEEP, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) diff --git a/src/tests/integration/tabs.rs b/src/tests/integration/tabs.rs index 0995d16c..90f82c6d 100644 --- a/src/tests/integration/tabs.rs +++ b/src/tests/integration/tabs.rs @@ -2,16 +2,17 @@ use insta::assert_snapshot; use crate::tests::fakes::FakeInputOutput; use crate::tests::start; +use crate::tests::utils::commands::CLOSE_PANE_IN_PANE_MODE; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::CliArgs; -use crate::{panes::PositionAndSize, tests::utils::commands::CLOSE_PANE_IN_PANE_MODE}; -use crate::common::input::config::Config; use crate::tests::utils::commands::{ CLOSE_TAB_IN_TAB_MODE, NEW_TAB_IN_TAB_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SWITCH_NEXT_TAB_IN_TAB_MODE, SWITCH_PREV_TAB_IN_TAB_MODE, TAB_MODE, TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; +use zellij_utils::pane_size::PositionAndSize; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) diff --git a/src/tests/integration/terminal_window_resize.rs b/src/tests/integration/terminal_window_resize.rs index 2e56300f..2be41c3a 100644 --- a/src/tests/integration/terminal_window_resize.rs +++ b/src/tests/integration/terminal_window_resize.rs @@ -1,12 +1,12 @@ -use crate::panes::PositionAndSize; use ::insta::assert_snapshot; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::config::Config; use crate::tests::fakes::FakeInputOutput; use crate::tests::start; use crate::tests::utils::commands::QUIT; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::CliArgs; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(fake_win_size.clone()) diff --git a/src/tests/integration/toggle_fullscreen.rs b/src/tests/integration/toggle_fullscreen.rs index 00d98170..80257982 100644 --- a/src/tests/integration/toggle_fullscreen.rs +++ b/src/tests/integration/toggle_fullscreen.rs @@ -1,16 +1,16 @@ use insta::assert_snapshot; -use crate::panes::PositionAndSize; use crate::tests::fakes::FakeInputOutput; use crate::tests::start; use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}; use crate::CliArgs; +use zellij_utils::pane_size::PositionAndSize; -use crate::common::input::config::Config; use crate::tests::utils::commands::{ MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT, SPLIT_DOWN_IN_PANE_MODE, SPLIT_RIGHT_IN_PANE_MODE, TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE, }; +use zellij_utils::input::config::Config; fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput { FakeInputOutput::new(*fake_win_size) diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 7aaad79f..3e548db7 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -4,12 +4,10 @@ pub mod possible_tty_inputs; pub mod tty_inputs; pub mod utils; -use crate::cli::CliArgs; -use crate::client::start_client; -use crate::common::input::config::Config; -use crate::os_input_output::{ClientOsApi, ServerOsApi}; -use crate::server::start_server; use std::path::PathBuf; +use zellij_client::{os_input_output::ClientOsApi, start_client}; +use zellij_server::{os_input_output::ServerOsApi, start_server}; +use zellij_utils::{cli::CliArgs, input::config::Config}; pub fn start( client_os_input: Box<dyn ClientOsApi>, diff --git a/src/tests/utils.rs b/src/tests/utils.rs index f01c1ac5..441f9bce 100644 --- a/src/tests/utils.rs +++ b/src/tests/utils.rs @@ -1,5 +1,6 @@ -use crate::panes::PositionAndSize; -use crate::panes::TerminalPane; +use zellij_server::{panes::TerminalPane, tab::Pane}; +use zellij_tile::data::Palette; +use zellij_utils::pane_size::PositionAndSize; pub fn get_output_frame_snapshots( output_frames: &[Vec<u8>], @@ -7,7 +8,7 @@ pub fn get_output_frame_snapshots( ) -> Vec<String> { let mut vte_parser = vte::Parser::new(); let main_pid = 0; - let mut terminal_output = TerminalPane::new(main_pid, *win_size); + let mut terminal_output = TerminalPane::new(main_pid, *win_size, Palette::default()); let mut snapshots = vec![]; for frame in output_frames.iter() { |
