aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorBrooks J Rady <b.j.rady@gmail.com>2021-04-13 16:17:40 +0100
committerBrooks J Rady <b.j.rady@gmail.com>2021-04-13 16:17:40 +0100
commit0e73227fe2418c3a4945f493afbc64ab66dcb763 (patch)
treee46256e314b2a70a5dc8f7281519695bf2a5ffad /src
parentdocs(build): documented the new build system (diff)
parentfix(performance): remove unnecessary allocations from pty (#264) (diff)
downloadsl0ppij-0e73227fe2418c3a4945f493afbc64ab66dcb763.tar.gz
sl0ppij-0e73227fe2418c3a4945f493afbc64ab66dcb763.tar.xz
sl0ppij-0e73227fe2418c3a4945f493afbc64ab66dcb763.zip
merge(main): update branch with changes from main
Diffstat (limited to 'src')
-rw-r--r--src/cli.rs17
-rw-r--r--src/client/boundaries.rs4
-rw-r--r--src/client/mod.rs1
-rw-r--r--src/client/pane_resizer.rs508
-rw-r--r--src/client/panes/plugin_pane.rs16
-rw-r--r--src/client/panes/terminal_character.rs64
-rw-r--r--src/client/panes/terminal_pane.rs85
-rw-r--r--src/client/tab.rs294
-rw-r--r--src/common/command_is_executing.rs4
-rw-r--r--src/common/errors.rs16
-rw-r--r--src/common/input/actions.rs9
-rw-r--r--src/common/input/config.rs158
-rw-r--r--src/common/input/handler.rs92
-rw-r--r--src/common/input/keybinds.rs611
-rw-r--r--src/common/input/mod.rs3
-rw-r--r--src/common/input/unit/keybinds_test.rs135
-rw-r--r--src/common/mod.rs58
-rw-r--r--src/common/os_input_output.rs40
-rw-r--r--src/common/pty_bus.rs92
-rw-r--r--src/common/screen.rs18
-rw-r--r--src/common/utils/shared.rs13
-rw-r--r--src/main.rs1
-rw-r--r--src/tests/fakes.rs38
-rw-r--r--src/tests/integration/close_pane.rs2
-rw-r--r--src/tests/integration/mod.rs1
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__terminal_window_resize__window_height_increase_with_one_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__terminal_window_resize__window_width_and_height_decrease_with_one_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__terminal_window_resize__window_width_decrease_with_one_pane.snap25
-rw-r--r--src/tests/integration/snapshots/zellij__tests__integration__terminal_window_resize__window_width_increase_with_one_pane.snap25
-rw-r--r--src/tests/integration/terminal_window_resize.rs127
-rw-r--r--src/tests/possible_tty_inputs.rs9
-rw-r--r--src/tests/tty_inputs.rs46
-rw-r--r--src/tests/utils.rs1
33 files changed, 2052 insertions, 511 deletions
diff --git a/src/cli.rs b/src/cli.rs
index 6e384e16..b1fb8f38 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -1,7 +1,7 @@
use std::path::PathBuf;
use structopt::StructOpt;
-#[derive(StructOpt, Debug, Default)]
+#[derive(StructOpt, Default, Debug)]
#[structopt(name = "zellij")]
pub struct CliArgs {
/// Send "split (direction h == horizontal / v == vertical)" to active zellij session
@@ -24,6 +24,21 @@ pub struct CliArgs {
#[structopt(short, long)]
pub layout: Option<PathBuf>,
+ #[structopt(subcommand)]
+ pub config: Option<ConfigCli>,
+
#[structopt(short, long)]
pub debug: bool,
}
+
+#[derive(Debug, StructOpt)]
+pub enum ConfigCli {
+ /// Path to the configuration yaml file
+ #[structopt(alias = "c")]
+ Config {
+ path: Option<PathBuf>,
+ #[structopt(long)]
+ /// Disables loading of configuration file at default location
+ clean: bool,
+ },
+}
diff --git a/src/client/boundaries.rs b/src/client/boundaries.rs
index e498da89..28e01a11 100644
--- a/src/client/boundaries.rs
+++ b/src/client/boundaries.rs
@@ -21,9 +21,9 @@ pub mod boundary_type {
pub mod colors {
use ansi_term::Colour::{self, Fixed};
- pub const WHITE: Colour = Fixed(255);
pub const GREEN: Colour = Fixed(154);
pub const GRAY: Colour = Fixed(238);
+ pub const ORANGE: Colour = Fixed(166);
}
pub type BoundaryType = &'static str; // easy way to refer to boundary_type above
@@ -768,7 +768,7 @@ impl Boundaries {
let color = match color.is_some() {
true => match input_mode {
InputMode::Normal | InputMode::Locked => Some(colors::GREEN),
- _ => Some(colors::WHITE),
+ _ => Some(colors::ORANGE),
},
false => None,
};
diff --git a/src/client/mod.rs b/src/client/mod.rs
index 93f35e06..cba12a46 100644
--- a/src/client/mod.rs
+++ b/src/client/mod.rs
@@ -1,5 +1,6 @@
pub mod boundaries;
pub mod layout;
+pub mod pane_resizer;
pub mod panes;
pub mod tab;
diff --git a/src/client/pane_resizer.rs b/src/client/pane_resizer.rs
new file mode 100644
index 00000000..007dc33f
--- /dev/null
+++ b/src/client/pane_resizer.rs
@@ -0,0 +1,508 @@
+use crate::os_input_output::OsApi;
+use crate::panes::{PaneId, PositionAndSize};
+use crate::tab::Pane;
+use std::collections::{BTreeMap, HashSet};
+
+pub struct PaneResizer<'a> {
+ panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>,
+ os_api: &'a mut Box<dyn OsApi>,
+}
+
+// TODO: currently there are some functions here duplicated with Tab
+// the reason for this is that we need to get rid of the expansion_boundary
+// otherwise we'll have a big separation of concerns issue
+// once that is done, 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 OsApi>,
+ ) -> 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;
+ if new_size.columns < current_size.columns {
+ let reduce_by = current_size.columns - new_size.columns;
+ find_reducible_vertical_chain(
+ &self.panes,
+ reduce_by,
+ current_size.columns,
+ current_size.rows,
+ )
+ .map(|panes_to_resize| {
+ 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;
+ });
+ } else if new_size.columns > current_size.columns {
+ let increase_by = new_size.columns - current_size.columns;
+ find_increasable_vertical_chain(
+ &self.panes,
+ increase_by,
+ current_size.columns,
+ current_size.rows,
+ )
+ .map(|panes_to_resize| {
+ 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;
+ });
+ }
+ if new_size.rows < current_size.rows {
+ let reduce_by = current_size.rows - new_size.rows;
+ find_reducible_horizontal_chain(
+ &self.panes,
+ reduce_by,
+ current_size.columns,
+ current_size.rows,
+ )
+ .map(|panes_to_resize| {
+ 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;
+ });
+ } else if new_size.rows > current_size.rows {
+ let increase_by = new_size.rows - current_size.rows;
+ find_increasable_horizontal_chain(
+ &self.panes,
+ increase_by,
+ current_size.columns,
+ current_size.rows,
+ )
+ .map(|panes_to_resize| {
+ 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;
+ });
+ }
+ 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: &Box<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.as_ref())
+ }, // 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: &Box<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.as_ref()), // 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: &Box<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.as_ref()), // 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: &Box<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.as_ref())
+ }, // 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, 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, 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, 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, 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/plugin_pane.rs b/src/client/panes/plugin_pane.rs
index 4cffff5f..49cf2aee 100644
--- a/src/client/panes/plugin_pane.rs
+++ b/src/client/panes/plugin_pane.rs
@@ -1,6 +1,6 @@
#![allow(clippy::clippy::if_same_then_else)]
-use crate::{common::SenderWithContext, pty_bus::VteEvent, tab::Pane, wasm_vm::PluginInstruction};
+use crate::{common::SenderWithContext, pty_bus::VteBytes, tab::Pane, wasm_vm::PluginInstruction};
use std::{sync::mpsc::channel, unimplemented};
@@ -79,7 +79,7 @@ impl Pane for PluginPane {
self.position_and_size_override = Some(position_and_size_override);
self.should_render = true;
}
- fn handle_event(&mut self, _event: VteEvent) {
+ fn handle_pty_bytes(&mut self, _event: VteBytes) {
unimplemented!()
}
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
@@ -173,6 +173,18 @@ impl Pane for PluginPane {
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!()
}
diff --git a/src/client/panes/terminal_character.rs b/src/client/panes/terminal_character.rs
index 053ebd3f..ce967ef8 100644
--- a/src/client/panes/terminal_character.rs
+++ b/src/client/panes/terminal_character.rs
@@ -37,6 +37,14 @@ pub enum NamedColor {
Magenta,
Cyan,
White,
+ BrightBlack,
+ BrightRed,
+ BrightGreen,
+ BrightYellow,
+ BrightBlue,
+ BrightMagenta,
+ BrightCyan,
+ BrightWhite,
}
impl NamedColor {
@@ -50,6 +58,14 @@ impl NamedColor {
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 {
@@ -62,6 +78,14 @@ impl NamedColor {
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),
}
}
}
@@ -383,6 +407,46 @@ impl CharacterStyles {
params_used += 1; // even if it's a bug, let's not create an endless loop, eh?
}
[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)))
+ }
_ => {
// if this happens, it's a bug
let _ = debug_log_to_file(format!("unhandled csi m code {:?}", ansi_params));
diff --git a/src/client/panes/terminal_pane.rs b/src/client/panes/terminal_pane.rs
index f0b15e50..d2473ba3 100644
--- a/src/client/panes/terminal_pane.rs
+++ b/src/client/panes/terminal_pane.rs
@@ -3,15 +3,14 @@
use crate::tab::Pane;
use ::nix::pty::Winsize;
use ::std::os::unix::io::RawFd;
-use ::vte::Perform;
use std::fmt::Debug;
use crate::panes::grid::Grid;
use crate::panes::terminal_character::{
CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
};
+use crate::pty_bus::VteBytes;
use crate::utils::logging::debug_log_to_file;
-use crate::VteEvent;
#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
pub enum PaneId {
@@ -86,34 +85,10 @@ impl Pane for TerminalPane {
self.position_and_size_override = Some(position_and_size_override);
self.reflow_lines();
}
- fn handle_event(&mut self, event: VteEvent) {
- match event {
- VteEvent::Print(c) => {
- self.print(c);
- self.mark_for_rerender();
- }
- VteEvent::Execute(byte) => {
- self.execute(byte);
- }
- VteEvent::Hook(params, intermediates, ignore, c) => {
- self.hook(&params, &intermediates, ignore, c);
- }
- VteEvent::Put(byte) => {
- self.put(byte);
- }
- VteEvent::Unhook => {
- self.unhook();
- }
- VteEvent::OscDispatch(params, bell_terminated) => {
- let params: Vec<&[u8]> = params.iter().map(|p| &p[..]).collect();
- self.osc_dispatch(&params[..], bell_terminated);
- }
- VteEvent::CsiDispatch(params, intermediates, ignore, c) => {
- self.csi_dispatch(&params, &intermediates, ignore, c);
- }
- VteEvent::EscDispatch(intermediates, ignore, byte) => {
- self.esc_dispatch(&intermediates, ignore, byte);
- }
+ fn handle_pty_bytes(&mut self, bytes: VteBytes) {
+ let mut vte_parser = vte::Parser::new();
+ for byte in bytes.iter() {
+ vte_parser.advance(self, *byte);
}
}
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
@@ -282,6 +257,18 @@ impl Pane for TerminalPane {
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.mark_for_rerender();
@@ -367,9 +354,15 @@ impl TerminalPane {
self.grid.rotate_scroll_region_down(count);
self.mark_for_rerender();
}
+ fn reset_terminal_state(&mut self) {
+ let rows = self.get_rows();
+ let columns = self.get_columns();
+ self.grid = Grid::new(rows, columns);
+ self.alternative_grid = None;
+ self.cursor_key_mode = false;
+ }
fn add_newline(&mut self) {
- let mut pad_character = EMPTY_TERMINAL_CHARACTER;
- pad_character.styles = self.pending_styles;
+ let pad_character = EMPTY_TERMINAL_CHARACTER;
self.grid.add_canonical_line(pad_character);
self.mark_for_rerender();
}
@@ -485,8 +478,7 @@ impl vte::Perform for TerminalPane {
} else {
(params[0] as usize - 1, params[1] as usize - 1)
};
- let mut pad_character = EMPTY_TERMINAL_CHARACTER;
- pad_character.styles = self.pending_styles;
+ let pad_character = EMPTY_TERMINAL_CHARACTER;
self.grid.move_cursor_to(col, row, pad_character);
} else if c == 'A' {
// move cursor up until edge of screen
@@ -495,8 +487,7 @@ impl vte::Perform for TerminalPane {
} else if c == 'B' {
// move cursor down until edge of screen
let move_down_count = if params[0] == 0 { 1 } else { params[0] };
- let mut pad_character = EMPTY_TERMINAL_CHARACTER;
- pad_character.styles = self.pending_styles;
+ let pad_character = EMPTY_TERMINAL_CHARACTER;
self.grid
.move_cursor_down(move_down_count as usize, pad_character);
} else if c == 'D' {
@@ -588,8 +579,7 @@ impl vte::Perform for TerminalPane {
} else {
params[0] as usize
};
- let mut pad_character = EMPTY_TERMINAL_CHARACTER;
- pad_character.styles = self.pending_styles;
+ let pad_character = EMPTY_TERMINAL_CHARACTER;
self.grid
.delete_lines_in_scroll_region(line_count_to_delete, pad_character);
} else if c == 'L' {
@@ -599,8 +589,7 @@ impl vte::Perform for TerminalPane {
} else {
params[0] as usize
};
- let mut pad_character = EMPTY_TERMINAL_CHARACTER;
- pad_character.styles = self.pending_styles;
+ let pad_character = EMPTY_TERMINAL_CHARACTER;
self.grid
.add_empty_lines_in_scroll_region(line_count_to_add, pad_character);
} else if c == 'q' {
@@ -620,8 +609,7 @@ impl vte::Perform for TerminalPane {
// minus 1 because this is 1 indexed
params[0] as usize - 1
};
- let mut pad_character = EMPTY_TERMINAL_CHARACTER;
- pad_character.styles = self.pending_styles;
+ let pad_character = EMPTY_TERMINAL_CHARACTER;
self.grid.move_cursor_to_line(line, pad_character);
} else if c == 'P' {
// erase characters
@@ -660,8 +648,7 @@ impl vte::Perform for TerminalPane {
} else {
params[0] as usize
};
- let mut pad_character = EMPTY_TERMINAL_CHARACTER;
- pad_character.styles = self.pending_styles;
+ let pad_character = EMPTY_TERMINAL_CHARACTER;
self.grid
.delete_lines_in_scroll_region(count, pad_character);
// TODO: since delete_lines_in_scroll_region also adds lines, is the below redundant?
@@ -673,8 +660,14 @@ impl vte::Perform for TerminalPane {
}
fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, byte: u8) {
- if let (b'M', None) = (byte, intermediates.get(0)) {
- self.grid.move_cursor_up_with_scrolling(1);
+ match (byte, intermediates.get(0)) {
+ (b'M', None) => {
+ self.grid.move_cursor_up_with_scrolling(1);
+ }
+ (b'c', None) => {
+ self.reset_terminal_state();
+ }
+ _ => {}
}
}
}
diff --git a/src/client/tab.rs b/src/client/tab.rs
index 6a0ec72f..9cafdcec 100644
--- a/src/client/tab.rs
+++ b/src/client/tab.rs
@@ -2,13 +2,16 @@
//! as well as how they should be resized
use crate::boundaries::colors;
+use crate::client::pane_resizer::PaneResizer;
use crate::common::{input::handler::parse_keys, AppInstruction, SenderWithContext};
use crate::layout::Layout;
+use crate::os_input_output::OsApi;
use crate::panes::{PaneId, PositionAndSize, TerminalPane};
-use crate::pty_bus::{PtyInstruction, VteEvent};
+use crate::pty_bus::{PtyInstruction, VteBytes};
+use crate::utils::shared::adjust_to_size;
use crate::wasm_vm::PluginInstruction;
use crate::{boundaries::Boundaries, panes::PluginPane};
-use crate::{os_input_output::OsApi, utils::shared::pad_to_size};
+use serde::{Deserialize, Serialize};
use std::os::unix::io::RawFd;
use std::{
cmp::Reverse,
@@ -66,6 +69,16 @@ pub struct Tab {
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
pub send_app_instructions: SenderWithContext<AppInstruction>,
expansion_boundary: Option<PositionAndSize>,
+ should_clear_display_before_rendering: bool,
+ pub mode_info: ModeInfo,
+}
+
+#[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,
}
@@ -78,7 +91,7 @@ pub trait Pane {
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_event(&mut self, event: VteEvent);
+ 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>;
@@ -99,6 +112,10 @@ pub trait Pane {
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);
@@ -153,6 +170,22 @@ pub trait Pane {
rows: self.rows(),
}
}
+ 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
}
@@ -213,6 +246,7 @@ impl Tab {
send_pty_instructions,
send_plugin_instructions,
expansion_boundary: None,
+ should_clear_display_before_rendering: false,
mode_info,
}
}
@@ -527,14 +561,17 @@ impl Tab {
None
}
}
- pub fn handle_pty_event(&mut self, pid: RawFd, event: VteEvent) {
+ 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_bus 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_event(event);
+ terminal_output.handle_pty_bytes(bytes);
}
}
pub fn write_to_active_terminal(&mut self, input_bytes: Vec<u8>) {
@@ -597,8 +634,17 @@ impl Tab {
}
});
self.panes_to_hide = pane_ids_to_hide.collect();
- let active_terminal = self.panes.get_mut(&active_pane_id).unwrap();
- active_terminal.override_size_and_position(expand_to.x, expand_to.y, &expand_to);
+ 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(
+ expand_to.x,
+ expand_to.y,
+ &expand_to,
+ );
+ }
}
let active_terminal = self.panes.get(&active_pane_id).unwrap();
if let PaneId::Terminal(active_pid) = active_pane_id {
@@ -630,28 +676,33 @@ impl Tab {
stdout
.write_all(&hide_cursor.as_bytes())
.expect("cannot write to stdout");
- for (kind, terminal) in self.panes.iter_mut() {
- if !self.panes_to_hide.contains(&terminal.pid()) {
- match self.active_terminal.unwrap() == terminal.pid() {
- true => boundaries.add_rect(
- terminal.as_ref(),
- self.mode_info.mode,
- Some(colors::GREEN),
- ),
- false => boundaries.add_rect(terminal.as_ref(), self.mode_info.mode, None),
+ if self.should_clear_display_before_rendering {
+ let clear_display = "\u{1b}[2J";
+ stdout
+ .write_all(&clear_display.as_bytes())
+ .expect("cannot write to stdout");
+ 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 => {
+ boundaries.add_rect(pane.as_ref(), self.mode_info.mode, Some(colors::GREEN))
+ }
+ false => boundaries.add_rect(pane.as_ref(), self.mode_info.mode, None),
}
- if let Some(vte_output) = terminal.render() {
+ if let Some(vte_output) = pane.render() {
let vte_output = if let PaneId::Terminal(_) = kind {
vte_output
} else {
- pad_to_size(&vte_output, terminal.rows(), terminal.columns())
+ adjust_to_size(&vte_output, pane.rows(), pane.columns())
};
// FIXME: Use Termion for cursor and style clearing?
write!(
stdout,
"\u{1b}[{};{}H\u{1b}[m{}",
- terminal.y() + 1,
- terminal.x() + 1,
+ pane.y() + 1,
+ pane.x() + 1,
vte_output
)
.expect("cannot write to stdout");
@@ -1664,17 +1715,30 @@ impl Tab {
false
}
}
- 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);
- }
+ pub fn resize_whole_tab(&mut self, new_screen_size: PositionAndSize) {
+ if self.fullscreen_is_active {
+ // this is not ideal but until we get rid of expansion_boundary, it's a necessity
+ self.toggle_active_pane_fullscreen();
}
- self.render();
+ match PaneResizer::new(&mut self.panes, &mut self.os_api)
+ .resize(self.full_screen_ws, new_screen_size)
+ {
+ Some((column_difference, row_difference)) => {
+ self.should_clear_display_before_rendering = true;
+ self.expansion_boundary.as_mut().map(|expansion_boundary| {
+ // TODO: this is not always accurate
+ expansion_boundary.columns =
+ (expansion_boundary.columns as isize + column_difference) as usize;
+ expansion_boundary.rows =
+ (expansion_boundary.rows as isize + row_difference) as usize;
+ });
+ 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;
+ }
+ None => {}
+ };
}
pub fn resize_left(&mut self) {
// TODO: find out by how much we actually reduced and only reduce by that much
@@ -1688,6 +1752,18 @@ impl Tab {
}
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;
@@ -1733,6 +1809,62 @@ impl Tab {
}
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();
+ }
pub fn move_focus_left(&mut self) {
if !self.has_selectable_panes() {
return;
@@ -2000,47 +2132,79 @@ impl Tab {
}
}
pub fn close_pane_without_rerender(&mut self, id: PaneId) {
- if let Some(terminal_to_close) = self.panes.get(&id) {
- let terminal_to_close_width = terminal_to_close.columns();
- let terminal_to_close_height = terminal_to_close.rows();
- if let Some(terminals) = self.panes_to_the_left_between_aligning_borders(id) {
- for terminal_id in terminals.iter() {
- self.increase_pane_width_right(&terminal_id, terminal_to_close_width + 1);
- // 1 for the border
- }
- if self.active_terminal == Some(id) {
- self.active_terminal = self.next_active_pane(terminals);
- }
- } else if let Some(terminals) = self.panes_to_the_right_between_aligning_borders(id) {
- for terminal_id in terminals.iter() {
- self.increase_pane_width_left(&terminal_id, terminal_to_close_width + 1);
- // 1 for the border
- }
- if self.active_terminal == Some(id) {
- self.active_terminal = self.next_active_pane(terminals);
- }
- } else if let Some(terminals) = self.panes_above_between_aligning_borders(id) {
- for terminal_id in terminals.iter() {
- self.increase_pane_height_down(&terminal_id, terminal_to_close_height + 1);
- // 1 for the border
+ 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 self.active_terminal == Some(id) {
- self.active_terminal = self.next_active_pane(terminals);
+ }
+ 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;
}
- } else if let Some(terminals) = self.panes_below_between_aligning_borders(id) {
- for terminal_id in terminals.iter() {
- self.increase_pane_height_up(&terminal_id, terminal_to_close_height + 1);
- // 1 for the border
+ }
+ 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 self.active_terminal == Some(id) {
- self.active_terminal = self.next_active_pane(terminals);
+ }
+ 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;
}
- } else {
}
+ // 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);
- if self.active_terminal.is_none() {
- self.active_terminal = self.next_active_pane(self.get_pane_ids());
- }
}
}
pub fn close_focused_pane(&mut self) {
diff --git a/src/common/command_is_executing.rs b/src/common/command_is_executing.rs
index 93c44eb6..775a7bfc 100644
--- a/src/common/command_is_executing.rs
+++ b/src/common/command_is_executing.rs
@@ -23,7 +23,7 @@ impl CommandIsExecuting {
let (lock, cvar) = &*self.closing_pane;
let mut closing_pane = lock.lock().unwrap();
*closing_pane = false;
- cvar.notify_one();
+ cvar.notify_all();
}
pub fn opening_new_pane(&mut self) {
let (lock, _cvar) = &*self.opening_new_pane;
@@ -34,7 +34,7 @@ impl CommandIsExecuting {
let (lock, cvar) = &*self.opening_new_pane;
let mut opening_new_pane = lock.lock().unwrap();
*opening_new_pane = false;
- cvar.notify_one();
+ cvar.notify_all();
}
pub fn wait_until_pane_is_closed(&self) {
let (lock, cvar) = &*self.closing_pane;
diff --git a/src/common/errors.rs b/src/common/errors.rs
index 56bf27f4..9801428c 100644
--- a/src/common/errors.rs
+++ b/src/common/errors.rs
@@ -42,7 +42,7 @@ pub fn handle_panic(
msg,
location.file(),
location.line(),
- backtrace
+ backtrace,
),
(Some(location), None) => format!(
"{}\n\u{1b}[0;0mError: \u{1b}[0;31mthread '{}' panicked: {}:{}\n\u{1b}[0;0m{:?}",
@@ -168,7 +168,7 @@ impl Display for ContextType {
/// Stack call representations corresponding to the different types of [`ScreenInstruction`]s.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ScreenContext {
- HandlePtyEvent,
+ HandlePtyBytes,
Render,
NewPane,
HorizontalSplit,
@@ -178,7 +178,9 @@ pub enum ScreenContext {
ResizeRight,
ResizeDown,
ResizeUp,
- MoveFocus,
+ SwitchFocus,
+ FocusNextPane,
+ FocusPreviousPane,
MoveFocusLeft,
MoveFocusDown,
MoveFocusUp,
@@ -200,6 +202,7 @@ pub enum ScreenContext {
CloseTab,
GoToTab,
UpdateTabName,
+ TerminalResize,
ChangeMode,
}
@@ -207,7 +210,7 @@ pub enum ScreenContext {
impl From<&ScreenInstruction> for ScreenContext {
fn from(screen_instruction: &ScreenInstruction) -> Self {
match *screen_instruction {
- ScreenInstruction::Pty(..) => ScreenContext::HandlePtyEvent,
+ ScreenInstruction::PtyBytes(..) => ScreenContext::HandlePtyBytes,
ScreenInstruction::Render => ScreenContext::Render,
ScreenInstruction::NewPane(_) => ScreenContext::NewPane,
ScreenInstruction::HorizontalSplit(_) => ScreenContext::HorizontalSplit,
@@ -217,7 +220,9 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::ResizeRight => ScreenContext::ResizeRight,
ScreenInstruction::ResizeDown => ScreenContext::ResizeDown,
ScreenInstruction::ResizeUp => ScreenContext::ResizeUp,
- ScreenInstruction::MoveFocus => ScreenContext::MoveFocus,
+ ScreenInstruction::SwitchFocus => ScreenContext::SwitchFocus,
+ ScreenInstruction::FocusNextPane => ScreenContext::FocusNextPane,
+ ScreenInstruction::FocusPreviousPane => ScreenContext::FocusPreviousPane,
ScreenInstruction::MoveFocusLeft => ScreenContext::MoveFocusLeft,
ScreenInstruction::MoveFocusDown => ScreenContext::MoveFocusDown,
ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp,
@@ -241,6 +246,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::CloseTab => ScreenContext::CloseTab,
ScreenInstruction::GoToTab(_) => ScreenContext::GoToTab,
ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName,
+ ScreenInstruction::TerminalResize => ScreenContext::TerminalResize,
ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode,
}
}
diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs
index f4226864..47a5f763 100644
--- a/src/common/input/actions.rs
+++ b/src/common/input/actions.rs
@@ -1,9 +1,10 @@
//! 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)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum Direction {
Left,
Right,
@@ -12,7 +13,7 @@ pub enum Direction {
}
/// Actions that can be bound to keys.
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum Action {
/// Quit Zellij.
Quit,
@@ -23,8 +24,10 @@ pub enum Action {
/// Resize focus pane in specified direction.
Resize(Direction),
/// Switch focus to next pane in specified direction.
- SwitchFocus(Direction),
+ FocusNextPane,
+ FocusPreviousPane,
/// Move the focus pane in specified direction.
+ SwitchFocus,
MoveFocus(Direction),
/// Scroll up in focus pane.
ScrollUp,
diff --git a/src/common/input/config.rs b/src/common/input/config.rs
new file mode 100644
index 00000000..4bb9e10f
--- /dev/null
+++ b/src/common/input/config.rs
@@ -0,0 +1,158 @@
+//! 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 crate::cli::ConfigCli;
+
+use directories_next::ProjectDirs;
+use serde::Deserialize;
+
+type ConfigResult = Result<Config, ConfigError>;
+
+/// Intermediate deserialisation config struct
+#[derive(Debug, Deserialize)]
+pub struct ConfigFromYaml {
+ pub keybinds: Option<KeybindsFromYaml>,
+}
+
+/// Main configuration.
+#[derive(Debug, Clone, PartialEq)]
+pub struct Config {
+ pub keybinds: Keybinds,
+}
+
+#[derive(Debug)]
+pub enum ConfigError {
+ // Deserialisation error
+ Serde(serde_yaml::Error),
+ // Io error
+ Io(io::Error),
+ // Io error with path context
+ IoPath(io::Error, PathBuf),
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ let keybinds = Keybinds::default();
+ Config { keybinds }
+ }
+}
+
+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);
+ Ok(Config { keybinds })
+ }
+
+ /// Deserializes from given path.
+ #[allow(unused_must_use)]
+ 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())),
+ }
+ }
+
+ /// Deserializes the config from a default platform specific path,
+ /// merges the default configuration - options take precedence.
+ fn from_default_path() -> ConfigResult {
+ let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap();
+ let mut config_path: PathBuf = project_dirs.config_dir().to_owned();
+ config_path.push("config.yaml");
+
+ match Config::new(&config_path) {
+ Ok(config) => Ok(config),
+ Err(ConfigError::IoPath(_, _)) => Ok(Config::default()),
+ Err(e) => Err(e),
+ }
+ }
+
+ /// Entry point of the configuration
+ #[cfg(not(test))]
+ pub fn from_cli_config(cli_config: Option<ConfigCli>) -> ConfigResult {
+ match cli_config {
+ Some(ConfigCli::Config { clean, .. }) if clean => Ok(Config::default()),
+ Some(ConfigCli::Config { path, .. }) if path.is_some() => {
+ Ok(Config::new(&path.unwrap())?)
+ }
+ Some(_) | None => Ok(Config::from_default_path()?),
+ }
+ }
+
+ //#[allow(unused_must_use)]
+ /// In order not to mess up tests from changing configurations
+ #[cfg(test)]
+ pub fn from_cli_config(_: Option<ConfigCli>) -> ConfigResult {
+ Ok(Config::default())
+ }
+}
+
+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, "Deserialisation error: {}", 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),
+ }
+ }
+}
+
+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)
+ }
+}
+
+// The unit test location.
+#[cfg(test)]
+mod config_test {
+ use super::*;
+
+ #[test]
+ fn clean_option_equals_default_config() {
+ let no_file = PathBuf::from(r"../fixtures/config/config.yamlll");
+ let cli_config = ConfigCli::Config {
+ path: Some(no_file),
+ clean: true,
+ };
+ let config = Config::from_cli_config(Some(cli_config)).unwrap();
+ let default = Config::default();
+ assert_eq!(config, default);
+ }
+
+ #[test]
+ fn no_config_option_file_equals_default_config() {
+ let config = Config::from_cli_config(None).unwrap();
+ let default = Config::default();
+ assert_eq!(config, default);
+ }
+}
diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs
index 7f73b449..44065c67 100644
--- a/src/common/input/handler.rs
+++ b/src/common/input/handler.rs
@@ -1,7 +1,8 @@
//! Main input logic.
use super::actions::Action;
-use super::keybinds::get_default_keybinds;
+use super::keybinds::Keybinds;
+use crate::common::input::config::Config;
use crate::common::{AppInstruction, SenderWithContext, OPENCALLS};
use crate::errors::ContextType;
use crate::os_input_output::OsApi;
@@ -13,19 +14,19 @@ use crate::CommandIsExecuting;
use termion::input::{TermRead, TermReadEventsAndRaw};
use zellij_tile::data::{Event, InputMode, Key, ModeInfo};
-use super::keybinds::key_to_actions;
-
/// 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 OsApi>,
+ config: Config,
command_is_executing: CommandIsExecuting,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
+ should_exit: bool,
}
impl InputHandler {
@@ -33,6 +34,7 @@ impl InputHandler {
fn new(
os_input: Box<dyn OsApi>,
command_is_executing: CommandIsExecuting,
+ config: Config,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
@@ -41,11 +43,13 @@ impl InputHandler {
InputHandler {
mode: InputMode::Normal,
os_input,
+ config,
command_is_executing,
send_screen_instructions,
send_pty_instructions,
send_plugin_instructions,
send_app_instructions,
+ should_exit: false,
}
}
@@ -57,40 +61,44 @@ impl InputHandler {
self.send_pty_instructions.update(err_ctx);
self.send_app_instructions.update(err_ctx);
self.send_screen_instructions.update(err_ctx);
- if let Ok(keybinds) = get_default_keybinds() {
- 'input_loop: loop {
- //@@@ I think this should actually just iterate over stdin directly
- 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);
- // FIXME this explicit break is needed because the current test
- // framework relies on it to not create dead threads that loop
- // and eat up CPUs. Do not remove until the test framework has
- // been revised. Sorry about this (@categorille)
- let mut should_break = false;
- for action in key_to_actions(&key, raw_bytes, &self.mode, &keybinds)
- {
- should_break |= self.dispatch_action(action);
- }
- if should_break {
- break 'input_loop;
- }
- }
- termion::event::Event::Mouse(_)
- | termion::event::Event::Unsupported(_) => {
- unimplemented!("Mouse and unsupported events aren't supported!");
+ let keybinds = self.config.keybinds.clone();
+ let alt_left_bracket = vec![27, 91];
+ 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, &keybinds);
+ }
+ 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, &keybinds);
}
- },
- Err(err) => panic!("Encountered read error: {:?}", err),
- }
+ }
+ termion::event::Event::Mouse(_) => {
+ // Mouse events aren't implemented yet,
+ // use a NoOp untill then.
+ }
+ },
+ Err(err) => panic!("Encountered read error: {:?}", err),
}
}
- } else {
- //@@@ Error handling?
- self.exit();
+ }
+ }
+ fn handle_key(&mut self, key: &Key, raw_bytes: Vec<u8>, keybinds: &Keybinds) {
+ 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;
+ }
}
}
@@ -145,9 +153,19 @@ impl InputHandler {
};
self.send_screen_instructions.send(screen_instr).unwrap();
}
- Action::SwitchFocus(_) => {
+ Action::SwitchFocus => {
+ self.send_screen_instructions
+ .send(ScreenInstruction::SwitchFocus)
+ .unwrap();
+ }
+ Action::FocusNextPane => {
+ self.send_screen_instructions
+ .send(ScreenInstruction::FocusNextPane)
+ .unwrap();
+ }
+ Action::FocusPreviousPane => {
self.send_screen_instructions
- .send(ScreenInstruction::MoveFocus)
+ .send(ScreenInstruction::FocusPreviousPane)
.unwrap();
}
Action::MoveFocus(direction) => {
@@ -290,6 +308,7 @@ pub fn get_mode_info(mode: InputMode) -> ModeInfo {
/// its [`InputHandler::handle_input()`] loop.
pub fn input_loop(
os_input: Box<dyn OsApi>,
+ config: Config,
command_is_executing: CommandIsExecuting,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
@@ -299,6 +318,7 @@ pub fn input_loop(
let _handler = InputHandler::new(
os_input,
command_is_executing,
+ config,
send_screen_instructions,
send_pty_instructions,
send_plugin_instructions,
diff --git a/src/common/input/keybinds.rs b/src/common/input/keybinds.rs
index 7b1e8779..8a064970 100644
--- a/src/common/input/keybinds.rs
+++ b/src/common/input/keybinds.rs
@@ -1,268 +1,415 @@
//! Mapping of inputs to sequences of actions.
+use std::collections::HashMap;
use super::actions::{Action, Direction};
-use std::collections::HashMap;
-
+use serde::Deserialize;
use strum::IntoEnumIterator;
use zellij_tile::data::*;
-type Keybinds = HashMap<InputMode, ModeKeybinds>;
-type ModeKeybinds = HashMap<Key, Vec<Action>>;
+#[derive(Clone, Debug, PartialEq)]
+pub struct Keybinds(HashMap<InputMode, ModeKeybinds>);
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct ModeKeybinds(HashMap<Key, Vec<Action>>);
-/// Populates the default hashmap of keybinds.
-/// @@@khs26 What about an input config file?
-pub fn get_default_keybinds() -> Result<Keybinds, String> {
- let mut defaults = Keybinds::new();
+/// Intermediate struct used for deserialisation
+#[derive(Clone, Debug, PartialEq, Deserialize)]
+pub struct KeybindsFromYaml(HashMap<InputMode, Vec<KeyActionFromYaml>>);
- for mode in InputMode::iter() {
- defaults.insert(mode, get_defaults_for_mode(&mode));
- }
-
- Ok(defaults)
+/// Intermediate struct used for deserialisation
+#[derive(Clone, Debug, PartialEq, Deserialize)]
+pub struct KeyActionFromYaml {
+ action: Vec<Action>,
+ key: Vec<Key>,
}
-/// Returns the default keybinds for a givent [`InputMode`].
-fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
- let mut defaults = ModeKeybinds::new();
+impl Default for Keybinds {
+ fn default() -> Keybinds {
+ let mut defaults = Keybinds::new();
- match *mode {
- InputMode::Normal => {
- defaults.insert(
- Key::Ctrl('g'),
- vec![Action::SwitchToMode(InputMode::Locked)],
- );
- defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
- defaults.insert(
- Key::Ctrl('r'),
- vec![Action::SwitchToMode(InputMode::Resize)],
- );
- defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
- defaults.insert(
- Key::Ctrl('s'),
- vec![Action::SwitchToMode(InputMode::Scroll)],
- );
- defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
+ for mode in InputMode::iter() {
+ defaults
+ .0
+ .insert(mode, Keybinds::get_defaults_for_mode(&mode));
}
- InputMode::Locked => {
- defaults.insert(
- Key::Ctrl('g'),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
+ defaults
+ }
+}
+
+impl Keybinds {
+ pub fn new() -> Keybinds {
+ Keybinds(HashMap::<InputMode, ModeKeybinds>::new())
+ }
+
+ pub fn get_default_keybinds_with_config(keybinds: Option<KeybindsFromYaml>) -> Keybinds {
+ let default_keybinds = Keybinds::default();
+ if let Some(keybinds) = keybinds {
+ default_keybinds.merge_keybinds(Keybinds::from(keybinds))
+ } else {
+ default_keybinds
}
- InputMode::Resize => {
- defaults.insert(
- Key::Ctrl('g'),
- vec![Action::SwitchToMode(InputMode::Locked)],
- );
- defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
- defaults.insert(
- Key::Ctrl('r'),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
- defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
- defaults.insert(
- Key::Ctrl('s'),
- vec![Action::SwitchToMode(InputMode::Scroll)],
- );
- defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
- defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
- defaults.insert(
- Key::Char('\n'),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
- defaults.insert(
- Key::Char(' '),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
+ }
- defaults.insert(Key::Char('h'), vec![Action::Resize(Direction::Left)]);
- defaults.insert(Key::Char('j'), vec![Action::Resize(Direction::Down)]);
- defaults.insert(Key::Char('k'), vec![Action::Resize(Direction::Up)]);
- defaults.insert(Key::Char('l'), vec![Action::Resize(Direction::Right)]);
+ /// 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();
- defaults.insert(Key::Left, vec![Action::Resize(Direction::Left)]);
- defaults.insert(Key::Down, vec![Action::Resize(Direction::Down)]);
- defaults.insert(Key::Up, vec![Action::Resize(Direction::Up)]);
- defaults.insert(Key::Right, vec![Action::Resize(Direction::Right)]);
+ 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);
+ }
}
- InputMode::Pane => {
- defaults.insert(
- Key::Ctrl('g'),
- vec![Action::SwitchToMode(InputMode::Locked)],
- );
- defaults.insert(
- Key::Ctrl('p'),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
- defaults.insert(
- Key::Ctrl('r'),
- vec![Action::SwitchToMode(InputMode::Resize)],
- );
- defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
- defaults.insert(
- Key::Ctrl('s'),
- vec![Action::SwitchToMode(InputMode::Scroll)],
- );
- defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
- defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
- defaults.insert(
- Key::Char('\n'),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
- defaults.insert(
- Key::Char(' '),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
+ keybinds
+ }
- defaults.insert(Key::Char('h'), vec![Action::MoveFocus(Direction::Left)]);
- defaults.insert(Key::Char('j'), vec![Action::MoveFocus(Direction::Down)]);
- defaults.insert(Key::Char('k'), vec![Action::MoveFocus(Direction::Up)]);
- defaults.insert(Key::Char('l'), vec![Action::MoveFocus(Direction::Right)]);
+ /// Returns the default keybinds for a given [`InputMode`].
+ fn get_defaults_for_mode(mode: &InputMode) -> ModeKeybinds {
+ let mut defaults = HashMap::new();
- defaults.insert(Key::Left, vec![Action::MoveFocus(Direction::Left)]);
- defaults.insert(Key::Down, vec![Action::MoveFocus(Direction::Down)]);
- defaults.insert(Key::Up, vec![Action::MoveFocus(Direction::Up)]);
- defaults.insert(Key::Right, vec![Action::MoveFocus(Direction::Right)]);
+ match *mode {
+ InputMode::Normal => {
+ defaults.insert(
+ Key::Ctrl('g'),
+ vec![Action::SwitchToMode(InputMode::Locked)],
+ );
+ defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
+ defaults.insert(
+ Key::Ctrl('r'),
+ vec![Action::SwitchToMode(InputMode::Resize)],
+ );
+ defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
+ defaults.insert(
+ Key::Ctrl('s'),
+ vec![Action::SwitchToMode(InputMode::Scroll)],
+ );
+ defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
- defaults.insert(Key::Char('p'), vec![Action::SwitchFocus(Direction::Right)]);
- defaults.insert(Key::Char('n'), vec![Action::NewPane(None)]);
- defaults.insert(Key::Char('d'), vec![Action::NewPane(Some(Direction::Down))]);
- defaults.insert(
- Key::Char('r'),
- vec![Action::NewPane(Some(Direction::Right))],
- );
- defaults.insert(Key::Char('x'), vec![Action::CloseFocus]);
- defaults.insert(Key::Char('f'), vec![Action::ToggleFocusFullscreen]);
- }
- InputMode::Tab => {
- defaults.insert(
- Key::Ctrl('g'),
- vec![Action::SwitchToMode(InputMode::Locked)],
- );
- defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
- defaults.insert(
- Key::Ctrl('r'),
- vec![Action::SwitchToMode(InputMode::Resize)],
- );
- defaults.insert(
- Key::Ctrl('t'),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
- defaults.insert(
- Key::Ctrl('s'),
- vec![Action::SwitchToMode(InputMode::Scroll)],
- );
- defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
- defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
- defaults.insert(
- Key::Char('\n'),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
- defaults.insert(
- Key::Char(' '),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
+ defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
+ defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
+ defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
+ defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
+ defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
+ defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
+ defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
+ }
+ InputMode::Locked => {
+ defaults.insert(
+ Key::Ctrl('g'),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+ }
+ InputMode::Resize => {
+ defaults.insert(
+ Key::Ctrl('g'),
+ vec![Action::SwitchToMode(InputMode::Locked)],
+ );
+ defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
+ defaults.insert(
+ Key::Ctrl('r'),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+ defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
+ defaults.insert(
+ Key::Ctrl('s'),
+ vec![Action::SwitchToMode(InputMode::Scroll)],
+ );
+ defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
+ defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
+ defaults.insert(
+ Key::Char('\n'),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+ defaults.insert(
+ Key::Char(' '),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
- defaults.insert(Key::Char('h'), vec![Action::GoToPreviousTab]);
- defaults.insert(Key::Char('j'), vec![Action::GoToNextTab]);
- defaults.insert(Key::Char('k'), vec![Action::GoToPreviousTab]);
- defaults.insert(Key::Char('l'), vec![Action::GoToNextTab]);
+ defaults.insert(Key::Char('h'), vec![Action::Resize(Direction::Left)]);
+ defaults.insert(Key::Char('j'), vec![Action::Resize(Direction::Down)]);
+ defaults.insert(Key::Char('k'), vec![Action::Resize(Direction::Up)]);
+ defaults.insert(Key::Char('l'), vec![Action::Resize(Direction::Right)]);
- defaults.insert(Key::Left, vec![Action::GoToPreviousTab]);
- defaults.insert(Key::Down, vec![Action::GoToNextTab]);
- defaults.insert(Key::Up, vec![Action::GoToPreviousTab]);
- defaults.insert(Key::Right, vec![Action::GoToNextTab]);
+ defaults.insert(Key::Left, vec![Action::Resize(Direction::Left)]);
+ defaults.insert(Key::Down, vec![Action::Resize(Direction::Down)]);
+ defaults.insert(Key::Up, vec![Action::Resize(Direction::Up)]);
+ defaults.insert(Key::Right, vec![Action::Resize(Direction::Right)]);
- defaults.insert(Key::Char('n'), vec![Action::NewTab]);
- defaults.insert(Key::Char('x'), vec![Action::CloseTab]);
+ defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
+ defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
+ defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
+ defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
+ defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
+ defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
+ defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
+ }
+ InputMode::Pane => {
+ defaults.insert(
+ Key::Ctrl('g'),
+ vec![Action::SwitchToMode(InputMode::Locked)],
+ );
+ defaults.insert(
+ Key::Ctrl('p'),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+ defaults.insert(
+ Key::Ctrl('r'),
+ vec![Action::SwitchToMode(InputMode::Resize)],
+ );
+ defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
+ defaults.insert(
+ Key::Ctrl('s'),
+ vec![Action::SwitchToMode(InputMode::Scroll)],
+ );
+ defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
+ defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
+ defaults.insert(
+ Key::Char('\n'),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+ defaults.insert(
+ Key::Char(' '),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+
+ defaults.insert(Key::Char('h'), vec![Action::MoveFocus(Direction::Left)]);
+ defaults.insert(Key::Char('j'), vec![Action::MoveFocus(Direction::Down)]);
+ defaults.insert(Key::Char('k'), vec![Action::MoveFocus(Direction::Up)]);
+ defaults.insert(Key::Char('l'), vec![Action::MoveFocus(Direction::Right)]);
+
+ defaults.insert(Key::Left, vec![Action::MoveFocus(Direction::Left)]);
+ defaults.insert(Key::Down, vec![Action::MoveFocus(Direction::Down)]);
+ defaults.insert(Key::Up, vec![Action::MoveFocus(Direction::Up)]);
+ defaults.insert(Key::Right, vec![Action::MoveFocus(Direction::Right)]);
- defaults.insert(
- Key::Char('r'),
- vec![
- Action::SwitchToMode(InputMode::RenameTab),
- Action::TabNameInput(vec![0]),
- ],
- );
- defaults.insert(Key::Char('q'), vec![Action::Quit]);
- defaults.insert(
- Key::Ctrl('g'),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
- for i in '1'..='9' {
- defaults.insert(Key::Char(i), vec![Action::GoToTab(i.to_digit(10).unwrap())]);
+ defaults.insert(Key::Char('p'), vec![Action::SwitchFocus]);
+ defaults.insert(Key::Char('n'), vec![Action::NewPane(None)]);
+ defaults.insert(Key::Char('d'), vec![Action::NewPane(Some(Direction::Down))]);
+ defaults.insert(
+ Key::Char('r'),
+ vec![Action::NewPane(Some(Direction::Right))],
+ );
+ defaults.insert(Key::Char('x'), vec![Action::CloseFocus]);
+ defaults.insert(Key::Char('f'), vec![Action::ToggleFocusFullscreen]);
+
+ defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
+ defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
+ defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
+ defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
+ defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
+ defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
+ defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
}
- }
- InputMode::Scroll => {
- defaults.insert(
- Key::Ctrl('g'),
- vec![Action::SwitchToMode(InputMode::Locked)],
- );
- defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
- defaults.insert(
- Key::Ctrl('r'),
- vec![Action::SwitchToMode(InputMode::Resize)],
- );
- defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
- defaults.insert(
- Key::Ctrl('s'),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
- defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
- defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
- defaults.insert(
- Key::Char('\n'),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
- defaults.insert(
- Key::Char(' '),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
+ InputMode::Tab => {
+ defaults.insert(
+ Key::Ctrl('g'),
+ vec![Action::SwitchToMode(InputMode::Locked)],
+ );
+ defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
+ defaults.insert(
+ Key::Ctrl('r'),
+ vec![Action::SwitchToMode(InputMode::Resize)],
+ );
+ defaults.insert(
+ Key::Ctrl('t'),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+ defaults.insert(
+ Key::Ctrl('s'),
+ vec![Action::SwitchToMode(InputMode::Scroll)],
+ );
+ defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
+ defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
+ defaults.insert(
+ Key::Char('\n'),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+ defaults.insert(
+ Key::Char(' '),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
- defaults.insert(Key::Char('j'), vec![Action::ScrollDown]);
- defaults.insert(Key::Char('k'), vec![Action::ScrollUp]);
+ defaults.insert(Key::Char('h'), vec![Action::GoToPreviousTab]);
+ defaults.insert(Key::Char('j'), vec![Action::GoToNextTab]);
+ defaults.insert(Key::Char('k'), vec![Action::GoToPreviousTab]);
+ defaults.insert(Key::Char('l'), vec![Action::GoToNextTab]);
- defaults.insert(Key::Down, vec![Action::ScrollDown]);
- defaults.insert(Key::Up, vec![Action::ScrollUp]);
+ defaults.insert(Key::Left, vec![Action::GoToPreviousTab]);
+ defaults.insert(Key::Down, vec![Action::GoToNextTab]);
+ defaults.insert(Key::Up, vec![Action::GoToPreviousTab]);
+ defaults.insert(Key::Right, vec![Action::GoToNextTab]);
+
+ defaults.insert(Key::Char('n'), vec![Action::NewTab]);
+ defaults.insert(Key::Char('x'), vec![Action::CloseTab]);
+
+ defaults.insert(
+ Key::Char('r'),
+ vec![
+ Action::SwitchToMode(InputMode::RenameTab),
+ Action::TabNameInput(vec![0]),
+ ],
+ );
+ defaults.insert(Key::Char('q'), vec![Action::Quit]);
+ defaults.insert(
+ Key::Ctrl('g'),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+ for i in '1'..='9' {
+ defaults.insert(Key::Char(i), vec![Action::GoToTab(i.to_digit(10).unwrap())]);
+ }
+ defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
+ defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
+ defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
+ defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
+ defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
+ defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
+ defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
+ }
+ InputMode::Scroll => {
+ defaults.insert(
+ Key::Ctrl('g'),
+ vec![Action::SwitchToMode(InputMode::Locked)],
+ );
+ defaults.insert(Key::Ctrl('p'), vec![Action::SwitchToMode(InputMode::Pane)]);
+ defaults.insert(
+ Key::Ctrl('r'),
+ vec![Action::SwitchToMode(InputMode::Resize)],
+ );
+ defaults.insert(Key::Ctrl('t'), vec![Action::SwitchToMode(InputMode::Tab)]);
+ defaults.insert(
+ Key::Ctrl('s'),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+ defaults.insert(Key::Ctrl('q'), vec![Action::Quit]);
+ defaults.insert(Key::Esc, vec![Action::SwitchToMode(InputMode::Normal)]);
+ defaults.insert(
+ Key::Char('\n'),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+ defaults.insert(
+ Key::Char(' '),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+
+ defaults.insert(Key::Char('j'), vec![Action::ScrollDown]);
+ defaults.insert(Key::Char('k'), vec![Action::ScrollUp]);
+
+ defaults.insert(Key::Down, vec![Action::ScrollDown]);
+ defaults.insert(Key::Up, vec![Action::ScrollUp]);
+
+ defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
+ defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
+ defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
+ defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
+ defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
+ defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
+ defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
+ }
+ InputMode::RenameTab => {
+ defaults.insert(Key::Char('\n'), vec![Action::SwitchToMode(InputMode::Tab)]);
+ defaults.insert(
+ Key::Ctrl('g'),
+ vec![Action::SwitchToMode(InputMode::Normal)],
+ );
+ defaults.insert(
+ Key::Esc,
+ vec![
+ Action::TabNameInput(vec![0x1b]),
+ Action::SwitchToMode(InputMode::Tab),
+ ],
+ );
+
+ defaults.insert(Key::Alt('n'), vec![Action::NewPane(None)]);
+ defaults.insert(Key::Alt('h'), vec![Action::MoveFocus(Direction::Left)]);
+ defaults.insert(Key::Alt('j'), vec![Action::MoveFocus(Direction::Down)]);
+ defaults.insert(Key::Alt('k'), vec![Action::MoveFocus(Direction::Up)]);
+ defaults.insert(Key::Alt('l'), vec![Action::MoveFocus(Direction::Right)]);
+ defaults.insert(Key::Alt('['), vec![Action::FocusPreviousPane]);
+ defaults.insert(Key::Alt(']'), vec![Action::FocusNextPane]);
+ }
}
- InputMode::RenameTab => {
- defaults.insert(Key::Char('\n'), vec![Action::SwitchToMode(InputMode::Tab)]);
- defaults.insert(
- Key::Ctrl('g'),
- vec![Action::SwitchToMode(InputMode::Normal)],
- );
- defaults.insert(
- Key::Esc,
- vec![
- Action::TabNameInput(vec![0x1b]),
- Action::SwitchToMode(InputMode::Tab),
- ],
- );
+ ModeKeybinds(defaults)
+ }
+
+ /// 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),
}
}
+}
- defaults
+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
+ }
}
-/// 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| {
+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.0.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
- .get(mode)
- .unwrap_or_else(|| unreachable!("Unrecognized mode: {:?}", mode))
- .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),
}
}
+
+/// 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 keys = key_action.key;
+ let actions = key_action.action;
+
+ ModeKeybinds(
+ keys.into_iter()
+ .map(|k| (k, actions.clone()))
+ .collect::<HashMap<Key, Vec<Action>>>(),
+ )
+ }
+}
+
+// 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
index 20c30dae..6589cac2 100644
--- a/src/common/input/mod.rs
+++ b/src/common/input/mod.rs
@@ -1,5 +1,6 @@
-//! The way terminal iput is handled.
+//! The way terminal input is handled.
pub mod actions;
+pub mod config;
pub mod handler;
pub mod keybinds;
diff --git a/src/common/input/unit/keybinds_test.rs b/src/common/input/unit/keybinds_test.rs
new file mode 100644
index 00000000..6411d017
--- /dev/null
+++ b/src/common/input/unit/keybinds_test.rs
@@ -0,0 +1,135 @@
+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 from_keybinds_from_yaml_to_keybinds(){
+//let mut keybinds_from_yaml = KeybindsFromYaml(HashMap<InputMode, Vec<KeyActionFromYaml>>);
+//let actions = vec![Action::NoOp, Action::GoToTab(1), ];
+//let keyaction = KeyActionFromYaml {
+//action : actions.clone(),
+//key : vec![ Key::F(1), Key::Backspace , Key::Char('t'), ],
+//};
+//}
diff --git a/src/common/mod.rs b/src/common/mod.rs
index a566d655..2466aecf 100644
--- a/src/common/mod.rs
+++ b/src/common/mod.rs
@@ -22,6 +22,7 @@ use std::{
};
use crate::cli::CliArgs;
+use crate::common::input::config::Config;
use crate::layout::Layout;
use crate::panes::PaneId;
use command_is_executing::CommandIsExecuting;
@@ -125,6 +126,13 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
.write(take_snapshot.as_bytes())
.unwrap();
+ let config = Config::from_cli_config(opts.config)
+ .map_err(|e| {
+ eprintln!("There was an error in the config file:\n{}", e);
+ std::process::exit(1);
+ })
+ .unwrap();
+
let command_is_executing = CommandIsExecuting::new();
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
@@ -267,11 +275,23 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
screen.send_app_instructions.update(err_ctx);
screen.send_pty_instructions.update(err_ctx);
match event {
- ScreenInstruction::Pty(pid, vte_event) => {
- screen
- .get_active_tab_mut()
- .unwrap()
- .handle_pty_event(pid, vte_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();
@@ -306,9 +326,15 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
ScreenInstruction::ResizeUp => {
screen.get_active_tab_mut().unwrap().resize_up();
}
- ScreenInstruction::MoveFocus => {
+ 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();
}
@@ -389,6 +415,9 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
ScreenInstruction::UpdateTabName(c) => {
screen.update_active_tab_name(c);
}
+ ScreenInstruction::TerminalResize => {
+ screen.resize_to_screen();
+ }
ScreenInstruction::ChangeMode(mode_info) => {
screen.change_mode(mode_info);
}
@@ -506,6 +535,19 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
})
.unwrap();
+ let _signal_thread = thread::Builder::new()
+ .name("signal_listener".to_string())
+ .spawn({
+ let os_input = os_input.clone();
+ let send_screen_instructions = send_screen_instructions.clone();
+ move || {
+ os_input.receive_sigwinch(Box::new(move || {
+ let _ = send_screen_instructions.send(ScreenInstruction::TerminalResize);
+ }));
+ }
+ })
+ .unwrap();
+
// TODO: currently we don't wait for this to quit
// because otherwise the app will hang. Need to fix this so it both
// listens to the ipc-bus and is able to quit cleanly
@@ -553,7 +595,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
}
ApiCommand::MoveFocus => {
send_screen_instructions
- .send(ScreenInstruction::MoveFocus)
+ .send(ScreenInstruction::FocusNextPane)
.unwrap();
}
}
@@ -574,9 +616,11 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs) {
let send_pty_instructions = send_pty_instructions.clone();
let send_plugin_instructions = send_plugin_instructions.clone();
let os_input = os_input.clone();
+ let config = config;
move || {
input_loop(
os_input,
+ config,
command_is_executing,
send_screen_instructions,
send_pty_instructions,
diff --git a/src/common/os_input_output.rs b/src/common/os_input_output.rs
index 043a9aea..013b9f12 100644
--- a/src/common/os_input_output.rs
+++ b/src/common/os_input_output.rs
@@ -13,6 +13,8 @@ use std::path::PathBuf;
use std::process::{Child, Command};
use std::sync::{Arc, Mutex};
+use signal_hook::{consts::signal::*, iterator::Signals};
+
use std::env;
fn into_raw_mode(pid: RawFd) {
@@ -65,7 +67,7 @@ pub fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) {
/// process exits.
fn handle_command_exit(mut child: Child) {
// register the SIGINT signal (TODO handle more signals)
- let signals = ::signal_hook::iterator::Signals::new(&[::signal_hook::SIGINT]).unwrap();
+ let mut signals = ::signal_hook::iterator::Signals::new(&[SIGINT]).unwrap();
'handle_exit: loop {
// test whether the child process has exited
match child.try_wait() {
@@ -82,10 +84,15 @@ fn handle_command_exit(mut child: Child) {
}
for signal in signals.pending() {
- if signal == signal_hook::SIGINT {
- child.kill().unwrap();
- child.wait().unwrap();
- break 'handle_exit;
+ // FIXME: We need to handle more signals here!
+ #[allow(clippy::single_match)]
+ match signal {
+ SIGINT => {
+ child.kill().unwrap();
+ child.wait().unwrap();
+ break 'handle_exit;
+ }
+ _ => {}
}
}
}
@@ -188,6 +195,7 @@ pub trait OsApi: Send + Sync {
fn get_stdout_writer(&self) -> Box<dyn io::Write>;
/// Returns a [`Box`] pointer to this [`OsApi`] struct.
fn box_clone(&self) -> Box<dyn OsApi>;
+ fn receive_sigwinch(&self, cb: Box<dyn Fn()>);
}
impl OsApi for OsInputOutput {
@@ -234,10 +242,30 @@ impl OsApi for OsInputOutput {
Box::new(stdout)
}
fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error> {
- kill(Pid::from_raw(pid), Some(Signal::SIGINT)).unwrap();
+ // 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 receive_sigwinch(&self, cb: Box<dyn Fn()>) {
+ let mut signals = Signals::new(&[SIGWINCH, SIGTERM, SIGINT, SIGQUIT]).unwrap();
+ for signal in signals.forever() {
+ match signal {
+ SIGWINCH => {
+ cb();
+ }
+ SIGTERM | SIGINT | SIGQUIT => {
+ break;
+ }
+ _ => unreachable!(),
+ }
+ }
+ }
}
impl Clone for Box<dyn OsApi> {
diff --git a/src/common/pty_bus.rs b/src/common/pty_bus.rs
index 7e854257..2cab8ae8 100644
--- a/src/common/pty_bus.rs
+++ b/src/common/pty_bus.rs
@@ -6,7 +6,6 @@ use ::std::os::unix::io::RawFd;
use ::std::pin::*;
use ::std::sync::mpsc::Receiver;
use ::std::time::{Duration, Instant};
-use ::vte;
use std::path::PathBuf;
use super::{ScreenInstruction, SenderWithContext, OPENCALLS};
@@ -64,86 +63,7 @@ impl Stream for ReadFromPid {
}
}
-#[derive(Debug, Clone)]
-pub enum VteEvent {
- // TODO: try not to allocate Vecs
- Print(char),
- Execute(u8), // byte
- Hook(Vec<i64>, Vec<u8>, bool, char), // params, intermediates, ignore, char
- Put(u8), // byte
- Unhook,
- OscDispatch(Vec<Vec<u8>>, bool), // params, bell_terminated
- CsiDispatch(Vec<i64>, Vec<u8>, bool, char), // params, intermediates, ignore, char
- EscDispatch(Vec<u8>, bool, u8), // intermediates, ignore, byte
-}
-
-struct VteEventSender {
- id: RawFd,
- sender: SenderWithContext<ScreenInstruction>,
-}
-
-impl VteEventSender {
- pub fn new(id: RawFd, sender: SenderWithContext<ScreenInstruction>) -> Self {
- VteEventSender { id, sender }
- }
-}
-
-impl vte::Perform for VteEventSender {
- fn print(&mut self, c: char) {
- let _ = self
- .sender
- .send(ScreenInstruction::Pty(self.id, VteEvent::Print(c)));
- }
- fn execute(&mut self, byte: u8) {
- let _ = self
- .sender
- .send(ScreenInstruction::Pty(self.id, VteEvent::Execute(byte)));
- }
-
- fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) {
- let params = params.iter().copied().collect();
- let intermediates = intermediates.iter().copied().collect();
- let instruction =
- ScreenInstruction::Pty(self.id, VteEvent::Hook(params, intermediates, ignore, c));
- let _ = self.sender.send(instruction);
- }
-
- fn put(&mut self, byte: u8) {
- let _ = self
- .sender
- .send(ScreenInstruction::Pty(self.id, VteEvent::Put(byte)));
- }
-
- fn unhook(&mut self) {
- let _ = self
- .sender
- .send(ScreenInstruction::Pty(self.id, VteEvent::Unhook));
- }
-
- fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
- let params = params.iter().map(|p| p.to_vec()).collect();
- let instruction =
- ScreenInstruction::Pty(self.id, VteEvent::OscDispatch(params, bell_terminated));
- let _ = self.sender.send(instruction);
- }
-
- fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) {
- let params = params.iter().copied().collect();
- let intermediates = intermediates.iter().copied().collect();
- let instruction = ScreenInstruction::Pty(
- self.id,
- VteEvent::CsiDispatch(params, intermediates, ignore, c),
- );
- let _ = self.sender.send(instruction);
- }
-
- fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
- let intermediates = intermediates.iter().copied().collect();
- let instruction =
- ScreenInstruction::Pty(self.id, VteEvent::EscDispatch(intermediates, ignore, byte));
- let _ = self.sender.send(instruction);
- }
-}
+pub type VteBytes = Vec<u8>;
/// Instructions related to PTYs (pseudoterminals).
#[derive(Clone, Debug)]
@@ -178,8 +98,6 @@ fn stream_terminal_bytes(
async move {
err_ctx.add_call(ContextType::AsyncTask);
send_screen_instructions.update(err_ctx);
- let mut vte_parser = vte::Parser::new();
- let mut vte_event_sender = VteEventSender::new(pid, send_screen_instructions.clone());
let mut terminal_bytes = ReadFromPid::new(&pid, os_input);
let mut last_byte_receive_time: Option<Instant> = None;
@@ -188,13 +106,13 @@ fn stream_terminal_bytes(
while let Some(bytes) = terminal_bytes.next().await {
let bytes_is_empty = bytes.is_empty();
- for byte in bytes {
- if debug {
- debug_to_file(byte, pid).unwrap();
+ if debug {
+ for byte in bytes.iter() {
+ debug_to_file(*byte, pid).unwrap();
}
- vte_parser.advance(&mut vte_event_sender, byte);
}
if !bytes_is_empty {
+ let _ = send_screen_instructions.send(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
diff --git a/src/common/screen.rs b/src/common/screen.rs
index 178e8be9..1e37ca4f 100644
--- a/src/common/screen.rs
+++ b/src/common/screen.rs
@@ -8,7 +8,7 @@ use std::sync::mpsc::Receiver;
use super::{AppInstruction, SenderWithContext};
use crate::os_input_output::OsApi;
use crate::panes::PositionAndSize;
-use crate::pty_bus::{PtyInstruction, VteEvent};
+use crate::pty_bus::{PtyInstruction, VteBytes};
use crate::tab::Tab;
use crate::{errors::ErrorContext, wasm_vm::PluginInstruction};
use crate::{layout::Layout, panes::PaneId};
@@ -18,7 +18,7 @@ use zellij_tile::data::{Event, ModeInfo, TabInfo};
/// Instructions that can be sent to the [`Screen`].
#[derive(Debug, Clone)]
pub enum ScreenInstruction {
- Pty(RawFd, VteEvent),
+ PtyBytes(RawFd, VteBytes),
Render,
NewPane(PaneId),
HorizontalSplit(PaneId),
@@ -28,7 +28,9 @@ pub enum ScreenInstruction {
ResizeRight,
ResizeDown,
ResizeUp,
- MoveFocus,
+ SwitchFocus,
+ FocusNextPane,
+ FocusPreviousPane,
MoveFocusLeft,
MoveFocusDown,
MoveFocusUp,
@@ -50,6 +52,7 @@ pub enum ScreenInstruction {
CloseTab,
GoToTab(u32),
UpdateTabName(Vec<u8>),
+ TerminalResize,
ChangeMode(ModeInfo),
}
@@ -213,6 +216,15 @@ impl Screen {
}
}
+ pub fn resize_to_screen(&mut self) {
+ let new_screen_size = self.os_api.get_terminal_size_using_fd(0);
+ self.full_screen_ws = new_screen_size;
+ for (_, tab) in self.tabs.iter_mut() {
+ tab.resize_whole_tab(new_screen_size);
+ }
+ 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() {
diff --git a/src/common/utils/shared.rs b/src/common/utils/shared.rs
index 9717837a..93892d4f 100644
--- a/src/common/utils/shared.rs
+++ b/src/common/utils/shared.rs
@@ -11,9 +11,18 @@ fn ansi_len(s: &str) -> usize {
.count()
}
-pub fn pad_to_size(s: &str, rows: usize, columns: usize) -> String {
+pub fn adjust_to_size(s: &str, rows: usize, columns: usize) -> String {
s.lines()
- .map(|l| [l, &str::repeat(" ", columns - ansi_len(l))].concat())
+ .map(|l| {
+ let actual_len = ansi_len(l);
+ if actual_len > columns {
+ let mut line = String::from(l);
+ line.truncate(columns);
+ return line;
+ } else {
+ return [l, &str::repeat(" ", columns - ansi_len(l))].concat();
+ }
+ })
.chain(iter::repeat(str::repeat(" ", columns)))
.take(rows)
.collect::<Vec<_>>()
diff --git a/src/main.rs b/src/main.rs
index 0d31573c..d6cbb05d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -21,7 +21,6 @@ use structopt::StructOpt;
use crate::cli::CliArgs;
use crate::command_is_executing::CommandIsExecuting;
use crate::os_input_output::get_os_input;
-use crate::pty_bus::VteEvent;
use crate::utils::{
consts::{ZELLIJ_IPC_PIPE, ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR},
logging::*,
diff --git a/src/tests/fakes.rs b/src/tests/fakes.rs
index c800de97..c9504a98 100644
--- a/src/tests/fakes.rs
+++ b/src/tests/fakes.rs
@@ -3,14 +3,13 @@ use std::collections::{HashMap, VecDeque};
use std::io::Write;
use std::os::unix::io::RawFd;
use std::path::PathBuf;
-use std::sync::atomic::{AtomicBool, Ordering};
-use std::sync::{Arc, Mutex};
+use std::sync::{Arc, Condvar, Mutex};
use std::time::{Duration, Instant};
use crate::os_input_output::OsApi;
use crate::tests::possible_tty_inputs::{get_possible_tty_inputs, Bytes};
-use crate::tests::utils::commands::SLEEP;
+use crate::tests::utils::commands::{QUIT, SLEEP};
const MIN_TIME_BETWEEN_SNAPSHOTS: Duration = Duration::from_millis(50);
@@ -72,7 +71,8 @@ pub struct FakeInputOutput {
win_sizes: Arc<Mutex<HashMap<RawFd, PositionAndSize>>>,
possible_tty_inputs: HashMap<u16, Bytes>,
last_snapshot_time: Arc<Mutex<Instant>>,
- started_reading_from_pty: Arc<AtomicBool>,
+ should_trigger_sigwinch: Arc<(Mutex<bool>, Condvar)>,
+ sigwinch_event: Option<PositionAndSize>,
}
impl FakeInputOutput {
@@ -91,7 +91,8 @@ impl FakeInputOutput {
io_events: Arc::new(Mutex::new(vec![])),
win_sizes: Arc::new(Mutex::new(win_sizes)),
possible_tty_inputs: get_possible_tty_inputs(),
- started_reading_from_pty: Arc::new(AtomicBool::new(false)),
+ should_trigger_sigwinch: Arc::new((Mutex::new(false), Condvar::new())),
+ sigwinch_event: None,
}
}
pub fn with_tty_inputs(mut self, tty_inputs: HashMap<u16, Bytes>) -> Self {
@@ -108,10 +109,20 @@ impl FakeInputOutput {
pub fn add_terminal(&mut self, fd: RawFd) {
self.stdin_writes.lock().unwrap().insert(fd, vec![]);
}
+ pub fn add_sigwinch_event(&mut self, new_position_and_size: PositionAndSize) {
+ self.sigwinch_event = Some(new_position_and_size);
+ }
}
impl OsApi for FakeInputOutput {
fn get_terminal_size_using_fd(&self, pid: RawFd) -> PositionAndSize {
+ if let Some(new_position_and_size) = self.sigwinch_event {
+ let (lock, _cvar) = &*self.should_trigger_sigwinch;
+ let should_trigger_sigwinch = lock.lock().unwrap();
+ if *should_trigger_sigwinch && pid == 0 {
+ return new_position_and_size;
+ }
+ }
let win_sizes = self.win_sizes.lock().unwrap();
let winsize = win_sizes.get(&pid).unwrap();
*winsize
@@ -159,7 +170,6 @@ impl OsApi for FakeInputOutput {
if bytes_read > bytes.read_position {
bytes.set_read_position(bytes_read);
}
- self.started_reading_from_pty.store(true, Ordering::Release);
return Ok(bytes_read);
}
None => Err(nix::Error::Sys(nix::errno::Errno::EAGAIN)),
@@ -199,6 +209,12 @@ impl OsApi for FakeInputOutput {
.unwrap_or(vec![]);
if command == SLEEP {
std::thread::sleep(std::time::Duration::from_millis(200));
+ } else if command == QUIT && self.sigwinch_event.is_some() {
+ let (lock, cvar) = &*self.should_trigger_sigwinch;
+ let mut should_trigger_sigwinch = lock.lock().unwrap();
+ *should_trigger_sigwinch = true;
+ cvar.notify_one();
+ ::std::thread::sleep(MIN_TIME_BETWEEN_SNAPSHOTS); // give some time for the app to resize before quitting
}
command
}
@@ -209,4 +225,14 @@ impl OsApi for FakeInputOutput {
self.io_events.lock().unwrap().push(IoEvent::Kill(fd));
Ok(())
}
+ fn receive_sigwinch(&self, cb: Box<dyn Fn()>) {
+ if self.sigwinch_event.is_some() {
+ let (lock, cvar) = &*self.should_trigger_sigwinch;
+ let mut should_trigger_sigwinch = lock.lock().unwrap();
+ while !*should_trigger_sigwinch {
+ should_trigger_sigwinch = cvar.wait(should_trigger_sigwinch).unwrap();
+ }
+ cb();
+ }
+ }
}
diff --git a/src/tests/integration/close_pane.rs b/src/tests/integration/close_pane.rs
index 0a7aea48..7421ba37 100644
--- a/src/tests/integration/close_pane.rs
+++ b/src/tests/integration/close_pane.rs
@@ -6,7 +6,7 @@ use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots}
use crate::{start, CliArgs};
use crate::tests::utils::commands::{
- CLOSE_PANE_IN_PANE_MODE, COMMAND_TOGGLE, ESC, MOVE_FOCUS_IN_PANE_MODE, PANE_MODE, QUIT,
+ 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,
};
diff --git a/src/tests/integration/mod.rs b/src/tests/integration/mod.rs
index 9036ad6d..83cb7d22 100644
--- a/src/tests/integration/mod.rs
+++ b/src/tests/integration/mod.rs
@@ -12,4 +12,5 @@ pub mod resize_left;
pub mod resize_right;
pub mod resize_up;
pub mod tabs;
+pub mod terminal_window_resize;
pub mod toggle_fullscreen;
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__terminal_window_resize__window_height_increase_with_one_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__terminal_window_resize__window_height_increase_with_one_pane.snap
new file mode 100644
index 00000000..2b8efe26
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__terminal_window_resize__window_height_increase_with_one_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/terminal_window_resize.rs
+expression: snapshot_before_quit
+
+---
+line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__terminal_window_resize__window_width_and_height_decrease_with_one_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__terminal_window_resize__window_width_and_height_decrease_with_one_pane.snap
new file mode 100644
index 00000000..55f32b5d
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__terminal_window_resize__window_width_and_height_decrease_with_one_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/terminal_window_resize.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__terminal_window_resize__window_width_decrease_with_one_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__terminal_window_resize__window_width_decrease_with_one_pane.snap
new file mode 100644
index 00000000..2f72d22a
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__terminal_window_resize__window_width_decrease_with_one_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/terminal_window_resize.rs
+expression: snapshot_before_quit
+
+---
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $ █
diff --git a/src/tests/integration/snapshots/zellij__tests__integration__terminal_window_resize__window_width_increase_with_one_pane.snap b/src/tests/integration/snapshots/zellij__tests__integration__terminal_window_resize__window_width_increase_with_one_pane.snap
new file mode 100644
index 00000000..95cc6c36
--- /dev/null
+++ b/src/tests/integration/snapshots/zellij__tests__integration__terminal_window_resize__window_width_increase_with_one_pane.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/integration/terminal_window_resize.rs
+expression: snapshot_before_quit
+
+---
+line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+prompt $
+ █
diff --git a/src/tests/integration/terminal_window_resize.rs b/src/tests/integration/terminal_window_resize.rs
new file mode 100644
index 00000000..ab73d24e
--- /dev/null
+++ b/src/tests/integration/terminal_window_resize.rs
@@ -0,0 +1,127 @@
+use crate::panes::PositionAndSize;
+use ::insta::assert_snapshot;
+
+use crate::tests::fakes::FakeInputOutput;
+use crate::tests::utils::commands::QUIT;
+use crate::tests::utils::{get_next_to_last_snapshot, get_output_frame_snapshots};
+use crate::{start, CliArgs};
+
+fn get_fake_os_input(fake_win_size: &PositionAndSize) -> FakeInputOutput {
+ FakeInputOutput::new(fake_win_size.clone())
+}
+
+#[test]
+pub fn window_width_decrease_with_one_pane() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[&QUIT]);
+ fake_input_output.add_sigwinch_event(PositionAndSize {
+ columns: 90,
+ rows: 20,
+ x: 0,
+ y: 0,
+ });
+ let opts = CliArgs::default();
+ start(Box::new(fake_input_output.clone()), opts);
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn window_width_increase_with_one_pane() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[&QUIT]);
+ fake_input_output.add_sigwinch_event(PositionAndSize {
+ columns: 141,
+ rows: 20,
+ x: 0,
+ y: 0,
+ });
+ let opts = CliArgs::default();
+ start(Box::new(fake_input_output.clone()), opts);
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn window_height_increase_with_one_pane() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[&QUIT]);
+ fake_input_output.add_sigwinch_event(PositionAndSize {
+ columns: 121,
+ rows: 30,
+ x: 0,
+ y: 0,
+ });
+ let opts = CliArgs::default();
+ start(Box::new(fake_input_output.clone()), opts);
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
+
+#[test]
+pub fn window_width_and_height_decrease_with_one_pane() {
+ let fake_win_size = PositionAndSize {
+ columns: 121,
+ rows: 20,
+ x: 0,
+ y: 0,
+ };
+ let mut fake_input_output = get_fake_os_input(&fake_win_size);
+ fake_input_output.add_terminal_input(&[&QUIT]);
+ fake_input_output.add_sigwinch_event(PositionAndSize {
+ columns: 90,
+ rows: 10,
+ x: 0,
+ y: 0,
+ });
+ let opts = CliArgs::default();
+ start(Box::new(fake_input_output.clone()), opts);
+ let output_frames = fake_input_output
+ .stdout_writer
+ .output_frames
+ .lock()
+ .unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ let snapshot_before_quit =
+ get_next_to_last_snapshot(snapshots).expect("could not find snapshot");
+ assert_snapshot!(snapshot_before_quit);
+}
diff --git a/src/tests/possible_tty_inputs.rs b/src/tests/possible_tty_inputs.rs
index fa918be0..30668360 100644
--- a/src/tests/possible_tty_inputs.rs
+++ b/src/tests/possible_tty_inputs.rs
@@ -1,6 +1,7 @@
use crate::tests::tty_inputs::{
- COL_10, COL_121, COL_14, COL_15, COL_19, COL_20, COL_24, COL_25, COL_29, COL_30, COL_34,
- COL_39, COL_4, COL_40, COL_47, COL_50, COL_60, COL_70, COL_8, COL_9, COL_90, COL_96,
+ COL_10, COL_121, COL_14, COL_141, COL_15, COL_19, COL_20, COL_24, COL_25, COL_29, COL_30,
+ COL_34, COL_39, COL_4, COL_40, COL_47, COL_50, COL_60, COL_70, COL_8, COL_80, COL_9, COL_90,
+ COL_96,
};
use std::collections::HashMap;
use std::fs;
@@ -69,9 +70,11 @@ pub fn get_possible_tty_inputs() -> HashMap<u16, Bytes> {
let col_50_bytes = Bytes::new().content_from_str(&COL_50);
let col_60_bytes = Bytes::new().content_from_str(&COL_60);
let col_70_bytes = Bytes::new().content_from_str(&COL_70);
+ let col_80_bytes = Bytes::new().content_from_str(&COL_80);
let col_90_bytes = Bytes::new().content_from_str(&COL_90);
let col_96_bytes = Bytes::new().content_from_str(&COL_96);
let col_121_bytes = Bytes::new().content_from_str(&COL_121);
+ let col_141_bytes = Bytes::new().content_from_str(&COL_141);
possible_inputs.insert(4, col_4_bytes);
possible_inputs.insert(8, col_8_bytes);
possible_inputs.insert(9, col_9_bytes);
@@ -91,8 +94,10 @@ pub fn get_possible_tty_inputs() -> HashMap<u16, Bytes> {
possible_inputs.insert(50, col_50_bytes);
possible_inputs.insert(60, col_60_bytes);
possible_inputs.insert(70, col_70_bytes);
+ possible_inputs.insert(80, col_80_bytes);
possible_inputs.insert(90, col_90_bytes);
possible_inputs.insert(96, col_96_bytes);
possible_inputs.insert(121, col_121_bytes);
+ possible_inputs.insert(141, col_141_bytes);
possible_inputs
}
diff --git a/src/tests/tty_inputs.rs b/src/tests/tty_inputs.rs
index cf49290e..43ac8abc 100644
--- a/src/tests/tty_inputs.rs
+++ b/src/tests/tty_inputs.rs
@@ -1,3 +1,26 @@
+pub const COL_141: [&str; 20] = [
+ "line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line3-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line4-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line5-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line6-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line7-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line8-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line9-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line10-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line11-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line12-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line13-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line14-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line15-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line16-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line17-baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line18-baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "line19-baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
+ "prompt $ ",
+];
+
pub const COL_121: [&str; 20] = [
"line1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
"line2-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n",
@@ -457,6 +480,29 @@ pub const COL_70: [&str; 20] = [
"prompt $ ",
];
+pub const COL_80: [&str; 20] = [
+ "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
+ "prompt $ ",
+];
+
pub const COL_90: [&str; 20] = [
"line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
"line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\r\n",
diff --git a/src/tests/utils.rs b/src/tests/utils.rs
index 14f4cfa0..915adfb0 100644
--- a/src/tests/utils.rs
+++ b/src/tests/utils.rs
@@ -45,7 +45,6 @@ pub fn get_next_to_last_snapshot(mut snapshots: Vec<String>) -> Option<String> {
}
pub mod commands {
- pub const COMMAND_TOGGLE: [u8; 1] = [7]; // ctrl-g
pub const QUIT: [u8; 1] = [17]; // ctrl-q
pub const ESC: [u8; 1] = [27];