aboutsummaryrefslogtreecommitdiffstats
path: root/src/client
diff options
context:
space:
mode:
authordenis <denismaximov98@gmail.com>2021-04-22 11:45:29 +0300
committerdenis <denismaximov98@gmail.com>2021-04-22 11:45:29 +0300
commit8d98ca7da0bc278a832e4e7de23ff63a84deb529 (patch)
treee7fdfaa6c1ae25e7737dbb36bd1dbcb94d62ab47 /src/client
parentwip: fixing the first arrow (diff)
parentfix(cargo): reduce crate size by 4.8MB using `cargo diet`, to 77kB (diff)
downloadsl0ppij-8d98ca7da0bc278a832e4e7de23ff63a84deb529.tar.gz
sl0ppij-8d98ca7da0bc278a832e4e7de23ff63a84deb529.tar.xz
sl0ppij-8d98ca7da0bc278a832e4e7de23ff63a84deb529.zip
wip: merge main in
Diffstat (limited to 'src/client')
-rw-r--r--src/client/layout.rs11
-rw-r--r--src/client/pane_resizer.rs172
-rw-r--r--src/client/panes/grid.rs498
-rw-r--r--src/client/panes/plugin_pane.rs2
-rw-r--r--src/client/panes/terminal_character.rs4
-rw-r--r--src/client/panes/terminal_pane.rs376
-rw-r--r--src/client/tab.rs171
7 files changed, 636 insertions, 598 deletions
diff --git a/src/client/layout.rs b/src/client/layout.rs
index 2d1daaf4..e82f9045 100644
--- a/src/client/layout.rs
+++ b/src/client/layout.rs
@@ -1,6 +1,5 @@
-use directories_next::ProjectDirs;
use serde::{Deserialize, Serialize};
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
use std::{fs::File, io::prelude::*};
use crate::panes::PositionAndSize;
@@ -177,16 +176,12 @@ pub struct Layout {
pub split_size: Option<SplitSize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub plugin: Option<PathBuf>,
- #[serde(default)]
- pub expansion_boundary: bool,
}
impl Layout {
- pub fn new(layout_path: PathBuf) -> Self {
- let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap();
- let layout_dir = project_dirs.data_dir().join("layouts/");
+ pub fn new(layout_path: &Path, data_dir: &Path) -> Self {
+ let layout_dir = data_dir.join("layouts/");
let mut layout_file = File::open(&layout_path)
- .or_else(|_| File::open(&layout_path.with_extension("yaml")))
.or_else(|_| File::open(&layout_dir.join(&layout_path).with_extension("yaml")))
.unwrap_or_else(|_| panic!("cannot find layout {}", &layout_path.display()));
diff --git a/src/client/pane_resizer.rs b/src/client/pane_resizer.rs
index 007dc33f..f9ec71b8 100644
--- a/src/client/pane_resizer.rs
+++ b/src/client/pane_resizer.rs
@@ -1,7 +1,10 @@
use crate::os_input_output::OsApi;
use crate::panes::{PaneId, PositionAndSize};
use crate::tab::Pane;
-use std::collections::{BTreeMap, HashSet};
+use std::{
+ cmp::Ordering,
+ collections::{BTreeMap, HashSet},
+};
pub struct PaneResizer<'a> {
panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>,
@@ -9,9 +12,7 @@ pub struct PaneResizer<'a> {
}
// 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
+// all resizing functions should move here
impl<'a> PaneResizer<'a> {
pub fn new(
@@ -29,63 +30,72 @@ impl<'a> PaneResizer<'a> {
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;
- });
+ match new_size.columns.cmp(&current_size.columns) {
+ Ordering::Greater => {
+ let increase_by = new_size.columns - current_size.columns;
+ if let Some(panes_to_resize) = find_increasable_vertical_chain(
+ &self.panes,
+ increase_by,
+ current_size.columns,
+ current_size.rows,
+ ) {
+ self.increase_panes_right_and_push_adjacents_right(
+ panes_to_resize,
+ increase_by,
+ );
+ column_difference = new_size.columns as isize - current_size.columns as isize;
+ current_size.columns =
+ (current_size.columns as isize + column_difference) as usize;
+ successfully_resized = true;
+ };
+ }
+ Ordering::Less => {
+ let reduce_by = current_size.columns - new_size.columns;
+ if let Some(panes_to_resize) = find_reducible_vertical_chain(
+ &self.panes,
+ reduce_by,
+ current_size.columns,
+ current_size.rows,
+ ) {
+ self.reduce_panes_left_and_pull_adjacents_left(panes_to_resize, reduce_by);
+ column_difference = new_size.columns as isize - current_size.columns as isize;
+ current_size.columns =
+ (current_size.columns as isize + column_difference) as usize;
+ successfully_resized = true;
+ };
+ }
+ Ordering::Equal => (),
}
- 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;
- });
+ match new_size.rows.cmp(&current_size.rows) {
+ Ordering::Greater => {
+ let increase_by = new_size.rows - current_size.rows;
+ if let Some(panes_to_resize) = find_increasable_horizontal_chain(
+ &self.panes,
+ increase_by,
+ current_size.columns,
+ current_size.rows,
+ ) {
+ self.increase_panes_down_and_push_down_adjacents(panes_to_resize, increase_by);
+ row_difference = new_size.rows as isize - current_size.rows as isize;
+ current_size.rows = (current_size.rows as isize + row_difference) as usize;
+ successfully_resized = true;
+ };
+ }
+ Ordering::Less => {
+ let reduce_by = current_size.rows - new_size.rows;
+ if let Some(panes_to_resize) = find_reducible_horizontal_chain(
+ &self.panes,
+ reduce_by,
+ current_size.columns,
+ current_size.rows,
+ ) {
+ self.reduce_panes_up_and_pull_adjacents_up(panes_to_resize, reduce_by);
+ row_difference = new_size.rows as isize - current_size.rows as isize;
+ current_size.rows = (current_size.rows as isize + row_difference) as usize;
+ successfully_resized = true;
+ };
+ }
+ Ordering::Equal => (),
}
if successfully_resized {
Some((column_difference, row_difference))
@@ -229,13 +239,12 @@ impl<'a> PaneResizer<'a> {
fn find_next_increasable_horizontal_pane(
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
- right_of: &Box<dyn Pane>,
+ right_of: &dyn Pane,
increase_by: usize,
) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter(
|p| {
- p.x() == right_of.x() + right_of.columns() + 1
- && p.horizontally_overlaps_with(right_of.as_ref())
+ p.x() == right_of.x() + right_of.columns() + 1 && p.horizontally_overlaps_with(right_of)
}, // TODO: the name here is wrong, it should be vertically_overlaps_with
);
let resizable_candidates =
@@ -255,11 +264,11 @@ fn find_next_increasable_horizontal_pane(
fn find_next_increasable_vertical_pane(
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
- below: &Box<dyn Pane>,
+ below: &dyn Pane,
increase_by: usize,
) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter(
- |p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below.as_ref()), // TODO: the name here is wrong, it should be horizontally_overlaps_with
+ |p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with
);
let resizable_candidates =
next_pane_candidates.filter(|p| p.can_increase_width_by(increase_by));
@@ -278,11 +287,11 @@ fn find_next_increasable_vertical_pane(
fn find_next_reducible_vertical_pane(
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
- below: &Box<dyn Pane>,
+ below: &dyn Pane,
reduce_by: usize,
) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter(
- |p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below.as_ref()), // TODO: the name here is wrong, it should be horizontally_overlaps_with
+ |p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with
);
let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_width_by(reduce_by));
resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id {
@@ -300,13 +309,12 @@ fn find_next_reducible_vertical_pane(
fn find_next_reducible_horizontal_pane(
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
- right_of: &Box<dyn Pane>,
+ right_of: &dyn Pane,
reduce_by: usize,
) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter(
|p| {
- p.x() == right_of.x() + right_of.columns() + 1
- && p.horizontally_overlaps_with(right_of.as_ref())
+ p.x() == right_of.x() + right_of.columns() + 1 && p.horizontally_overlaps_with(right_of)
}, // TODO: the name here is wrong, it should be vertically_overlaps_with
);
let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_height_by(reduce_by));
@@ -351,7 +359,11 @@ fn find_increasable_horizontal_chain(
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) {
+ match find_next_increasable_horizontal_pane(
+ panes,
+ current_pane.as_ref(),
+ increase_by,
+ ) {
Some(next_pane_id) => {
current_pane = panes.get(&next_pane_id).unwrap();
}
@@ -397,7 +409,11 @@ fn find_increasable_vertical_chain(
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) {
+ match find_next_increasable_vertical_pane(
+ panes,
+ current_pane.as_ref(),
+ increase_by,
+ ) {
Some(next_pane_id) => {
current_pane = panes.get(&next_pane_id).unwrap();
}
@@ -443,7 +459,11 @@ fn find_reducible_horizontal_chain(
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) {
+ match find_next_reducible_horizontal_pane(
+ panes,
+ current_pane.as_ref(),
+ reduce_by,
+ ) {
Some(next_pane_id) => {
current_pane = panes.get(&next_pane_id).unwrap();
}
@@ -489,7 +509,11 @@ fn find_reducible_vertical_chain(
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) {
+ match find_next_reducible_vertical_pane(
+ panes,
+ current_pane.as_ref(),
+ increase_by,
+ ) {
Some(next_pane_id) => {
current_pane = panes.get(&next_pane_id).unwrap();
}
diff --git a/src/client/panes/grid.rs b/src/client/panes/grid.rs
index 7651bdc0..c6dd201e 100644
--- a/src/client/panes/grid.rs
+++ b/src/client/panes/grid.rs
@@ -1,9 +1,13 @@
use std::{
cmp::Ordering,
+ collections::VecDeque,
fmt::{self, Debug, Formatter},
};
-static TABSTOP_WIDTH: usize = 8; // TODO: is this always right?
+const TABSTOP_WIDTH: usize = 8; // TODO: is this always right?
+const SCROLL_BACK: usize = 10_000;
+
+use crate::utils::logging::debug_log_to_file;
use crate::panes::terminal_character::{
CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
@@ -26,14 +30,12 @@ fn get_top_non_canonical_rows(rows: &mut Vec<Row>) -> Vec<Row> {
}
}
-fn get_bottom_canonical_row_and_wraps(rows: &mut Vec<Row>) -> Vec<Row> {
+fn get_bottom_canonical_row_and_wraps(rows: &mut VecDeque<Row>) -> Vec<Row> {
let mut index_of_last_non_canonical_row = None;
for (i, row) in rows.iter().enumerate().rev() {
+ index_of_last_non_canonical_row = Some(i);
if row.is_canonical {
- index_of_last_non_canonical_row = Some(i);
break;
- } else {
- index_of_last_non_canonical_row = Some(i);
}
}
match index_of_last_non_canonical_row {
@@ -45,7 +47,7 @@ fn get_bottom_canonical_row_and_wraps(rows: &mut Vec<Row>) -> Vec<Row> {
}
fn transfer_rows_down(
- source: &mut Vec<Row>,
+ source: &mut VecDeque<Row>,
destination: &mut Vec<Row>,
count: usize,
max_src_width: Option<usize>,
@@ -58,7 +60,7 @@ fn transfer_rows_down(
break;
}
if next_lines.is_empty() {
- match source.pop() {
+ match source.pop_back() {
Some(next_line) => {
let mut top_non_canonical_rows_in_dst = get_top_non_canonical_rows(destination);
lines_added_to_destination -= top_non_canonical_rows_in_dst.len() as isize;
@@ -84,13 +86,12 @@ fn transfer_rows_down(
if !next_lines.is_empty() {
match max_src_width {
Some(max_row_width) => {
- let mut excess_rows =
- Row::from_rows(next_lines).split_to_rows_of_length(max_row_width);
- source.append(&mut excess_rows);
+ let excess_rows = Row::from_rows(next_lines).split_to_rows_of_length(max_row_width);
+ source.extend(excess_rows);
}
None => {
let excess_row = Row::from_rows(next_lines);
- source.push(excess_row);
+ bounded_push(source, excess_row);
}
}
}
@@ -98,7 +99,7 @@ fn transfer_rows_down(
fn transfer_rows_up(
source: &mut Vec<Row>,
- destination: &mut Vec<Row>,
+ destination: &mut VecDeque<Row>,
count: usize,
max_src_width: Option<usize>,
max_dst_width: Option<usize>,
@@ -124,7 +125,7 @@ fn transfer_rows_up(
break; // no more rows
}
}
- destination.push(next_lines.remove(0));
+ bounded_push(destination, next_lines.remove(0));
}
if !next_lines.is_empty() {
match max_src_width {
@@ -142,13 +143,25 @@ fn transfer_rows_up(
}
}
+fn bounded_push(vec: &mut VecDeque<Row>, value: Row) {
+ if vec.len() >= SCROLL_BACK {
+ vec.pop_front();
+ }
+ vec.push_back(value)
+}
+
#[derive(Clone)]
pub struct Grid {
- lines_above: Vec<Row>,
+ lines_above: VecDeque<Row>,
viewport: Vec<Row>,
lines_below: Vec<Row>,
+ alternative_lines_above_viewport_and_cursor: Option<(VecDeque<Row>, Vec<Row>, Cursor)>,
cursor: Cursor,
scroll_region: Option<(usize, usize)>,
+ pending_styles: CharacterStyles,
+ pub should_render: bool,
+ pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "")
+ pub clear_viewport_before_rendering: bool,
pub width: usize,
pub height: usize,
}
@@ -169,13 +182,18 @@ impl Debug for Grid {
impl Grid {
pub fn new(rows: usize, columns: usize) -> Self {
Grid {
- lines_above: vec![],
+ lines_above: VecDeque::with_capacity(SCROLL_BACK),
viewport: vec![Row::new().canonical()],
lines_below: vec![],
cursor: Cursor::new(0, 0),
scroll_region: None,
width: columns,
height: rows,
+ pending_styles: CharacterStyles::new(),
+ should_render: true,
+ cursor_key_mode: false,
+ alternative_lines_above_viewport_and_cursor: None,
+ clear_viewport_before_rendering: false,
}
}
pub fn advance_to_next_tabstop(&mut self, styles: CharacterStyles) {
@@ -237,7 +255,7 @@ impl Grid {
if !self.lines_above.is_empty() && self.viewport.len() == self.height {
let line_to_push_down = self.viewport.pop().unwrap();
self.lines_below.insert(0, line_to_push_down);
- let line_to_insert_at_viewport_top = self.lines_above.pop().unwrap();
+ let line_to_insert_at_viewport_top = self.lines_above.pop_back().unwrap();
self.viewport.insert(0, line_to_insert_at_viewport_top);
}
}
@@ -245,11 +263,11 @@ impl Grid {
if !self.lines_below.is_empty() && self.viewport.len() == self.height {
let mut line_to_push_up = self.viewport.remove(0);
if line_to_push_up.is_canonical {
- self.lines_above.push(line_to_push_up);
+ bounded_push(&mut self.lines_above, line_to_push_up);
} else {
- let mut last_line_above = self.lines_above.pop().unwrap();
+ let mut last_line_above = self.lines_above.pop_back().unwrap();
last_line_above.append(&mut line_to_push_up.columns);
- self.lines_above.push(last_line_above);
+ bounded_push(&mut self.lines_above, last_line_above);
}
let line_to_insert_at_viewport_bottom = self.lines_below.remove(0);
self.viewport.push(line_to_insert_at_viewport_bottom);
@@ -265,7 +283,7 @@ impl Grid {
&& viewport_canonical_lines.is_empty()
&& !self.lines_above.is_empty()
{
- let mut first_line_above = self.lines_above.pop().unwrap();
+ let mut first_line_above = self.lines_above.pop_back().unwrap();
first_line_above.append(&mut row.columns);
viewport_canonical_lines.push(first_line_above);
cursor_canonical_line_index += 1;
@@ -429,13 +447,35 @@ impl Grid {
self.scroll_down_one_line();
}
}
- pub fn rotate_scroll_region_up(&mut self, _count: usize) {
- // TBD
+ pub fn rotate_scroll_region_up(&mut self, count: usize) {
+ if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region {
+ for _ in 0..count {
+ let columns = vec![EMPTY_TERMINAL_CHARACTER; self.width];
+ if scroll_region_bottom < self.viewport.len() {
+ self.viewport.remove(scroll_region_bottom);
+ }
+ if scroll_region_top < self.viewport.len() {
+ self.viewport
+ .insert(scroll_region_top, Row::from_columns(columns).canonical());
+ }
+ }
+ }
}
- pub fn rotate_scroll_region_down(&mut self, _count: usize) {
- // TBD
+ pub fn rotate_scroll_region_down(&mut self, count: usize) {
+ if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region {
+ for _ in 0..count {
+ let columns = vec![EMPTY_TERMINAL_CHARACTER; self.width];
+ self.viewport.remove(scroll_region_top);
+ if self.viewport.len() > scroll_region_top {
+ self.viewport
+ .insert(scroll_region_bottom, Row::from_columns(columns).canonical());
+ } else {
+ self.viewport.push(Row::from_columns(columns).canonical());
+ }
+ }
+ }
}
- pub fn add_canonical_line(&mut self, pad_character: TerminalCharacter) {
+ pub fn add_canonical_line(&mut self) {
if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region {
if self.cursor.y == scroll_region_bottom {
// end of scroll region
@@ -444,8 +484,12 @@ impl Grid {
// then we add an empty line at its end which will be filled by the application
// controlling the scroll region (presumably filled by whatever comes next in the
// scroll buffer, but that's not something we control)
+ if scroll_region_top >= self.viewport.len() {
+ // the state is corrupted
+ return;
+ }
self.viewport.remove(scroll_region_top);
- let columns = vec![pad_character; self.width];
+ let columns = vec![EMPTY_TERMINAL_CHARACTER; self.width];
self.viewport
.insert(scroll_region_bottom, Row::from_columns(columns).canonical());
return;
@@ -483,6 +527,24 @@ impl Grid {
}
pub fn insert_character_at_cursor_position(&mut self, terminal_character: TerminalCharacter) {
match self.viewport.get_mut(self.cursor.y) {
+ Some(row) => {
+ row.insert_character_at(terminal_character, self.cursor.x);
+ if row.len() > self.width {
+ row.truncate(self.width);
+ }
+ }
+ None => {
+ // pad lines until cursor if they do not exist
+ for _ in self.viewport.len()..self.cursor.y {
+ self.viewport.push(Row::new().canonical());
+ }
+ self.viewport
+ .push(Row::new().with_character(terminal_character).canonical());
+ }
+ }
+ }
+ pub fn add_character_at_cursor_position(&mut self, terminal_character: TerminalCharacter) {
+ match self.viewport.get_mut(self.cursor.y) {
Some(row) => row.add_character_at(terminal_character, self.cursor.x),
None => {
// pad lines until cursor if they do not exist
@@ -496,9 +558,7 @@ impl Grid {
}
pub fn add_character(&mut self, terminal_character: TerminalCharacter) {
// TODO: try to separate adding characters from moving the cursors in this function
- if self.cursor.x < self.width {
- self.insert_character_at_cursor_position(terminal_character);
- } else {
+ if self.cursor.x >= self.width {
// line wrap
self.cursor.x = 0;
if self.cursor.y == self.height - 1 {
@@ -519,8 +579,8 @@ impl Grid {
self.viewport.push(line_wrapped_row);
}
}
- self.insert_character_at_cursor_position(terminal_character);
}
+ self.add_character_at_cursor_position(terminal_character);
self.move_cursor_forward_until_edge(1);
}
pub fn move_cursor_forward_until_edge(&mut self, count: usize) {
@@ -546,15 +606,17 @@ impl Grid {
row.replace_beginning_with(line_part);
}
pub fn clear_all_after_cursor(&mut self, replace_with: TerminalCharacter) {
- let cursor_row = self.viewport.get_mut(self.cursor.y).unwrap();
- cursor_row.truncate(self.cursor.x);
- let mut replace_with_columns_in_cursor_row = vec![replace_with; self.width - self.cursor.x];
- cursor_row.append(&mut replace_with_columns_in_cursor_row);
+ if let Some(cursor_row) = self.viewport.get_mut(self.cursor.y).as_mut() {
+ cursor_row.truncate(self.cursor.x);
+ let mut replace_with_columns_in_cursor_row =
+ vec![replace_with; self.width - self.cursor.x];
+ cursor_row.append(&mut replace_with_columns_in_cursor_row);
- let replace_with_columns = vec![replace_with; self.width];
- self.replace_characters_in_line_after_cursor(replace_with);
- for row in self.viewport.iter_mut().skip(self.cursor.y + 1) {
- row.replace_columns(replace_with_columns.clone());
+ let replace_with_columns = vec![replace_with; self.width];
+ self.replace_characters_in_line_after_cursor(replace_with);
+ for row in self.viewport.iter_mut().skip(self.cursor.y + 1) {
+ row.replace_columns(replace_with_columns.clone());
+ }
}
}
pub fn clear_cursor_line(&mut self) {
@@ -600,7 +662,9 @@ impl Grid {
if current_line_index == scroll_region_top {
// if we're at the top line, we create a new line and remove the last line that
// would otherwise overflow
- self.viewport.remove(scroll_region_bottom);
+ if scroll_region_bottom < self.viewport.len() {
+ self.viewport.remove(scroll_region_bottom);
+ }
self.viewport.insert(current_line_index, Row::new()); // TODO: .canonical() ?
} else if current_line_index > scroll_region_top
&& current_line_index <= scroll_region_bottom
@@ -621,7 +685,7 @@ impl Grid {
self.cursor.y + count
};
for _ in 0..lines_to_add {
- self.add_canonical_line(pad_character);
+ self.add_canonical_line();
}
self.pad_lines_until(self.cursor.y, pad_character);
}
@@ -663,8 +727,12 @@ impl Grid {
for _ in 0..count {
self.viewport.remove(current_line_index);
let columns = vec![pad_character; self.width];
- self.viewport
- .insert(scroll_region_bottom, Row::from_columns(columns).canonical());
+ if self.viewport.len() > scroll_region_bottom {
+ self.viewport
+ .insert(scroll_region_bottom, Row::from_columns(columns).canonical());
+ } else {
+ self.viewport.push(Row::from_columns(columns).canonical());
+ }
}
}
}
@@ -683,7 +751,9 @@ impl Grid {
// so we add an empty line where the cursor currently is, and delete the last line
// of the scroll region
for _ in 0..count {
- self.viewport.remove(scroll_region_bottom);
+ if scroll_region_bottom < self.viewport.len() {
+ self.viewport.remove(scroll_region_bottom);
+ }
let columns = vec![pad_character; self.width];
self.viewport
.insert(current_line_index, Row::from_columns(columns).canonical());
@@ -723,6 +793,320 @@ impl Grid {
.unwrap()
.append(&mut empty_space_to_append);
}
+ fn add_newline(&mut self) {
+ self.add_canonical_line();
+ self.mark_for_rerender();
+ }
+ pub fn mark_for_rerender(&mut self) {
+ self.should_render = true;
+ }
+ fn reset_terminal_state(&mut self) {
+ self.lines_above = VecDeque::with_capacity(SCROLL_BACK);
+ self.lines_below = vec![];
+ self.viewport = vec![Row::new().canonical()];
+ self.alternative_lines_above_viewport_and_cursor = None;
+ self.cursor_key_mode = false;
+ self.scroll_region = None;
+ self.clear_viewport_before_rendering = true;
+ self.cursor = Cursor::new(0, 0);
+ }
+}
+
+impl vte::Perform for Grid {
+ fn print(&mut self, c: char) {
+ // apparently, building TerminalCharacter like this without a "new" method
+ // is a little faster
+ let terminal_character = TerminalCharacter {
+ character: c,
+ styles: self.pending_styles,
+ };
+ self.add_character(terminal_character);
+ }
+
+ fn execute(&mut self, byte: u8) {
+ match byte {
+ 8 => {
+ // backspace
+ self.move_cursor_backwards(1);
+ }
+ 9 => {
+ // tab
+ self.advance_to_next_tabstop(self.pending_styles);
+ }
+ 10 => {
+ // 0a, newline
+ self.add_newline();
+ }
+ 13 => {
+ // 0d, carriage return
+ self.move_cursor_to_beginning_of_line();
+ }
+ _ => {}
+ }
+ }
+
+ fn hook(&mut self, _params: &[i64], _intermediates: &[u8], _ignore: bool, _c: char) {
+ // TBD
+ }
+
+ fn put(&mut self, _byte: u8) {
+ // TBD
+ }
+
+ fn unhook(&mut self) {
+ // TBD
+ }
+
+ fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {
+ // TBD
+ }
+
+ fn csi_dispatch(&mut self, params: &[i64], _intermediates: &[u8], _ignore: bool, c: char) {
+ if c == 'm' {
+ self.pending_styles.add_style_from_ansi_params(params);
+ } else if c == 'C' {
+ // move cursor forward
+ let move_by = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.move_cursor_forward_until_edge(move_by);
+ } else if c == 'K' {
+ // clear line (0 => right, 1 => left, 2 => all)
+ if params[0] == 0 {
+ let mut char_to_replace = EMPTY_TERMINAL_CHARACTER;
+ char_to_replace.styles = self.pending_styles;
+ self.replace_characters_in_line_after_cursor(char_to_replace);
+ } else if params[0] == 1 {
+ let mut char_to_replace = EMPTY_TERMINAL_CHARACTER;
+ char_to_replace.styles = self.pending_styles;
+ self.replace_characters_in_line_before_cursor(char_to_replace);
+ } else if params[0] == 2 {
+ self.clear_cursor_line();
+ }
+ } else if c == 'J' {
+ // clear all (0 => below, 1 => above, 2 => all, 3 => saved)
+ let mut char_to_replace = EMPTY_TERMINAL_CHARACTER;
+ char_to_replace.styles = self.pending_styles;
+ if params[0] == 0 {
+ self.clear_all_after_cursor(char_to_replace);
+ } else if params[0] == 2 {
+ self.clear_all(char_to_replace);
+ }
+ // TODO: implement 1
+ } else if c == 'H' {
+ // goto row/col
+ // we subtract 1 from the row/column because these are 1 indexed
+ // (except when they are 0, in which case they should be 1
+ // don't look at me, I don't make the rules)
+ let (row, col) = if params.len() == 1 {
+ if params[0] == 0 {
+ (0, params[0] as usize)
+ } else {
+ ((params[0] as usize).saturating_sub(1), params[0] as usize)
+ }
+ } else if params[0] == 0 {
+ (0, (params[1] as usize).saturating_sub(1))
+ } else {
+ (
+ (params[0] as usize).saturating_sub(1),
+ (params[1] as usize).saturating_sub(1),
+ )
+ };
+ let pad_character = EMPTY_TERMINAL_CHARACTER;
+ self.move_cursor_to(col, row, pad_character);
+ } else if c == 'A' {
+ // move cursor up until edge of screen
+ let move_up_count = if params[0] == 0 { 1 } else { params[0] };
+ self.move_cursor_up(move_up_count as usize);
+ } else if c == 'B' {
+ // move cursor down until edge of screen
+ let move_down_count = if params[0] == 0 { 1 } else { params[0] };
+ let pad_character = EMPTY_TERMINAL_CHARACTER;
+ self.move_cursor_down(move_down_count as usize, pad_character);
+ } else if c == 'D' {
+ let move_back_count = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.move_cursor_back(move_back_count);
+ } else if c == 'l' {
+ let first_intermediate_is_questionmark = match _intermediates.get(0) {
+ Some(b'?') => true,
+ None => false,
+ _ => false,
+ };
+ if first_intermediate_is_questionmark {
+ match params.get(0) {
+ Some(&1049) => {
+ if let Some((
+ alternative_lines_above,
+ alternative_viewport,
+ alternative_cursor,
+ )) = self.alternative_lines_above_viewport_and_cursor.as_mut()
+ {
+ std::mem::swap(&mut self.lines_above, alternative_lines_above);
+ std::mem::swap(&mut self.viewport, alternative_viewport);
+ std::mem::swap(&mut self.cursor, alternative_cursor);
+ }
+ self.alternative_lines_above_viewport_and_cursor = None;
+ self.clear_viewport_before_rendering = true;
+ self.change_size(self.height, self.width); // the alternative_viewport might have been of a different size...
+ self.mark_for_rerender();
+ }
+ Some(&25) => {
+ self.hide_cursor();
+ self.mark_for_rerender();
+ }
+ Some(&1) => {
+ self.cursor_key_mode = false;
+ }
+ _ => {}
+ };
+ }
+ } else if c == 'h' {
+ let first_intermediate_is_questionmark = match _intermediates.get(0) {
+ Some(b'?') => true,
+ None => false,
+ _ => false,
+ };
+ if first_intermediate_is_questionmark {
+ match params.get(0) {
+ Some(&25) => {
+ self.show_cursor();
+ self.mark_for_rerender();
+ }
+ Some(&1049) => {
+ let current_lines_above = std::mem::replace(
+ &mut self.lines_above,
+ VecDeque::with_capacity(SCROLL_BACK),
+ );
+ let current_viewport =
+ std::mem::replace(&mut self.viewport, vec![Row::new().canonical()]);
+ let current_cursor = std::mem::replace(&mut self.cursor, Cursor::new(0, 0));
+ self.alternative_lines_above_viewport_and_cursor =
+ Some((current_lines_above, current_viewport, current_cursor));
+ self.clear_viewport_before_rendering = true;
+ }
+ Some(&1) => {
+ self.cursor_key_mode = true;
+ }
+ _ => {}
+ };
+ }
+ } else if c == 'r' {
+ if params.len() > 1 {
+ // minus 1 because these are 1 indexed
+ let top_line_index = (params[0] as usize).saturating_sub(1);
+ let bottom_line_index = (params[1] as usize).saturating_sub(1);
+ self.set_scroll_region(top_line_index, bottom_line_index);
+ self.show_cursor();
+ } else {
+ self.clear_scroll_region();
+ }
+ } else if c == 'M' {
+ // delete lines if currently inside scroll region
+ let line_count_to_delete = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ let pad_character = EMPTY_TERMINAL_CHARACTER;
+ self.delete_lines_in_scroll_region(line_count_to_delete, pad_character);
+ } else if c == 'L' {
+ // insert blank lines if inside scroll region
+ let line_count_to_add = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ let pad_character = EMPTY_TERMINAL_CHARACTER;
+ self.add_empty_lines_in_scroll_region(line_count_to_add, pad_character);
+ } else if c == 'q' {
+ // ignore for now to run on mac
+ } else if c == 'G' {
+ let column = if params[0] == 0 {
+ 0
+ } else {
+ params[0] as usize - 1
+ };
+ self.move_cursor_to_column(column);
+ } else if c == 'd' {
+ // goto line
+ let line = if params[0] == 0 {
+ 1
+ } else {
+ // minus 1 because this is 1 indexed
+ params[0] as usize - 1
+ };
+ let pad_character = EMPTY_TERMINAL_CHARACTER;
+ self.move_cursor_to_line(line, pad_character);
+ } else if c == 'P' {
+ // erase characters
+ let count = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.erase_characters(count, self.pending_styles);
+ } else if c == 'X' {
+ // erase characters and replace with empty characters of current style
+ let count = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.replace_with_empty_chars(count, self.pending_styles);
+ } else if c == 'T' {
+ /*
+ * 124 54 T SD
+ * Scroll down, new lines inserted at top of screen
+ * [4T = Scroll down 4, bring previous lines back into view
+ */
+ let line_count: i64 = *params.get(0).expect("A number of lines was expected.");
+
+ if line_count >= 0 {
+ self.rotate_scroll_region_up(line_count as usize);
+ } else {
+ // TODO: can this actually happen?
+ self.rotate_scroll_region_down(line_count.abs() as usize);
+ }
+ } else if c == 'S' {
+ // move scroll up
+ let count = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.rotate_scroll_region_down(count);
+ } else if c == '@' {
+ let count = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ for _ in 0..count {
+ // TODO: should this be styled?
+ self.insert_character_at_cursor_position(EMPTY_TERMINAL_CHARACTER);
+ }
+ } else {
+ let _ = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params));
+ }
+ }
+
+ fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, byte: u8) {
+ match (byte, intermediates.get(0)) {
+ (b'M', None) => {
+ self.move_cursor_up_with_scrolling(1);
+ }
+ (b'c', None) => {
+ self.reset_terminal_state();
+ }
+ _ => {}
+ }
+ }
}
#[derive(Clone)]
@@ -786,10 +1170,24 @@ impl Row {
}
}
}
+ pub fn insert_character_at(&mut self, terminal_character: TerminalCharacter, x: usize) {
+ match self.columns.len().cmp(&x) {
+ Ordering::Equal => self.columns.push(terminal_character),
+ Ordering::Less => {
+ self.columns.resize(x, EMPTY_TERMINAL_CHARACTER);
+ self.columns.push(terminal_character);
+ }
+ Ordering::Greater => {
+ self.columns.insert(x, terminal_character);
+ }
+ }
+ }
pub fn replace_character_at(&mut self, terminal_character: TerminalCharacter, x: usize) {
// this is much more performant than remove/insert
- self.columns.push(terminal_character);
- self.columns.swap_remove(x);
+ if x < self.columns.len() {
+ self.columns.push(terminal_character);
+ self.columns.swap_remove(x);
+ }
}
pub fn replace_columns(&mut self, columns: Vec<TerminalCharacter>) {
self.columns = columns;
@@ -804,7 +1202,11 @@ impl Row {
self.columns.append(to_append);
}
pub fn replace_beginning_with(&mut self, mut line_part: Vec<TerminalCharacter>) {
- drop(self.columns.drain(0..line_part.len()));
+ if line_part.len() > self.columns.len() {
+ self.columns.clear();
+ } else {
+ drop(self.columns.drain(0..line_part.len()));
+ }
line_part.append(&mut self.columns);
self.columns = line_part;
}
@@ -812,7 +1214,9 @@ impl Row {
self.columns.len()
}
pub fn delete_character(&mut self, x: usize) {
- self.columns.remove(x);
+ if x < self.columns.len() {
+ self.columns.remove(x);
+ }
}
pub fn split_to_rows_of_length(&mut self, max_row_length: usize) -> Vec<Row> {
let mut parts: Vec<Row> = vec![];
diff --git a/src/client/panes/plugin_pane.rs b/src/client/panes/plugin_pane.rs
index 49cf2aee..24daec08 100644
--- a/src/client/panes/plugin_pane.rs
+++ b/src/client/panes/plugin_pane.rs
@@ -1,5 +1,3 @@
-#![allow(clippy::clippy::if_same_then_else)]
-
use crate::{common::SenderWithContext, pty_bus::VteBytes, tab::Pane, wasm_vm::PluginInstruction};
use std::{sync::mpsc::channel, unimplemented};
diff --git a/src/client/panes/terminal_character.rs b/src/client/panes/terminal_character.rs
index ce967ef8..7569e993 100644
--- a/src/client/panes/terminal_character.rs
+++ b/src/client/panes/terminal_character.rs
@@ -48,7 +48,7 @@ pub enum NamedColor {
}
impl NamedColor {
- fn to_foreground_ansi_code(&self) -> String {
+ fn to_foreground_ansi_code(self) -> String {
match self {
NamedColor::Black => format!("{}", 30),
NamedColor::Red => format!("{}", 31),
@@ -68,7 +68,7 @@ impl NamedColor {
NamedColor::BrightWhite => format!("{}", 97),
}
}
- fn to_background_ansi_code(&self) -> String {
+ fn to_background_ansi_code(self) -> String {
match self {
NamedColor::Black => format!("{}", 40),
NamedColor::Red => format!("{}", 41),
diff --git a/src/client/panes/terminal_pane.rs b/src/client/panes/terminal_pane.rs
index d2473ba3..5d63a100 100644
--- a/src/client/panes/terminal_pane.rs
+++ b/src/client/panes/terminal_pane.rs
@@ -1,5 +1,3 @@
-#![allow(clippy::clippy::if_same_then_else)]
-
use crate::tab::Pane;
use ::nix::pty::Winsize;
use ::std::os::unix::io::RawFd;
@@ -10,7 +8,6 @@ use crate::panes::terminal_character::{
CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
};
use crate::pty_bus::VteBytes;
-use crate::utils::logging::debug_log_to_file;
#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
pub enum PaneId {
@@ -38,19 +35,14 @@ impl From<Winsize> for PositionAndSize {
}
}
-#[derive(Debug)]
pub struct TerminalPane {
pub grid: Grid,
- pub alternative_grid: Option<Grid>, // for 1049h/l instructions which tell us to switch between these two
pub pid: RawFd,
- pub should_render: bool,
pub selectable: bool,
pub position_and_size: PositionAndSize,
pub position_and_size_override: Option<PositionAndSize>,
- pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "")
pub max_height: Option<usize>,
- pending_styles: CharacterStyles,
- clear_viewport_before_rendering: bool,
+ vte_parser: vte::Parser,
}
impl Pane for TerminalPane {
@@ -86,9 +78,8 @@ impl Pane for TerminalPane {
self.reflow_lines();
}
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);
+ self.vte_parser.advance(&mut self.grid, *byte);
}
}
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
@@ -103,7 +94,7 @@ impl Pane for TerminalPane {
match input_bytes.as_slice() {
[27, 91, 68] => {
// left arrow
- if self.cursor_key_mode {
+ if self.grid.cursor_key_mode {
// please note that in the line below, there is an ANSI escape code (27) at the beginning of the string,
// some editors will not show this
return "OD".as_bytes().to_vec();
@@ -111,7 +102,7 @@ impl Pane for TerminalPane {
}
[27, 91, 67] => {
// right arrow
- if self.cursor_key_mode {
+ if self.grid.cursor_key_mode {
// please note that in the line below, there is an ANSI escape code (27) at the beginning of the string,
// some editors will not show this
return "OC".as_bytes().to_vec();
@@ -119,7 +110,7 @@ impl Pane for TerminalPane {
}
[27, 91, 65] => {
// up arrow
- if self.cursor_key_mode {
+ if self.grid.cursor_key_mode {
// please note that in the line below, there is an ANSI escape code (27) at the beginning of the string,
// some editors will not show this
return "OA".as_bytes().to_vec();
@@ -127,7 +118,7 @@ impl Pane for TerminalPane {
}
[27, 91, 66] => {
// down arrow
- if self.cursor_key_mode {
+ if self.grid.cursor_key_mode {
// please note that in the line below, there is an ANSI escape code (27) at the beginning of the string,
// some editors will not show this
return "OB".as_bytes().to_vec();
@@ -142,10 +133,10 @@ impl Pane for TerminalPane {
self.position_and_size_override
}
fn should_render(&self) -> bool {
- self.should_render
+ self.grid.should_render
}
fn set_should_render(&mut self, should_render: bool) {
- self.should_render = should_render;
+ self.grid.should_render = should_render;
}
fn selectable(&self) -> bool {
self.selectable
@@ -175,7 +166,7 @@ impl Pane for TerminalPane {
let buffer_lines = &self.read_buffer_as_lines();
let display_cols = self.get_columns();
let mut character_styles = CharacterStyles::new();
- if self.clear_viewport_before_rendering {
+ if self.grid.clear_viewport_before_rendering {
for line_index in 0..self.grid.height {
let x = self.get_x();
let y = self.get_y();
@@ -188,7 +179,7 @@ impl Pane for TerminalPane {
vte_output.push(EMPTY_TERMINAL_CHARACTER.character);
}
}
- self.clear_viewport_before_rendering = false;
+ self.grid.clear_viewport_before_rendering = false;
}
for (row, line) in buffer_lines.iter().enumerate() {
let x = self.get_x();
@@ -212,7 +203,7 @@ impl Pane for TerminalPane {
}
character_styles.clear();
}
- self.should_render = false;
+ self.grid.should_render = false;
Some(vte_output)
} else {
None
@@ -271,40 +262,31 @@ impl Pane for TerminalPane {
}
fn scroll_up(&mut self, count: usize) {
self.grid.move_viewport_up(count);
- self.mark_for_rerender();
+ self.grid.should_render = true;
}
fn scroll_down(&mut self, count: usize) {
self.grid.move_viewport_down(count);
- self.mark_for_rerender();
+ self.grid.should_render = true;
}
fn clear_scroll(&mut self) {
self.grid.reset_viewport();
- self.mark_for_rerender();
+ self.grid.should_render = true;
}
}
impl TerminalPane {
pub fn new(pid: RawFd, position_and_size: PositionAndSize) -> TerminalPane {
let grid = Grid::new(position_and_size.rows, position_and_size.columns);
- let pending_styles = CharacterStyles::new();
-
TerminalPane {
pid,
grid,
- alternative_grid: None,
- should_render: true,
selectable: true,
- pending_styles,
position_and_size,
position_and_size_override: None,
- cursor_key_mode: false,
- clear_viewport_before_rendering: false,
max_height: None,
+ vte_parser: vte::Parser::new(),
}
}
- pub fn mark_for_rerender(&mut self) {
- self.should_render = true;
- }
pub fn get_x(&self) -> usize {
match self.position_and_size_override {
Some(position_and_size_override) => position_and_size_override.x,
@@ -333,11 +315,7 @@ impl TerminalPane {
let rows = self.get_rows();
let columns = self.get_columns();
self.grid.change_size(rows, columns);
- if let Some(alternative_grid) = self.alternative_grid.as_mut() {
- alternative_grid.change_size(rows, columns);
- }
}
-
pub fn read_buffer_as_lines(&self) -> Vec<Vec<TerminalCharacter>> {
self.grid.as_character_lines()
}
@@ -346,328 +324,4 @@ impl TerminalPane {
// (x, y)
self.grid.cursor_coordinates()
}
- pub fn rotate_scroll_region_up(&mut self, count: usize) {
- self.grid.rotate_scroll_region_up(count);
- self.mark_for_rerender();
- }
- pub fn rotate_scroll_region_down(&mut self, count: usize) {
- 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 pad_character = EMPTY_TERMINAL_CHARACTER;
- self.grid.add_canonical_line(pad_character);
- self.mark_for_rerender();
- }
- fn move_to_beginning_of_line(&mut self) {
- self.grid.move_cursor_to_beginning_of_line();
- }
- fn move_cursor_backwards(&mut self, count: usize) {
- self.grid.move_cursor_backwards(count);
- }
- fn _reset_all_ansi_codes(&mut self) {
- self.pending_styles.clear();
- }
-}
-
-impl vte::Perform for TerminalPane {
- fn print(&mut self, c: char) {
- // apparently, building TerminalCharacter like this without a "new" method
- // is a little faster
- let terminal_character = TerminalCharacter {
- character: c,
- styles: self.pending_styles,
- };
- self.grid.add_character(terminal_character);
- }
-
- fn execute(&mut self, byte: u8) {
- match byte {
- 8 => {
- // backspace
- self.move_cursor_backwards(1);
- }
- 9 => {
- // tab
- self.grid.advance_to_next_tabstop(self.pending_styles);
- }
- 10 => {
- // 0a, newline
- self.add_newline();
- }
- 13 => {
- // 0d, carriage return
- self.move_to_beginning_of_line();
- }
- _ => {}
- }
- }
-
- fn hook(&mut self, _params: &[i64], _intermediates: &[u8], _ignore: bool, _c: char) {
- // TBD
- }
-
- fn put(&mut self, _byte: u8) {
- // TBD
- }
-
- fn unhook(&mut self) {
- // TBD
- }
-
- fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {
- // TBD
- }
-
- fn csi_dispatch(&mut self, params: &[i64], _intermediates: &[u8], _ignore: bool, c: char) {
- if c == 'm' {
- self.pending_styles.add_style_from_ansi_params(params);
- } else if c == 'C' {
- // move cursor forward
- let move_by = if params[0] == 0 {
- 1
- } else {
- params[0] as usize
- };
- self.grid.move_cursor_forward_until_edge(move_by);
- } else if c == 'K' {
- // clear line (0 => right, 1 => left, 2 => all)
- if params[0] == 0 {
- let mut char_to_replace = EMPTY_TERMINAL_CHARACTER;
- char_to_replace.styles = self.pending_styles;
- self.grid
- .replace_characters_in_line_after_cursor(char_to_replace);
- } else if params[0] == 1 {
- let mut char_to_replace = EMPTY_TERMINAL_CHARACTER;
- char_to_replace.styles = self.pending_styles;
- self.grid
- .replace_characters_in_line_before_cursor(char_to_replace);
- } else if params[0] == 2 {
- self.grid.clear_cursor_line();
- }
- } else if c == 'J' {
- // clear all (0 => below, 1 => above, 2 => all, 3 => saved)
- let mut char_to_replace = EMPTY_TERMINAL_CHARACTER;
- char_to_replace.styles = self.pending_styles;
- if params[0] == 0 {
- self.grid.clear_all_after_cursor(char_to_replace);
- } else if params[0] == 2 {
- self.grid.clear_all(char_to_replace);
- }
- // TODO: implement 1
- } else if c == 'H' {
- // goto row/col
- // we subtract 1 from the row/column because these are 1 indexed
- // (except when they are 0, in which case they should be 1
- // don't look at me, I don't make the rules)
- let (row, col) = if params.len() == 1 {
- if params[0] == 0 {
- (0, params[0] as usize)
- } else {
- (params[0] as usize - 1, params[0] as usize)
- }
- } else if params[0] == 0 {
- (0, params[1] as usize - 1)
- } else {
- (params[0] as usize - 1, params[1] as usize - 1)
- };
- 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
- let move_up_count = if params[0] == 0 { 1 } else { params[0] };
- self.grid.move_cursor_up(move_up_count as usize);
- } else if c == 'B' {
- // move cursor down until edge of screen
- let move_down_count = if params[0] == 0 { 1 } else { params[0] };
- let pad_character = EMPTY_TERMINAL_CHARACTER;
- self.grid
- .move_cursor_down(move_down_count as usize, pad_character);
- } else if c == 'D' {
- let move_back_count = if params[0] == 0 {
- 1
- } else {
- params[0] as usize
- };
- self.grid.move_cursor_back(move_back_count);
- } else if c == 'l' {
- let first_intermediate_is_questionmark = match _intermediates.get(0) {
- Some(b'?') => true,
- None => false,
- _ => false,
- };
- if first_intermediate_is_questionmark {
- match params.get(0) {
- Some(&1049) => {
- if let Some(alternative_grid) = self.alternative_grid.as_mut() {
- std::mem::swap(&mut self.grid, alternative_grid);
- }
- self.alternative_grid = None;
- self.clear_viewport_before_rendering = true;
- self.mark_for_rerender();
- }
- Some(&25) => {
- self.grid.hide_cursor();
- self.mark_for_rerender();
- }
- Some(&1) => {
- self.cursor_key_mode = false;
- }
- _ => {}
- };
- }
- } else if c == 'h' {
- let first_intermediate_is_questionmark = match _intermediates.get(0) {
- Some(b'?') => true,
- None => false,
- _ => false,
- };
- if first_intermediate_is_questionmark {
- match params.get(0) {
- Some(&25) => {
- self.grid.show_cursor();
- self.mark_for_rerender();
- }
- Some(&1049) => {
- let columns = self
- .position_and_size_override
- .map(|x| x.columns)
- .unwrap_or(self.position_and_size.columns);
- let rows = self
- .position_and_size_override
- .map(|x| x.rows)
- .unwrap_or(self.position_and_size.rows);
- let current_grid =
- std::mem::replace(&mut self.grid, Grid::new(rows, columns));
- self.alternative_grid = Some(current_grid);
- self.clear_viewport_before_rendering = true;
- }
- Some(&1) => {
- self.cursor_key_mode = true;
- }
- _ => {}
- };
- }
- } else if c == 'r' {
- if params.len() > 1 {
- // minus 1 because these are 1 indexed
- let top_line_index = params[0] as usize - 1;
- let bottom_line_index = params[1] as usize - 1;
- self.grid
- .set_scroll_region(top_line_index, bottom_line_index);
- self.grid.show_cursor();
- } else {
- self.grid.clear_scroll_region();
- }
- } else if c == 't' {
- // TBD - title?
- } else if c == 'n' {
- // TBD - device status report
- } else if c == 'c' {
- // TBD - identify terminal
- } else if c == 'M' {
- // delete lines if currently inside scroll region
- let line_count_to_delete = if params[0] == 0 {
- 1
- } else {
- params[0] as usize
- };
- let pad_character = EMPTY_TERMINAL_CHARACTER;
- self.grid
- .delete_lines_in_scroll_region(line_count_to_delete, pad_character);
- } else if c == 'L' {
- // insert blank lines if inside scroll region
- let line_count_to_add = if params[0] == 0 {
- 1
- } else {
- params[0] as usize
- };
- let pad_character = EMPTY_TERMINAL_CHARACTER;
- self.grid
- .add_empty_lines_in_scroll_region(line_count_to_add, pad_character);
- } else if c == 'q' {
- // ignore for now to run on mac
- } else if c == 'G' {
- let column = if params[0] == 0 {
- 0
- } else {
- params[0] as usize - 1
- };
- self.grid.move_cursor_to_column(column);
- } else if c == 'd' {
- // goto line
- let line = if params[0] == 0 {
- 1
- } else {
- // minus 1 because this is 1 indexed
- params[0] as usize - 1
- };
- let pad_character = EMPTY_TERMINAL_CHARACTER;
- self.grid.move_cursor_to_line(line, pad_character);
- } else if c == 'P' {
- // erase characters
- let count = if params[0] == 0 {
- 1
- } else {
- params[0] as usize
- };
- self.grid.erase_characters(count, self.pending_styles);
- } else if c == 'X' {
- // erase characters and replace with empty characters of current style
- let count = if params[0] == 0 {
- 1
- } else {
- params[0] as usize
- };
- self.grid
- .replace_with_empty_chars(count, self.pending_styles);
- } else if c == 'T' {
- /*
- * 124 54 T SD
- * Scroll down, new lines inserted at top of screen
- * [4T = Scroll down 4, bring previous lines back into view
- */
- let line_count: i64 = *params.get(0).expect("A number of lines was expected.");
-
- if line_count >= 0 {
- self.rotate_scroll_region_up(line_count as usize);
- } else {
- self.rotate_scroll_region_down(line_count.abs() as usize);
- }
- } else if c == 'S' {
- // move scroll up
- let count = if params[0] == 0 {
- 1
- } else {
- params[0] as usize
- };
- 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?
- self.grid
- .add_empty_lines_in_scroll_region(count, pad_character);
- } else {
- let _ = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params));
- }
- }
-
- fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, byte: u8) {
- 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 35fc3915..656c6093 100644
--- a/src/client/tab.rs
+++ b/src/client/tab.rs
@@ -20,7 +20,10 @@ use std::{io::Write, sync::mpsc::channel};
use zellij_tile::data::{colors, Event, InputMode, ModeInfo, Palette};
const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this
-const MIN_TERMINAL_HEIGHT: usize = 2;
+
+// MIN_TERMINAL_HEIGHT here must be larger than the height of any of the status bars
+// this is a dirty hack until we implement fixed panes
+const MIN_TERMINAL_HEIGHT: usize = 3;
const MIN_TERMINAL_WIDTH: usize = 4;
type BorderAndPaneIds = (usize, Vec<PaneId>);
@@ -67,7 +70,6 @@ pub struct Tab {
pub send_pty_instructions: SenderWithContext<PtyInstruction>,
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,
pub input_mode: InputMode,
@@ -208,6 +210,7 @@ pub trait Pane {
impl Tab {
// FIXME: Too many arguments here! Maybe bundle all of the senders for the whole program in a struct?
+ #[allow(clippy::too_many_arguments)]
pub fn new(
index: usize,
position: usize,
@@ -250,7 +253,6 @@ impl Tab {
send_app_instructions,
send_pty_instructions,
send_plugin_instructions,
- expansion_boundary: None,
should_clear_display_before_rendering: false,
mode_info,
input_mode,
@@ -293,9 +295,6 @@ impl Tab {
let mut new_pids = new_pids.iter();
for (layout, position_and_size) in positions_and_size {
// Just a regular terminal
- if layout.expansion_boundary {
- self.expansion_boundary = Some(*position_and_size);
- }
if let Some(plugin) = &layout.plugin {
let (pid_tx, pid_rx) = channel();
self.send_plugin_instructions
@@ -365,9 +364,7 @@ impl Tab {
let (id_of_terminal_to_check, terminal_to_check) = id_and_terminal_to_check;
let terminal_size = (terminal_to_check.rows() * CURSOR_HEIGHT_WIDTH_RATIO)
* terminal_to_check.columns();
- let terminal_pos_and_size = terminal_to_check.position_and_size();
let terminal_can_be_split = terminal_to_check.columns() >= MIN_TERMINAL_WIDTH
- && self.pos_and_size_is_within_expansion_boundary(terminal_pos_and_size)
&& terminal_to_check.rows() >= MIN_TERMINAL_HEIGHT
&& ((terminal_to_check.columns() > terminal_to_check.min_width() * 2)
|| (terminal_to_check.rows() > terminal_to_check.min_height() * 2));
@@ -627,19 +624,17 @@ impl Tab {
let active_terminal = self.panes.get_mut(&active_pane_id).unwrap();
active_terminal.reset_size_and_position_override();
} else {
- let expand_to = self.expansion_boundary.unwrap_or(self.full_screen_ws);
let panes = self.get_panes();
- let pane_ids_to_hide = panes.filter_map(|(&id, pane)| {
- let position_and_size_for_pane = pane.position_and_size();
- if id != active_pane_id
- && self
- .pos_and_size_is_within_expansion_boundary(position_and_size_for_pane)
- {
- Some(id)
- } else {
- None
- }
- });
+ let pane_ids_to_hide =
+ panes.filter_map(
+ |(&id, _)| {
+ if id != active_pane_id {
+ Some(id)
+ } else {
+ None
+ }
+ },
+ );
self.panes_to_hide = pane_ids_to_hide.collect();
if self.panes_to_hide.is_empty() {
// nothing to do, pane is already as fullscreen as it can be, let's bail
@@ -647,9 +642,9 @@ impl Tab {
} 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,
+ self.full_screen_ws.x,
+ self.full_screen_ws.y,
+ &self.full_screen_ws,
);
}
}
@@ -1503,21 +1498,6 @@ impl Tab {
self.increase_pane_width_left(&terminal_id, count);
}
}
- fn pos_and_size_is_within_expansion_boundary(&self, pos_and_size: PositionAndSize) -> bool {
- match self.expansion_boundary {
- Some(expansion_boundary) => {
- pos_and_size.x >= expansion_boundary.x
- && pos_and_size.x < expansion_boundary.x + expansion_boundary.columns
- && pos_and_size.y >= expansion_boundary.y
- && pos_and_size.y < expansion_boundary.y + expansion_boundary.rows
- && pos_and_size.x + pos_and_size.columns
- <= expansion_boundary.x + expansion_boundary.columns
- && pos_and_size.y + pos_and_size.rows
- <= expansion_boundary.y + expansion_boundary.rows
- }
- None => true, // no expansion boundary, no problem
- }
- }
fn can_increase_pane_and_surroundings_right(
&self,
pane_id: &PaneId,
@@ -1533,17 +1513,11 @@ impl Tab {
}
let mut new_pos_and_size_for_pane = pane.position_and_size();
new_pos_and_size_for_pane.columns += increase_by;
- if !self.pos_and_size_is_within_expansion_boundary(new_pos_and_size_for_pane) {
- return false;
- }
if let Some(panes_to_the_right) = self.pane_ids_directly_right_of(&pane_id) {
return panes_to_the_right.iter().all(|id| {
let p = self.panes.get(id).unwrap();
- let position_and_size_for_pane = p.position_and_size();
- self.pos_and_size_is_within_expansion_boundary(position_and_size_for_pane)
- && p.columns() > increase_by
- && p.columns() - increase_by >= p.min_width()
+ p.columns() > increase_by && p.columns() - increase_by >= p.min_width()
});
} else {
false
@@ -1564,17 +1538,11 @@ impl Tab {
}
let mut new_pos_and_size_for_pane = pane.position_and_size();
new_pos_and_size_for_pane.x = new_pos_and_size_for_pane.x.saturating_sub(increase_by);
- if !self.pos_and_size_is_within_expansion_boundary(new_pos_and_size_for_pane) {
- return false;
- }
if let Some(panes_to_the_left) = self.pane_ids_directly_left_of(&pane_id) {
return panes_to_the_left.iter().all(|id| {
let p = self.panes.get(id).unwrap();
- let position_and_size_for_pane = p.position_and_size();
- self.pos_and_size_is_within_expansion_boundary(position_and_size_for_pane)
- && p.columns() > increase_by
- && p.columns() - increase_by >= p.min_width()
+ p.columns() > increase_by && p.columns() - increase_by >= p.min_width()
});
} else {
false
@@ -1595,17 +1563,11 @@ impl Tab {
}
let mut new_pos_and_size_for_pane = pane.position_and_size();
new_pos_and_size_for_pane.rows += increase_by;
- if !self.pos_and_size_is_within_expansion_boundary(new_pos_and_size_for_pane) {
- return false;
- }
if let Some(panes_below) = self.pane_ids_directly_below(&pane_id) {
return panes_below.iter().all(|id| {
let p = self.panes.get(id).unwrap();
- let position_and_size_for_pane = p.position_and_size();
- self.pos_and_size_is_within_expansion_boundary(position_and_size_for_pane)
- && p.rows() > increase_by
- && p.rows() - increase_by >= p.min_height()
+ p.rows() > increase_by && p.rows() - increase_by >= p.min_height()
});
} else {
false
@@ -1622,17 +1584,11 @@ impl Tab {
}
let mut new_pos_and_size_for_pane = pane.position_and_size();
new_pos_and_size_for_pane.y = new_pos_and_size_for_pane.y.saturating_sub(increase_by);
- if !self.pos_and_size_is_within_expansion_boundary(new_pos_and_size_for_pane) {
- return false;
- }
if let Some(panes_above) = self.pane_ids_directly_above(&pane_id) {
return panes_above.iter().all(|id| {
let p = self.panes.get(id).unwrap();
- let position_and_size_for_pane = p.position_and_size();
- self.pos_and_size_is_within_expansion_boundary(position_and_size_for_pane)
- && p.rows() > increase_by
- && p.rows() - increase_by >= p.min_height()
+ p.rows() > increase_by && p.rows() - increase_by >= p.min_height()
});
} else {
false
@@ -1649,11 +1605,9 @@ impl Tab {
if let Some(panes_to_the_left) = self.pane_ids_directly_left_of(&pane_id) {
return panes_to_the_left.iter().all(|id| {
let p = self.panes.get(id).unwrap();
- let position_and_size_for_pane = p.position_and_size();
- self.pos_and_size_is_within_expansion_boundary(position_and_size_for_pane)
- && p.max_width()
- .map(|max_width| p.columns() + reduce_by <= max_width)
- .unwrap_or(true) // no max width, increase to your heart's content
+ p.max_width()
+ .map(|max_width| p.columns() + reduce_by <= max_width)
+ .unwrap_or(true) // no max width, increase to your heart's content
});
} else {
false
@@ -1670,11 +1624,9 @@ impl Tab {
if let Some(panes_to_the_right) = self.pane_ids_directly_right_of(&pane_id) {
return panes_to_the_right.iter().all(|id| {
let p = self.panes.get(id).unwrap();
- let position_and_size_for_pane = p.position_and_size();
- self.pos_and_size_is_within_expansion_boundary(position_and_size_for_pane)
- && p.max_width()
- .map(|max_width| p.columns() + reduce_by <= max_width)
- .unwrap_or(true) // no max width, increase to your heart's content
+ p.max_width()
+ .map(|max_width| p.columns() + reduce_by <= max_width)
+ .unwrap_or(true) // no max width, increase to your heart's content
});
} else {
false
@@ -1691,11 +1643,9 @@ impl Tab {
if let Some(panes_above) = self.pane_ids_directly_above(&pane_id) {
return panes_above.iter().all(|id| {
let p = self.panes.get(id).unwrap();
- let position_and_size_for_pane = p.position_and_size();
- self.pos_and_size_is_within_expansion_boundary(position_and_size_for_pane)
- && p.max_height()
- .map(|max_height| p.rows() + reduce_by <= max_height)
- .unwrap_or(true) // no max height, increase to your heart's content
+ p.max_height()
+ .map(|max_height| p.rows() + reduce_by <= max_height)
+ .unwrap_or(true) // no max height, increase to your heart's content
});
} else {
false
@@ -1712,11 +1662,9 @@ impl Tab {
if let Some(panes_below) = self.pane_ids_directly_below(&pane_id) {
return panes_below.iter().all(|id| {
let p = self.panes.get(id).unwrap();
- let position_and_size_for_pane = p.position_and_size();
- self.pos_and_size_is_within_expansion_boundary(position_and_size_for_pane)
- && p.max_height()
- .map(|max_height| p.rows() + reduce_by <= max_height)
- .unwrap_or(true) // no max height, increase to your heart's content
+ p.max_height()
+ .map(|max_height| p.rows() + reduce_by <= max_height)
+ .unwrap_or(true) // no max height, increase to your heart's content
});
} else {
false
@@ -1724,27 +1672,18 @@ impl Tab {
}
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
+ // this is not ideal, we can improve this
self.toggle_active_pane_fullscreen();
}
- match PaneResizer::new(&mut self.panes, &mut self.os_api)
- .resize(self.full_screen_ws, new_screen_size)
+ if let Some((column_difference, row_difference)) =
+ 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 => {}
+ self.should_clear_display_before_rendering = true;
+ self.full_screen_ws.columns =
+ (self.full_screen_ws.columns as isize + column_difference) as usize;
+ self.full_screen_ws.rows =
+ (self.full_screen_ws.rows as isize + row_difference) as usize;
};
}
pub fn resize_left(&mut self) {
@@ -2242,6 +2181,30 @@ impl Tab {
self.render();
}
}
+ pub fn scroll_active_terminal_up_page(&mut self) {
+ if let Some(active_terminal_id) = self.get_active_terminal_id() {
+ let active_terminal = self
+ .panes
+ .get_mut(&PaneId::Terminal(active_terminal_id))
+ .unwrap();
+ // prevent overflow when row == 0
+ let scroll_columns = active_terminal.rows().max(1) - 1;
+ active_terminal.scroll_up(scroll_columns);
+ self.render();
+ }
+ }
+ pub fn scroll_active_terminal_down_page(&mut self) {
+ if let Some(active_terminal_id) = self.get_active_terminal_id() {
+ let active_terminal = self
+ .panes
+ .get_mut(&PaneId::Terminal(active_terminal_id))
+ .unwrap();
+ // prevent overflow when row == 0
+ let scroll_columns = active_terminal.rows().max(1) - 1;
+ active_terminal.scroll_down(scroll_columns);
+ self.render();
+ }
+ }
pub fn clear_active_terminal_scroll(&mut self) {
if let Some(active_terminal_id) = self.get_active_terminal_id() {
let active_terminal = self