feat(ui): pane frames (new pane UI) (#643)

* work

* resize working

* move focus working

* close pane working

* selection and fullscreen working

* pane title line

* titles and conditional scroll title

* whole tab resize working

* plugin frames working

* plugin splitting working

* truncate pane frame titles

* cleanup

* panes always draw their own borders - also fix gap

* toggle pane frames

* move toggle to screen and fix some bugs

* fix plugin frame toggle

* fix terminal window resize

* fix scrolling and fullscreen bugs

* unit tests passing

* e2e tests passing and new test for new frames added

* refactor: TerminalPane and PluginPane

* refactor: Tab

* refactor: moar Tab

* refactor: Boundaries

* only render and calculate boundaries when there are no pane frames

* refactor: Layout

* fix(grid): properly resize when coming back from alternative viewport

* style: remove commented code

* style: fmt

* style: fmt

* style: fmt + clippy

* docs(changelog): update change
This commit is contained in:
Aram Drevekenin 2021-08-12 14:50:00 +02:00 committed by GitHub
parent 426cee728a
commit a37d3e5889
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 2420 additions and 1079 deletions

View File

@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
* Bound by default to `^c` in `scroll` mode, scrolls to bottom and exists the scroll mode
* Simplify deserialization slightly (https://github.com/zellij-org/zellij/pull/633)
* Fix update plugin attributes on inactive tab (https://github.com/zellij-org/zellij/pull/634)
* New pane UI: draw pane frames - can be disabled with ctrl-p + z, or through configuration (https://github.com/zellij-org/zellij/pull/643)
## [0.15.0] - 2021-07-19
* Kill children properly (https://github.com/zellij-org/zellij/pull/601)

View File

@ -154,7 +154,11 @@ impl ZellijPlugin for State {
let colored_elements = color_elements(self.mode_info.palette);
let superkey = superkey(colored_elements, separator);
let ctrl_keys = ctrl_keys(&self.mode_info, cols - superkey.len, separator);
let ctrl_keys = ctrl_keys(
&self.mode_info,
cols.saturating_sub(superkey.len),
separator,
);
let first_line = format!("{}{}", superkey, ctrl_keys);
let second_line = keybinds(&self.mode_info, cols);

View File

@ -47,10 +47,10 @@ impl FsEntry {
FsEntry::Dir(_, s) => s.to_string(),
FsEntry::File(_, s) => pb::convert(*s as f64),
};
let space = width - info.len();
let space = width.saturating_sub(info.len());
let name = self.name();
if space - 1 < name.len() {
[&name[..space - 2], &info].join("~ ")
if space.saturating_sub(1) < name.len() {
[&name[..space.saturating_sub(2)], &info].join("~ ")
} else {
let padding = " ".repeat(space - name.len());
[name, padding, info].concat()

View File

@ -191,7 +191,7 @@ pub fn tab_line(
&mut tabs_before_active,
&mut tabs_after_active,
&mut tabs_to_render,
cols - prefix.len,
cols.saturating_sub(prefix.len),
);
let mut tab_line: Vec<LinePart> = vec![];
@ -200,7 +200,7 @@ pub fn tab_line(
&mut tabs_before_active,
&mut tabs_to_render,
&mut tab_line,
cols - prefix.len,
cols.saturating_sub(prefix.len),
palette,
tab_separator(capabilities),
);
@ -210,7 +210,7 @@ pub fn tab_line(
add_next_tabs_msg(
&mut tabs_after_active,
&mut tab_line,
cols - prefix.len,
cols.saturating_sub(prefix.len),
palette,
tab_separator(capabilities),
);

View File

@ -221,26 +221,26 @@ pub fn scrolling_inside_a_pane() {
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<57}", "line1 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line2 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line3 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line4 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line5 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line6 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line7 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line8 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line9 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line10 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line11 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line12 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line13 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line14 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line15 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line16 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line17 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line18 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line19 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line20 ").as_bytes());
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
@ -250,7 +250,7 @@ pub fn scrolling_inside_a_pane() {
name: "Scroll up inside pane",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(119, 20) {
if remote_terminal.cursor_position_is(118, 20) {
// all lines have been written to the pane
remote_terminal.send_key(&SCROLL_MODE);
remote_terminal.send_key(&SCROLL_UP_IN_SCROLL_MODE);
@ -263,7 +263,7 @@ pub fn scrolling_inside_a_pane() {
name: "Wait for scroll to finish",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(119, 20)
if remote_terminal.cursor_position_is(118, 20)
&& remote_terminal.snapshot_contains("line1 ")
{
// scrolled up one line
@ -321,7 +321,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, 0) {
if remote_terminal.cursor_position_is(2, 2) {
// cursor is in full screen pane now
step_is_complete = true;
}
@ -785,8 +785,8 @@ pub fn accepts_basic_layout() {
name: "Wait for app to load",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(2, 0)
&& remote_terminal.snapshot_contains("$ █ │$")
if remote_terminal.cursor_position_is(3, 1)
&& remote_terminal.snapshot_contains("$ █ │$")
&& remote_terminal.snapshot_contains("$ ") {
step_is_complete = true;
}
@ -839,7 +839,7 @@ fn focus_pane_with_mouse() {
name: "Wait for left pane to be focused",
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 newly opened second pane
step_is_complete = true;
}
@ -884,26 +884,26 @@ pub fn scrolling_inside_a_pane_with_mouse() {
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<57}", "line1 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line2 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line3 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line4 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line5 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line6 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line7 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line8 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line9 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line10 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line11 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line12 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line13 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line14 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line15 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line16 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line17 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line18 ").as_bytes());
remote_terminal.send_key(&format!("{:0<59}", "line19 ").as_bytes());
remote_terminal.send_key(&format!("{:0<58}", "line20 ").as_bytes());
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
@ -913,7 +913,7 @@ pub fn scrolling_inside_a_pane_with_mouse() {
name: "Scroll up inside pane",
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(119, 20) {
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;
@ -925,7 +925,7 @@ pub fn scrolling_inside_a_pane_with_mouse() {
name: "Wait for scroll to finish",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(119, 20)
if remote_terminal.cursor_position_is(118, 20)
&& remote_terminal.snapshot_contains("line1 ")
{
// scrolled up one line
@ -937,3 +937,45 @@ pub fn scrolling_inside_a_pane_with_mouse() {
.run_all_steps();
assert_snapshot!(last_snapshot);
}
#[test]
#[ignore]
pub fn start_without_pane_frames() {
let fake_win_size = PositionAndSize {
cols: 120,
rows: 24,
x: 0,
y: 0,
..Default::default()
};
let last_snapshot = RemoteRunner::new_without_frames("no_pane_frames", 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, 1)
{
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: "Wait for new pane to appear",
instruction: |remote_terminal: RemoteTerminal| -> bool {
let mut step_is_complete = false;
if remote_terminal.cursor_position_is(62, 1) && remote_terminal.tip_appears() {
// cursor is in the newly opened second pane
step_is_complete = true;
}
step_is_complete
},
})
.run_all_steps();
assert_snapshot!(last_snapshot);
}

View File

@ -57,6 +57,13 @@ fn start_zellij(channel: &mut ssh2::Channel, session_name: Option<&String>) {
channel.flush().unwrap();
}
fn start_zellij_without_frames(channel: &mut ssh2::Channel) {
channel
.write_all(format!("{} options --no-pane-frames\n", ZELLIJ_EXECUTABLE_LOCATION).as_bytes())
.unwrap();
channel.flush().unwrap();
}
fn start_zellij_with_layout(
channel: &mut ssh2::Channel,
layout_path: &str,
@ -136,7 +143,8 @@ impl<'a> RemoteTerminal<'a> {
self.current_snapshot.contains("Tip:")
}
pub fn status_bar_appears(&self) -> bool {
self.current_snapshot.contains("Ctrl +") && !self.current_snapshot.contains("─────")
self.current_snapshot.contains("Ctrl +")
// self.current_snapshot.contains("Ctrl +") && !self.current_snapshot.contains("─────")
// this is a bug that happens because the app draws borders around the status bar momentarily on first render
}
pub fn snapshot_contains(&self, text: &str) -> bool {
@ -198,6 +206,7 @@ pub struct RemoteRunner {
retries_left: usize,
win_size: PositionAndSize,
layout_file_name: Option<&'static str>,
without_frames: bool,
}
impl RemoteRunner {
@ -209,7 +218,7 @@ impl RemoteRunner {
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());
let terminal_output = TerminalPane::new(0, win_size, Palette::default(), 0); // 0 is the pane index
setup_remote_environment(&mut channel, win_size);
start_zellij(&mut channel, session_name.as_ref());
RemoteRunner {
@ -224,6 +233,33 @@ impl RemoteRunner {
retries_left: 3,
win_size,
layout_file_name: None,
without_frames: false,
}
}
pub fn new_without_frames(
test_name: &'static str,
win_size: PositionAndSize,
session_name: Option<String>,
) -> 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
setup_remote_environment(&mut channel, win_size);
start_zellij_without_frames(&mut channel);
RemoteRunner {
steps: vec![],
channel,
terminal_output,
vte_parser,
session_name,
test_name,
currently_running_step: None,
current_step_index: 0,
retries_left: 3,
win_size,
layout_file_name: None,
without_frames: true,
}
}
pub fn new_with_layout(
@ -232,12 +268,11 @@ impl RemoteRunner {
layout_file_name: &'static str,
session_name: Option<String>,
) -> Self {
// let layout_file_name = local_layout_path.file_name().unwrap();
let remote_path = Path::new(ZELLIJ_LAYOUT_PATH).join(layout_file_name); // TODO: not hardcoded
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());
let terminal_output = TerminalPane::new(0, win_size, Palette::default(), 0); // 0 is the pane index
setup_remote_environment(&mut channel, win_size);
start_zellij_with_layout(
&mut channel,
@ -256,6 +291,7 @@ impl RemoteRunner {
retries_left: 3,
win_size,
layout_file_name: Some(layout_file_name),
without_frames: false,
}
}
pub fn add_step(mut self, step: Step) -> Self {
@ -315,6 +351,13 @@ impl RemoteRunner {
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);
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);
new_runner.retries_left = self.retries_left - 1;

View File

@ -3,27 +3,27 @@ source: src/tests/e2e/cases.rs
expression: last_snapshot
---
$ █ │$
───────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────
$
┌ Pane #1 ─────────────┐┌ Pane #2 ─────────────────────────────────────────────────────────────────────────────────────┐
│$ █ ││$ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
──────────────────────┘└──────────────────────────────────────────────────────────────────────────────────────────────
┌ Pane #3 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│$
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

View File

@ -1,10 +1,10 @@
---
source: src/tests/integration/e2e.rs
source: src/tests/e2e/cases.rs
expression: last_snapshot
---
────────
$ Hi!█

View File

@ -1,10 +1,10 @@
---
source: src/tests/integration/e2e.rs
source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
─ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ █

View File

@ -1,29 +1,29 @@
---
source: src/tests/integration/e2e.rs
source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
$ │$ I am some text█
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐
$ │$ I am some text█
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
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

@ -4,26 +4,26 @@ expression: last_snapshot
---
Zellij  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

@ -1,10 +1,10 @@
---
source: src/tests/integration/e2e.rs
source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
─ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ nabc█

View File

@ -1,10 +1,10 @@
---
source: src/tests/integration/e2e.rs
source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1  Tab #2 
─ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ █

View File

@ -1,29 +1,29 @@
---
source: src/tests/integration/e2e.rs
source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  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

@ -1,29 +1,29 @@
---
source: src/tests/integration/e2e.rs
source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  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

@ -1,29 +1,29 @@
---
source: src/tests/integration/e2e.rs
source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
$ │$ line1 000000000000000000000000000000000000000000000000000
│line2 00000000000000000000000000000000000000000000000000000
│line3 00000000000000000000000000000000000000000000000000000
│line4 00000000000000000000000000000000000000000000000000000
│line5 00000000000000000000000000000000000000000000000000000
│line6 00000000000000000000000000000000000000000000000000000
│line7 00000000000000000000000000000000000000000000000000000
│line8 00000000000000000000000000000000000000000000000000000
│line9 00000000000000000000000000000000000000000000000000000
│line10 0000000000000000000000000000000000000000000000000000
│line11 0000000000000000000000000000000000000000000000000000
│line12 0000000000000000000000000000000000000000000000000000
│line13 0000000000000000000000000000000000000000000000000000
│line14 0000000000000000000000000000000000000000000000000000
│line15 0000000000000000000000000000000000000000000000000000
│line16 0000000000000000000000000000000000000000000000000000
│line17 0000000000000000000000000000000000000000000000000000
│line18 0000000000000000000000000000000000000000000000000000
│line19 000000000000000000000000000000000000000000000000000
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────── SCROLL: 1/1 ┐
$ │$ line1 00000000000000000000000000000000000000000000000000
line2 0000000000000000000000000000000000000000000000000000
line3 0000000000000000000000000000000000000000000000000000
line4 0000000000000000000000000000000000000000000000000000
line5 0000000000000000000000000000000000000000000000000000
line6 0000000000000000000000000000000000000000000000000000
line7 0000000000000000000000000000000000000000000000000000
line8 0000000000000000000000000000000000000000000000000000
line9 0000000000000000000000000000000000000000000000000000
line10 000000000000000000000000000000000000000000000000000
line11 000000000000000000000000000000000000000000000000000
line12 000000000000000000000000000000000000000000000000000
line13 000000000000000000000000000000000000000000000000000
line14 000000000000000000000000000000000000000000000000000
line15 000000000000000000000000000000000000000000000000000
line16 000000000000000000000000000000000000000000000000000
line17 000000000000000000000000000000000000000000000000000
line18 000000000000000000000000000000000000000000000000000
line19 00000000000000000000000000000000000000000000000000█
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <r> RESIZE  <s> SCROLL  <o> SESSION  <q> QUIT 
<↓↑> Scroll / <PgUp/PgDn> Scroll Page / <ENTER> Select pane

View File

@ -4,26 +4,26 @@ expression: last_snapshot
---
Zellij  Tab #1 
$ │$ line1 000000000000000000000000000000000000000000000000000
│line2 00000000000000000000000000000000000000000000000000000
│line3 00000000000000000000000000000000000000000000000000000
│line4 00000000000000000000000000000000000000000000000000000
│line5 00000000000000000000000000000000000000000000000000000
│line6 00000000000000000000000000000000000000000000000000000
│line7 00000000000000000000000000000000000000000000000000000
│line8 00000000000000000000000000000000000000000000000000000
│line9 00000000000000000000000000000000000000000000000000000
│line10 0000000000000000000000000000000000000000000000000000
│line11 0000000000000000000000000000000000000000000000000000
│line12 0000000000000000000000000000000000000000000000000000
│line13 0000000000000000000000000000000000000000000000000000
│line14 0000000000000000000000000000000000000000000000000000
│line15 0000000000000000000000000000000000000000000000000000
│line16 0000000000000000000000000000000000000000000000000000
│line17 0000000000000000000000000000000000000000000000000000
│line18 0000000000000000000000000000000000000000000000000000
│line19 000000000000000000000000000000000000000000000000000
┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────── SCROLL: 1/1 ┐
$ │$ line1 00000000000000000000000000000000000000000000000000
line2 0000000000000000000000000000000000000000000000000000
line3 0000000000000000000000000000000000000000000000000000
line4 0000000000000000000000000000000000000000000000000000
line5 0000000000000000000000000000000000000000000000000000
line6 0000000000000000000000000000000000000000000000000000
line7 0000000000000000000000000000000000000000000000000000
line8 0000000000000000000000000000000000000000000000000000
line9 0000000000000000000000000000000000000000000000000000
line10 000000000000000000000000000000000000000000000000000
line11 000000000000000000000000000000000000000000000000000
line12 000000000000000000000000000000000000000000000000000
line13 000000000000000000000000000000000000000000000000000
line14 000000000000000000000000000000000000000000000000000
line15 000000000000000000000000000000000000000000000000000
line16 000000000000000000000000000000000000000000000000000
line17 000000000000000000000000000000000000000000000000000
line18 000000000000000000000000000000000000000000000000000
line19 00000000000000000000000000000000000000000000000000█
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
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

@ -1,29 +1,29 @@
---
source: src/tests/integration/e2e.rs
source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  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

@ -0,0 +1,29 @@
---
source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #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

@ -1,10 +1,10 @@
---
source: src/tests/integration/e2e.rs
source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
─ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ █

View File

@ -1,8 +1,10 @@
---
source: src/tests/integration/e2e.rs
source: src/tests/e2e/cases.rs
expression: last_snapshot
---
Zellij  Tab #1 
─ Pane #2 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────
$ █
@ -23,7 +25,5 @@ $ █
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

@ -10,7 +10,8 @@ use std::{
use zellij_utils::{position::Position, vte, zellij_tile};
const TABSTOP_WIDTH: usize = 8; // TODO: is this always right?
const SCROLL_BACK: usize = 10_000;
pub const SCROLL_BACK: usize = 10_000;
pub const MAX_TITLE_STACK_SIZE: usize = 1000;
use vte::{Params, Perform};
use zellij_tile::data::{Palette, PaletteColor};
@ -308,6 +309,7 @@ pub struct Grid {
preceding_char: Option<TerminalCharacter>,
colors: Palette,
output_buffer: OutputBuffer,
title_stack: Vec<String>,
pub should_render: bool,
pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "")
pub erasure_mode: bool, // ERM
@ -318,6 +320,7 @@ pub struct Grid {
pub height: usize,
pub pending_messages_to_pty: Vec<Vec<u8>>,
pub selection: Selection,
pub title: Option<String>,
}
impl Debug for Grid {
@ -358,6 +361,8 @@ impl Grid {
colors,
output_buffer: Default::default(),
selection: Default::default(),
title_stack: vec![],
title: None,
}
}
pub fn render_full_viewport(&mut self) {
@ -404,6 +409,23 @@ impl Grid {
pub fn cursor_shape(&self) -> CursorShape {
self.cursor.get_shape()
}
pub fn scrollback_position_and_length(&self) -> (usize, usize) {
// (position, length)
let mut scrollback_buffer_count = 0;
for row in self.lines_above.iter() {
let row_width = row.width();
// rows in lines_above are unwrapped, so we need to account for that
if row_width > self.width {
scrollback_buffer_count += (row_width as f64 / self.width as f64).ceil() as usize;
} else {
scrollback_buffer_count += 1;
}
}
(
self.lines_below.len(),
(scrollback_buffer_count + self.lines_below.len()),
)
}
fn set_horizontal_tabstop(&mut self) {
self.horizontal_tabstops.insert(self.cursor.x);
}
@ -475,8 +497,17 @@ impl Grid {
if !self.lines_above.is_empty() && self.viewport.len() == self.height {
let line_to_push_down = self.viewport.pop().unwrap();
self.lines_below.insert(0, line_to_push_down);
let line_to_insert_at_viewport_top = self.lines_above.pop_back().unwrap();
self.viewport.insert(0, line_to_insert_at_viewport_top);
transfer_rows_down(
&mut self.lines_above,
&mut self.viewport,
1,
None,
Some(self.width),
);
// let line_to_insert_at_viewport_top = self.lines_above.pop_back().unwrap();
// self.viewport.insert(0, line_to_insert_at_viewport_top);
self.selection.move_down(1);
}
self.output_buffer.update_all_lines();
@ -497,6 +528,24 @@ impl Grid {
self.output_buffer.update_all_lines();
}
}
fn force_change_size(&mut self, new_rows: usize, new_columns: usize) {
// this is an ugly hack - it's here because sometimes we need to change_size to the
// existing size (eg. when resizing an alternative_grid to the current height/width) and
// the change_size method is a no-op in that case. Should be fixed by making the
// change_size method atomic
let intermediate_rows = if new_rows == self.height {
new_rows + 1
} else {
new_rows
};
let intermediate_columns = if new_columns == self.width {
new_columns + 1
} else {
new_columns
};
self.change_size(intermediate_rows, intermediate_columns);
self.change_size(new_rows, new_columns);
}
pub fn change_size(&mut self, new_rows: usize, new_columns: usize) {
self.selection.reset();
if new_columns != self.width {
@ -1200,7 +1249,7 @@ impl Grid {
let empty_row = Row::from_columns(vec![EMPTY_TERMINAL_CHARACTER; self.width]);
// get the row from lines_above, viewport, or lines below depending on index
let row = if l < 0 {
let row = if l < 0 && self.lines_above.len() > l.abs() as usize {
let offset_from_end = l.abs();
&self.lines_above[self
.lines_above
@ -1211,8 +1260,12 @@ impl Grid {
} else if (l as usize) < self.height {
// index is in viewport but there is no line
&empty_row
} else {
} else if self.lines_below.len() > (l as usize).saturating_sub(self.viewport.len()) {
&self.lines_below[(l as usize) - self.viewport.len()]
} else {
// can't find the line, this probably it's on the pane border
// is on the pane border
continue;
};
let excess_width = row.excess_width();
@ -1242,6 +1295,22 @@ impl Grid {
self.output_buffer.update_line(l as usize);
}
}
fn set_title(&mut self, title: String) {
self.title = Some(title);
}
fn push_current_title_to_stack(&mut self) {
if self.title_stack.len() > MAX_TITLE_STACK_SIZE {
self.title_stack.remove(0);
}
if let Some(title) = self.title.as_ref() {
self.title_stack.push(title.clone());
}
}
fn pop_title_from_stack(&mut self) {
if let Some(popped_title) = self.title_stack.pop() {
self.title = Some(popped_title);
}
}
}
impl Perform for Grid {
@ -1311,7 +1380,7 @@ impl Perform for Grid {
// Set window title.
b"0" | b"2" => {
if params.len() >= 2 {
let _title = params[1..]
let title = params[1..]
.iter()
.flat_map(|x| str::from_utf8(x))
.collect::<Vec<&str>>()
@ -1319,6 +1388,7 @@ impl Perform for Grid {
.trim()
.to_owned();
// TBD: do something with title?
self.set_title(title);
}
}
@ -1521,7 +1591,7 @@ impl Perform for Grid {
}
self.alternative_lines_above_viewport_and_cursor = None;
self.clear_viewport_before_rendering = true;
self.change_size(self.height, self.width); // the alternative_viewport might have been of a different size...
self.force_change_size(self.height, self.width); // the alternative_viewport might have been of a different size...
self.mark_for_rerender();
}
Some(25) => {
@ -1758,10 +1828,10 @@ impl Perform for Grid {
.push(text_area_report.as_bytes().to_vec());
}
22 => {
// TODO: push title
self.push_current_title_to_stack();
}
23 => {
// TODO: pop title
self.pop_title_from_stack();
}
_ => {}
}

View File

@ -2,10 +2,13 @@ use std::sync::mpsc::channel;
use std::time::Instant;
use std::unimplemented;
use crate::panes::PaneId;
use crate::panes::{PaneDecoration, PaneId};
use crate::pty::VteBytes;
use crate::tab::Pane;
use crate::ui::pane_boundaries_frame::PaneBoundariesFrame;
use crate::wasm_vm::PluginInstruction;
use zellij_utils::shared::ansi_len;
use zellij_utils::zellij_tile::prelude::PaletteColor;
use zellij_utils::{channels::SenderWithContext, pane_size::PositionAndSize};
pub(crate) struct PluginPane {
@ -15,8 +18,11 @@ pub(crate) struct PluginPane {
pub invisible_borders: bool,
pub position_and_size: PositionAndSize,
pub position_and_size_override: Option<PositionAndSize>,
pub content_position_and_size: PositionAndSize,
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
pub active_at: Instant,
pub pane_title: String,
pane_decoration: PaneDecoration,
}
impl PluginPane {
@ -24,6 +30,7 @@ impl PluginPane {
pid: u32,
position_and_size: PositionAndSize,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
title: String,
) -> Self {
Self {
pid,
@ -34,8 +41,48 @@ impl PluginPane {
position_and_size_override: None,
send_plugin_instructions,
active_at: Instant::now(),
pane_decoration: PaneDecoration::ContentOffset((0, 0)),
content_position_and_size: position_and_size,
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 {
@ -62,13 +109,20 @@ impl Pane for PluginPane {
.unwrap_or(self.position_and_size)
.cols
}
fn get_content_columns(&self) -> usize {
self.get_content_columns()
}
fn get_content_rows(&self) -> usize {
self.get_content_rows()
}
fn reset_size_and_position_override(&mut self) {
self.position_and_size_override = None;
self.redistribute_space();
self.should_render = true;
}
fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) {
self.position_and_size = *position_and_size;
self.should_render = true;
self.redistribute_space();
}
// 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) {
@ -80,7 +134,7 @@ impl Pane for PluginPane {
..Default::default()
};
self.position_and_size_override = Some(position_and_size_override);
self.should_render = true;
self.redistribute_space();
}
fn handle_pty_bytes(&mut self, _event: VteBytes) {
unimplemented!()
@ -103,6 +157,11 @@ 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
}
@ -127,19 +186,70 @@ impl Pane for PluginPane {
// is more performant, it causes some problems when the pane to the left should be
// rendered and has wide characters (eg. Chinese characters or emoji)
// as a (hopefully) temporary hack, we render all panes until we find a better solution
let mut vte_output = String::new();
let (buf_tx, buf_rx) = channel();
self.send_plugin_instructions
.send(PluginInstruction::Render(
buf_tx,
self.pid,
self.rows(),
self.columns(),
self.get_content_rows(),
self.get_content_columns(),
))
.unwrap();
self.should_render = false;
Some(buf_rx.recv().unwrap())
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);
}
}
for (index, line) in contents.lines().enumerate() {
let actual_len = ansi_len(line);
let line_to_print = if actual_len > self.get_content_columns() {
let mut line = String::from(line);
line.truncate(self.get_content_columns());
line
} else {
[
line,
&str::repeat(" ", self.get_content_columns() - ansi_len(line)),
]
.concat()
};
vte_output.push_str(&format!(
"\u{1b}[{};{}H\u{1b}[m{}",
self.get_content_y() + 1 + index,
self.get_content_x() + 1,
line_to_print,
)); // goto row/col and reset styles
let line_len = line_to_print.len();
if line_len < self.get_content_columns() {
// pad line
for _ in line_len..self.get_content_columns() {
vte_output.push(' ');
}
}
}
let total_line_count = contents.lines().count();
if total_line_count < self.get_content_rows() {
// pad lines
for line_index in total_line_count..self.get_content_rows() {
let x = self.get_content_x();
let y = self.get_content_y();
vte_output.push_str(&format!(
"\u{1b}[{};{}H\u{1b}[m",
y + line_index + 1,
x + 1
)); // goto row/col and reset styles
for _col_index in 0..self.get_content_columns() {
vte_output.push(' ');
}
}
}
Some(vte_output)
} else {
None
}
@ -150,50 +260,66 @@ impl Pane for PluginPane {
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 increase_height_down(&mut self, count: usize) {
self.position_and_size.rows += count;
self.redistribute_space();
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_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 push_down(&mut self, count: usize) {
self.position_and_size.y += count;
self.redistribute_space();
self.should_render = true;
}
fn push_right(&mut self, count: usize) {
self.position_and_size.x += count;
self.redistribute_space();
self.should_render = true;
}
fn pull_left(&mut self, count: usize) {
self.position_and_size.x -= count;
self.redistribute_space();
self.should_render = true;
}
fn pull_up(&mut self, count: usize) {
self.position_and_size.y -= count;
self.redistribute_space();
self.should_render = true;
}
fn scroll_up(&mut self, _count: usize) {
//unimplemented!()
@ -231,4 +357,61 @@ impl Pane for PluginPane {
fn set_active_at(&mut self, time: Instant) {
self.active_at = time;
}
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.set_should_render(true);
}
}

View File

@ -6,6 +6,7 @@ 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;
@ -20,22 +21,32 @@ use crate::tab::Pane;
pub const SELECTION_SCROLL_INTERVAL_MS: u64 = 10;
use crate::ui::pane_boundaries_frame::PaneBoundariesFrame;
#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
pub enum PaneId {
Terminal(RawFd),
Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct?
}
pub enum PaneDecoration {
BoundariesFrame(PaneBoundariesFrame),
ContentOffset((usize, usize)), // (columns, rows)
}
pub struct TerminalPane {
pub grid: Grid,
pub pid: RawFd,
pub selectable: bool,
pub position_and_size: PositionAndSize,
pub position_and_size_override: Option<PositionAndSize>,
position_and_size: PositionAndSize,
position_and_size_override: Option<PositionAndSize>,
pub active_at: Instant,
pub colors: Palette,
vte_parser: vte::Parser,
selection_scrolled_at: time::Instant,
content_position_and_size: PositionAndSize,
pane_title: String,
pane_decoration: PaneDecoration,
}
impl Pane for TerminalPane {
@ -51,24 +62,29 @@ impl Pane for TerminalPane {
fn columns(&self) -> usize {
self.get_columns()
}
fn get_content_columns(&self) -> usize {
self.get_content_columns()
}
fn get_content_rows(&self) -> usize {
self.get_content_rows()
}
fn reset_size_and_position_override(&mut self) {
self.position_and_size_override = None;
self.reflow_lines();
self.redistribute_space();
}
fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) {
self.position_and_size = *position_and_size;
self.reflow_lines();
self.redistribute_space();
}
fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) {
let position_and_size_override = PositionAndSize {
self.position_and_size_override = Some(PositionAndSize {
x,
y,
rows: size.rows,
cols: size.cols,
..Default::default()
};
self.position_and_size_override = Some(position_and_size_override);
self.reflow_lines();
});
self.redistribute_space();
}
fn handle_pty_bytes(&mut self, bytes: VteBytes) {
for byte in bytes.iter() {
@ -78,7 +94,17 @@ impl Pane for TerminalPane {
}
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
// (x, y)
self.grid.cursor_coordinates()
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),
};
self.grid
.cursor_coordinates()
.map(|(x, y)| (x + x_offset, y + y_offset))
}
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
@ -134,7 +160,14 @@ 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.grid.render_full_viewport();
}
fn selectable(&self) -> bool {
@ -173,10 +206,10 @@ impl Pane for TerminalPane {
}
self.grid.clear_viewport_before_rendering = false;
}
let max_width = self.columns();
let max_width = self.get_content_columns();
for character_chunk in self.grid.read_changes() {
let pane_x = self.get_x();
let pane_y = self.get_y();
let pane_x = self.get_content_x();
let pane_y = self.get_content_y();
let chunk_absolute_x = pane_x + character_chunk.x;
let chunk_absolute_y = pane_y + character_chunk.y;
let terminal_characters = character_chunk.terminal_characters;
@ -212,6 +245,13 @@ 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);
}
}
self.set_should_render(false);
Some(vte_output)
} else {
@ -224,50 +264,54 @@ impl Pane for TerminalPane {
fn reduce_height_down(&mut self, count: usize) {
self.position_and_size.y += count;
self.position_and_size.rows -= count;
self.reflow_lines();
self.redistribute_space();
}
fn increase_height_down(&mut self, count: usize) {
self.position_and_size.rows += count;
self.reflow_lines();
self.redistribute_space();
}
fn increase_height_up(&mut self, count: usize) {
self.position_and_size.y -= count;
self.position_and_size.rows += count;
self.reflow_lines();
self.redistribute_space();
}
fn reduce_height_up(&mut self, count: usize) {
self.position_and_size.rows -= count;
self.reflow_lines();
self.redistribute_space();
}
fn reduce_width_right(&mut self, count: usize) {
self.position_and_size.x += count;
self.position_and_size.cols -= count;
self.reflow_lines();
self.redistribute_space();
}
fn reduce_width_left(&mut self, count: usize) {
self.position_and_size.cols -= count;
self.reflow_lines();
self.redistribute_space();
}
fn increase_width_left(&mut self, count: usize) {
self.position_and_size.x -= count;
self.position_and_size.cols += count;
self.reflow_lines();
self.redistribute_space();
}
fn increase_width_right(&mut self, count: usize) {
self.position_and_size.cols += count;
self.reflow_lines();
self.redistribute_space();
}
fn push_down(&mut self, count: usize) {
self.position_and_size.y += count;
self.redistribute_space();
}
fn push_right(&mut self, count: usize) {
self.position_and_size.x += count;
self.redistribute_space();
}
fn pull_left(&mut self, count: usize) {
self.position_and_size.x -= count;
self.redistribute_space();
}
fn pull_up(&mut self, count: usize) {
self.position_and_size.y -= count;
self.redistribute_space();
}
fn scroll_up(&mut self, count: usize) {
self.grid.move_viewport_up(count);
@ -337,12 +381,69 @@ impl Pane for TerminalPane {
fn get_selected_text(&self) -> Option<String> {
self.grid.get_selected_text()
}
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();
}
}
impl TerminalPane {
pub fn new(pid: RawFd, position_and_size: PositionAndSize, palette: Palette) -> TerminalPane {
pub fn new(
pid: RawFd,
position_and_size: PositionAndSize,
palette: Palette,
pane_position: usize,
) -> TerminalPane {
let initial_pane_title = format!("Pane #{}", pane_position);
let grid = Grid::new(position_and_size.rows, position_and_size.cols, palette);
TerminalPane {
pane_decoration: PaneDecoration::ContentOffset((0, 0)),
content_position_and_size: position_and_size,
pid,
grid,
selectable: true,
@ -352,35 +453,55 @@ impl TerminalPane {
active_at: Instant::now(),
colors: palette,
selection_scrolled_at: time::Instant::now(),
pane_title: initial_pane_title,
}
}
pub fn get_x(&self) -> usize {
match self.position_and_size_override {
match self.position_and_size_override.as_ref() {
Some(position_and_size_override) => position_and_size_override.x,
None => self.position_and_size.x as usize,
None => self.position_and_size.x,
}
}
pub fn get_y(&self) -> usize {
match self.position_and_size_override {
match self.position_and_size_override.as_ref() {
Some(position_and_size_override) => position_and_size_override.y,
None => self.position_and_size.y as usize,
None => self.position_and_size.y,
}
}
pub fn get_columns(&self) -> usize {
match &self.position_and_size_override.as_ref() {
match self.position_and_size_override.as_ref() {
Some(position_and_size_override) => position_and_size_override.cols,
None => self.position_and_size.cols as usize,
None => self.position_and_size.cols,
}
}
pub fn get_rows(&self) -> usize {
match &self.position_and_size_override.as_ref() {
match self.position_and_size_override.as_ref() {
Some(position_and_size_override) => position_and_size_override.rows,
None => self.position_and_size.rows as usize,
None => self.position_and_size.rows,
}
}
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_rows();
let columns = self.get_columns();
let rows = self.get_content_rows();
let columns = self.get_content_columns();
self.grid.change_size(rows, columns);
self.set_should_render(true);
}
@ -391,6 +512,24 @@ 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

@ -608,9 +608,9 @@ fn copy_selected_text_from_lines_below() {
grid.move_viewport_up(40);
grid.start_selection(&Position::new(63, 6));
grid.start_selection(&Position::new(35, 6));
// check for widechar, 📦 occupies columns 34, 35, and gets selected even if only the first column is selected
grid.end_selection(Some(&Position::new(65, 35)));
grid.end_selection(Some(&Position::new(37, 35)));
let text = grid.get_selected_text();
assert_eq!(
text.unwrap(),

View File

@ -15,7 +15,7 @@ pub fn scrolling_inside_a_pane() {
};
let pid = 1;
let palette = Palette::default();
let mut terminal_pane = TerminalPane::new(pid, fake_win_size, palette);
let mut terminal_pane = TerminalPane::new(pid, fake_win_size, palette, 0); // 0 is the pane index
let mut text_to_fill_pane = String::new();
for i in 0..30 {
text_to_fill_pane.push_str(&format!("\rline {}\n", i + 1));

View File

@ -151,6 +151,12 @@ fn route_action(
.send_to_screen(ScreenInstruction::ToggleActiveTerminalFullscreen)
.unwrap();
}
Action::TogglePaneFrames => {
session
.senders
.send_to_screen(ScreenInstruction::TogglePaneFrames)
.unwrap();
}
Action::NewPane(direction) => {
let shell = session.default_shell.clone();
let pty_instr = match direction {

View File

@ -56,6 +56,7 @@ pub(crate) enum ScreenInstruction {
ClearScroll,
CloseFocusedPane,
ToggleActiveTerminalFullscreen,
TogglePaneFrames,
SetSelectable(PaneId, bool, usize),
SetFixedHeight(PaneId, usize, usize),
SetFixedWidth(PaneId, usize, usize),
@ -112,6 +113,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::ToggleActiveTerminalFullscreen => {
ScreenContext::ToggleActiveTerminalFullscreen
}
ScreenInstruction::TogglePaneFrames => ScreenContext::TogglePaneFrames,
ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable,
ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders,
ScreenInstruction::SetFixedHeight(..) => ScreenContext::SetFixedHeight,
@ -153,6 +155,7 @@ pub(crate) struct Screen {
mode_info: ModeInfo,
colors: Palette,
session_state: Arc<RwLock<SessionState>>,
draw_pane_frames: bool,
}
impl Screen {
@ -163,6 +166,7 @@ impl Screen {
max_panes: Option<usize>,
mode_info: ModeInfo,
session_state: Arc<RwLock<SessionState>>,
draw_pane_frames: bool,
) -> Self {
Screen {
bus,
@ -173,6 +177,7 @@ impl Screen {
tabs: BTreeMap::new(),
mode_info,
session_state,
draw_pane_frames,
}
}
@ -193,6 +198,7 @@ impl Screen {
self.mode_info.clone(),
self.colors,
self.session_state.clone(),
self.draw_pane_frames,
);
self.active_tab_index = Some(tab_index);
self.tabs.insert(tab_index, tab);
@ -359,6 +365,7 @@ impl Screen {
self.mode_info.clone(),
self.colors,
self.session_state.clone(),
self.draw_pane_frames,
);
tab.apply_layout(layout, new_pids, tab_index);
self.active_tab_index = Some(tab_index);
@ -405,6 +412,7 @@ impl Screen {
self.mode_info = mode_info;
for tab in self.tabs.values_mut() {
tab.mode_info = self.mode_info.clone();
tab.mark_active_pane_for_rerender();
}
}
pub fn move_focus_left_or_previous_tab(&mut self) {
@ -430,6 +438,7 @@ pub(crate) fn screen_thread_main(
session_state: Arc<RwLock<SessionState>>,
) {
let capabilities = config_options.simplified_ui;
let draw_pane_frames = !config_options.no_pane_frames;
let mut screen = Screen::new(
bus,
@ -443,6 +452,7 @@ pub(crate) fn screen_thread_main(
},
),
session_state,
draw_pane_frames,
);
loop {
let (event, mut err_ctx) = screen
@ -663,6 +673,13 @@ pub(crate) fn screen_thread_main(
.unwrap()
.toggle_active_pane_fullscreen();
}
ScreenInstruction::TogglePaneFrames => {
screen.draw_pane_frames = !screen.draw_pane_frames;
for (_, tab) in screen.tabs.iter_mut() {
tab.set_pane_frames(screen.draw_pane_frames);
}
screen.render();
}
ScreenInstruction::NewTab(pane_id) => {
screen.new_tab(pane_id);
screen

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
use zellij_utils::pane_size::PositionAndSize;
use zellij_utils::zellij_tile;
use crate::tab::Pane;
@ -405,73 +406,22 @@ impl Coordinates {
}
}
pub(crate) trait Rect {
fn x(&self) -> usize;
fn y(&self) -> usize;
fn rows(&self) -> usize;
fn columns(&self) -> usize;
fn right_boundary_x_coords(&self) -> usize {
self.x() + self.columns()
}
fn bottom_boundary_y_coords(&self) -> usize {
self.y() + self.rows()
}
fn is_directly_right_of(&self, other: &Self) -> bool {
self.x() == other.x() + other.columns() + 1
}
fn is_directly_left_of(&self, other: &Self) -> bool {
self.x() + self.columns() + 1 == other.x()
}
fn is_directly_below(&self, other: &Self) -> bool {
self.y() == other.y() + other.rows() + 1
}
fn is_directly_above(&self, other: &Self) -> bool {
self.y() + self.rows() + 1 == other.y()
}
fn horizontally_overlaps_with(&self, other: &Self) -> bool {
(self.y() >= other.y() && self.y() <= (other.y() + other.rows()))
|| ((self.y() + self.rows()) <= (other.y() + other.rows())
&& (self.y() + self.rows()) > other.y())
|| (self.y() <= other.y() && (self.y() + self.rows() >= (other.y() + other.rows())))
|| (other.y() <= self.y() && (other.y() + other.rows() >= (self.y() + self.rows())))
}
fn get_horizontal_overlap_with(&self, other: &Self) -> usize {
std::cmp::min(self.y() + self.rows(), other.y() + other.rows())
- std::cmp::max(self.y(), other.y())
}
fn vertically_overlaps_with(&self, other: &Self) -> bool {
(self.x() >= other.x() && self.x() <= (other.x() + other.columns()))
|| ((self.x() + self.columns()) <= (other.x() + other.columns())
&& (self.x() + self.columns()) > other.x())
|| (self.x() <= other.x()
&& (self.x() + self.columns() >= (other.x() + other.columns())))
|| (other.x() <= self.x()
&& (other.x() + other.columns() >= (self.x() + self.columns())))
}
fn get_vertical_overlap_with(&self, other: &Self) -> usize {
std::cmp::min(self.x() + self.columns(), other.x() + other.columns())
- std::cmp::max(self.x(), other.x())
}
}
pub struct Boundaries {
columns: usize,
rows: usize,
// boundary_characters: HashMap<Coordinates, BoundaryType>,
position_and_size: PositionAndSize,
boundary_characters: HashMap<Coordinates, BoundarySymbol>,
}
impl Boundaries {
pub fn new(columns: u16, rows: u16) -> Self {
let columns = columns as usize;
let rows = rows as usize;
pub fn new(position_and_size: &PositionAndSize) -> Self {
Boundaries {
columns,
rows,
position_and_size: *position_and_size,
boundary_characters: HashMap::new(),
}
}
pub fn add_rect(&mut self, rect: &dyn Pane, input_mode: InputMode, palette: Option<Palette>) {
if !self.is_fully_inside_screen(rect) {
return;
}
let color = match palette.is_some() {
true => match input_mode {
InputMode::Normal | InputMode::Locked => Some(palette.unwrap().green),
@ -479,15 +429,19 @@ impl Boundaries {
},
false => None,
};
if rect.x() > 0 {
if rect.x() > self.position_and_size.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 != 0 {
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.rows - 1 {
} 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)
@ -503,15 +457,19 @@ impl Boundaries {
self.boundary_characters.insert(coordinates, next_symbol);
}
}
if rect.y() > 0 {
if rect.y() > self.position_and_size.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 != 0 {
let mut symbol_to_add = if col == first_col_coordinates
&& col != self.position_and_size.x
{
BoundarySymbol::new(boundary_type::TOP_LEFT).color(color)
} else if col == last_col_coordinates - 1 && col != self.columns - 1 {
} else if col == last_col_coordinates - 1 && col != self.position_and_size.cols - 1
{
BoundarySymbol::new(boundary_type::TOP_RIGHT).color(color)
} else {
BoundarySymbol::new(boundary_type::HORIZONTAL).color(color)
@ -528,15 +486,18 @@ impl Boundaries {
}
}
if self.rect_right_boundary_is_before_screen_edge(rect) {
// let boundary_x_coords = self.rect_right_boundary_x_coords(rect);
let boundary_x_coords = rect.right_boundary_x_coords();
// right boundary
let boundary_x_coords = rect.right_boundary_x_coords() - 1;
let first_row_coordinates = self.rect_right_boundary_row_start(rect);
let last_row_coordinates = self.rect_right_boundary_row_end(rect);
for row in first_row_coordinates..last_row_coordinates {
let coordinates = Coordinates::new(boundary_x_coords, row);
let mut symbol_to_add = if row == first_row_coordinates && row != 0 {
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.rows - 1 {
} 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)
@ -553,14 +514,18 @@ impl Boundaries {
}
}
if self.rect_bottom_boundary_is_before_screen_edge(rect) {
let boundary_y_coords = rect.bottom_boundary_y_coords();
// bottom boundary
let boundary_y_coords = rect.bottom_boundary_y_coords() - 1;
let first_col_coordinates = self.rect_bottom_boundary_col_start(rect);
let last_col_coordinates = self.rect_bottom_boundary_col_end(rect);
for col in first_col_coordinates..last_col_coordinates {
let coordinates = Coordinates::new(col, boundary_y_coords);
let mut symbol_to_add = if col == first_col_coordinates && col != 0 {
let mut symbol_to_add = if col == first_col_coordinates
&& col != self.position_and_size.x
{
BoundarySymbol::new(boundary_type::BOTTOM_LEFT).color(color)
} else if col == last_col_coordinates - 1 && col != self.columns - 1 {
} else if col == last_col_coordinates - 1 && col != self.position_and_size.cols - 1
{
BoundarySymbol::new(boundary_type::BOTTOM_RIGHT).color(color)
} else {
BoundarySymbol::new(boundary_type::HORIZONTAL).color(color)
@ -590,27 +555,20 @@ impl Boundaries {
vte_output
}
fn rect_right_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool {
rect.x() + rect.columns() < self.columns
rect.x() + rect.columns() < self.position_and_size.cols
}
fn rect_bottom_boundary_is_before_screen_edge(&self, rect: &dyn Pane) -> bool {
rect.y() + rect.rows() < self.rows
rect.y() + rect.rows() < self.position_and_size.y + self.position_and_size.rows
}
fn rect_right_boundary_row_start(&self, rect: &dyn Pane) -> usize {
if rect.y() == 0 {
0
} else {
if rect.y() > self.position_and_size.y {
rect.y() - 1
} else {
self.position_and_size.y
}
}
fn rect_right_boundary_row_end(&self, rect: &dyn Pane) -> usize {
let rect_bottom_row = rect.y() + rect.rows();
// we do this because unless we're on the screen edge, we'd like to go one extra row to
// connect to whatever boundary is beneath us
if rect_bottom_row == self.rows {
rect_bottom_row
} else {
rect_bottom_row + 1
}
rect.y() + rect.rows()
}
fn rect_bottom_boundary_col_start(&self, rect: &dyn Pane) -> usize {
if rect.x() == 0 {
@ -620,13 +578,12 @@ impl Boundaries {
}
}
fn rect_bottom_boundary_col_end(&self, rect: &dyn Pane) -> usize {
let rect_right_col = rect.x() + rect.columns();
// we do this because unless we're on the screen edge, we'd like to go one extra column to
// connect to whatever boundary is right of us
if rect_right_col == self.columns {
rect_right_col
} else {
rect_right_col + 1
rect.x() + rect.columns()
}
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
}
}

View File

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

View File

@ -0,0 +1,277 @@
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::zellij_tile::prelude::PaletteColor;
fn color_string(character: &str, color: Option<PaletteColor>) -> String {
match color {
Some(color) => match color {
PaletteColor::Rgb((r, g, b)) => {
format!("{}", RGB(r, g, b).bold().paint(character))
}
PaletteColor::EightBit(color) => {
format!("{}", Fixed(color).bold().paint(character))
}
},
None => format!("{}", Style::new().bold().paint(character)),
}
}
pub struct PaneBoundariesFrame {
pub position_and_size: PositionAndSize,
base_title: String,
title: String,
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;
}
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: ";
let full_indication =
format!(" {}/{} ", self.scroll_position.0, self.scroll_position.1);
let short_indication = format!(" {} ", self.scroll_position.0);
if prefix.chars().count() + full_indication.chars().count() <= max_length {
Some(format!("{}{}", prefix, full_indication))
} else if full_indication.chars().count() <= max_length {
Some(full_indication)
} else if short_indication.chars().count() <= max_length {
Some(short_indication)
} else {
None
}
} else {
None
}
}
fn render_title_left_side(&self, max_length: usize) -> Option<String> {
let middle_truncated_sign = "[..]";
let middle_truncated_sign_long = "[...]";
let full_text = format!(" {} ", &self.title);
if max_length <= 6 {
None
} else if full_text.chars().count() <= max_length {
Some(full_text)
} else {
let length_of_each_half = (max_length - middle_truncated_sign.chars().count()) / 2;
let first_part: String = full_text.chars().take(length_of_each_half).collect();
let second_part: String = full_text
.chars()
.skip(full_text.chars().count() - length_of_each_half)
.collect();
let title_left_side = if first_part.chars().count()
+ middle_truncated_sign.chars().count()
+ second_part.chars().count()
< max_length
{
// this means we lost 1 character when dividing the total length into halves
format!(
"{}{}{}",
first_part, middle_truncated_sign_long, second_part
)
} else {
format!("{}{}{}", first_part, middle_truncated_sign, second_part)
};
Some(title_left_side)
}
}
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 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
self.render_title_right_side(space_left)
});
let title_text = match (left_side, right_side) {
(Some(left_side), Some(right_side)) => {
let mut middle = String::new();
for _ in
(left_side.chars().count() + right_side.chars().count())..total_title_length
{
middle.push_str(boundary_type::HORIZONTAL);
}
format!(
"{}{}{}{}{}",
left_boundary, left_side, middle, right_side, right_boundary
)
}
(Some(left_side), None) => {
let mut middle_padding = String::new();
for _ in left_side.chars().count()..total_title_length {
middle_padding.push_str(boundary_type::HORIZONTAL);
}
format!(
"{}{}{}{}",
left_boundary, left_side, middle_padding, right_boundary
)
}
_ => {
let mut middle_padding = String::new();
for _ in 0..total_title_length {
middle_padding.push_str(boundary_type::HORIZONTAL);
}
format!("{}{}{}", left_boundary, middle_padding, right_boundary)
}
};
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
color_string(&title_text, self.color),
)); // goto row/col + boundary character
}
pub fn render(&mut self) -> Option<String> {
if !self.should_render {
return None;
}
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
}
}
} 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
}
}
}
self.should_render = false;
Some(vte_output)
}
}

View File

@ -112,7 +112,7 @@ impl<'a> PaneResizer<'a> {
(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.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)
});
@ -137,7 +137,7 @@ impl<'a> PaneResizer<'a> {
(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.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)
});
@ -162,7 +162,7 @@ impl<'a> PaneResizer<'a> {
(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.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)
});
@ -187,7 +187,7 @@ impl<'a> PaneResizer<'a> {
(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.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)
});
@ -204,32 +204,44 @@ impl<'a> PaneResizer<'a> {
let pane = self.panes.get_mut(id).unwrap();
pane.reduce_height_up(count);
if let PaneId::Terminal(pid) = id {
self.os_api
.set_terminal_size_using_fd(*pid, pane.columns() as u16, pane.rows() as u16);
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.columns() as u16, pane.rows() as u16);
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.columns() as u16, pane.rows() as u16);
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.columns() as u16, pane.rows() as u16);
self.os_api.set_terminal_size_using_fd(
pid,
pane.get_content_columns() as u16,
pane.get_content_rows() as u16,
);
}
}
}
@ -240,9 +252,7 @@ fn find_next_increasable_horizontal_pane(
increase_by: usize,
) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter(
|p| {
p.x() == right_of.x() + right_of.columns() + 1 && p.horizontally_overlaps_with(right_of)
}, // TODO: the name here is wrong, it should be vertically_overlaps_with
|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));
@ -265,7 +275,7 @@ fn find_next_increasable_vertical_pane(
increase_by: usize,
) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter(
|p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with
|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));
@ -288,7 +298,7 @@ fn find_next_reducible_vertical_pane(
reduce_by: usize,
) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter(
|p| p.y() == below.y() + below.rows() + 1 && p.vertically_overlaps_with(below), // TODO: the name here is wrong, it should be horizontally_overlaps_with
|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 {
@ -310,9 +320,7 @@ fn find_next_reducible_horizontal_pane(
reduce_by: usize,
) -> Option<PaneId> {
let next_pane_candidates = panes.values().filter(
|p| {
p.x() == right_of.x() + right_of.columns() + 1 && p.horizontally_overlaps_with(right_of)
}, // TODO: the name here is wrong, it should be vertically_overlaps_with
|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 {
@ -346,7 +354,7 @@ fn find_increasable_horizontal_chain(
{
Some(leftmost_pane) => {
if !leftmost_pane.can_increase_height_by(increase_by) {
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1;
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows();
continue;
}
let mut panes_to_resize = vec![];
@ -365,7 +373,7 @@ fn find_increasable_horizontal_chain(
current_pane = panes.get(&next_pane_id).unwrap();
}
None => {
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1;
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows();
break;
}
};
@ -396,7 +404,7 @@ fn find_increasable_vertical_chain(
{
Some(topmost_pane) => {
if !topmost_pane.can_increase_width_by(increase_by) {
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1;
vertical_coordinate = topmost_pane.x() + topmost_pane.columns();
continue;
}
let mut panes_to_resize = vec![];
@ -415,7 +423,7 @@ fn find_increasable_vertical_chain(
current_pane = panes.get(&next_pane_id).unwrap();
}
None => {
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1;
vertical_coordinate = topmost_pane.x() + topmost_pane.columns();
break;
}
};
@ -446,7 +454,7 @@ fn find_reducible_horizontal_chain(
{
Some(leftmost_pane) => {
if !leftmost_pane.can_reduce_height_by(reduce_by) {
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1;
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows();
continue;
}
let mut panes_to_resize = vec![];
@ -465,7 +473,7 @@ fn find_reducible_horizontal_chain(
current_pane = panes.get(&next_pane_id).unwrap();
}
None => {
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows() + 1;
horizontal_coordinate = leftmost_pane.y() + leftmost_pane.rows();
break;
}
};
@ -496,7 +504,7 @@ fn find_reducible_vertical_chain(
{
Some(topmost_pane) => {
if !topmost_pane.can_reduce_width_by(increase_by) {
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1;
vertical_coordinate = topmost_pane.x() + topmost_pane.columns();
continue;
}
let mut panes_to_resize = vec![];
@ -515,7 +523,7 @@ fn find_reducible_vertical_chain(
current_pane = panes.get(&next_pane_id).unwrap();
}
None => {
vertical_coordinate = topmost_pane.x() + topmost_pane.columns() + 1;
vertical_coordinate = topmost_pane.x() + topmost_pane.columns();
break;
}
};

View File

View File

@ -82,7 +82,14 @@ fn create_new_screen(position_and_size: PositionAndSize) -> Screen {
let max_panes = None;
let mode_info = ModeInfo::default();
let session_state = Arc::new(RwLock::new(SessionState::Attached));
Screen::new(bus, &client_attributes, max_panes, mode_info, session_state)
Screen::new(
bus,
&client_attributes,
max_panes,
mode_info,
session_state,
false, // draw_pane_frames
)
}
#[test]

File diff suppressed because it is too large Load Diff

View File

@ -133,6 +133,9 @@ pub(crate) fn wasm_thread_main(bus: Bus<PluginInstruction>, store: Store, data_d
drop(bus.senders.send_to_screen(ScreenInstruction::Render));
}
PluginInstruction::Render(buf_tx, pid, rows, cols) => {
if rows == 0 || cols == 0 {
buf_tx.send(String::new()).unwrap();
} else {
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
let render = instance.exports.get_function("render").unwrap();
@ -143,6 +146,7 @@ pub(crate) fn wasm_thread_main(bus: Bus<PluginInstruction>, store: Store, data_d
buf_tx.send(wasi_read_string(&plugin_env.wasi_env)).unwrap();
}
}
PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
PluginInstruction::Exit => break,
}

View File

@ -105,6 +105,8 @@ keybinds:
key: [Char: 'x',]
- action: [ToggleFocusFullscreen,]
key: [Char: 'f',]
- action: [TogglePaneFrames,]
key: [Char: 'z',]
- action: [FocusPreviousPane,]
key: [ Alt: '[',]
- action: [FocusNextPane,]

View File

@ -2,12 +2,14 @@
direction: Horizontal
parts:
- direction: Vertical
borderless: true
split_size:
Fixed: 1
run:
plugin: tab-bar
- direction: Vertical
- direction: Vertical
borderless: true
split_size:
Fixed: 2
run:

View File

@ -2,6 +2,7 @@
direction: Horizontal
parts:
- direction: Vertical
borderless: true
split_size:
Fixed: 1
run:

View File

@ -2,6 +2,7 @@
direction: Horizontal
parts:
- direction: Vertical
borderless: true
split_size:
Fixed: 1
run:
@ -15,6 +16,7 @@ parts:
plugin: strider
- direction: Horizontal
- direction: Vertical
borderless: true
split_size:
Fixed: 2
run:

View File

@ -209,6 +209,7 @@ pub enum ScreenContext {
CloseFocusedPane,
ToggleActiveSyncTab,
ToggleActiveTerminalFullscreen,
TogglePaneFrames,
SetSelectable,
SetInvisibleBorders,
SetFixedHeight,

View File

@ -56,6 +56,8 @@ pub enum Action {
PageScrollDown,
/// Toggle between fullscreen focus pane and normal layout.
ToggleFocusFullscreen,
/// Toggle frames around panes in the UI
TogglePaneFrames,
/// Toggle between sending text commands to all panes on the current tab and normal mode.
ToggleActiveSyncTab,
/// Open a new pane in the specified direction (relative to focus).

View File

@ -50,6 +50,8 @@ pub struct Layout {
pub parts: Vec<Layout>,
pub split_size: Option<SplitSize>,
pub run: Option<Run>,
#[serde(default)]
pub borderless: bool,
}
type LayoutResult = Result<Layout, ConfigError>;
@ -141,6 +143,14 @@ impl Layout {
total_panes
}
pub fn total_borderless_panes(&self) -> usize {
let mut total_borderless_panes = 0;
total_borderless_panes += self.parts.iter().filter(|p| p.borderless).count();
for part in self.parts.iter() {
total_borderless_panes += part.total_borderless_panes();
}
total_borderless_panes
}
pub fn extract_run_instructions(&self) -> Vec<Option<Run>> {
let mut run_instructions = vec![];
if self.parts.is_empty() {
@ -168,7 +178,7 @@ fn split_space_to_parts_vertically(
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 - (sizes.len() - 1); // minus space for gaps
let max_width = space_to_split.cols;
let mut parts_to_grow = Vec::new();
@ -192,7 +202,7 @@ fn split_space_to_parts_vertically(
..Default::default()
});
current_width += columns;
current_x_position += columns + 1; // 1 for gap
current_x_position += columns;
}
if current_width > max_width {
@ -210,7 +220,7 @@ fn split_space_to_parts_vertically(
last_flexible_index = idx;
}
current_width += part.cols;
current_x_position += part.cols + 1; // 1 for gap
current_x_position += part.cols;
}
}
@ -233,7 +243,7 @@ fn split_space_to_parts_horizontally(
let mut split_parts = Vec::new();
let mut current_y_position = space_to_split.y;
let mut current_height = 0;
let max_height = space_to_split.rows - (sizes.len() - 1); // minus space for gaps
let max_height = space_to_split.rows;
let mut parts_to_grow = Vec::new();
@ -256,7 +266,7 @@ fn split_space_to_parts_horizontally(
..Default::default()
});
current_height += rows;
current_y_position += rows + 1; // 1 for gap
current_y_position += rows;
}
if current_height > max_height {
@ -275,7 +285,7 @@ fn split_space_to_parts_horizontally(
last_flexible_index = idx;
}
current_height += part.rows;
current_y_position += part.rows + 1; // 1 for gap
current_y_position += part.rows;
}
}

View File

@ -30,6 +30,7 @@ pub fn get_mode_info(
("r".to_string(), "Right split".to_string()),
("x".to_string(), "Close".to_string()),
("f".to_string(), "Fullscreen".to_string()),
("z".to_string(), "Frames".to_string()),
],
InputMode::Tab => vec![
("←↓↑→".to_string(), "Move focus".to_string()),

View File

@ -58,6 +58,9 @@ pub struct Options {
#[serde(default)]
/// Disable handling of mouse events
pub disable_mouse_mode: bool,
#[structopt(long)]
#[serde(default)]
pub no_pane_frames: bool,
/// Set behaviour on force close (quit or detach)
#[structopt(long)]
pub on_force_close: Option<OnForceClose>,
@ -80,6 +83,7 @@ impl Options {
let simplified_ui = merge_bool(other.simplified_ui, self.simplified_ui);
let disable_mouse_mode = merge_bool(other.disable_mouse_mode, self.disable_mouse_mode);
let no_pane_frames = merge_bool(other.no_pane_frames, self.no_pane_frames);
let default_mode = other.default_mode.or(self.default_mode);
let default_shell = other.default_shell.or_else(|| self.default_shell.clone());
@ -94,6 +98,7 @@ impl Options {
default_shell,
layout_dir,
disable_mouse_mode,
no_pane_frames,
on_force_close,
}
}

View File

@ -5,7 +5,7 @@ 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, Debug, Default, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct PositionAndSize {
pub x: usize,
pub y: usize,
@ -34,4 +34,16 @@ impl PositionAndSize {
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
}
}

View File

@ -18,7 +18,7 @@ pub fn set_permissions(path: &Path) -> io::Result<()> {
fs::set_permissions(path, permissions)
}
fn ansi_len(s: &str) -> usize {
pub fn ansi_len(s: &str) -> usize {
from_utf8(&strip(s.as_bytes()).unwrap())
.unwrap()
.chars()