feat(ui): overhauled resize and layout systems

* refactor(panes): move to parametric pane sizes

* Fixed the simpler errors by casting to usize

* The least I can do is pass the formatting check...

* Move to stable toolchain

* Well, it compiles?

* And now it doesn't! ;)

* Baseline functionality with the new Dimension type

* Working POC for percent-based resizing

* REVERT THIS COMMIT – DELETES TESTS

* Perfected the discrete resize algorithm

* Fixed fixed-size panes

* Basic bidirectional resize

* feat(resize): finalised parametric resize algorithm

* Reduce the logging level a bit

* Fixed nested layouts using percents

* Bug squishing for implicit sizing

* Here is a funky (read: rubbish) rounding approach

* And now it's gone again!

* Improve discretisation algorithm to fix rounding errors

* Fix the last layout bug (maybe?)

* Mixed explicit and implied percents work now

* Let's pretend that didn't happen...

* Make things a bit less crashy

* Crash slightly more for now (to find bugs)

* Manaually splitting of panes works now

* Start moving to percent-based resizes

* Everything but fullscreen seems to be working

* Fix compilatation errors

* Culled a massive amount of border code

* Why not pause to please rustfmt?

* Turns out I was still missing a few tests...

* Bringing back even more tests!

* Fix tests and pane boarders

* Fix the resize system without gaps

* Fix content offset

* Fixed a bug with pane closing

* Add a hack to fix setting of the viewport

* Fix toggling between shared borders and frames

* fix(tests): make e2e properly use PaneGeom

* style(fmt): make rustfmt happy

* Revert unintentional rounding of borders

* Purge some old borderless stuff

* Fix busted tab-bar shrinking

* Update E2E tests

* Finish implementing fullscreen!

* Don't crash anymore?

* Fix (almost) all tests

* Fix a lack of tab-stops

* All tests passing

* I really can't be bothered to debug a CI issue

* Tie up loose ends

* Knock out some lingering FIXMEs

* Continue to clean things up

* Change some naming and address FIXMEs

* Cull more code + FIXMEs

* Refactor of the resize system + polish

* Only draw frames when absolutely necessary

* Fix the tab-bar crash

* Fix rendering of boarders on reattach

* Fix resizing at small pane sizes

* Deduplicate code in the layout system

* Update tab-bar WASM

* Fixed the pinching of panes during resize

* Unexpose needlessly public type

* Add back a lost test

* Re-add tab tests and get them to compile

* All tabs need layouts

* Start fixing tests + bug in main

* Stabilize the resize algorithm rounding

* All tests from main are now passing

* Cull more dead code
This commit is contained in:
Brooks Rady 2021-08-28 17:46:24 +01:00 committed by GitHub
parent 1544de2665
commit 76a5bc8a05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 3480 additions and 4771 deletions

68
Cargo.lock generated
View File

@ -4,11 +4,11 @@ version = 3
[[package]]
name = "addr2line"
version = "0.15.2"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a2e47a1fbe209ee101dd6d61285226744c6c8d3c21c8dc878ba6cb9f467f3a"
checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd"
dependencies = [
"gimli 0.24.0",
"gimli 0.25.0",
]
[[package]]
@ -227,16 +227,16 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "backtrace"
version = "0.3.60"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7815ea54e4d821e791162e078acbebfd6d8c8939cd559c9335dceb1c8ca7282"
checksum = "e7a905d892734eea339e896738c14b9afce22b5318f64b951e70bf3844419b01"
dependencies = [
"addr2line",
"cc",
"cfg-if 1.0.0",
"libc",
"miniz_oxide",
"object 0.25.3",
"object 0.26.0",
"rustc-demangle",
]
@ -715,9 +715,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fastrand"
version = "1.4.1"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77b705829d1e87f762c2df6da140b26af5839e1033aa84aa5f56bb688e4e1bdb"
checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e"
dependencies = [
"instant",
]
@ -736,9 +736,9 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "futures"
version = "0.3.15"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27"
checksum = "1adc00f486adfc9ce99f77d717836f0c5aa84965eb0b4f051f4e83f7cab53f8b"
dependencies = [
"futures-channel",
"futures-core",
@ -751,9 +751,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.15"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2"
checksum = "74ed2411805f6e4e3d9bc904c95d5d423b89b3b25dc0250aa74729de20629ff9"
dependencies = [
"futures-core",
"futures-sink",
@ -761,15 +761,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.15"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1"
checksum = "af51b1b4a7fdff033703db39de8802c673eb91855f2e0d47dcf3bf2c0ef01f99"
[[package]]
name = "futures-executor"
version = "0.3.15"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79"
checksum = "4d0d535a57b87e1ae31437b892713aee90cd2d7b0ee48727cd11fc72ef54761c"
dependencies = [
"futures-core",
"futures-task",
@ -778,9 +778,9 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.15"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1"
checksum = "0b0e06c393068f3a6ef246c75cdca793d6a46347e75286933e5e75fd2fd11582"
[[package]]
name = "futures-lite"
@ -799,9 +799,9 @@ dependencies = [
[[package]]
name = "futures-macro"
version = "0.3.15"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121"
checksum = "c54913bae956fb8df7f4dc6fc90362aa72e69148e3f39041fbe8742d21e0ac57"
dependencies = [
"autocfg",
"proc-macro-hack",
@ -812,21 +812,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.15"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282"
checksum = "c0f30aaa67363d119812743aa5f33c201a7a66329f97d1a887022971feea4b53"
[[package]]
name = "futures-task"
version = "0.3.15"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae"
checksum = "bbe54a98670017f3be909561f6ad13e810d9a51f3f061b902062ca3da80799f2"
[[package]]
name = "futures-util"
version = "0.3.15"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967"
checksum = "67eb846bfd58e44a8481a00049e82c43e0ccb5d61f8dc071057cb19249dd4d78"
dependencies = [
"autocfg",
"futures-channel",
@ -897,9 +897,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.24.0"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189"
checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
[[package]]
name = "gloo-timers"
@ -1325,9 +1325,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.25.3"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a38f2be3697a57b4060074ff41b44c16870d916ad7877c17696e063257482bc7"
checksum = "c55827317fb4c08822499848a14237d2874d6f139828893017237e7ab93eb386"
dependencies = [
"memchr",
]
@ -1500,9 +1500,9 @@ checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
[[package]]
name = "proc-macro2"
version = "1.0.27"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
dependencies = [
"unicode-xid",
]
@ -1966,9 +1966,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.73"
version = "1.0.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
dependencies = [
"proc-macro2",
"quote",

View File

@ -57,4 +57,3 @@ assets = [
[features]
disable_automatic_asset_installation = []
parametric_resize_beta = []

View File

@ -70,7 +70,7 @@ args = ["clippy", "--", "@@split(CARGO_MAKE_TASK_ARGS,;)"]
# Release building and installing Zellij
[tasks.install]
workspace = false
dependencies = ["build-plugins-release", "wasm-opt-plugins", "build-release", "manpage"]
dependencies = ["wasm-opt-plugins", "build-release", "manpage"]
script_runner = "@duckscript"
script = '''
if is_dir ${CARGO_MAKE_TASK_ARGS}
@ -89,6 +89,7 @@ env = { "CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS" = ["default-plugins/status-bar",
run_task = { name = "build", fork = true }
[tasks.wasm-opt-plugins]
dependencies = ["build-plugins-release"]
script_runner = "@duckscript"
script = '''
plugins = glob_array ${CARGO_TARGET_DIR}/wasm32-wasi/release/*.wasm
@ -134,6 +135,7 @@ args = ["build", "--verbose", "--target", "x86_64-unknown-linux-musl"]
# Run e2e tests - we mark the e2e tests as "ignored" so they will not be run with the normal ones
[tasks.e2e-test]
workspace = false
dependencies = ["build-e2e"]
command = "cargo"
args = ["test", "--", "--ignored", "--nocapture", "--test-threads", "1", "@@split(CARGO_MAKE_TASK_ARGS,;)"]

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -135,8 +135,6 @@ fn color_elements(palette: Palette) -> ColoredElements {
impl ZellijPlugin for State {
fn load(&mut self) {
set_selectable(false);
set_invisible_borders(true);
set_fixed_height(2);
subscribe(&[
EventType::ModeUpdate,
EventType::CopyToClipboard,

View File

@ -5,9 +5,7 @@ use zellij_tile::prelude::*;
use zellij_tile_utils::style;
fn get_current_title_len(current_title: &[LinePart]) -> usize {
current_title
.iter()
.fold(0, |acc, title_part| acc + title_part.len)
current_title.iter().map(|p| p.len).sum()
}
fn populate_tabs_in_tab_line(
@ -144,20 +142,29 @@ fn add_next_tabs_msg(
title_bar.push(right_more_message);
}
fn tab_line_prefix(session_name: Option<&str>, palette: Palette) -> LinePart {
let mut prefix_text = " Zellij ".to_string();
if let Some(name) = session_name {
prefix_text.push_str(&format!("({}) ", name));
}
fn tab_line_prefix(session_name: Option<&str>, palette: Palette, cols: usize) -> Vec<LinePart> {
let prefix_text = " Zellij ".to_string();
let prefix_text_len = prefix_text.chars().count();
let prefix_styled_text = style!(palette.white, palette.cyan)
.bold()
.paint(prefix_text);
LinePart {
let mut parts = vec![LinePart {
part: format!("{}", prefix_styled_text),
len: prefix_text_len,
}];
if let Some(name) = session_name {
let name_part = format!("({}) ", name);
let name_part_len = name_part.chars().count();
let name_part_styled_text = style!(palette.white, palette.cyan).bold().paint(name_part);
if cols.saturating_sub(prefix_text_len) >= name_part_len {
parts.push(LinePart {
part: format!("{}", name_part_styled_text),
len: name_part_len,
})
}
}
parts
}
pub fn tab_separator(capabilities: PluginCapabilities) -> &'static str {
@ -176,7 +183,7 @@ pub fn tab_line(
palette: Palette,
capabilities: PluginCapabilities,
) -> Vec<LinePart> {
let mut tabs_to_render: Vec<LinePart> = vec![];
let mut tabs_to_render = Vec::new();
let mut tabs_after_active = all_tabs.split_off(active_tab_index);
let mut tabs_before_active = all_tabs;
let active_tab = if !tabs_after_active.is_empty() {
@ -184,14 +191,17 @@ pub fn tab_line(
} else {
tabs_before_active.pop().unwrap()
};
tabs_to_render.push(active_tab);
let mut prefix = tab_line_prefix(session_name, palette, cols);
let prefix_len = get_current_title_len(&prefix);
if prefix_len + active_tab.len <= cols {
tabs_to_render.push(active_tab);
}
let prefix = tab_line_prefix(session_name, palette);
populate_tabs_in_tab_line(
&mut tabs_before_active,
&mut tabs_after_active,
&mut tabs_to_render,
cols.saturating_sub(prefix.len),
cols.saturating_sub(prefix_len),
);
let mut tab_line: Vec<LinePart> = vec![];
@ -200,7 +210,7 @@ pub fn tab_line(
&mut tabs_before_active,
&mut tabs_to_render,
&mut tab_line,
cols.saturating_sub(prefix.len),
cols.saturating_sub(prefix_len),
palette,
tab_separator(capabilities),
);
@ -210,11 +220,11 @@ pub fn tab_line(
add_next_tabs_msg(
&mut tabs_after_active,
&mut tab_line,
cols.saturating_sub(prefix.len),
cols.saturating_sub(prefix_len),
palette,
tab_separator(capabilities),
);
}
tab_line.insert(0, prefix);
tab_line
prefix.append(&mut tab_line);
prefix
}

View File

@ -25,8 +25,6 @@ register_plugin!(State);
impl ZellijPlugin for State {
fn load(&mut self) {
set_selectable(false);
set_invisible_borders(true);
set_fixed_height(1);
subscribe(&[EventType::TabUpdate, EventType::ModeUpdate]);
}

View File

@ -1,4 +1,4 @@
[toolchain]
channel = "beta"
channel = "stable"
components = ["rustfmt", "clippy", "rust-analysis"]
targets = ["wasm32-wasi"]

View File

@ -1,7 +1,7 @@
#![allow(unused)]
use ::insta::assert_snapshot;
use zellij_utils::{pane_size::PositionAndSize, position::Position};
use zellij_utils::{pane_size::Size, position::Position};
use rand::Rng;
@ -71,19 +71,16 @@ pub fn normal_mouse_report(position: Position, button: u8) -> Vec<u8> {
#[test]
#[ignore]
pub fn starts_with_one_terminal() {
let fake_win_size = PositionAndSize {
let fake_win_size = Size {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let last_snapshot = RemoteRunner::new("starts_with_one_terminal", fake_win_size, None)
let last_snapshot = RemoteRunner::new("starts_with_one_terminal", fake_win_size)
.add_step(Step {
name: "Wait for app to load",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
{
step_is_complete = true;
}
@ -97,20 +94,17 @@ pub fn starts_with_one_terminal() {
#[test]
#[ignore]
pub fn split_terminals_vertically() {
let fake_win_size = PositionAndSize {
let fake_win_size = Size {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let last_snapshot = RemoteRunner::new("split_terminals_vertically", fake_win_size, None)
let last_snapshot = RemoteRunner::new("split_terminals_vertically", fake_win_size)
.add_step(Step {
name: "Split pane to the right",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
{
remote_terminal.send_key(&PANE_MODE);
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
@ -139,23 +133,16 @@ pub fn split_terminals_vertically() {
#[test]
#[ignore]
pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() {
let fake_win_size = PositionAndSize {
cols: 8,
rows: 20,
x: 0,
y: 0,
..Default::default()
};
let fake_win_size = Size { cols: 8, rows: 20 };
let last_snapshot = RemoteRunner::new(
"cannot_split_terminals_vertically_when_active_terminal_is_too_small",
fake_win_size,
None,
)
.add_step(Step {
name: "Split pane to the right",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2) {
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2) {
remote_terminal.send_key(&PANE_MODE);
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
// back to normal mode after split
@ -178,7 +165,7 @@ pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() {
name: "Wait for text to appear",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(5, 2) && remote_terminal.snapshot_contains("Hi!")
if remote_terminal.cursor_position_is(6, 2) && remote_terminal.snapshot_contains("Hi!")
{
step_is_complete = true;
}
@ -192,19 +179,16 @@ pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() {
#[test]
#[ignore]
pub fn scrolling_inside_a_pane() {
let fake_win_size = PositionAndSize {
let fake_win_size = Size {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let last_snapshot = RemoteRunner::new("scrolling_inside_a_pane", fake_win_size, None)
let last_snapshot = RemoteRunner::new("scrolling_inside_a_pane", fake_win_size)
.add_step(Step {
name: "Split pane to the right",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
{
remote_terminal.send_key(&PANE_MODE);
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
@ -279,19 +263,16 @@ pub fn scrolling_inside_a_pane() {
#[test]
#[ignore]
pub fn toggle_pane_fullscreen() {
let fake_win_size = PositionAndSize {
let fake_win_size = Size {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let last_snapshot = RemoteRunner::new("toggle_pane_fullscreen", fake_win_size, None)
let last_snapshot = RemoteRunner::new("toggle_pane_fullscreen", fake_win_size)
.add_step(Step {
name: "Split pane to the right",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
{
remote_terminal.send_key(&PANE_MODE);
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
@ -321,7 +302,7 @@ pub fn toggle_pane_fullscreen() {
name: "Wait for pane to become fullscreen",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(2, 2) {
if remote_terminal.cursor_position_is(3, 2) {
// cursor is in full screen pane now
step_is_complete = true;
}
@ -335,19 +316,16 @@ pub fn toggle_pane_fullscreen() {
#[test]
#[ignore]
pub fn open_new_tab() {
let fake_win_size = PositionAndSize {
let fake_win_size = Size {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let last_snapshot = RemoteRunner::new("open_new_tab", fake_win_size, None)
let last_snapshot = RemoteRunner::new("open_new_tab", fake_win_size)
.add_step(Step {
name: "Split pane to the right",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
{
remote_terminal.send_key(&PANE_MODE);
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
@ -377,7 +355,7 @@ pub fn open_new_tab() {
name: "Wait for new tab to open",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(2, 2)
if remote_terminal.cursor_position_is(3, 2)
&& remote_terminal.tip_appears()
&& remote_terminal.snapshot_contains("Tab #2")
&& remote_terminal.status_bar_appears()
@ -395,19 +373,16 @@ pub fn open_new_tab() {
#[test]
#[ignore]
pub fn close_pane() {
let fake_win_size = PositionAndSize {
let fake_win_size = Size {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let last_snapshot = RemoteRunner::new("close_pane", fake_win_size, None)
let last_snapshot = RemoteRunner::new("close_pane", fake_win_size)
.add_step(Step {
name: "Split pane to the right",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
{
remote_terminal.send_key(&PANE_MODE);
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
@ -437,7 +412,7 @@ pub fn close_pane() {
name: "Wait for pane to close",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(2, 2) && remote_terminal.tip_appears() {
if remote_terminal.cursor_position_is(3, 2) && remote_terminal.tip_appears() {
// cursor is in the original pane
step_is_complete = true;
}
@ -451,19 +426,16 @@ pub fn close_pane() {
#[test]
#[ignore]
pub fn exit_zellij() {
let fake_win_size = PositionAndSize {
let fake_win_size = Size {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let last_snapshot = RemoteRunner::new("exit_zellij", fake_win_size, None)
let last_snapshot = RemoteRunner::new("exit_zellij", fake_win_size)
.add_step(Step {
name: "Wait for app to load",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
{
remote_terminal.send_key(&QUIT);
step_is_complete = true;
@ -490,19 +462,16 @@ pub fn exit_zellij() {
#[test]
#[ignore]
pub fn closing_last_pane_exits_zellij() {
let fake_win_size = PositionAndSize {
let fake_win_size = Size {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let last_snapshot = RemoteRunner::new("closing_last_pane_exits_zellij", fake_win_size, None)
let last_snapshot = RemoteRunner::new("closing_last_pane_exits_zellij", fake_win_size)
.add_step(Step {
name: "Close pane",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
{
remote_terminal.send_key(&PANE_MODE);
remote_terminal.send_key(&CLOSE_PANE_IN_PANE_MODE);
@ -528,19 +497,16 @@ pub fn closing_last_pane_exits_zellij() {
#[test]
#[ignore]
pub fn resize_pane() {
let fake_win_size = PositionAndSize {
let fake_win_size = Size {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let last_snapshot = RemoteRunner::new("resize_pane", fake_win_size, None)
let last_snapshot = RemoteRunner::new("resize_pane", fake_win_size)
.add_step(Step {
name: "Split pane to the right",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
{
remote_terminal.send_key(&PANE_MODE);
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
@ -570,7 +536,7 @@ pub fn resize_pane() {
name: "Wait for pane to be resized",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(53, 2) && remote_terminal.tip_appears() {
if remote_terminal.cursor_position_is(57, 2) && remote_terminal.tip_appears() {
// pane has been resized
step_is_complete = true;
}
@ -584,19 +550,16 @@ pub fn resize_pane() {
#[test]
#[ignore]
pub fn lock_mode() {
let fake_win_size = PositionAndSize {
let fake_win_size = Size {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let last_snapshot = RemoteRunner::new("lock_mode", fake_win_size, None)
let last_snapshot = RemoteRunner::new("lock_mode", fake_win_size)
.add_step(Step {
name: "Enter lock mode",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
{
remote_terminal.send_key(&LOCK_MODE);
step_is_complete = true;
@ -621,7 +584,7 @@ pub fn lock_mode() {
name: "Wait for terminal to render sent keys",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(6, 2) {
if remote_terminal.cursor_position_is(7, 2) {
// text has been entered into the only terminal pane
step_is_complete = true;
}
@ -636,19 +599,16 @@ pub fn lock_mode() {
#[ignore]
pub fn resize_terminal_window() {
// this checks the resizing of the whole terminal window (reaction to SIGWINCH) and not just one pane
let fake_win_size = PositionAndSize {
let fake_win_size = Size {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let last_snapshot = RemoteRunner::new("resize_terminal_window", fake_win_size, None)
let last_snapshot = RemoteRunner::new("resize_terminal_window", fake_win_size)
.add_step(Step {
name: "Split pane to the right",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
{
remote_terminal.send_key(&PANE_MODE);
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
@ -675,7 +635,7 @@ pub fn resize_terminal_window() {
name: "wait for terminal to be resized and app to be re-rendered",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(43, 2) && remote_terminal.tip_appears() {
if remote_terminal.cursor_position_is(53, 2) && remote_terminal.tip_appears() {
// size has been changed
step_is_complete = true;
}
@ -689,98 +649,87 @@ pub fn resize_terminal_window() {
#[test]
#[ignore]
pub fn detach_and_attach_session() {
let fake_win_size = PositionAndSize {
let fake_win_size = Size {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let session_id = rand::thread_rng().gen_range(0..10000);
let session_name = format!("session_{}", session_id);
let last_snapshot = RemoteRunner::new(
"detach_and_attach_session",
fake_win_size,
Some(session_name),
)
.add_step(Step {
name: "Split pane to the right",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2) {
remote_terminal.send_key(&PANE_MODE);
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
// back to normal mode after split
remote_terminal.send_key(&ENTER);
step_is_complete = true;
}
step_is_complete
},
})
.add_step(Step {
name: "Send some text to the active pane",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
// new pane has been opened and focused
remote_terminal.send_key(&"I am some text".as_bytes());
step_is_complete = true;
}
step_is_complete
},
})
.add_step(Step {
name: "Detach session",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(77, 2) {
remote_terminal.send_key(&SESSION_MODE);
remote_terminal.send_key(&DETACH_IN_SESSION_MODE);
// text has been entered
step_is_complete = true;
}
step_is_complete
},
})
.add_step(Step {
name: "Reattach session",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if !remote_terminal.status_bar_appears() {
// we don't see the toolbar, so we can assume we've already detached
remote_terminal.attach_to_original_session();
step_is_complete = true;
}
step_is_complete
},
})
.add_step(Step {
name: "Wait for session to be attached",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(77, 2) {
// we're back inside the session
step_is_complete = true;
}
step_is_complete
},
})
.run_all_steps();
let last_snapshot = RemoteRunner::new("detach_and_attach_session", fake_win_size)
.add_step(Step {
name: "Split pane to the right",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
{
remote_terminal.send_key(&PANE_MODE);
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
// back to normal mode after split
remote_terminal.send_key(&ENTER);
step_is_complete = true;
}
step_is_complete
},
})
.add_step(Step {
name: "Send some text to the active pane",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
// new pane has been opened and focused
remote_terminal.send_key(&"I am some text".as_bytes());
step_is_complete = true;
}
step_is_complete
},
})
.add_step(Step {
name: "Detach session",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(77, 2) {
remote_terminal.send_key(&SESSION_MODE);
remote_terminal.send_key(&DETACH_IN_SESSION_MODE);
// text has been entered
step_is_complete = true;
}
step_is_complete
},
})
.add_step(Step {
name: "Reattach session",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if !remote_terminal.status_bar_appears() {
// we don't see the toolbar, so we can assume we've already detached
remote_terminal.attach_to_original_session();
step_is_complete = true;
}
step_is_complete
},
})
.add_step(Step {
name: "Wait for session to be attached",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(77, 2) {
// we're back inside the session
step_is_complete = true;
}
step_is_complete
},
})
.run_all_steps();
assert_snapshot!(last_snapshot);
}
#[test]
#[ignore]
pub fn accepts_basic_layout() {
let fake_win_size = PositionAndSize {
let fake_win_size = Size {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let layout_file_name = "three-panes-with-nesting.yaml";
let last_snapshot = RemoteRunner::new_with_layout("accepts_basic_layout", fake_win_size, layout_file_name, None)
let last_snapshot = RemoteRunner::new_with_layout("accepts_basic_layout", fake_win_size, layout_file_name)
.add_step(Step {
name: "Wait for app to load",
instruction: |remote_terminal: RemoteTerminal| -> bool {
@ -800,20 +749,17 @@ pub fn accepts_basic_layout() {
#[test]
#[ignore]
fn focus_pane_with_mouse() {
let fake_win_size = PositionAndSize {
let fake_win_size = Size {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let last_snapshot = RemoteRunner::new("split_terminals_vertically", fake_win_size, None)
let last_snapshot = RemoteRunner::new("split_terminals_vertically", fake_win_size)
.add_step(Step {
name: "Split pane to the right",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 2)
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
{
remote_terminal.send_key(&PANE_MODE);
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
@ -853,103 +799,95 @@ fn focus_pane_with_mouse() {
#[test]
#[ignore]
pub fn scrolling_inside_a_pane_with_mouse() {
let fake_win_size = PositionAndSize {
let fake_win_size = Size {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let last_snapshot =
RemoteRunner::new("scrolling_inside_a_pane_with_mouse", fake_win_size, None)
.add_step(Step {
name: "Split pane to the right",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears()
&& remote_terminal.cursor_position_is(2, 2)
{
remote_terminal.send_key(&PANE_MODE);
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
// back to normal mode after split
remote_terminal.send_key(&ENTER);
step_is_complete = true;
}
step_is_complete
},
})
.add_step(Step {
name: "Fill terminal with text",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
// cursor is in the newly opened second pane
remote_terminal.send_key(&format!("{:0<56}", "line1 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line2 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line3 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line4 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line5 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line6 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line7 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line8 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line9 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line10 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line11 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line12 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line13 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line14 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line15 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line16 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line17 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line18 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line19 ").as_bytes());
remote_terminal.send_key(&format!("{:0<57}", "line20 ").as_bytes());
step_is_complete = true;
}
step_is_complete
},
})
.add_step(Step {
name: "Scroll up inside pane",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(118, 20) {
// all lines have been written to the pane
remote_terminal.send_key(&normal_mouse_report(Position::new(2, 64), 64));
step_is_complete = true;
}
step_is_complete
},
})
.add_step(Step {
name: "Wait for scroll to finish",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(118, 20)
&& remote_terminal.snapshot_contains("line1 ")
{
// scrolled up one line
step_is_complete = true;
}
step_is_complete
},
})
.run_all_steps();
let last_snapshot = RemoteRunner::new("scrolling_inside_a_pane_with_mouse", fake_win_size)
.add_step(Step {
name: "Split pane to the right",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
{
remote_terminal.send_key(&PANE_MODE);
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
// back to normal mode after split
remote_terminal.send_key(&ENTER);
step_is_complete = true;
}
step_is_complete
},
})
.add_step(Step {
name: "Fill terminal with text",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
// cursor is in the newly opened second pane
remote_terminal.send_key(&format!("{:0<56}", "line1 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line2 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line3 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line4 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line5 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line6 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line7 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line8 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line9 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line10 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line11 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line12 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line13 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line14 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line15 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line16 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line17 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line18 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line19 ").as_bytes());
remote_terminal.send_key(&format!("{:0<57}", "line20 ").as_bytes());
step_is_complete = true;
}
step_is_complete
},
})
.add_step(Step {
name: "Scroll up inside pane",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(118, 20) {
// all lines have been written to the pane
remote_terminal.send_key(&normal_mouse_report(Position::new(2, 64), 64));
step_is_complete = true;
}
step_is_complete
},
})
.add_step(Step {
name: "Wait for scroll to finish",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(118, 20)
&& remote_terminal.snapshot_contains("line1 ")
{
// scrolled up one line
step_is_complete = true;
}
step_is_complete
},
})
.run_all_steps();
assert_snapshot!(last_snapshot);
}
#[test]
#[ignore]
pub fn start_without_pane_frames() {
let fake_win_size = PositionAndSize {
let fake_win_size = Size {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let last_snapshot = RemoteRunner::new_without_frames("no_pane_frames", fake_win_size, None)
let last_snapshot = RemoteRunner::new_without_frames("no_pane_frames", fake_win_size)
.add_step(Step {
name: "Split pane to the right",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {

View File

@ -1,7 +1,7 @@
use zellij_tile::data::Palette;
use zellij_utils::pane_size::PositionAndSize;
use zellij_server::panes::TerminalPane;
use zellij_utils::pane_size::{Dimension, PaneGeom, Size};
use zellij_utils::{vte, zellij_tile};
use ssh2::Session;
@ -15,6 +15,7 @@ const ZELLIJ_LAYOUT_PATH: &str = "/usr/src/zellij/fixtures/layouts";
const CONNECTION_STRING: &str = "127.0.0.1:2222";
const CONNECTION_USERNAME: &str = "test";
const CONNECTION_PASSWORD: &str = "test";
const SESSION_NAME: &str = "e2e-test";
fn ssh_connect() -> ssh2::Session {
let tcp = TcpStream::connect(CONNECTION_STRING).unwrap();
@ -27,7 +28,7 @@ fn ssh_connect() -> ssh2::Session {
sess
}
fn setup_remote_environment(channel: &mut ssh2::Channel, win_size: PositionAndSize) {
fn setup_remote_environment(channel: &mut ssh2::Channel, win_size: Size) {
let (columns, rows) = (win_size.cols as u32, win_size.rows as u32);
channel
.request_pty("xterm", None, Some((columns, rows, 0, 0)))
@ -39,60 +40,51 @@ fn setup_remote_environment(channel: &mut ssh2::Channel, win_size: PositionAndSi
channel.flush().unwrap();
}
fn start_zellij(channel: &mut ssh2::Channel, session_name: Option<&String>) {
match session_name.as_ref() {
Some(name) => {
channel
.write_all(
format!("{} --session {}\n", ZELLIJ_EXECUTABLE_LOCATION, name).as_bytes(),
)
.unwrap();
}
None => {
channel
.write_all(format!("{}\n", ZELLIJ_EXECUTABLE_LOCATION).as_bytes())
.unwrap();
}
};
channel.flush().unwrap();
fn stop_zellij(channel: &mut ssh2::Channel) {
channel
.write_all("killall -KILL zellij\n".as_bytes())
.unwrap();
}
fn start_zellij_without_frames(channel: &mut ssh2::Channel) {
fn start_zellij(channel: &mut ssh2::Channel) {
stop_zellij(channel);
channel
.write_all(format!("{} options --no-pane-frames\n", ZELLIJ_EXECUTABLE_LOCATION).as_bytes())
.write_all(
format!(
"{} --session {}\n",
ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME
)
.as_bytes(),
)
.unwrap();
channel.flush().unwrap();
}
fn start_zellij_with_layout(
channel: &mut ssh2::Channel,
layout_path: &str,
session_name: Option<&String>,
) {
match session_name.as_ref() {
Some(name) => {
channel
.write_all(
format!(
"{} --layout-path {} --session {}\n",
ZELLIJ_EXECUTABLE_LOCATION, layout_path, name
)
.as_bytes(),
)
.unwrap();
}
None => {
channel
.write_all(
format!(
"{} --layout-path {}\n",
ZELLIJ_EXECUTABLE_LOCATION, layout_path
)
.as_bytes(),
)
.unwrap();
}
};
fn start_zellij_without_frames(channel: &mut ssh2::Channel) {
stop_zellij(channel);
channel
.write_all(
format!(
"{} --session {} options --no-pane-frames\n",
ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME
)
.as_bytes(),
)
.unwrap();
channel.flush().unwrap();
}
fn start_zellij_with_layout(channel: &mut ssh2::Channel, layout_path: &str) {
stop_zellij(channel);
channel
.write_all(
format!(
"{} --layout-path {} --session {}\n",
ZELLIJ_EXECUTABLE_LOCATION, layout_path, SESSION_NAME
)
.as_bytes(),
)
.unwrap();
channel.flush().unwrap();
}
@ -119,7 +111,6 @@ pub fn take_snapshot(terminal_output: &mut TerminalPane) -> String {
pub struct RemoteTerminal<'a> {
channel: &'a mut ssh2::Channel,
session_name: Option<&'a String>,
cursor_x: usize,
cursor_y: usize,
current_snapshot: String,
@ -174,12 +165,7 @@ impl<'a> RemoteTerminal<'a> {
pub fn attach_to_original_session(&mut self) {
self.channel
.write_all(
format!(
"{} attach {}\n",
ZELLIJ_EXECUTABLE_LOCATION,
self.session_name.unwrap()
)
.as_bytes(),
format!("{} attach {}\n", ZELLIJ_EXECUTABLE_LOCATION, SESSION_NAME).as_bytes(),
)
.unwrap();
self.channel.flush().unwrap();
@ -198,33 +184,37 @@ pub struct RemoteRunner {
vte_parser: vte::Parser,
terminal_output: TerminalPane,
channel: ssh2::Channel,
session_name: Option<String>,
test_name: &'static str,
currently_running_step: Option<String>,
retries_left: usize,
win_size: PositionAndSize,
win_size: Size,
layout_file_name: Option<&'static str>,
without_frames: bool,
}
impl RemoteRunner {
pub fn new(
test_name: &'static str,
win_size: PositionAndSize,
session_name: Option<String>,
) -> Self {
pub fn new(test_name: &'static str, win_size: Size) -> Self {
let sess = ssh_connect();
let mut channel = sess.channel_session().unwrap();
let vte_parser = vte::Parser::new();
let terminal_output = TerminalPane::new(0, win_size, Palette::default(), 0); // 0 is the pane index
let mut rows = Dimension::fixed(win_size.rows);
let mut cols = Dimension::fixed(win_size.cols);
rows.set_inner(win_size.rows);
cols.set_inner(win_size.cols);
let pane_geom = PaneGeom {
x: 0,
y: 0,
rows,
cols,
};
let terminal_output = TerminalPane::new(0, pane_geom, Palette::default(), 0); // 0 is the pane index
setup_remote_environment(&mut channel, win_size);
start_zellij(&mut channel, session_name.as_ref());
start_zellij(&mut channel);
RemoteRunner {
steps: vec![],
channel,
terminal_output,
vte_parser,
session_name,
test_name,
currently_running_step: None,
current_step_index: 0,
@ -234,15 +224,21 @@ impl RemoteRunner {
without_frames: false,
}
}
pub fn new_without_frames(
test_name: &'static str,
win_size: PositionAndSize,
session_name: Option<String>,
) -> Self {
pub fn new_without_frames(test_name: &'static str, win_size: Size) -> Self {
let sess = ssh_connect();
let mut channel = sess.channel_session().unwrap();
let vte_parser = vte::Parser::new();
let terminal_output = TerminalPane::new(0, win_size, Palette::default(), 0); // 0 is the pane index
let mut rows = Dimension::fixed(win_size.rows);
let mut cols = Dimension::fixed(win_size.cols);
rows.set_inner(win_size.rows);
cols.set_inner(win_size.cols);
let pane_geom = PaneGeom {
x: 0,
y: 0,
rows,
cols,
};
let terminal_output = TerminalPane::new(0, pane_geom, Palette::default(), 0); // 0 is the pane index
setup_remote_environment(&mut channel, win_size);
start_zellij_without_frames(&mut channel);
RemoteRunner {
@ -250,7 +246,6 @@ impl RemoteRunner {
channel,
terminal_output,
vte_parser,
session_name,
test_name,
currently_running_step: None,
current_step_index: 0,
@ -262,27 +257,31 @@ impl RemoteRunner {
}
pub fn new_with_layout(
test_name: &'static str,
win_size: PositionAndSize,
win_size: Size,
layout_file_name: &'static str,
session_name: Option<String>,
) -> Self {
let remote_path = Path::new(ZELLIJ_LAYOUT_PATH).join(layout_file_name);
let sess = ssh_connect();
let mut channel = sess.channel_session().unwrap();
let vte_parser = vte::Parser::new();
let terminal_output = TerminalPane::new(0, win_size, Palette::default(), 0); // 0 is the pane index
let mut rows = Dimension::fixed(win_size.rows);
let mut cols = Dimension::fixed(win_size.cols);
rows.set_inner(win_size.rows);
cols.set_inner(win_size.cols);
let pane_geom = PaneGeom {
x: 0,
y: 0,
rows,
cols,
};
let terminal_output = TerminalPane::new(0, pane_geom, Palette::default(), 0); // 0 is the pane index
setup_remote_environment(&mut channel, win_size);
start_zellij_with_layout(
&mut channel,
&remote_path.to_string_lossy(),
session_name.as_ref(),
);
start_zellij_with_layout(&mut channel, &remote_path.to_string_lossy());
RemoteRunner {
steps: vec![],
channel,
terminal_output,
vte_parser,
session_name,
test_name,
currently_running_step: None,
current_step_index: 0,
@ -307,7 +306,6 @@ impl RemoteRunner {
cursor_y,
current_snapshot,
channel: &mut self.channel,
session_name: self.session_name.as_ref(),
}
}
pub fn run_next_step(&mut self) {
@ -319,7 +317,6 @@ impl RemoteRunner {
cursor_y,
current_snapshot,
channel: &mut self.channel,
session_name: self.session_name.as_ref(),
};
let instruction = next_step.instruction;
self.currently_running_step = Some(String::from(next_step.name));
@ -332,32 +329,22 @@ impl RemoteRunner {
self.steps.get(self.current_step_index).is_some()
}
fn restart_test(&mut self) -> String {
let session_name = self.session_name.as_ref().map(|name| {
// this is so that we don't try to connect to the previous session if it's still stuck
// inside the container
format!("{}_{}", name, self.retries_left)
});
if let Some(layout_file_name) = self.layout_file_name.as_ref() {
// let mut new_runner = RemoteRunner::new_with_layout(self.test_name, self.win_size, Path::new(&local_layout_path), session_name);
let mut new_runner = RemoteRunner::new_with_layout(
self.test_name,
self.win_size,
layout_file_name,
session_name,
);
let mut new_runner =
RemoteRunner::new_with_layout(self.test_name, self.win_size, layout_file_name);
new_runner.retries_left = self.retries_left - 1;
new_runner.replace_steps(self.steps.clone());
drop(std::mem::replace(self, new_runner));
self.run_all_steps()
} else if self.without_frames {
let mut new_runner =
RemoteRunner::new_without_frames(self.test_name, self.win_size, session_name);
let mut new_runner = RemoteRunner::new_without_frames(self.test_name, self.win_size);
new_runner.retries_left = self.retries_left - 1;
new_runner.replace_steps(self.steps.clone());
drop(std::mem::replace(self, new_runner));
self.run_all_steps()
} else {
let mut new_runner = RemoteRunner::new(self.test_name, self.win_size, session_name);
let mut new_runner = RemoteRunner::new(self.test_name, self.win_size);
new_runner.retries_left = self.retries_left - 1;
new_runner.replace_steps(self.steps.clone());
drop(std::mem::replace(self, new_runner));

View File

@ -3,23 +3,23 @@ source: src/tests/e2e/cases.rs
expression: last_snapshot
---
────────
$ Hi!█
Zellij
┌──────┐
│$ Hi!█│
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└──────┘
Ctrl +

View File

@ -3,27 +3,27 @@ source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ █
Zellij (e2e-test)  Tab #1 
Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ █
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.

View File

@ -3,7 +3,7 @@ source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
Zellij (e2e-test)  Tab #1 
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
│$ ││$ I am some text█ │
│ ││ │

View File

@ -3,7 +3,7 @@ source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
Zellij (e2e-test)  Tab #1 
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
│$ █ ││$ │
│ ││ │

View File

@ -3,27 +3,27 @@ source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ nabc█
Zellij (e2e-test)  Tab #1 
Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ nabc█
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
-- INTERFACE LOCKED --

View File

@ -3,27 +3,27 @@ source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1  Tab #2 
Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ █
Zellij (e2e-test)  Tab #1  Tab #2 
Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ █
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.

View File

@ -3,27 +3,27 @@ source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
┌ Pane #1 ───────────────────────────────────────┐┌ Pane #2 ───────────────────────────────────────────────────────────┐
│$ ││$ █
││
││
││
││
││
││
││
││
││
││
││
││
││
││
││
││
││
││
└────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────────────┘
Zellij (e2e-test)  Tab #1 
┌ Pane #1 ───────────────────────────────────────────┐┌ Pane #2 ───────────────────────────────────────────────────────┐
│$ ││$ █ │
││
││
││
││
││
││
││
││
││
││
││
││
││
││
││
││
││
││
└────────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.

View File

@ -3,27 +3,27 @@ source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
┌ Pane #1 ─────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
│$ ││$ █
││
││
││
││
││
││
││
││
││
││
││
││
││
││
││
││
││
││
└──────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Zellij (e2e-test)  Tab #1 
┌ Pane #1 ───────────────────────────────────────┐┌ Pane #2 ───────────────────────────────────────┐
│$ ││$ █
││
││
││
││
││
││
││
││
││
││
││
││
││
││
││
││
││
││
└────────────────────────────────────────────────┘└────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.

View File

@ -3,7 +3,7 @@ source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
Zellij (e2e-test)  Tab #1 
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────── SCROLL: 1/1 ┐
│$ ││$ line1 00000000000000000000000000000000000000000000000000│
│ ││line2 0000000000000000000000000000000000000000000000000000│

View File

@ -3,7 +3,7 @@ source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
Zellij (e2e-test)  Tab #1 
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────── SCROLL: 1/1 ┐
│$ ││$ line1 00000000000000000000000000000000000000000000000000│
│ ││line2 0000000000000000000000000000000000000000000000000000│

View File

@ -3,7 +3,7 @@ source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
Zellij (e2e-test)  Tab #1 
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
│$ ││$ █ │
│ ││ │

View File

@ -3,7 +3,7 @@ source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
Zellij (e2e-test)  Tab #1 
$ │$ █

View File

@ -3,27 +3,27 @@ source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ █
Zellij (e2e-test)  Tab #1 
Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ █
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.

View File

@ -3,27 +3,27 @@ source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
Pane #2 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ █
Zellij (e2e-test)  Tab #1 
Pane #2 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ █
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
Tip: Alt + n => open new pane. Alt + [] or hjkl => navigate between panes.

View File

@ -116,7 +116,7 @@ pub fn start_client(
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
let client_attributes = ClientAttributes {
position_and_size: full_screen_ws,
size: full_screen_ws,
palette,
};
@ -137,7 +137,7 @@ pub fn start_client(
client_attributes,
Box::new(opts),
Box::new(config_options.clone()),
layout,
layout.unwrap(),
)
}
};

View File

@ -1,4 +1,5 @@
use zellij_utils::input::actions::Action;
use zellij_utils::pane_size::Size;
use zellij_utils::{interprocess, libc, nix, signal_hook, termion, zellij_tile};
use interprocess::local_socket::LocalSocketStream;
@ -15,7 +16,6 @@ use zellij_tile::data::Palette;
use zellij_utils::{
errors::ErrorContext,
ipc::{ClientToServerMsg, IpcReceiverWithContext, IpcSenderWithContext, ServerToClientMsg},
pane_size::PositionAndSize,
shared::default_palette,
};
@ -35,7 +35,7 @@ fn unset_raw_mode(pid: RawFd, orig_termios: termios::Termios) {
};
}
pub(crate) fn get_terminal_size_using_fd(fd: RawFd) -> PositionAndSize {
pub(crate) fn get_terminal_size_using_fd(fd: RawFd) -> Size {
// TODO: do this with the nix ioctl
use libc::ioctl;
use libc::TIOCGWINSZ;
@ -54,7 +54,10 @@ pub(crate) fn get_terminal_size_using_fd(fd: RawFd) -> PositionAndSize {
unsafe {
ioctl(fd, TIOCGWINSZ.into(), &mut winsize)
};
PositionAndSize::from(winsize)
Size {
rows: winsize.ws_row as usize,
cols: winsize.ws_col as usize,
}
}
#[derive(Clone)]
@ -69,7 +72,7 @@ pub struct ClientOsInputOutput {
/// Zellij client requires.
pub trait ClientOsApi: Send + Sync {
/// Returns the size of the terminal associated to file descriptor `fd`.
fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize;
fn get_terminal_size_using_fd(&self, fd: RawFd) -> Size;
/// Set the terminal associated to file descriptor `fd` to
/// [raw mode](https://en.wikipedia.org/wiki/Terminal_mode).
fn set_raw_mode(&mut self, fd: RawFd);
@ -98,7 +101,7 @@ pub trait ClientOsApi: Send + Sync {
}
impl ClientOsApi for ClientOsInputOutput {
fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize {
fn get_terminal_size_using_fd(&self, fd: RawFd) -> Size {
get_terminal_size_using_fd(fd)
}
fn set_raw_mode(&mut self, fd: RawFd) {

View File

@ -2,7 +2,7 @@ use super::input_loop;
use zellij_utils::input::actions::{Action, Direction};
use zellij_utils::input::config::Config;
use zellij_utils::input::options::Options;
use zellij_utils::pane_size::PositionAndSize;
use zellij_utils::pane_size::Size;
use zellij_utils::zellij_tile::data::Palette;
use crate::{os_input_output::ClientOsApi, ClientInstruction, CommandIsExecuting};
@ -93,7 +93,7 @@ impl FakeClientOsApi {
}
impl ClientOsApi for FakeClientOsApi {
fn get_terminal_size_using_fd(&self, _fd: RawFd) -> PositionAndSize {
fn get_terminal_size_using_fd(&self, _fd: RawFd) -> Size {
unimplemented!()
}
fn set_raw_mode(&mut self, _fd: RawFd) {

View File

@ -46,12 +46,7 @@ use zellij_utils::{
/// Instructions related to server-side application
#[derive(Debug, Clone)]
pub(crate) enum ServerInstruction {
NewClient(
ClientAttributes,
Box<CliArgs>,
Box<Options>,
Option<LayoutFromYaml>,
),
NewClient(ClientAttributes, Box<CliArgs>, Box<Options>, LayoutFromYaml),
Render(Option<String>),
UnblockInputThread,
ClientExit,
@ -127,8 +122,6 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
daemonize::Daemonize::new()
.working_directory(std::env::current_dir().unwrap())
.umask(0o077)
// FIXME: My cherished `dbg!` was broken, so this is a hack to bring it back
//.stderr(std::fs::File::create("dbg.log").unwrap())
.start()
.expect("could not daemonize the server process");
@ -237,19 +230,12 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
.unwrap()
};
match layout {
None => {
spawn_tabs(None);
}
Some(layout) => {
if !&layout.tabs.is_empty() {
for tab_layout in layout.tabs {
spawn_tabs(Some(tab_layout.clone()));
}
} else {
spawn_tabs(None);
}
if !&layout.tabs.is_empty() {
for tab_layout in layout.tabs {
spawn_tabs(Some(tab_layout.clone()));
}
} else {
spawn_tabs(None);
}
}
ServerInstruction::AttachClient(attrs, _, options) => {
@ -258,7 +244,7 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
let session_data = rlock.as_ref().unwrap();
session_data
.senders
.send_to_screen(ScreenInstruction::TerminalResize(attrs.position_and_size))
.send_to_screen(ScreenInstruction::TerminalResize(attrs.size))
.unwrap();
let default_mode = options.default_mode.unwrap_or_default();
let mode_info =
@ -326,7 +312,7 @@ fn init_session(
to_server: SenderWithContext<ServerInstruction>,
client_attributes: ClientAttributes,
session_state: Arc<RwLock<SessionState>>,
layout: Option<LayoutFromYaml>,
layout: LayoutFromYaml,
) -> SessionMetaData {
let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = channels::unbounded();
let to_screen = SenderWithContext::new(to_screen);

View File

@ -251,7 +251,9 @@ pub trait ServerOsApi: Send + Sync {
impl ServerOsApi for ServerOsInputOutput {
fn set_terminal_size_using_fd(&self, fd: RawFd, cols: u16, rows: u16) {
set_terminal_size_using_fd(fd, cols, rows);
if cols > 0 && rows > 0 {
set_terminal_size_using_fd(fd, cols, rows);
}
}
fn spawn_terminal(&self, terminal_action: Option<TerminalAction>) -> (RawFd, Pid) {
let orig_termios = self.orig_termios.lock().unwrap();

View File

@ -592,8 +592,13 @@ impl Grid {
self.change_size(new_rows, new_columns);
}
pub fn change_size(&mut self, new_rows: usize, new_columns: usize) {
// Do nothing if this pane hasn't been given a proper size yet
if new_columns == 0 || new_rows == 0 {
return;
}
self.selection.reset();
if new_columns != self.width {
self.horizontal_tabstops = create_horizontal_tabstops(new_columns);
let mut cursor_canonical_line_index = self.cursor_canonical_line_index();
let cursor_index_in_canonical_line = self.cursor_index_in_canonical_line();
let mut viewport_canonical_lines = vec![];
@ -635,6 +640,11 @@ impl Grid {
} else {
canonical_line.columns.drain(..).collect()
};
// If the next character is wider than the grid (i.e. there is nothing in
// `next_wrap`, then just abort the resizing
if next_wrap.is_empty() {
break;
}
let row = Row::from_columns(next_wrap);
// if there are no more parts, this row is canonical as long as it originally
// was canonical (it might not have been for example if it's the first row in
@ -956,10 +966,9 @@ impl Grid {
self.cursor.x += count_to_move;
}
pub fn replace_characters_in_line_after_cursor(&mut self, replace_with: TerminalCharacter) {
self.viewport
.get_mut(self.cursor.y)
.unwrap()
.replace_and_pad_end(self.cursor.x, self.width, replace_with);
if let Some(row) = self.viewport.get_mut(self.cursor.y) {
row.replace_and_pad_end(self.cursor.x, self.width, replace_with);
}
self.output_buffer.update_line(self.cursor.y);
}
pub fn replace_characters_in_line_before_cursor(&mut self, replace_with: TerminalCharacter) {
@ -1109,7 +1118,7 @@ impl Grid {
self.scroll_region = None;
}
pub fn set_scroll_region_to_viewport_size(&mut self) {
self.scroll_region = Some((0, self.height - 1));
self.scroll_region = Some((0, self.height.saturating_sub(1)));
}
pub fn delete_lines_in_scroll_region(
&mut self,

View File

@ -2,33 +2,37 @@ use std::sync::mpsc::channel;
use std::time::Instant;
use std::unimplemented;
use crate::panes::{PaneDecoration, PaneId};
use crate::panes::PaneId;
use crate::pty::VteBytes;
use crate::tab::Pane;
use crate::ui::pane_boundaries_frame::PaneBoundariesFrame;
use crate::ui::pane_boundaries_frame::PaneFrame;
use crate::wasm_vm::PluginInstruction;
use zellij_utils::pane_size::Offset;
use zellij_utils::shared::ansi_len;
use zellij_utils::zellij_tile::prelude::PaletteColor;
use zellij_utils::{channels::SenderWithContext, pane_size::PositionAndSize};
use zellij_utils::{
channels::SenderWithContext,
pane_size::{Dimension, PaneGeom},
};
pub(crate) struct PluginPane {
pub pid: u32,
pub should_render: bool,
pub selectable: bool,
pub invisible_borders: bool,
pub position_and_size: PositionAndSize,
pub position_and_size_override: Option<PositionAndSize>,
pub content_position_and_size: PositionAndSize,
pub geom: PaneGeom,
pub geom_override: Option<PaneGeom>,
pub content_offset: Offset,
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
pub active_at: Instant,
pub pane_title: String,
pane_decoration: PaneDecoration,
frame: bool,
frame_color: Option<PaletteColor>,
}
impl PluginPane {
pub fn new(
pid: u32,
position_and_size: PositionAndSize,
position_and_size: PaneGeom,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
title: String,
) -> Self {
@ -36,53 +40,16 @@ impl PluginPane {
pid,
should_render: true,
selectable: true,
invisible_borders: false,
position_and_size,
position_and_size_override: None,
geom: position_and_size,
geom_override: None,
send_plugin_instructions,
active_at: Instant::now(),
pane_decoration: PaneDecoration::ContentOffset((0, 0)),
content_position_and_size: position_and_size,
frame: false,
frame_color: None,
content_offset: Offset::default(),
pane_title: title,
}
}
pub fn get_content_x(&self) -> usize {
self.get_content_posision_and_size().x
}
pub fn get_content_y(&self) -> usize {
self.get_content_posision_and_size().y
}
pub fn get_content_columns(&self) -> usize {
// content columns might differ from the pane's columns if the pane has a frame
// in that case they would be 2 less
self.get_content_posision_and_size().cols
}
pub fn get_content_rows(&self) -> usize {
// content rows might differ from the pane's rows if the pane has a frame
// in that case they would be 2 less
self.get_content_posision_and_size().rows
}
pub fn get_content_posision_and_size(&self) -> PositionAndSize {
self.content_position_and_size
}
fn redistribute_space(&mut self) {
let position_and_size = self
.position_and_size_override
.unwrap_or_else(|| self.position_and_size());
match &mut self.pane_decoration {
PaneDecoration::BoundariesFrame(boundaries_frame) => {
boundaries_frame.change_pos_and_size(position_and_size);
self.content_position_and_size = boundaries_frame.content_position_and_size();
}
PaneDecoration::ContentOffset((content_columns_offset, content_rows_offset)) => {
self.content_position_and_size = position_and_size;
self.content_position_and_size.cols =
position_and_size.cols - *content_columns_offset;
self.content_position_and_size.rows = position_and_size.rows - *content_rows_offset;
}
};
self.set_should_render(true);
}
}
impl Pane for PluginPane {
@ -90,51 +57,46 @@ impl Pane for PluginPane {
// with something like a get_pos_and_sz() method underpinning all of them. Alternatively and
// preferably, just use an enum and not a trait object
fn x(&self) -> usize {
self.position_and_size_override
.unwrap_or(self.position_and_size)
.x
self.geom_override.unwrap_or(self.geom).x
}
fn y(&self) -> usize {
self.position_and_size_override
.unwrap_or(self.position_and_size)
.y
self.geom_override.unwrap_or(self.geom).y
}
fn rows(&self) -> usize {
self.position_and_size_override
.unwrap_or(self.position_and_size)
.rows
self.geom_override.unwrap_or(self.geom).rows.as_usize()
}
fn columns(&self) -> usize {
self.position_and_size_override
.unwrap_or(self.position_and_size)
.cols
fn cols(&self) -> usize {
self.geom_override.unwrap_or(self.geom).cols.as_usize()
}
fn get_content_x(&self) -> usize {
self.x() + self.content_offset.left
}
fn get_content_y(&self) -> usize {
self.y() + self.content_offset.top
}
fn get_content_columns(&self) -> usize {
self.get_content_columns()
// content columns might differ from the pane's columns if the pane has a frame
// in that case they would be 2 less
self.cols()
.saturating_sub(self.content_offset.left + self.content_offset.right)
}
fn get_content_rows(&self) -> usize {
self.get_content_rows()
// content rows might differ from the pane's rows if the pane has a frame
// in that case they would be 2 less
self.rows()
.saturating_sub(self.content_offset.top + self.content_offset.bottom)
}
fn reset_size_and_position_override(&mut self) {
self.position_and_size_override = None;
self.redistribute_space();
self.geom_override = None;
self.should_render = true;
}
fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) {
self.position_and_size = *position_and_size;
self.redistribute_space();
fn set_geom(&mut self, position_and_size: PaneGeom) {
self.geom = position_and_size;
self.should_render = true;
}
// FIXME: This is obviously a bit outdated and needs the x and y moved into `size`
fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) {
let position_and_size_override = PositionAndSize {
x,
y,
rows: size.rows,
cols: size.cols,
..Default::default()
};
self.position_and_size_override = Some(position_and_size_override);
self.redistribute_space();
fn get_geom_override(&mut self, pane_geom: PaneGeom) {
self.geom_override = Some(pane_geom);
self.should_render = true;
}
fn handle_pty_bytes(&mut self, _event: VteBytes) {
unimplemented!()
@ -143,13 +105,16 @@ impl Pane for PluginPane {
None
}
fn adjust_input_to_terminal(&self, _input_bytes: Vec<u8>) -> Vec<u8> {
unimplemented!() // FIXME: Shouldn't need this implmented?
unimplemented!()
}
fn position_and_size(&self) -> PositionAndSize {
self.position_and_size
fn position_and_size(&self) -> PaneGeom {
self.geom
}
fn position_and_size_override(&self) -> Option<PositionAndSize> {
self.position_and_size_override
fn current_geom(&self) -> PaneGeom {
self.geom_override.unwrap_or(self.geom)
}
fn geom_override(&self) -> Option<PaneGeom> {
self.geom_override
}
fn should_render(&self) -> bool {
self.should_render
@ -157,28 +122,12 @@ impl Pane for PluginPane {
fn set_should_render(&mut self, should_render: bool) {
self.should_render = should_render;
}
fn set_should_render_boundaries(&mut self, should_render: bool) {
if let PaneDecoration::BoundariesFrame(boundaries_frame) = &mut self.pane_decoration {
boundaries_frame.set_should_render(should_render);
}
}
fn selectable(&self) -> bool {
self.selectable
}
fn set_selectable(&mut self, selectable: bool) {
self.selectable = selectable;
}
fn set_invisible_borders(&mut self, invisible_borders: bool) {
self.invisible_borders = invisible_borders;
}
fn set_fixed_height(&mut self, fixed_height: usize) {
self.position_and_size.rows = fixed_height;
self.position_and_size.rows_fixed = true;
}
fn set_fixed_width(&mut self, fixed_width: usize) {
self.position_and_size.cols = fixed_width;
self.position_and_size.cols_fixed = true;
}
fn render(&mut self) -> Option<String> {
// if self.should_render {
if true {
@ -200,10 +149,16 @@ impl Pane for PluginPane {
self.should_render = false;
let contents = buf_rx.recv().unwrap();
if let PaneDecoration::BoundariesFrame(boundaries_frame) = &mut self.pane_decoration {
if let Some(boundaries_frame_vte) = boundaries_frame.render() {
vte_output.push_str(&boundaries_frame_vte);
}
// FIXME: This is a hack that assumes all fixed-size panes are borderless. This
// will eventually need fixing!
if self.frame && !(self.geom.rows.is_fixed() || self.geom.cols.is_fixed()) {
let frame = PaneFrame {
geom: self.current_geom().into(),
title: self.pane_title.clone(),
color: self.frame_color,
..Default::default()
};
vte_output.push_str(&frame.render());
}
for (index, line) in contents.lines().enumerate() {
let actual_len = ansi_len(line);
@ -257,68 +212,44 @@ impl Pane for PluginPane {
fn pid(&self) -> PaneId {
PaneId::Plugin(self.pid)
}
fn reduce_height_down(&mut self, count: usize) {
self.position_and_size.y += count;
self.position_and_size.rows -= count;
self.redistribute_space();
self.should_render = true;
fn reduce_height(&mut self, percent: f64) {
if let Some(p) = self.geom.rows.as_percent() {
self.geom.rows = Dimension::percent(p - percent);
self.should_render = true;
}
}
fn increase_height_down(&mut self, count: usize) {
self.position_and_size.rows += count;
self.redistribute_space();
self.should_render = true;
fn increase_height(&mut self, percent: f64) {
if let Some(p) = self.geom.rows.as_percent() {
self.geom.rows = Dimension::percent(p + percent);
self.should_render = true;
}
}
fn increase_height_up(&mut self, count: usize) {
self.position_and_size.y -= count;
self.position_and_size.rows += count;
self.redistribute_space();
self.should_render = true;
fn reduce_width(&mut self, percent: f64) {
if let Some(p) = self.geom.cols.as_percent() {
self.geom.cols = Dimension::percent(p - percent);
self.should_render = true;
}
}
fn reduce_height_up(&mut self, count: usize) {
self.position_and_size.rows -= count;
self.redistribute_space();
self.should_render = true;
}
fn reduce_width_right(&mut self, count: usize) {
self.position_and_size.x += count;
self.position_and_size.cols -= count;
self.redistribute_space();
self.should_render = true;
}
fn reduce_width_left(&mut self, count: usize) {
self.position_and_size.cols -= count;
self.redistribute_space();
self.should_render = true;
}
fn increase_width_left(&mut self, count: usize) {
self.position_and_size.x -= count;
self.position_and_size.cols += count;
self.redistribute_space();
self.should_render = true;
}
fn increase_width_right(&mut self, count: usize) {
self.position_and_size.cols += count;
self.redistribute_space();
self.should_render = true;
fn increase_width(&mut self, percent: f64) {
if let Some(p) = self.geom.cols.as_percent() {
self.geom.cols = Dimension::percent(p + percent);
self.should_render = true;
}
}
fn push_down(&mut self, count: usize) {
self.position_and_size.y += count;
self.redistribute_space();
self.geom.y += count;
self.should_render = true;
}
fn push_right(&mut self, count: usize) {
self.position_and_size.x += count;
self.redistribute_space();
self.geom.x += count;
self.should_render = true;
}
fn pull_left(&mut self, count: usize) {
self.position_and_size.x -= count;
self.redistribute_space();
self.geom.x -= count;
self.should_render = true;
}
fn pull_up(&mut self, count: usize) {
self.position_and_size.y -= count;
self.redistribute_space();
self.geom.y -= count;
self.should_render = true;
}
fn scroll_up(&mut self, _count: usize) {
@ -330,25 +261,6 @@ impl Pane for PluginPane {
fn clear_scroll(&mut self) {
//unimplemented!()
}
// FIXME: This need to be reevaluated and deleted if possible.
// `max` doesn't make sense when things are fixed...
fn max_height(&self) -> Option<usize> {
if self.position_and_size.rows_fixed {
Some(self.position_and_size.rows)
} else {
None
}
}
fn max_width(&self) -> Option<usize> {
if self.position_and_size.cols_fixed {
Some(self.position_and_size.cols)
} else {
None
}
}
fn invisible_borders(&self) -> bool {
self.invisible_borders
}
fn active_at(&self) -> Instant {
self.active_at
@ -357,61 +269,14 @@ impl Pane for PluginPane {
fn set_active_at(&mut self, time: Instant) {
self.active_at = time;
}
fn set_frame(&mut self, frame: bool) {
self.frame = frame;
}
fn set_content_offset(&mut self, offset: Offset) {
self.content_offset = offset;
}
fn set_boundary_color(&mut self, color: Option<PaletteColor>) {
if let PaneDecoration::BoundariesFrame(boundaries_frame) = &mut self.pane_decoration {
boundaries_frame.set_color(color);
}
}
fn offset_content_columns(&mut self, by: usize) {
if !self.selectable {
return;
}
if let PaneDecoration::ContentOffset(content_offset) = &mut self.pane_decoration {
content_offset.0 = by;
} else {
self.pane_decoration = PaneDecoration::ContentOffset((by, 0));
}
self.redistribute_space();
self.set_should_render(true);
}
fn offset_content_rows(&mut self, by: usize) {
if !self.selectable {
return;
}
if let PaneDecoration::ContentOffset(content_offset) = &mut self.pane_decoration {
content_offset.1 = by;
} else {
self.pane_decoration = PaneDecoration::ContentOffset((0, by));
}
self.redistribute_space();
self.set_should_render(true);
}
fn show_boundaries_frame(&mut self, should_render_only_title: bool) {
if !self.selectable {
return;
}
let position_and_size = self
.position_and_size_override
.unwrap_or(self.position_and_size);
if let PaneDecoration::BoundariesFrame(boundaries_frame) = &mut self.pane_decoration {
boundaries_frame.render_only_title(should_render_only_title);
self.content_position_and_size = boundaries_frame.content_position_and_size();
} else {
let mut boundaries_frame =
PaneBoundariesFrame::new(position_and_size, self.pane_title.clone());
boundaries_frame.render_only_title(should_render_only_title);
self.content_position_and_size = boundaries_frame.content_position_and_size();
self.pane_decoration = PaneDecoration::BoundariesFrame(boundaries_frame);
}
self.redistribute_space();
self.set_should_render(true);
}
fn remove_boundaries_frame(&mut self) {
if !self.selectable {
return;
}
self.pane_decoration = PaneDecoration::ContentOffset((0, 0));
self.redistribute_space();
self.frame_color = color;
self.set_should_render(true);
}
}

View File

@ -1,14 +1,3 @@
use zellij_utils::position::Position;
use zellij_utils::zellij_tile::prelude::PaletteColor;
use zellij_utils::{vte, zellij_tile};
use std::fmt::Debug;
use std::os::unix::io::RawFd;
use std::time::{self, Instant};
use zellij_tile::data::Palette;
use zellij_utils::pane_size::PositionAndSize;
use crate::panes::AnsiCode;
use crate::panes::{
grid::Grid,
@ -18,10 +7,20 @@ use crate::panes::{
};
use crate::pty::VteBytes;
use crate::tab::Pane;
use std::fmt::Debug;
use std::os::unix::io::RawFd;
use std::time::{self, Instant};
use zellij_utils::pane_size::Offset;
use zellij_utils::{
pane_size::{Dimension, PaneGeom},
position::Position,
vte,
zellij_tile::data::{Palette, PaletteColor},
};
pub const SELECTION_SCROLL_INTERVAL_MS: u64 = 10;
use crate::ui::pane_boundaries_frame::PaneBoundariesFrame;
use crate::ui::pane_boundaries_frame::PaneFrame;
#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
pub enum PaneId {
@ -29,24 +28,22 @@ pub enum PaneId {
Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct?
}
pub enum PaneDecoration {
BoundariesFrame(PaneBoundariesFrame),
ContentOffset((usize, usize)), // (columns, rows)
}
// FIXME: This should hold an os_api handle so that terminal panes can set their own size via FD in
// their `reflow_lines()` method. Drop a Box<dyn ServerOsApi> in here somewhere.
pub struct TerminalPane {
pub grid: Grid,
pub pid: RawFd,
pub selectable: bool,
position_and_size: PositionAndSize,
position_and_size_override: Option<PositionAndSize>,
pub geom: PaneGeom,
pub geom_override: Option<PaneGeom>,
pub active_at: Instant,
pub colors: Palette,
vte_parser: vte::Parser,
selection_scrolled_at: time::Instant,
content_position_and_size: PositionAndSize,
content_offset: Offset,
pane_title: String,
pane_decoration: PaneDecoration,
frame: Option<PaneFrame>,
frame_color: Option<PaletteColor>,
}
impl Pane for TerminalPane {
@ -59,32 +56,38 @@ impl Pane for TerminalPane {
fn rows(&self) -> usize {
self.get_rows()
}
fn columns(&self) -> usize {
fn cols(&self) -> usize {
self.get_columns()
}
fn get_content_x(&self) -> usize {
self.get_x() + self.content_offset.left
}
fn get_content_y(&self) -> usize {
self.get_y() + self.content_offset.top
}
fn get_content_columns(&self) -> usize {
self.get_content_columns()
// content columns might differ from the pane's columns if the pane has a frame
// in that case they would be 2 less
self.get_columns()
.saturating_sub(self.content_offset.left + self.content_offset.right)
}
fn get_content_rows(&self) -> usize {
self.get_content_rows()
// content rows might differ from the pane's rows if the pane has a frame
// in that case they would be 2 less
self.get_rows()
.saturating_sub(self.content_offset.top + self.content_offset.bottom)
}
fn reset_size_and_position_override(&mut self) {
self.position_and_size_override = None;
self.redistribute_space();
self.geom_override = None;
self.reflow_lines();
}
fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) {
self.position_and_size = *position_and_size;
self.redistribute_space();
fn set_geom(&mut self, position_and_size: PaneGeom) {
self.geom = position_and_size;
self.reflow_lines();
}
fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) {
self.position_and_size_override = Some(PositionAndSize {
x,
y,
rows: size.rows,
cols: size.cols,
..Default::default()
});
self.redistribute_space();
fn get_geom_override(&mut self, pane_geom: PaneGeom) {
self.geom_override = Some(pane_geom);
self.reflow_lines();
}
fn handle_pty_bytes(&mut self, bytes: VteBytes) {
for byte in bytes.iter() {
@ -94,17 +97,10 @@ impl Pane for TerminalPane {
}
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
// (x, y)
let (x_offset, y_offset) = match &self.pane_decoration {
PaneDecoration::BoundariesFrame(boundries_frame) => {
let (content_columns_offset, content_rows_offset) =
boundries_frame.content_offset();
(content_columns_offset, content_rows_offset)
}
PaneDecoration::ContentOffset(_) => (0, 0),
};
let Offset { top, left, .. } = self.content_offset;
self.grid
.cursor_coordinates()
.map(|(x, y)| (x + x_offset, y + y_offset))
.map(|(x, y)| (x + left, y + top))
}
fn adjust_input_to_terminal(&self, input_bytes: Vec<u8>) -> Vec<u8> {
// there are some cases in which the terminal state means that input sent to it
@ -157,11 +153,14 @@ impl Pane for TerminalPane {
};
input_bytes
}
fn position_and_size(&self) -> PositionAndSize {
self.position_and_size
fn position_and_size(&self) -> PaneGeom {
self.geom
}
fn position_and_size_override(&self) -> Option<PositionAndSize> {
self.position_and_size_override
fn current_geom(&self) -> PaneGeom {
self.geom_override.unwrap_or(self.geom)
}
fn geom_override(&self) -> Option<PaneGeom> {
self.geom_override
}
fn should_render(&self) -> bool {
self.grid.should_render
@ -169,14 +168,10 @@ impl Pane for TerminalPane {
fn set_should_render(&mut self, should_render: bool) {
self.grid.should_render = should_render;
}
fn set_should_render_boundaries(&mut self, should_render: bool) {
if let PaneDecoration::BoundariesFrame(boundaries_frame) = &mut self.pane_decoration {
boundaries_frame.set_should_render(should_render);
}
}
fn render_full_viewport(&mut self) {
// this marks the pane for a full re-render, rather than just rendering the
// diff as it usually does with the OutputBuffer
self.frame.replace(PaneFrame::default());
self.grid.render_full_viewport();
}
fn selectable(&self) -> bool {
@ -185,17 +180,6 @@ impl Pane for TerminalPane {
fn set_selectable(&mut self, selectable: bool) {
self.selectable = selectable;
}
fn set_fixed_height(&mut self, fixed_height: usize) {
self.position_and_size.rows = fixed_height;
self.position_and_size.rows_fixed = true;
}
fn set_fixed_width(&mut self, fixed_width: usize) {
self.position_and_size.cols = fixed_width;
self.position_and_size.cols_fixed = true;
}
fn set_invisible_borders(&mut self, _invisible_borders: bool) {
unimplemented!();
}
fn render(&mut self) -> Option<String> {
if self.should_render() {
let mut vte_output = String::new();
@ -254,11 +238,20 @@ impl Pane for TerminalPane {
}
character_styles.clear();
}
if let PaneDecoration::BoundariesFrame(boundaries_frame) = &mut self.pane_decoration {
boundaries_frame.update_scroll(self.grid.scrollback_position_and_length());
boundaries_frame.update_title(self.grid.title.as_ref());
if let Some(boundaries_frame_vte) = boundaries_frame.render() {
vte_output.push_str(&boundaries_frame_vte);
if let Some(last_frame) = &self.frame {
let frame = PaneFrame {
geom: self.current_geom().into(),
title: self
.grid
.title
.clone()
.unwrap_or_else(|| self.pane_title.clone()),
scroll_position: self.grid.scrollback_position_and_length(),
color: self.frame_color,
};
if &frame != last_frame {
vte_output.push_str(&frame.render());
self.frame = Some(frame);
}
}
self.set_should_render(false);
@ -270,57 +263,45 @@ impl Pane for TerminalPane {
fn pid(&self) -> PaneId {
PaneId::Terminal(self.pid)
}
fn reduce_height_down(&mut self, count: usize) {
self.position_and_size.y += count;
self.position_and_size.rows -= count;
self.redistribute_space();
fn reduce_height(&mut self, percent: f64) {
if let Some(p) = self.geom.rows.as_percent() {
self.geom.rows = Dimension::percent(p - percent);
self.set_should_render(true);
}
}
fn increase_height_down(&mut self, count: usize) {
self.position_and_size.rows += count;
self.redistribute_space();
fn increase_height(&mut self, percent: f64) {
if let Some(p) = self.geom.rows.as_percent() {
self.geom.rows = Dimension::percent(p + percent);
self.set_should_render(true);
}
}
fn increase_height_up(&mut self, count: usize) {
self.position_and_size.y -= count;
self.position_and_size.rows += count;
self.redistribute_space();
fn reduce_width(&mut self, percent: f64) {
if let Some(p) = self.geom.cols.as_percent() {
self.geom.cols = Dimension::percent(p - percent);
self.set_should_render(true);
}
}
fn reduce_height_up(&mut self, count: usize) {
self.position_and_size.rows -= count;
self.redistribute_space();
}
fn reduce_width_right(&mut self, count: usize) {
self.position_and_size.x += count;
self.position_and_size.cols -= count;
self.redistribute_space();
}
fn reduce_width_left(&mut self, count: usize) {
self.position_and_size.cols -= count;
self.redistribute_space();
}
fn increase_width_left(&mut self, count: usize) {
self.position_and_size.x -= count;
self.position_and_size.cols += count;
self.redistribute_space();
}
fn increase_width_right(&mut self, count: usize) {
self.position_and_size.cols += count;
self.redistribute_space();
fn increase_width(&mut self, percent: f64) {
if let Some(p) = self.geom.cols.as_percent() {
self.geom.cols = Dimension::percent(p + percent);
self.set_should_render(true);
}
}
fn push_down(&mut self, count: usize) {
self.position_and_size.y += count;
self.redistribute_space();
self.geom.y += count;
self.reflow_lines();
}
fn push_right(&mut self, count: usize) {
self.position_and_size.x += count;
self.redistribute_space();
self.geom.x += count;
self.reflow_lines();
}
fn pull_left(&mut self, count: usize) {
self.position_and_size.x -= count;
self.redistribute_space();
self.geom.x -= count;
self.reflow_lines();
}
fn pull_up(&mut self, count: usize) {
self.position_and_size.y -= count;
self.redistribute_space();
self.geom.y -= count;
self.reflow_lines();
}
fn scroll_up(&mut self, count: usize) {
self.grid.move_viewport_up(count);
@ -392,73 +373,47 @@ impl Pane for TerminalPane {
self.grid.get_selected_text()
}
fn set_frame(&mut self, frame: bool) {
self.frame = if frame {
Some(PaneFrame::default())
} else {
None
};
}
fn set_content_offset(&mut self, offset: Offset) {
self.content_offset = offset;
self.reflow_lines();
}
fn set_boundary_color(&mut self, color: Option<PaletteColor>) {
if let PaneDecoration::BoundariesFrame(boundaries_frame) = &mut self.pane_decoration {
if boundaries_frame.color != color {
boundaries_frame.set_color(color);
self.set_should_render(true);
}
}
}
fn relative_position(&self, position_on_screen: &Position) -> Position {
let pane_position_and_size = self.get_content_posision_and_size();
position_on_screen.relative_to(&pane_position_and_size)
}
fn offset_content_columns(&mut self, by: usize) {
if let PaneDecoration::ContentOffset(content_offset) = &mut self.pane_decoration {
content_offset.0 = by;
} else {
self.pane_decoration = PaneDecoration::ContentOffset((by, 0));
}
self.redistribute_space();
}
fn offset_content_rows(&mut self, by: usize) {
if let PaneDecoration::ContentOffset(content_offset) = &mut self.pane_decoration {
content_offset.1 = by;
} else {
self.pane_decoration = PaneDecoration::ContentOffset((0, by));
}
self.redistribute_space();
}
fn show_boundaries_frame(&mut self, only_title: bool) {
let position_and_size = self
.position_and_size_override
.unwrap_or(self.position_and_size);
if let PaneDecoration::BoundariesFrame(boundaries_frame) = &mut self.pane_decoration {
boundaries_frame.render_only_title(only_title);
self.content_position_and_size = boundaries_frame.content_position_and_size();
} else {
let mut boundaries_frame =
PaneBoundariesFrame::new(position_and_size, self.pane_title.clone());
boundaries_frame.render_only_title(only_title);
self.content_position_and_size = boundaries_frame.content_position_and_size();
self.pane_decoration = PaneDecoration::BoundariesFrame(boundaries_frame);
}
self.redistribute_space();
}
fn remove_boundaries_frame(&mut self) {
self.pane_decoration = PaneDecoration::ContentOffset((0, 0));
self.redistribute_space();
self.frame_color = color;
self.set_should_render(true);
}
}
impl TerminalPane {
pub fn new(
pid: RawFd,
position_and_size: PositionAndSize,
position_and_size: PaneGeom,
palette: Palette,
pane_position: usize,
pane_index: usize,
) -> TerminalPane {
let initial_pane_title = format!("Pane #{}", pane_position);
let grid = Grid::new(position_and_size.rows, position_and_size.cols, palette);
let initial_pane_title = format!("Pane #{}", pane_index);
let grid = Grid::new(
position_and_size.rows.as_usize(),
position_and_size.cols.as_usize(),
palette,
);
TerminalPane {
pane_decoration: PaneDecoration::ContentOffset((0, 0)),
content_position_and_size: position_and_size,
frame: None,
frame_color: None,
content_offset: Offset::default(),
pid,
grid,
selectable: true,
position_and_size,
position_and_size_override: None,
geom: position_and_size,
geom_override: None,
vte_parser: vte::Parser::new(),
active_at: Instant::now(),
colors: palette,
@ -467,52 +422,33 @@ impl TerminalPane {
}
}
pub fn get_x(&self) -> usize {
match self.position_and_size_override.as_ref() {
match self.geom_override {
Some(position_and_size_override) => position_and_size_override.x,
None => self.position_and_size.x,
None => self.geom.x,
}
}
pub fn get_y(&self) -> usize {
match self.position_and_size_override.as_ref() {
match self.geom_override {
Some(position_and_size_override) => position_and_size_override.y,
None => self.position_and_size.y,
None => self.geom.y,
}
}
pub fn get_columns(&self) -> usize {
match self.position_and_size_override.as_ref() {
Some(position_and_size_override) => position_and_size_override.cols,
None => self.position_and_size.cols,
match self.geom_override {
Some(position_and_size_override) => position_and_size_override.cols.as_usize(),
None => self.geom.cols.as_usize(),
}
}
pub fn get_rows(&self) -> usize {
match self.position_and_size_override.as_ref() {
Some(position_and_size_override) => position_and_size_override.rows,
None => self.position_and_size.rows,
match self.geom_override {
Some(position_and_size_override) => position_and_size_override.rows.as_usize(),
None => self.geom.rows.as_usize(),
}
}
pub fn get_content_x(&self) -> usize {
self.get_content_posision_and_size().x
}
pub fn get_content_y(&self) -> usize {
self.get_content_posision_and_size().y
}
pub fn get_content_columns(&self) -> usize {
// content columns might differ from the pane's columns if the pane has a frame
// in that case they would be 2 less
self.get_content_posision_and_size().cols
}
pub fn get_content_rows(&self) -> usize {
// content rows might differ from the pane's rows if the pane has a frame
// in that case they would be 2 less
self.get_content_posision_and_size().rows
}
pub fn get_content_posision_and_size(&self) -> PositionAndSize {
self.content_position_and_size
}
fn reflow_lines(&mut self) {
let rows = self.get_content_rows();
let columns = self.get_content_columns();
self.grid.change_size(rows, columns);
let cols = self.get_content_columns();
self.grid.change_size(rows, cols);
self.set_should_render(true);
}
pub fn read_buffer_as_lines(&self) -> Vec<Vec<TerminalCharacter>> {
@ -522,24 +458,6 @@ impl TerminalPane {
// (x, y)
self.grid.cursor_coordinates()
}
fn redistribute_space(&mut self) {
let position_and_size = self
.position_and_size_override
.unwrap_or_else(|| self.position_and_size());
match &mut self.pane_decoration {
PaneDecoration::BoundariesFrame(boundaries_frame) => {
boundaries_frame.change_pos_and_size(position_and_size);
self.content_position_and_size = boundaries_frame.content_position_and_size();
}
PaneDecoration::ContentOffset((content_columns_offset, content_rows_offset)) => {
self.content_position_and_size = position_and_size;
self.content_position_and_size.cols =
position_and_size.cols - *content_columns_offset;
self.content_position_and_size.rows = position_and_size.rows - *content_rows_offset;
}
};
self.reflow_lines();
}
}
#[cfg(test)]

View File

@ -1,18 +1,15 @@
use super::super::TerminalPane;
use crate::tab::Pane;
use ::insta::assert_snapshot;
use zellij_utils::pane_size::PositionAndSize;
use zellij_utils::pane_size::PaneGeom;
use zellij_utils::zellij_tile::data::Palette;
#[test]
pub fn scrolling_inside_a_pane() {
let fake_win_size = PositionAndSize {
cols: 121,
rows: 20,
x: 0,
y: 0,
..Default::default()
};
let mut fake_win_size = PaneGeom::default();
fake_win_size.cols.set_inner(121);
fake_win_size.rows.set_inner(20);
let pid = 1;
let palette = Palette::default();
let mut terminal_pane = TerminalPane::new(pid, fake_win_size, palette, 0); // 0 is the pane index

View File

@ -60,7 +60,7 @@ pub(crate) struct Pty {
task_handles: HashMap<RawFd, JoinHandle<()>>,
}
pub(crate) fn pty_thread_main(mut pty: Pty, maybe_layout: Option<LayoutFromYaml>) {
pub(crate) fn pty_thread_main(mut pty: Pty, layout: LayoutFromYaml) {
loop {
let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Pty((&event).into()));
@ -87,16 +87,8 @@ pub(crate) fn pty_thread_main(mut pty: Pty, maybe_layout: Option<LayoutFromYaml>
.unwrap();
}
PtyInstruction::NewTab(terminal_action, tab_layout) => {
if let Some(layout) = maybe_layout.clone() {
let merged_layout = layout.template.insert_tab_layout(tab_layout);
pty.spawn_terminals_for_layout(merged_layout.into(), terminal_action.clone());
} else {
let pid = pty.spawn_terminal(terminal_action.clone());
pty.bus
.senders
.send_to_screen(ScreenInstruction::NewTab(pid))
.unwrap();
}
let merged_layout = layout.template.clone().insert_tab_layout(tab_layout);
pty.spawn_terminals_for_layout(merged_layout.into(), terminal_action.clone());
}
PtyInstruction::ClosePane(id) => {
pty.close_pane(id);

View File

@ -5,6 +5,7 @@ use std::os::unix::io::RawFd;
use std::str;
use std::sync::{Arc, RwLock};
use zellij_utils::pane_size::Size;
use zellij_utils::{input::layout::Layout, position::Position, zellij_tile};
use crate::{
@ -20,7 +21,6 @@ use zellij_utils::{
errors::{ContextType, ScreenContext},
input::{get_mode_info, options::Options},
ipc::ClientAttributes,
pane_size::PositionAndSize,
};
/// Instructions that can be sent to the [`Screen`].
@ -58,12 +58,8 @@ pub(crate) enum ScreenInstruction {
ToggleActiveTerminalFullscreen,
TogglePaneFrames,
SetSelectable(PaneId, bool, usize),
SetFixedHeight(PaneId, usize, usize),
SetFixedWidth(PaneId, usize, usize),
SetInvisibleBorders(PaneId, bool, usize),
ClosePane(PaneId),
ApplyLayout(Layout, Vec<RawFd>),
NewTab(RawFd),
SwitchTabNext,
SwitchTabPrev,
ToggleActiveSyncTab,
@ -71,7 +67,7 @@ pub(crate) enum ScreenInstruction {
GoToTab(u32),
ToggleTab,
UpdateTabName(Vec<u8>),
TerminalResize(PositionAndSize),
TerminalResize(Size),
ChangeMode(ModeInfo),
LeftClick(Position),
MouseRelease(Position),
@ -116,12 +112,8 @@ impl From<&ScreenInstruction> for ScreenContext {
}
ScreenInstruction::TogglePaneFrames => ScreenContext::TogglePaneFrames,
ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable,
ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders,
ScreenInstruction::SetFixedHeight(..) => ScreenContext::SetFixedHeight,
ScreenInstruction::SetFixedWidth(..) => ScreenContext::SetFixedWidth,
ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane,
ScreenInstruction::ApplyLayout(..) => ScreenContext::ApplyLayout,
ScreenInstruction::NewTab(_) => ScreenContext::NewTab,
ScreenInstruction::SwitchTabNext => ScreenContext::SwitchTabNext,
ScreenInstruction::SwitchTabPrev => ScreenContext::SwitchTabPrev,
ScreenInstruction::CloseTab => ScreenContext::CloseTab,
@ -151,7 +143,7 @@ pub(crate) struct Screen {
/// A map between this [`Screen`]'s tabs and their ID/key.
tabs: BTreeMap<usize, Tab>,
/// The full size of this [`Screen`].
position_and_size: PositionAndSize,
size: Size,
/// The index of this [`Screen`]'s active [`Tab`].
active_tab_index: Option<usize>,
tab_history: Vec<Option<usize>>,
@ -174,7 +166,7 @@ impl Screen {
Screen {
bus,
max_panes,
position_and_size: client_attributes.position_and_size,
size: client_attributes.size,
colors: client_attributes.palette,
active_tab_index: None,
tabs: BTreeMap::new(),
@ -185,32 +177,6 @@ impl Screen {
}
}
/// Creates a new [`Tab`] in this [`Screen`], containing a single
/// [pane](crate::client::panes) with PTY file descriptor `pane_id`.
pub fn new_tab(&mut self, pane_id: RawFd) {
let tab_index = self.get_new_tab_index();
let position = self.tabs.len();
let tab = Tab::new(
tab_index,
position,
String::new(),
&self.position_and_size,
self.bus.os_input.as_ref().unwrap().clone(),
self.bus.senders.clone(),
self.max_panes,
Some(PaneId::Terminal(pane_id)),
self.mode_info.clone(),
self.colors,
self.session_state.clone(),
self.draw_pane_frames,
);
self.tab_history.push(self.active_tab_index);
self.active_tab_index = Some(tab_index);
self.tabs.insert(tab_index, tab);
self.update_tabs();
self.render();
}
/// Returns the index where a new [`Tab`] should be created in this [`Screen`].
/// Currently, this is right after the last currently existing tab, or `0` if
/// no tabs exist in this screen yet.
@ -308,8 +274,8 @@ impl Screen {
}
}
pub fn resize_to_screen(&mut self, new_screen_size: PositionAndSize) {
self.position_and_size = new_screen_size;
pub fn resize_to_screen(&mut self, new_screen_size: Size) {
self.size = new_screen_size;
for (_, tab) in self.tabs.iter_mut() {
tab.resize_whole_tab(new_screen_size);
}
@ -377,11 +343,10 @@ impl Screen {
tab_index,
position,
String::new(),
&self.position_and_size,
self.size,
self.bus.os_input.as_ref().unwrap().clone(),
self.bus.senders.clone(),
self.max_panes,
None,
self.mode_info.clone(),
self.colors,
self.session_state.clone(),
@ -657,43 +622,6 @@ pub(crate) fn screen_thread_main(
|tab| tab.set_pane_selectable(id, selectable),
);
}
ScreenInstruction::SetFixedHeight(id, fixed_height, tab_index) => {
screen.get_indexed_tab_mut(tab_index).map_or_else(
|| {
log::warn!(
"Tab index #{} not found, could not set fixed height for plugin #{:?}.",
tab_index,
id
)
},
|tab| tab.set_pane_fixed_height(id, fixed_height),
);
}
ScreenInstruction::SetFixedWidth(id, fixed_width, tab_index) => {
screen.get_indexed_tab_mut(tab_index).map_or_else(
|| {
log::warn!(
"Tab index #{} not found, could not set fixed width for plugin #{:?}.",
tab_index,
id
)
},
|tab| tab.set_pane_fixed_width(id, fixed_width),
);
}
ScreenInstruction::SetInvisibleBorders(id, invisible_borders, tab_index) => {
screen.get_indexed_tab_mut(tab_index).map_or_else(
|| {
log::warn!(
r#"Tab index #{} not found, could not set invisible borders for plugin #{:?}."#,
tab_index,
id
)
},
|tab| tab.set_pane_invisible_borders(id, invisible_borders),
);
screen.render();
}
ScreenInstruction::ClosePane(id) => {
screen.get_active_tab_mut().unwrap().close_pane(id);
screen.render();
@ -711,14 +639,6 @@ pub(crate) fn screen_thread_main(
}
screen.render();
}
ScreenInstruction::NewTab(pane_id) => {
screen.new_tab(pane_id);
screen
.bus
.senders
.send_to_server(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::SwitchTabNext => {
screen.switch_tab_next();
screen

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,4 @@
use zellij_utils::pane_size::PositionAndSize;
use zellij_utils::zellij_tile;
use zellij_utils::{pane_size::Viewport, zellij_tile};
use crate::tab::Pane;
use ansi_term::Colour::{Fixed, RGB};
@ -39,10 +38,6 @@ impl BoundarySymbol {
color: Some(PaletteColor::EightBit(colors::GRAY)),
}
}
pub fn invisible(mut self) -> Self {
self.invisible = true;
self
}
pub fn color(&mut self, color: Option<PaletteColor>) -> Self {
self.color = color;
*self
@ -407,14 +402,14 @@ impl Coordinates {
}
pub struct Boundaries {
position_and_size: PositionAndSize,
viewport: Viewport,
boundary_characters: HashMap<Coordinates, BoundarySymbol>,
}
impl Boundaries {
pub fn new(position_and_size: &PositionAndSize) -> Self {
pub fn new(viewport: Viewport) -> Self {
Boundaries {
position_and_size: *position_and_size,
viewport,
boundary_characters: HashMap::new(),
}
}
@ -429,26 +424,22 @@ impl Boundaries {
},
false => None,
};
if rect.x() > self.position_and_size.x {
if rect.x() > self.viewport.x {
// left boundary
let boundary_x_coords = rect.x() - 1;
let first_row_coordinates = self.rect_right_boundary_row_start(rect);
let last_row_coordinates = self.rect_right_boundary_row_end(rect);
for row in first_row_coordinates..last_row_coordinates {
let coordinates = Coordinates::new(boundary_x_coords, row);
let mut symbol_to_add =
if row == first_row_coordinates && row != self.position_and_size.y {
BoundarySymbol::new(boundary_type::TOP_LEFT).color(color)
} else if row == last_row_coordinates - 1
&& row != self.position_and_size.y + self.position_and_size.rows - 1
{
BoundarySymbol::new(boundary_type::BOTTOM_LEFT).color(color)
} else {
BoundarySymbol::new(boundary_type::VERTICAL).color(color)
};
if rect.invisible_borders() {
symbol_to_add = symbol_to_add.invisible();
}
let symbol_to_add = if row == first_row_coordinates && row != self.viewport.y {
BoundarySymbol::new(boundary_type::TOP_LEFT).color(color)
} else if row == last_row_coordinates - 1
&& row != self.viewport.y + self.viewport.rows - 1
{
BoundarySymbol::new(boundary_type::BOTTOM_LEFT).color(color)
} else {
BoundarySymbol::new(boundary_type::VERTICAL).color(color)
};
let next_symbol = self
.boundary_characters
.remove(&coordinates)
@ -457,26 +448,20 @@ impl Boundaries {
self.boundary_characters.insert(coordinates, next_symbol);
}
}
if rect.y() > self.position_and_size.y {
if rect.y() > self.viewport.y {
// top boundary
let boundary_y_coords = rect.y() - 1;
let first_col_coordinates = self.rect_bottom_boundary_col_start(rect);
let last_col_coordinates = self.rect_bottom_boundary_col_end(rect);
for col in first_col_coordinates..last_col_coordinates {
let coordinates = Coordinates::new(col, boundary_y_coords);
let mut symbol_to_add = if col == first_col_coordinates
&& col != self.position_and_size.x
{
let symbol_to_add = if col == first_col_coordinates && col != self.viewport.x {
BoundarySymbol::new(boundary_type::TOP_LEFT).color(color)
} else if col == last_col_coordinates - 1 && col != self.position_and_size.cols - 1
{
} else if col == last_col_coordinates - 1 && col != self.viewport.cols - 1 {
BoundarySymbol::new(boundary_type::TOP_RIGHT).color(color)
} else {
BoundarySymbol::new(boundary_type::HORIZONTAL).color(color)
};
if rect.invisible_borders() {
symbol_to_add = symbol_to_add.invisible();
}
let next_symbol = self
.boundary_characters
.remove(&coordinates)
@ -492,19 +477,15 @@ impl Boundaries {
let last_row_coordinates = self.rect_right_boundary_row_end(rect);
for row in first_row_coordinates..last_row_coordinates {
let coordinates = Coordinates::new(boundary_x_coords, row);
let mut symbol_to_add =
if row == first_row_coordinates && row != self.position_and_size.y {
BoundarySymbol::new(boundary_type::TOP_RIGHT).color(color)
} else if row == last_row_coordinates - 1
&& row != self.position_and_size.y + self.position_and_size.rows - 1
{
BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color)
} else {
BoundarySymbol::new(boundary_type::VERTICAL).color(color)
};
if rect.invisible_borders() {
symbol_to_add = symbol_to_add.invisible();
}
let symbol_to_add = if row == first_row_coordinates && row != self.viewport.y {
BoundarySymbol::new(boundary_type::TOP_RIGHT).color(color)
} else if row == last_row_coordinates - 1
&& row != self.viewport.y + self.viewport.rows - 1
{
BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color)
} else {
BoundarySymbol::new(boundary_type::VERTICAL).color(color)
};
let next_symbol = self
.boundary_characters
.remove(&coordinates)
@ -520,19 +501,13 @@ impl Boundaries {
let last_col_coordinates = self.rect_bottom_boundary_col_end(rect);
for col in first_col_coordinates..last_col_coordinates {
let coordinates = Coordinates::new(col, boundary_y_coords);
let mut symbol_to_add = if col == first_col_coordinates
&& col != self.position_and_size.x
{
let symbol_to_add = if col == first_col_coordinates && col != self.viewport.x {
BoundarySymbol::new(boundary_type::BOTTOM_LEFT).color(color)
} else if col == last_col_coordinates - 1 && col != self.position_and_size.cols - 1
{
} else if col == last_col_coordinates - 1 && col != self.viewport.cols - 1 {
BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color)
} else {
BoundarySymbol::new(boundary_type::HORIZONTAL).color(color)
};
if rect.invisible_borders() {
symbol_to_add = symbol_to_add.invisible();
}
let next_symbol = self
.boundary_characters
.remove(&coordinates)
@ -555,16 +530,16 @@ impl Boundaries {
vte_output
}
fn rect_right_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool {
rect.x() + rect.columns() < self.position_and_size.cols
rect.x() + rect.cols() < self.viewport.cols
}
fn rect_bottom_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool {
rect.y() + rect.rows() < self.position_and_size.y + self.position_and_size.rows
rect.y() + rect.rows() < self.viewport.y + self.viewport.rows
}
fn rect_right_boundary_row_start(&self, rect: &dyn Pane) -> usize {
if rect.y() > self.position_and_size.y {
if rect.y() > self.viewport.y {
rect.y() - 1
} else {
self.position_and_size.y
self.viewport.y
}
}
fn rect_right_boundary_row_end(&self, rect: &dyn Pane) -> usize {
@ -578,12 +553,12 @@ impl Boundaries {
}
}
fn rect_bottom_boundary_col_end(&self, rect: &dyn Pane) -> usize {
rect.x() + rect.columns()
rect.x() + rect.cols()
}
fn is_fully_inside_screen(&self, rect: &dyn Pane) -> bool {
rect.x() >= self.position_and_size.x
&& rect.x() + rect.columns() <= self.position_and_size.x + self.position_and_size.cols
&& rect.y() >= self.position_and_size.y
&& rect.y() + rect.rows() <= self.position_and_size.y + self.position_and_size.rows
rect.x() >= self.viewport.x
&& rect.x() + rect.cols() <= self.viewport.x + self.viewport.cols
&& rect.y() >= self.viewport.y
&& rect.y() + rect.rows() <= self.viewport.y + self.viewport.rows
}
}

View File

@ -1,4 +1,3 @@
pub mod boundaries;
pub mod pane_boundaries_frame;
pub mod pane_resizer;
pub mod pane_resizer_beta;

View File

@ -1,7 +1,7 @@
use crate::ui::boundaries::boundary_type;
use ansi_term::Colour::{Fixed, RGB};
use ansi_term::Style;
use zellij_utils::pane_size::PositionAndSize;
use zellij_utils::pane_size::Viewport;
use zellij_utils::zellij_tile::prelude::PaletteColor;
fn color_string(character: &str, color: Option<PaletteColor>) -> String {
@ -18,93 +18,15 @@ fn color_string(character: &str, color: Option<PaletteColor>) -> String {
}
}
pub struct PaneBoundariesFrame {
pub position_and_size: PositionAndSize,
base_title: String,
title: String,
scroll_position: (usize, usize), // (position, length)
#[derive(Default, PartialEq)]
pub struct PaneFrame {
pub geom: Viewport,
pub title: String,
pub scroll_position: (usize, usize), // (position, length)
pub color: Option<PaletteColor>,
draw_title_only: bool,
should_render: bool,
}
impl PaneBoundariesFrame {
pub fn new(position_and_size: PositionAndSize, title: String) -> Self {
PaneBoundariesFrame {
position_and_size,
color: None,
base_title: title.clone(),
title,
scroll_position: (0, 0),
draw_title_only: false,
should_render: true,
}
}
pub fn frame_title_only(mut self) -> Self {
// TODO: remove this?
self.draw_title_only = true;
self.should_render = true;
self
}
pub fn render_only_title(&mut self, should_render_only_title: bool) {
if should_render_only_title != self.draw_title_only {
self.should_render = true;
self.draw_title_only = should_render_only_title;
}
}
pub fn change_pos_and_size(&mut self, position_and_size: PositionAndSize) {
if position_and_size != self.position_and_size {
self.position_and_size = position_and_size;
self.should_render = true;
}
}
pub fn set_color(&mut self, color: Option<PaletteColor>) {
if color != self.color {
self.color = color;
self.should_render = true;
}
}
pub fn update_scroll(&mut self, scroll_position: (usize, usize)) {
if scroll_position != self.scroll_position {
self.scroll_position = scroll_position;
self.should_render = true;
}
}
pub fn update_title(&mut self, title: Option<&String>) {
match title {
Some(title) => {
if title != &self.title {
self.title = title.clone();
self.should_render = true;
}
}
None => {
self.title = self.base_title.clone();
self.should_render = true;
}
}
}
pub fn content_position_and_size(&self) -> PositionAndSize {
if self.draw_title_only {
self.position_and_size.reduce_top_line()
} else {
self.position_and_size.reduce_outer_frame(1)
}
}
pub fn content_offset(&self) -> (usize, usize) {
// (column_difference, row_difference)
let content_position_and_size = self.content_position_and_size();
let column_difference = content_position_and_size
.x
.saturating_sub(self.position_and_size.x);
let row_difference = content_position_and_size
.y
.saturating_sub(self.position_and_size.y);
(column_difference, row_difference)
}
pub fn set_should_render(&mut self, should_render: bool) {
self.should_render = should_render;
}
impl PaneFrame {
fn render_title_right_side(&self, max_length: usize) -> Option<String> {
if self.scroll_position.0 > 0 || self.scroll_position.1 > 0 {
let prefix = " SCROLL: ";
@ -156,17 +78,9 @@ impl PaneBoundariesFrame {
}
}
fn render_title(&self, vte_output: &mut String) {
let total_title_length = self.position_and_size.cols - 2; // 2 for the left and right corners
let left_boundary = if self.draw_title_only {
boundary_type::HORIZONTAL
} else {
boundary_type::TOP_LEFT
};
let right_boundary = if self.draw_title_only {
boundary_type::HORIZONTAL
} else {
boundary_type::TOP_RIGHT
};
let total_title_length = self.geom.cols.saturating_sub(2); // 2 for the left and right corners
let left_boundary = boundary_type::TOP_LEFT;
let right_boundary = boundary_type::TOP_RIGHT;
let left_side = self.render_title_left_side(total_title_length);
let right_side = left_side.as_ref().and_then(|left_side| {
let space_left = total_title_length.saturating_sub(left_side.chars().count() + 1); // 1 for a middle separator
@ -205,73 +119,60 @@ impl PaneBoundariesFrame {
};
vte_output.push_str(&format!(
"\u{1b}[{};{}H\u{1b}[m{}",
self.position_and_size.y + 1, // +1 because goto is 1 indexed
self.position_and_size.x + 1, // +1 because goto is 1 indexed
self.geom.y + 1, // +1 because goto is 1 indexed
self.geom.x + 1, // +1 because goto is 1 indexed
color_string(&title_text, self.color),
)); // goto row/col + boundary character
}
pub fn render(&mut self) -> Option<String> {
if !self.should_render {
return None;
}
pub fn render(&self) -> String {
let mut vte_output = String::new();
if self.draw_title_only {
self.render_title(&mut vte_output);
} else {
for row in
self.position_and_size.y..(self.position_and_size.y + self.position_and_size.rows)
{
if row == self.position_and_size.y {
// top row
self.render_title(&mut vte_output);
} else if row == self.position_and_size.y + self.position_and_size.rows - 1 {
// bottom row
for col in self.position_and_size.x
..(self.position_and_size.x + self.position_and_size.cols)
{
if col == self.position_and_size.x {
// bottom left corner
vte_output.push_str(&format!(
"\u{1b}[{};{}H\u{1b}[m{}",
row + 1, // +1 because goto is 1 indexed
col + 1,
color_string(boundary_type::BOTTOM_LEFT, self.color),
)); // goto row/col + boundary character
} else if col == self.position_and_size.x + self.position_and_size.cols - 1
{
// bottom right corner
vte_output.push_str(&format!(
"\u{1b}[{};{}H\u{1b}[m{}",
row + 1, // +1 because goto is 1 indexed
col + 1,
color_string(boundary_type::BOTTOM_RIGHT, self.color),
)); // goto row/col + boundary character
} else {
vte_output.push_str(&format!(
"\u{1b}[{};{}H\u{1b}[m{}",
row + 1, // +1 because goto is 1 indexed
col + 1,
color_string(boundary_type::HORIZONTAL, self.color),
)); // goto row/col + boundary character
}
for row in self.geom.y..(self.geom.y + self.geom.rows) {
if row == self.geom.y {
// top row
self.render_title(&mut vte_output);
} else if row == self.geom.y + self.geom.rows - 1 {
// bottom row
for col in self.geom.x..(self.geom.x + self.geom.cols) {
if col == self.geom.x {
// bottom left corner
vte_output.push_str(&format!(
"\u{1b}[{};{}H\u{1b}[m{}",
row + 1, // +1 because goto is 1 indexed
col + 1,
color_string(boundary_type::BOTTOM_LEFT, self.color),
)); // goto row/col + boundary character
} else if col == self.geom.x + self.geom.cols - 1 {
// bottom right corner
vte_output.push_str(&format!(
"\u{1b}[{};{}H\u{1b}[m{}",
row + 1, // +1 because goto is 1 indexed
col + 1,
color_string(boundary_type::BOTTOM_RIGHT, self.color),
)); // goto row/col + boundary character
} else {
vte_output.push_str(&format!(
"\u{1b}[{};{}H\u{1b}[m{}",
row + 1, // +1 because goto is 1 indexed
col + 1,
color_string(boundary_type::HORIZONTAL, self.color),
)); // goto row/col + boundary character
}
} else {
vte_output.push_str(&format!(
"\u{1b}[{};{}H\u{1b}[m{}",
row + 1, // +1 because goto is 1 indexed
self.position_and_size.x + 1,
color_string(boundary_type::VERTICAL, self.color),
)); // goto row/col + boundary character
vte_output.push_str(&format!(
"\u{1b}[{};{}H\u{1b}[m{}",
row + 1, // +1 because goto is 1 indexed
self.position_and_size.x + self.position_and_size.cols,
color_string(boundary_type::VERTICAL, self.color),
)); // goto row/col + boundary character
}
} else {
vte_output.push_str(&format!(
"\u{1b}[{};{}H\u{1b}[m{}",
row + 1, // +1 because goto is 1 indexed
self.geom.x + 1,
color_string(boundary_type::VERTICAL, self.color),
)); // goto row/col + boundary character
vte_output.push_str(&format!(
"\u{1b}[{};{}H\u{1b}[m{}",
row + 1, // +1 because goto is 1 indexed
self.geom.x + self.geom.cols,
color_string(boundary_type::VERTICAL, self.color),
)); // goto row/col + boundary character
}
}
self.should_render = false;
Some(vte_output)
vte_output
}
}

View File

@ -1,537 +1,253 @@
use crate::{os_input_output::ServerOsApi, panes::PaneId, tab::Pane};
use std::{
cmp::Ordering,
collections::{BTreeMap, HashSet},
use cassowary::{
strength::{REQUIRED, STRONG},
Expression, Solver, Variable,
WeightedRelation::EQ,
};
use std::collections::{HashMap, HashSet};
use zellij_utils::{
input::layout::Direction,
pane_size::{Constraint, Dimension, PaneGeom},
};
use zellij_utils::pane_size::PositionAndSize;
pub(crate) struct PaneResizer<'a> {
panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>,
pub struct PaneResizer<'a> {
panes: HashMap<&'a PaneId, &'a mut Box<dyn Pane>>,
os_api: &'a mut Box<dyn ServerOsApi>,
vars: HashMap<PaneId, Variable>,
solver: Solver,
}
// TODO: currently there are some functions here duplicated with Tab
// all resizing functions should move here
// FIXME: Just hold a mutable Pane reference instead of the PaneId, fixed, pos, and size?
// Do this after panes are no longer trait-objects!
#[derive(Debug, Clone, Copy)]
struct Span {
pid: PaneId,
direction: Direction,
pos: usize,
size: Dimension,
size_var: Variable,
}
type Grid = Vec<Vec<Span>>;
impl<'a> PaneResizer<'a> {
pub fn new(
panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>,
panes: impl Iterator<Item = (&'a PaneId, &'a mut Box<dyn Pane>)>,
os_api: &'a mut Box<dyn ServerOsApi>,
) -> Self {
PaneResizer { panes, os_api }
let panes: HashMap<_, _> = panes.collect();
let mut vars = HashMap::new();
for &&k in panes.keys() {
vars.insert(k, Variable::new());
}
PaneResizer {
panes,
os_api,
vars,
solver: Solver::new(),
}
}
pub fn resize(
&mut self,
mut current_size: PositionAndSize,
new_size: PositionAndSize,
) -> Option<(isize, isize)> {
// (column_difference, row_difference)
let mut successfully_resized = false;
let mut column_difference: isize = 0;
let mut row_difference: isize = 0;
match new_size.cols.cmp(&current_size.cols) {
Ordering::Greater => {
let increase_by = new_size.cols - current_size.cols;
if let Some(panes_to_resize) = find_increasable_vertical_chain(
self.panes,
increase_by,
current_size.cols,
current_size.rows,
) {
self.increase_panes_right_and_push_adjacents_right(
panes_to_resize,
increase_by,
);
column_difference = new_size.cols as isize - current_size.cols as isize;
current_size.cols = (current_size.cols as isize + column_difference) as usize;
successfully_resized = true;
};
pub fn layout(&mut self, direction: Direction, space: usize) -> Result<(), String> {
self.solver.reset();
let grid = self.solve(direction, space)?;
let spans = self.discretize_spans(grid, space)?;
self.apply_spans(spans);
Ok(())
}
fn solve(&mut self, direction: Direction, space: usize) -> Result<Grid, String> {
let grid: Grid = self
.grid_boundaries(direction)
.into_iter()
.map(|b| self.spans_in_boundary(direction, b))
.collect();
let constraints: HashSet<_> = grid
.iter()
.flat_map(|s| constrain_spans(space, s))
.collect();
self.solver
.add_constraints(&constraints)
.map_err(|e| format!("{:?}", e))?;
Ok(grid)
}
fn discretize_spans(&mut self, mut grid: Grid, space: usize) -> Result<Vec<Span>, String> {
let mut rounded_sizes: HashMap<_, _> = grid
.iter()
.flatten()
.map(|s| {
(
s.size_var,
stable_round(self.solver.get_value(s.size_var)) as isize,
)
})
.collect();
// Round f64 pane sizes to usize without gaps or overlap
let mut finalised = Vec::new();
for spans in grid.iter_mut() {
let rounded_size: isize = spans.iter().map(|s| rounded_sizes[&s.size_var]).sum();
let mut error = space as isize - rounded_size;
let mut flex_spans: Vec<_> = spans
.iter_mut()
.filter(|s| !s.size.is_fixed() && !finalised.contains(&s.pid))
.collect();
flex_spans.sort_by_key(|s| rounded_sizes[&s.size_var]);
if error < 0 {
flex_spans.reverse();
}
Ordering::Less => {
let reduce_by = current_size.cols - new_size.cols;
if let Some(panes_to_resize) = find_reducible_vertical_chain(
self.panes,
reduce_by,
current_size.cols,
current_size.rows,
) {
self.reduce_panes_left_and_pull_adjacents_left(panes_to_resize, reduce_by);
column_difference = new_size.cols as isize - current_size.cols as isize;
current_size.cols = (current_size.cols as isize + column_difference) as usize;
successfully_resized = true;
};
for span in flex_spans {
rounded_sizes
.entry(span.size_var)
.and_modify(|s| *s += error.signum());
error -= error.signum();
}
Ordering::Equal => (),
finalised.extend(spans.iter().map(|s| s.pid));
}
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.cols,
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;
};
// Update span positions based on their rounded sizes
for spans in grid.iter_mut() {
let mut offset = 0;
for span in spans.iter_mut() {
span.pos = offset;
let sz = rounded_sizes[&span.size_var];
if sz < 1 {
return Err("Ran out of room for spans".into());
}
span.size.set_inner(sz as usize);
offset += span.size.as_usize();
}
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.cols,
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))
Ok(grid.into_iter().flatten().collect())
}
fn apply_spans(&mut self, spans: Vec<Span>) {
for span in spans {
let pane = self.panes.get_mut(&span.pid).unwrap();
let new_geom = match span.direction {
Direction::Horizontal => PaneGeom {
x: span.pos,
cols: span.size,
..pane.current_geom()
},
Direction::Vertical => PaneGeom {
y: span.pos,
rows: span.size,
..pane.current_geom()
},
};
if pane.geom_override().is_some() {
pane.get_geom_override(new_geom);
} else {
pane.set_geom(new_geom);
}
if let PaneId::Terminal(pid) = pane.pid() {
self.os_api.set_terminal_size_using_fd(
pid,
pane.get_content_columns() as u16,
pane.get_content_rows() as u16,
);
}
}
}
// FIXME: Functions like this should have unit tests!
fn grid_boundaries(&self, direction: Direction) -> Vec<(usize, usize)> {
// Select the spans running *perpendicular* to the direction of resize
let spans: Vec<Span> = self
.panes
.values()
.map(|p| self.get_span(!direction, p.as_ref()))
.collect();
let mut last_edge = 0;
let mut bounds = Vec::new();
let mut edges: Vec<usize> = spans.iter().map(|s| s.pos + s.size.as_usize()).collect();
edges.sort_unstable();
edges.dedup();
for next in edges {
let next_edge = next;
bounds.push((last_edge, next_edge));
last_edge = next_edge;
}
bounds
}
fn spans_in_boundary(&self, direction: Direction, boundary: (usize, usize)) -> Vec<Span> {
let bwn = |v, (s, e)| s <= v && v < e;
let mut spans: Vec<_> = self
.panes
.values()
.filter(|p| {
let s = self.get_span(!direction, p.as_ref());
let span_bounds = (s.pos, s.pos + s.size.as_usize());
bwn(span_bounds.0, boundary)
|| (bwn(boundary.0, span_bounds)
&& (bwn(boundary.1, span_bounds) || boundary.1 == span_bounds.1))
})
.map(|p| self.get_span(direction, p.as_ref()))
.collect();
spans.sort_unstable_by_key(|s| s.pos);
spans
}
fn get_span(&self, direction: Direction, pane: &dyn Pane) -> Span {
let pas = pane.current_geom();
let size_var = self.vars[&pane.pid()];
match direction {
Direction::Horizontal => Span {
pid: pane.pid(),
direction,
pos: pas.x,
size: pas.cols,
size_var,
},
Direction::Vertical => Span {
pid: pane.pid(),
direction,
pos: pas.y,
size: pas.rows,
size_var,
},
}
}
}
fn constrain_spans(space: usize, spans: &[Span]) -> HashSet<cassowary::Constraint> {
let mut constraints = HashSet::new();
// Calculating "flexible" space (space not consumed by fixed-size spans)
let new_flex_space = spans.iter().fold(space, |a, s| {
if let Constraint::Fixed(sz) = s.size.constraint {
a.saturating_sub(sz)
} 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.get_content_columns() as u16,
pane.get_content_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.get_content_columns() as u16,
pane.get_content_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.get_content_columns() as u16,
pane.get_content_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.get_content_columns() as u16,
pane.get_content_rows() as u16,
);
a
}
});
// Spans must use all of the available space
let full_size = spans
.iter()
.fold(Expression::from_constant(0.0), |acc, s| acc + s.size_var);
constraints.insert(full_size | EQ(REQUIRED) | space as f64);
// Try to maintain ratios and lock non-flexible sizes
for span in spans {
match span.size.constraint {
Constraint::Fixed(s) => constraints.insert(span.size_var | EQ(REQUIRED) | s as f64),
Constraint::Percent(p) => constraints
.insert((span.size_var / new_flex_space as f64) | EQ(STRONG) | (p / 100.0)),
};
}
constraints
}
fn find_next_increasable_horizontal_pane(
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
right_of: &dyn Pane,
increase_by: usize,
) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter(
|p| p.x() == right_of.x() + right_of.columns() && p.horizontally_overlaps_with(right_of), // TODO: the name here is wrong, it should be vertically_overlaps_with
);
let resizable_candidates =
next_pane_candidates.filter(|p| p.can_increase_height_by(increase_by));
resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id {
Some(next_pane) => {
let next_pane = panes.get(&next_pane).unwrap();
if next_pane.y() < p.y() {
next_pane_id
} else {
Some(p.pid())
}
}
None => Some(p.pid()),
})
}
fn find_next_increasable_vertical_pane(
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
below: &dyn Pane,
increase_by: usize,
) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter(
|p| p.y() == below.y() + below.rows() && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with
);
let resizable_candidates =
next_pane_candidates.filter(|p| p.can_increase_width_by(increase_by));
resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id {
Some(next_pane) => {
let next_pane = panes.get(&next_pane).unwrap();
if next_pane.x() < p.x() {
next_pane_id
} else {
Some(p.pid())
}
}
None => Some(p.pid()),
})
}
fn find_next_reducible_vertical_pane(
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
below: &dyn Pane,
reduce_by: usize,
) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter(
|p| p.y() == below.y() + below.rows() && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with
);
let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_width_by(reduce_by));
resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id {
Some(next_pane) => {
let next_pane = panes.get(&next_pane).unwrap();
if next_pane.x() < p.x() {
next_pane_id
} else {
Some(p.pid())
}
}
None => Some(p.pid()),
})
}
fn find_next_reducible_horizontal_pane(
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
right_of: &dyn Pane,
reduce_by: usize,
) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter(
|p| p.x() == right_of.x() + right_of.columns() && p.horizontally_overlaps_with(right_of), // TODO: the name here is wrong, it should be vertically_overlaps_with
);
let resizable_candidates = next_pane_candidates.filter(|p| p.can_reduce_height_by(reduce_by));
resizable_candidates.fold(None, |next_pane_id, p| match next_pane_id {
Some(next_pane) => {
let next_pane = panes.get(&next_pane).unwrap();
if next_pane.y() < p.y() {
next_pane_id
} else {
Some(p.pid())
}
}
None => Some(p.pid()),
})
}
fn find_increasable_horizontal_chain(
panes: &BTreeMap<PaneId, Box<dyn Pane>>,
increase_by: usize,
screen_width: usize,
screen_height: usize, // TODO: this is the previous size (make this clearer)
) -> Option<Vec<PaneId>> {
let mut horizontal_coordinate = 0;
loop {
if horizontal_coordinate == screen_height {
return None;
}
match panes
.values()
.find(|p| p.x() == 0 && p.y() == horizontal_coordinate)
{
Some(leftmost_pane) => {
if !leftmost_pane.can_increase_height_by(increase_by) {
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows();
continue;
}
let mut panes_to_resize = vec![];
let mut current_pane = leftmost_pane;
loop {
panes_to_resize.push(current_pane.pid());
if current_pane.x() + current_pane.columns() == screen_width {
return Some(panes_to_resize);
}
match find_next_increasable_horizontal_pane(
panes,
current_pane.as_ref(),
increase_by,
) {
Some(next_pane_id) => {
current_pane = panes.get(&next_pane_id).unwrap();
}
None => {
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows();
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();
continue;
}
let mut panes_to_resize = vec![];
let mut current_pane = topmost_pane;
loop {
panes_to_resize.push(current_pane.pid());
if current_pane.y() + current_pane.rows() == screen_height {
return Some(panes_to_resize);
}
match find_next_increasable_vertical_pane(
panes,
current_pane.as_ref(),
increase_by,
) {
Some(next_pane_id) => {
current_pane = panes.get(&next_pane_id).unwrap();
}
None => {
vertical_coordinate = topmost_pane.x() + topmost_pane.columns();
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();
continue;
}
let mut panes_to_resize = vec![];
let mut current_pane = leftmost_pane;
loop {
panes_to_resize.push(current_pane.pid());
if current_pane.x() + current_pane.columns() == screen_width {
return Some(panes_to_resize);
}
match find_next_reducible_horizontal_pane(
panes,
current_pane.as_ref(),
reduce_by,
) {
Some(next_pane_id) => {
current_pane = panes.get(&next_pane_id).unwrap();
}
None => {
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows();
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();
continue;
}
let mut panes_to_resize = vec![];
let mut current_pane = topmost_pane;
loop {
panes_to_resize.push(current_pane.pid());
if current_pane.y() + current_pane.rows() == screen_height {
return Some(panes_to_resize);
}
match find_next_reducible_vertical_pane(
panes,
current_pane.as_ref(),
increase_by,
) {
Some(next_pane_id) => {
current_pane = panes.get(&next_pane_id).unwrap();
}
None => {
vertical_coordinate = topmost_pane.x() + topmost_pane.columns();
break;
}
};
}
}
None => {
return None;
}
}
}
fn stable_round(x: f64) -> f64 {
((x * 100.0).round() / 100.0).round()
}

View File

@ -1,248 +0,0 @@
#![allow(dead_code)]
use crate::{os_input_output::ServerOsApi, panes::PaneId, tab::Pane};
use cassowary::{
strength::{REQUIRED, STRONG},
Constraint, Solver, Variable,
WeightedRelation::*,
};
use std::{
collections::{BTreeMap, HashSet},
ops::Not,
};
use zellij_utils::pane_size::PositionAndSize;
const GAP_SIZE: usize = 1; // Panes are separated by this number of rows / columns
pub struct PaneResizer<'a> {
panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>,
vars: BTreeMap<PaneId, (Variable, Variable)>,
solver: Solver,
os_api: &'a mut Box<dyn ServerOsApi>,
}
#[derive(Debug, Clone, Copy)]
enum Direction {
Horizontal,
Vertical,
}
impl Not for Direction {
type Output = Self;
fn not(self) -> Self::Output {
match self {
Direction::Horizontal => Direction::Vertical,
Direction::Vertical => Direction::Horizontal,
}
}
}
#[derive(Debug, Clone, Copy)]
struct Span {
pid: PaneId,
direction: Direction,
fixed: bool,
pos: usize,
size: usize,
pos_var: Variable,
size_var: Variable,
}
// TODO: currently there are some functions here duplicated with Tab
// all resizing functions should move here
// FIXME:
// 1. Rounding causes a loss of ratios, I need to store an internal f64 for
// each pane as well as the displayed usize and add custom rounding logic.
// 2. Vertical resizing doesn't seem to respect the space consumed by the tab
// and status bars?
// 3. A 2x2 layout and simultaneous vertical + horizontal resizing sometimes
// leads to unsolvable constraints? Maybe related to 2 (and possibly 1).
// I should sanity-check the `spans_in_boundary()` here!
impl<'a> PaneResizer<'a> {
pub fn new(
panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>,
os_api: &'a mut Box<dyn ServerOsApi>,
) -> Self {
let mut vars = BTreeMap::new();
for &k in panes.keys() {
vars.insert(k, (Variable::new(), Variable::new()));
}
PaneResizer {
panes,
vars,
solver: Solver::new(),
os_api,
}
}
pub fn resize(
&mut self,
current_size: PositionAndSize,
new_size: PositionAndSize,
) -> Option<(isize, isize)> {
let col_delta = new_size.cols as isize - current_size.cols as isize;
let row_delta = new_size.rows as isize - current_size.rows as isize;
if col_delta != 0 {
let spans = self.solve_direction(Direction::Horizontal, new_size.cols)?;
self.collapse_spans(&spans);
}
self.solver.reset();
if row_delta != 0 {
let spans = self.solve_direction(Direction::Vertical, new_size.rows)?;
self.collapse_spans(&spans);
}
Some((col_delta, row_delta))
}
fn solve_direction(&mut self, direction: Direction, space: usize) -> Option<Vec<Span>> {
let mut grid = Vec::new();
for boundary in self.grid_boundaries(direction) {
grid.push(self.spans_in_boundary(direction, boundary));
}
let constraints: Vec<_> = grid
.iter()
.flat_map(|s| constrain_spans(space, s))
.collect();
// FIXME: This line needs to be restored before merging!
//self.solver.add_constraints(&constraints).ok()?;
self.solver.add_constraints(&constraints).unwrap();
Some(grid.into_iter().flatten().collect())
}
fn grid_boundaries(&self, direction: Direction) -> Vec<(usize, usize)> {
// Select the spans running *perpendicular* to the direction of resize
let spans: Vec<Span> = self
.panes
.values()
.map(|p| self.get_span(!direction, p.as_ref()))
.collect();
let mut last_edge = 0;
let mut bounds = Vec::new();
loop {
let mut spans_on_edge: Vec<&Span> =
spans.iter().filter(|p| p.pos == last_edge).collect();
spans_on_edge.sort_unstable_by_key(|s| s.size);
if let Some(next) = spans_on_edge.first() {
let next_edge = last_edge + next.size;
bounds.push((last_edge, next_edge));
last_edge = next_edge + GAP_SIZE;
} else {
break;
}
}
bounds
}
fn spans_in_boundary(&self, direction: Direction, boundary: (usize, usize)) -> Vec<Span> {
let (start, end) = boundary;
let bwn = |v| start <= v && v < end;
let mut spans: Vec<_> = self
.panes
.values()
.filter(|p| {
let s = self.get_span(!direction, p.as_ref());
bwn(s.pos) || bwn(s.pos + s.size)
})
.map(|p| self.get_span(direction, p.as_ref()))
.collect();
spans.sort_unstable_by_key(|s| s.pos);
spans
}
fn get_span(&self, direction: Direction, pane: &dyn Pane) -> Span {
let pas = pane.position_and_size();
let (pos_var, size_var) = self.vars[&pane.pid()];
match direction {
Direction::Horizontal => Span {
pid: pane.pid(),
direction,
fixed: pas.cols_fixed,
pos: pas.x,
size: pas.cols,
pos_var,
size_var,
},
Direction::Vertical => Span {
pid: pane.pid(),
direction,
fixed: pas.rows_fixed,
pos: pas.y,
size: pas.rows,
pos_var,
size_var,
},
}
}
fn collapse_spans(&mut self, spans: &[Span]) {
for span in spans {
let solver = &self.solver; // Hand-holding the borrow-checker
let pane = self.panes.get_mut(&span.pid).unwrap();
let fetch_usize = |v| solver.get_value(v).round() as usize;
match span.direction {
Direction::Horizontal => pane.change_pos_and_size(&PositionAndSize {
x: fetch_usize(span.pos_var),
cols: fetch_usize(span.size_var),
..pane.position_and_size()
}),
Direction::Vertical => pane.change_pos_and_size(&PositionAndSize {
y: fetch_usize(span.pos_var),
rows: fetch_usize(span.size_var),
..pane.position_and_size()
}),
}
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 constrain_spans(space: usize, spans: &[Span]) -> HashSet<Constraint> {
let mut constraints = HashSet::new();
// The first span needs to start at 0
constraints.insert(spans[0].pos_var | EQ(REQUIRED) | 0.0);
// Calculating "flexible" space (space not consumed by fixed-size spans)
let gap_space = GAP_SIZE * (spans.len() - 1);
let old_flex_space = spans
.iter()
.fold(0, |a, s| if !s.fixed { a + s.size } else { a });
let new_flex_space = spans.iter().fold(
space - gap_space,
|a, s| if s.fixed { a - s.size } else { a },
);
// Keep spans stuck together
for pair in spans.windows(2) {
let (ls, rs) = (pair[0], pair[1]);
constraints
.insert((ls.pos_var + ls.size_var + GAP_SIZE as f64) | EQ(REQUIRED) | rs.pos_var);
}
// Try to maintain ratios and lock non-flexible sizes
for span in spans {
if span.fixed {
constraints.insert(span.size_var | EQ(REQUIRED) | span.size as f64);
} else {
let ratio = span.size as f64 / old_flex_space as f64;
constraints.insert((span.size_var / new_flex_space as f64) | EQ(STRONG) | ratio);
}
}
// The last pane needs to end at the end of the space
let last = spans.last().unwrap();
constraints.insert((last.pos_var + last.size_var) | EQ(REQUIRED) | space as f64);
constraints
}

View File

@ -6,7 +6,9 @@ use crate::{
SessionState,
};
use std::sync::{Arc, RwLock};
use zellij_utils::{input::command::TerminalAction, pane_size::PositionAndSize};
use zellij_utils::input::command::TerminalAction;
use zellij_utils::input::layout::LayoutTemplate;
use zellij_utils::pane_size::Size;
use std::os::unix::io::RawFd;
@ -73,12 +75,12 @@ impl ServerOsApi for FakeInputOutput {
}
}
fn create_new_screen(position_and_size: PositionAndSize) -> Screen {
fn create_new_screen(size: Size) -> Screen {
let mut bus: Bus<ScreenInstruction> = Bus::empty();
let fake_os_input = FakeInputOutput {};
bus.os_input = Some(Box::new(fake_os_input));
let mut client_attributes = ClientAttributes::default();
client_attributes.position_and_size = position_and_size;
client_attributes.size = size;
let max_panes = None;
let mode_info = ModeInfo::default();
let session_state = Arc::new(RwLock::new(SessionState::Attached));
@ -92,19 +94,20 @@ fn create_new_screen(position_and_size: PositionAndSize) -> Screen {
)
}
fn new_tab(screen: &mut Screen, pid: i32) {
screen.apply_layout(LayoutTemplate::default().into(), vec![pid]);
}
#[test]
fn open_new_tab() {
let position_and_size = PositionAndSize {
let size = Size {
cols: 121,
rows: 20,
x: 0,
y: 0,
..Default::default()
};
let mut screen = create_new_screen(position_and_size);
let mut screen = create_new_screen(size);
screen.new_tab(1);
screen.new_tab(2);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
assert_eq!(screen.tabs.len(), 2, "Screen now has two tabs");
assert_eq!(
@ -116,17 +119,14 @@ fn open_new_tab() {
#[test]
pub fn switch_to_prev_tab() {
let position_and_size = PositionAndSize {
let size = Size {
cols: 121,
rows: 20,
x: 0,
y: 0,
..Default::default()
};
let mut screen = create_new_screen(position_and_size);
let mut screen = create_new_screen(size);
screen.new_tab(1);
screen.new_tab(2);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
screen.switch_tab_prev();
assert_eq!(
@ -138,17 +138,14 @@ pub fn switch_to_prev_tab() {
#[test]
pub fn switch_to_next_tab() {
let position_and_size = PositionAndSize {
let size = Size {
cols: 121,
rows: 20,
x: 0,
y: 0,
..Default::default()
};
let mut screen = create_new_screen(position_and_size);
let mut screen = create_new_screen(size);
screen.new_tab(1);
screen.new_tab(2);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
screen.switch_tab_prev();
screen.switch_tab_next();
@ -161,17 +158,14 @@ pub fn switch_to_next_tab() {
#[test]
pub fn close_tab() {
let position_and_size = PositionAndSize {
let size = Size {
cols: 121,
rows: 20,
x: 0,
y: 0,
..Default::default()
};
let mut screen = create_new_screen(position_and_size);
let mut screen = create_new_screen(size);
screen.new_tab(1);
screen.new_tab(2);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
screen.close_tab();
assert_eq!(screen.tabs.len(), 1, "Only one tab left");
@ -184,20 +178,32 @@ pub fn close_tab() {
#[test]
pub fn close_the_middle_tab() {
let position_and_size = PositionAndSize {
let size = Size {
cols: 121,
rows: 20,
x: 0,
y: 0,
..Default::default()
};
let mut screen = create_new_screen(position_and_size);
let mut screen = create_new_screen(size);
screen.new_tab(1);
screen.new_tab(2);
screen.new_tab(3);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
new_tab(&mut screen, 3);
dbg!(screen
.tabs
.values()
.map(|t| (t.index, t.position, t.name.clone(), t.get_pane_ids()))
.collect::<Vec<_>>());
screen.switch_tab_prev();
dbg!(screen
.tabs
.values()
.map(|t| (t.index, t.position, t.name.clone(), t.get_pane_ids()))
.collect::<Vec<_>>());
screen.close_tab();
dbg!(screen
.tabs
.values()
.map(|t| (t.index, t.position, t.name.clone(), t.get_pane_ids()))
.collect::<Vec<_>>());
assert_eq!(screen.tabs.len(), 2, "Two tabs left");
assert_eq!(
@ -209,18 +215,15 @@ pub fn close_the_middle_tab() {
#[test]
fn move_focus_left_at_left_screen_edge_changes_tab() {
let position_and_size = PositionAndSize {
let size = Size {
cols: 121,
rows: 20,
x: 0,
y: 0,
..Default::default()
};
let mut screen = create_new_screen(position_and_size);
let mut screen = create_new_screen(size);
screen.new_tab(1);
screen.new_tab(2);
screen.new_tab(3);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
new_tab(&mut screen, 3);
screen.switch_tab_prev();
screen.move_focus_left_or_previous_tab();
@ -233,18 +236,15 @@ fn move_focus_left_at_left_screen_edge_changes_tab() {
#[test]
fn move_focus_right_at_right_screen_edge_changes_tab() {
let position_and_size = PositionAndSize {
let size = Size {
cols: 121,
rows: 20,
x: 0,
y: 0,
..Default::default()
};
let mut screen = create_new_screen(position_and_size);
let mut screen = create_new_screen(size);
screen.new_tab(1);
screen.new_tab(2);
screen.new_tab(3);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
new_tab(&mut screen, 3);
screen.switch_tab_prev();
screen.move_focus_right_or_next_tab();
@ -257,17 +257,14 @@ fn move_focus_right_at_right_screen_edge_changes_tab() {
#[test]
pub fn toggle_to_previous_tab_simple() {
let position_and_size = PositionAndSize {
let position_and_size = Size {
cols: 121,
rows: 20,
x: 0,
y: 0,
..Default::default()
};
let mut screen = create_new_screen(position_and_size);
screen.new_tab(1);
screen.new_tab(2);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
screen.go_to_tab(1);
screen.go_to_tab(2);
@ -288,18 +285,15 @@ pub fn toggle_to_previous_tab_simple() {
#[test]
pub fn toggle_to_previous_tab_create_tabs_only() {
let position_and_size = PositionAndSize {
let position_and_size = Size {
cols: 121,
rows: 20,
x: 0,
y: 0,
..Default::default()
};
let mut screen = create_new_screen(position_and_size);
screen.new_tab(1);
screen.new_tab(2);
screen.new_tab(3);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
new_tab(&mut screen, 3);
assert_eq!(
screen.tab_history,
@ -341,19 +335,16 @@ pub fn toggle_to_previous_tab_create_tabs_only() {
#[test]
pub fn toggle_to_previous_tab_delete() {
let position_and_size = PositionAndSize {
let position_and_size = Size {
cols: 121,
rows: 20,
x: 0,
y: 0,
..Default::default()
};
let mut screen = create_new_screen(position_and_size);
screen.new_tab(1); // 0
screen.new_tab(2); // 1
screen.new_tab(3); // 2
screen.new_tab(4); // 3
new_tab(&mut screen, 1); // 0
new_tab(&mut screen, 2); // 1
new_tab(&mut screen, 3); // 2
new_tab(&mut screen, 4); // 3
assert_eq!(
screen.tab_history,

File diff suppressed because it is too large Load Diff

View File

@ -170,9 +170,6 @@ pub(crate) fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObj
zellij_export! {
host_subscribe,
host_unsubscribe,
host_set_invisible_borders,
host_set_fixed_height,
host_set_fixed_width,
host_set_selectable,
host_get_plugin_ids,
host_open_file,
@ -204,42 +201,6 @@ fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) {
.unwrap()
}
fn host_set_fixed_height(plugin_env: &PluginEnv, fixed_height: i32) {
let fixed_height = fixed_height as usize;
plugin_env
.senders
.send_to_screen(ScreenInstruction::SetFixedHeight(
PaneId::Plugin(plugin_env.plugin_id),
fixed_height,
plugin_env.tab_index,
))
.unwrap()
}
fn host_set_fixed_width(plugin_env: &PluginEnv, fixed_width: i32) {
let fixed_width = fixed_width as usize;
plugin_env
.senders
.send_to_screen(ScreenInstruction::SetFixedWidth(
PaneId::Plugin(plugin_env.plugin_id),
fixed_width,
plugin_env.tab_index,
))
.unwrap()
}
fn host_set_invisible_borders(plugin_env: &PluginEnv, invisible_borders: i32) {
let invisible_borders = invisible_borders != 0;
plugin_env
.senders
.send_to_screen(ScreenInstruction::SetInvisibleBorders(
PaneId::Plugin(plugin_env.plugin_id),
invisible_borders,
plugin_env.tab_index,
))
.unwrap()
}
fn host_get_plugin_ids(plugin_env: &PluginEnv) {
let ids = PluginIds {
plugin_id: plugin_env.plugin_id,
@ -289,7 +250,6 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
// Helper Functions ---------------------------------------------------------------------------------------------------
// FIXME: Unwrap city
pub fn wasi_read_string(wasi_env: &WasiEnv) -> String {
let mut state = wasi_env.state();
let wasi_file = state.fs.stdout_mut().unwrap().as_mut().unwrap();

View File

@ -17,22 +17,10 @@ pub fn unsubscribe(event_types: &[EventType]) {
// Plugin Settings
pub fn set_fixed_height(fixed_height: i32) {
unsafe { host_set_fixed_height(fixed_height) };
}
pub fn set_fixed_width(fixed_width: i32) {
unsafe { host_set_fixed_width(fixed_width) };
}
pub fn set_selectable(selectable: bool) {
unsafe { host_set_selectable(if selectable { 1 } else { 0 }) };
}
pub fn set_invisible_borders(invisible_borders: bool) {
unsafe { host_set_invisible_borders(if invisible_borders { 1 } else { 0 }) };
}
// Query Functions
pub fn get_plugin_ids() -> PluginIds {
unsafe { host_get_plugin_ids() };
@ -68,10 +56,7 @@ pub fn object_to_stdout(object: &impl Serialize) {
extern "C" {
fn host_subscribe();
fn host_unsubscribe();
fn host_set_fixed_height(fixed_height: i32);
fn host_set_fixed_width(fixed_width: i32);
fn host_set_selectable(selectable: i32);
fn host_set_invisible_borders(invisible_borders: i32);
fn host_get_plugin_ids();
fn host_open_file();
fn host_set_timeout(secs: f64);

View File

@ -22,7 +22,7 @@ pub enum Direction {
// They might need to be adjusted in the default config
// as well `../../assets/config/default.yaml`
/// Actions that can be bound to keys.
#[derive(Eq, Clone, Debug, PartialEq, Deserialize, Serialize)]
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum Action {
/// Quit Zellij.
Quit,

View File

@ -10,28 +10,43 @@
// then [`zellij-utils`] could be a proper place.
use crate::{
input::{command::RunCommand, config::ConfigError},
pane_size::PositionAndSize,
pane_size::{Dimension, PaneGeom},
setup,
};
use crate::{serde, serde_yaml};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::vec::Vec;
use std::{
cmp::max,
ops::Not,
path::{Path, PathBuf},
};
use std::{fs::File, io::prelude::*};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
#[serde(crate = "self::serde")]
pub enum Direction {
Horizontal,
Vertical,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
impl Not for Direction {
type Output = Self;
fn not(self) -> Self::Output {
match self {
Direction::Horizontal => Direction::Vertical,
Direction::Vertical => Direction::Horizontal,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
#[serde(crate = "self::serde")]
pub enum SplitSize {
Percent(u8), // 1 to 100
Fixed(u16), // An absolute number of columns or rows
Percent(f64), // 1 to 100
Fixed(usize), // An absolute number of columns or rows
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
@ -44,7 +59,7 @@ pub enum Run {
}
// The layout struct ultimately used to build the layouts.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(crate = "self::serde")]
pub struct Layout {
pub direction: Direction,
@ -58,7 +73,7 @@ pub struct Layout {
// The struct that is used to deserialize the layout from
// a yaml configuration file
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(crate = "self::serde")]
#[serde(default)]
pub struct LayoutFromYaml {
@ -148,7 +163,7 @@ impl LayoutFromYaml {
// The struct that carries the information template that is used to
// construct the layout
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(crate = "self::serde")]
pub struct LayoutTemplate {
pub direction: Direction,
@ -191,7 +206,7 @@ impl LayoutTemplate {
}
// The tab-layout struct used to specify each individual tab.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(crate = "self::serde")]
pub struct TabLayout {
pub direction: Direction,
@ -238,10 +253,7 @@ impl Layout {
run_instructions
}
pub fn position_panes_in_space(
&self,
space: &PositionAndSize,
) -> Vec<(Layout, PositionAndSize)> {
pub fn position_panes_in_space(&self, space: &PaneGeom) -> Vec<(Layout, PaneGeom)> {
split_space(space, self)
}
@ -268,149 +280,90 @@ impl Layout {
}
}
fn split_space_to_parts_vertically(
space_to_split: &PositionAndSize,
sizes: Vec<Option<SplitSize>>,
) -> Vec<PositionAndSize> {
let mut split_parts = Vec::new();
let mut current_x_position = space_to_split.x;
let mut current_width = 0;
let max_width = space_to_split.cols;
let mut parts_to_grow = Vec::new();
// First fit in the parameterized sizes
for size in sizes {
let columns = match size {
Some(SplitSize::Percent(percent)) => {
(max_width as f32 * (percent as f32 / 100.0)) as usize
} // TODO: round properly
Some(SplitSize::Fixed(size)) => size as usize,
None => {
parts_to_grow.push(current_x_position);
1 // This is grown later on
}
};
split_parts.push(PositionAndSize {
x: current_x_position,
y: space_to_split.y,
cols: columns,
rows: space_to_split.rows,
..Default::default()
});
current_width += columns;
current_x_position += columns;
}
if current_width > max_width {
panic!("Layout contained too many columns to fit onto the screen!");
}
let mut last_flexible_index = split_parts.len() - 1;
if let Some(new_columns) = (max_width - current_width).checked_div(parts_to_grow.len()) {
current_width = 0;
current_x_position = 0;
for (idx, part) in split_parts.iter_mut().enumerate() {
part.x = current_x_position;
if parts_to_grow.contains(&part.x) {
part.cols = new_columns;
last_flexible_index = idx;
}
current_width += part.cols;
current_x_position += part.cols;
fn layout_size(direction: Direction, layout: &Layout) -> usize {
fn child_layout_size(
direction: Direction,
parent_direction: Direction,
layout: &Layout,
) -> usize {
let size = if parent_direction == direction { 1 } else { 0 };
if layout.parts.is_empty() {
size
} else {
let children_size = layout
.parts
.iter()
.map(|p| child_layout_size(direction, layout.direction, p))
.sum::<usize>();
max(size, children_size)
}
}
if current_width < max_width {
// we have some extra space left, let's add it to the last flexible part
let extra = max_width - current_width;
let mut last_part = split_parts.get_mut(last_flexible_index).unwrap();
last_part.cols += extra;
for part in (&mut split_parts[last_flexible_index + 1..]).iter_mut() {
part.x += extra;
}
}
split_parts
child_layout_size(direction, direction, layout)
}
fn split_space_to_parts_horizontally(
space_to_split: &PositionAndSize,
sizes: Vec<Option<SplitSize>>,
) -> Vec<PositionAndSize> {
let mut split_parts = Vec::new();
let mut current_y_position = space_to_split.y;
let mut current_height = 0;
let max_height = space_to_split.rows;
let mut parts_to_grow = Vec::new();
for size in sizes {
let rows = match size {
Some(SplitSize::Percent(percent)) => {
(max_height as f32 * (percent as f32 / 100.0)) as usize
} // TODO: round properly
Some(SplitSize::Fixed(size)) => size as usize,
None => {
parts_to_grow.push(current_y_position);
1 // This is grown later on
}
};
split_parts.push(PositionAndSize {
x: space_to_split.x,
y: current_y_position,
cols: space_to_split.cols,
rows,
..Default::default()
});
current_height += rows;
current_y_position += rows;
}
if current_height > max_height {
panic!("Layout contained too many rows to fit onto the screen!");
}
let mut last_flexible_index = split_parts.len() - 1;
if let Some(new_rows) = (max_height - current_height).checked_div(parts_to_grow.len()) {
current_height = 0;
current_y_position = 0;
for (idx, part) in split_parts.iter_mut().enumerate() {
part.y = current_y_position;
if parts_to_grow.contains(&part.y) {
part.rows = new_rows;
last_flexible_index = idx;
}
current_height += part.rows;
current_y_position += part.rows;
}
}
if current_height < max_height {
// we have some extra space left, let's add it to the last flexible part
let extra = max_height - current_height;
let mut last_part = split_parts.get_mut(last_flexible_index).unwrap();
last_part.rows += extra;
for part in (&mut split_parts[last_flexible_index + 1..]).iter_mut() {
part.y += extra;
}
}
split_parts
}
fn split_space(
space_to_split: &PositionAndSize,
layout: &Layout,
) -> Vec<(Layout, PositionAndSize)> {
fn split_space(space_to_split: &PaneGeom, layout: &Layout) -> Vec<(Layout, PaneGeom)> {
let mut pane_positions = Vec::new();
let sizes: Vec<Option<SplitSize>> = layout.parts.iter().map(|part| part.split_size).collect();
let split_parts = match layout.direction {
Direction::Vertical => split_space_to_parts_vertically(space_to_split, sizes),
Direction::Horizontal => split_space_to_parts_horizontally(space_to_split, sizes),
};
let mut split_geom = Vec::new();
let (mut current_position, split_dimension_space, mut inherited_dimension) =
match layout.direction {
Direction::Vertical => (space_to_split.x, space_to_split.cols, space_to_split.rows),
Direction::Horizontal => (space_to_split.y, space_to_split.rows, space_to_split.cols),
};
let flex_parts = sizes.iter().filter(|s| s.is_none()).count();
for (&size, part) in sizes.iter().zip(&layout.parts) {
let split_dimension = match size {
Some(SplitSize::Percent(percent)) => Dimension::percent(percent),
Some(SplitSize::Fixed(size)) => Dimension::fixed(size),
None => {
let free_percent = if let Some(p) = split_dimension_space.as_percent() {
p - sizes
.iter()
.map(|&s| {
if let Some(SplitSize::Percent(ip)) = s {
ip
} else {
0.0
}
})
.sum::<f64>()
} else {
panic!("Implicit sizing within fixed-size panes is not supported");
};
Dimension::percent(free_percent / flex_parts as f64)
}
};
inherited_dimension.set_inner(
layout
.parts
.iter()
.map(|p| layout_size(!layout.direction, p))
.max()
.unwrap(),
);
let geom = match layout.direction {
Direction::Vertical => PaneGeom {
x: current_position,
y: space_to_split.y,
cols: split_dimension,
rows: inherited_dimension,
},
Direction::Horizontal => PaneGeom {
x: space_to_split.x,
y: current_position,
cols: inherited_dimension,
rows: split_dimension,
},
};
split_geom.push(geom);
current_position += layout_size(layout.direction, part);
}
for (i, part) in layout.parts.iter().enumerate() {
let part_position_and_size = split_parts.get(i).unwrap();
let part_position_and_size = split_geom.get(i).unwrap();
if !part.parts.is_empty() {
let mut part_positions = split_space(part_position_and_size, part);
pane_positions.append(&mut part_positions);

View File

@ -163,7 +163,7 @@ fn three_panes_with_tab_merged_correctly() {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
@ -174,14 +174,14 @@ fn three_panes_with_tab_merged_correctly() {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
],
@ -263,7 +263,7 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
@ -274,14 +274,14 @@ fn three_panes_with_tab_and_default_plugins_merged_correctly() {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
],
@ -380,7 +380,7 @@ fn deeply_nested_tab_merged_correctly() {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(21)),
split_size: Some(SplitSize::Percent(21.0)),
run: None,
},
Layout {
@ -391,7 +391,7 @@ fn deeply_nested_tab_merged_correctly() {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(22)),
split_size: Some(SplitSize::Percent(22.0)),
run: None,
},
Layout {
@ -402,47 +402,47 @@ fn deeply_nested_tab_merged_correctly() {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(23)),
split_size: Some(SplitSize::Percent(23.0)),
run: None,
},
Layout {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(24)),
split_size: Some(SplitSize::Percent(24.0)),
run: None,
},
],
split_size: Some(SplitSize::Percent(78)),
split_size: Some(SplitSize::Percent(78.0)),
run: None,
},
],
split_size: Some(SplitSize::Percent(79)),
split_size: Some(SplitSize::Percent(79.0)),
run: None,
},
],
split_size: Some(SplitSize::Percent(90)),
split_size: Some(SplitSize::Percent(90.0)),
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(15)),
split_size: Some(SplitSize::Percent(15.0)),
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(15)),
split_size: Some(SplitSize::Percent(15.0)),
run: None,
},
Layout {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(15)),
split_size: Some(SplitSize::Percent(15.0)),
run: None,
},
],
@ -484,7 +484,7 @@ fn three_tabs_tab_one_merged_correctly() {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
@ -523,7 +523,7 @@ fn three_tabs_tab_two_merged_correctly() {
direction: Direction::Horizontal,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
@ -534,7 +534,7 @@ fn three_tabs_tab_two_merged_correctly() {
run: None,
},
],
split_size: Some(SplitSize::Percent(50)),
split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
@ -573,7 +573,7 @@ fn three_tabs_tab_three_merged_correctly() {
direction: Direction::Vertical,
borderless: false,
parts: vec![],
split_size: Some(SplitSize::Percent(50)),
split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {
@ -584,7 +584,7 @@ fn three_tabs_tab_three_merged_correctly() {
run: None,
},
],
split_size: Some(SplitSize::Percent(50)),
split_size: Some(SplitSize::Percent(50.0)),
run: None,
},
Layout {

View File

@ -4,7 +4,7 @@ use crate::{
cli::CliArgs,
errors::{get_current_ctx, ErrorContext},
input::{actions::Action, layout::LayoutFromYaml, options::Options},
pane_size::PositionAndSize,
pane_size::Size,
};
use interprocess::local_socket::LocalSocketStream;
use nix::unistd::dup;
@ -39,7 +39,7 @@ pub enum ClientType {
#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy)]
pub struct ClientAttributes {
pub position_and_size: PositionAndSize,
pub size: Size,
pub palette: Palette,
}
@ -57,13 +57,8 @@ pub enum ClientToServerMsg {
DetachSession(SessionId),
// Disconnect from the session we're connected to
DisconnectFromSession,*/
TerminalResize(PositionAndSize),
NewClient(
ClientAttributes,
Box<CliArgs>,
Box<Options>,
Option<LayoutFromYaml>,
),
TerminalResize(Size),
NewClient(ClientAttributes, Box<CliArgs>, Box<Options>, LayoutFromYaml),
AttachClient(ClientAttributes, bool, Options),
Action(Action),
ClientExited,

View File

@ -1,49 +1,144 @@
use nix::pty::Winsize;
use serde::{Deserialize, Serialize};
use crate::position::Position;
/// Contains the position and size of a [`Pane`], or more generally of any terminal, measured
/// in character rows and columns.
#[derive(Clone, Copy, Default, PartialEq, Debug, Serialize, Deserialize)]
pub struct PaneGeom {
pub x: usize,
pub y: usize,
pub rows: Dimension,
pub cols: Dimension,
}
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct PositionAndSize {
pub struct Viewport {
pub x: usize,
pub y: usize,
pub rows: usize,
pub cols: usize,
// FIXME: Honestly, these shouldn't exist and rows / columns should be enums like:
// Dimension::Flex(usize) / Dimension::Fixed(usize), but 400+ compiler errors is more than
// I'm in the mood for right now...
pub rows_fixed: bool,
pub cols_fixed: bool,
}
impl From<Winsize> for PositionAndSize {
fn from(winsize: Winsize) -> PositionAndSize {
PositionAndSize {
cols: winsize.ws_col as usize,
rows: winsize.ws_row as usize,
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct Offset {
pub top: usize,
pub bottom: usize,
pub right: usize,
pub left: usize,
}
#[derive(Clone, Copy, Default, PartialEq, Debug, Serialize, Deserialize)]
pub struct Size {
pub rows: usize,
pub cols: usize,
}
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
pub struct Dimension {
pub constraint: Constraint,
inner: usize,
}
impl Default for Dimension {
fn default() -> Self {
Self::percent(100.0)
}
}
impl Dimension {
pub fn fixed(size: usize) -> Dimension {
Self {
constraint: Constraint::Fixed(size),
inner: 1,
}
}
pub fn percent(percent: f64) -> Dimension {
Self {
constraint: Constraint::Percent(percent),
inner: 1,
}
}
pub fn as_usize(&self) -> usize {
self.inner
}
pub fn as_percent(&self) -> Option<f64> {
if let Constraint::Percent(p) = self.constraint {
Some(p)
} else {
None
}
}
pub fn set_inner(&mut self, inner: usize) {
self.inner = inner;
}
pub fn is_fixed(&self) -> bool {
matches!(self.constraint, Constraint::Fixed(_))
}
}
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
pub enum Constraint {
/// Constrains the dimension to a fixed, integer number of rows / columns
Fixed(usize),
/// Constrains the dimension to a flexible percent size of the total screen
Percent(f64),
}
impl PaneGeom {
pub fn contains(&self, point: &Position) -> bool {
let col = point.column.0 as usize;
let row = point.line.0 as usize;
self.x <= col
&& col < self.x + self.cols.as_usize()
&& self.y <= row
&& row < self.y + self.rows.as_usize()
}
}
impl Offset {
pub fn frame(size: usize) -> Self {
Self {
top: size,
bottom: size,
right: size,
left: size,
}
}
// FIXME: This should be top and left, not bottom and right, but `boundaries.rs` would need
// some changing
pub fn shift(bottom: usize, right: usize) -> Self {
Self {
bottom,
right,
..Default::default()
}
}
}
impl PositionAndSize {
pub fn contains(&self, point: &Position) -> bool {
let col = point.column.0 as usize;
let row = point.line.0 as usize;
self.x <= col && col < self.x + self.cols && self.y <= row && row < self.y + self.rows
}
pub fn reduce_outer_frame(mut self, frame_width: usize) -> Self {
self.x += frame_width;
self.rows -= frame_width * 2;
self.y += frame_width;
self.cols -= frame_width * 2;
self
}
pub fn reduce_top_line(mut self) -> Self {
self.y += 1;
self.rows -= 1;
self
impl From<PaneGeom> for Viewport {
fn from(pane: PaneGeom) -> Self {
Self {
x: pane.x,
y: pane.y,
rows: pane.rows.as_usize(),
cols: pane.cols.as_usize(),
}
}
}
impl From<Size> for Viewport {
fn from(size: Size) -> Self {
Self {
rows: size.rows,
cols: size.cols,
..Default::default()
}
}
}

View File

@ -1,7 +1,5 @@
use serde::{Deserialize, Serialize};
use crate::pane_size::PositionAndSize;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Deserialize, Serialize)]
pub struct Position {
pub line: Line,
@ -16,10 +14,10 @@ impl Position {
}
}
pub fn relative_to(&self, position_and_size: &PositionAndSize) -> Self {
pub fn relative_to(&self, line: usize, column: usize) -> Self {
Self {
line: Line(self.line.0 - position_and_size.y as isize),
column: Column(self.column.0.saturating_sub(position_and_size.x)),
line: Line(self.line.0 - line as isize),
column: Column(self.column.0.saturating_sub(column)),
}
}
}

View File

@ -2,12 +2,11 @@
use std::{iter, str::from_utf8};
use strip_ansi_escapes::strip;
use colors_transform::{Color, Rgb};
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::{fs, io};
use strip_ansi_escapes::strip;
use zellij_tile::data::{Palette, PaletteColor, PaletteSource, ThemeHue};
const UNIX_PERMISSIONS: u32 = 0o700;